Appearance
| 1 | namespace Syntax.Trees.Expressions is | |
| 2 | ||
| 3 | use Source; | |
| 4 | use Logging; | |
| 5 | ||
| 6 | use Ghul.Pipes; | |
| 7 | ||
| 8 | class CALL(location: LOCATION, function: Expression, arguments: LIST): Expression is | |
| 9 | right_location: LOCATION => function.right_location; | |
| 10 | could_be_nested_function_definition: bool => function.is_unqualified_identifier; | |
| 11 | ||
| 12 | // Constraint pushed in by the parent context. For a constructor | |
| 13 | // call (e.g. `Box()` against `let b: Box[int]`) the visitor uses | |
| 14 | // it to bind the owner generic arguments when they can't be | |
| 15 | // inferred from the supplied constructor arguments alone. | |
| 16 | ||
| 17 | // Parallel to `arguments` when the call uses named-argument | |
| 18 | // syntax (`f(width = 800, height = 600)`); null for a | |
| 19 | // positional call. Set by `rewrite_named_arguments`, which | |
| 20 | // also unwraps each argument to its value expression so the | |
| 21 | // rest of the pipeline sees an ordinary positional list. | |
| 22 | argument_names: Collections.List[Identifiers.Identifier]?; | |
| 23 | ||
| 24 | // True for the `Ghul.Pipes.pipe(x)` call synthesised by the | |
| 25 | // postfix `|` operator. When the operand is already a Pipe[T], | |
| 26 | // compile-expressions short-circuits the wrap to the operand — | |
| 27 | // `x |` is then a no-op (a compile-time decision on x's type). | |
| 28 | is_pipe_wrap: bool public; | |
| 29 | ||
| 30 | // True for a `|>` thread-first call. The subject has been inserted | |
| 31 | // as the first element of `arguments`, so the call resolves and | |
| 32 | // emits like any other; the flag only tells the formatter to | |
| 33 | // render `arguments[0] |> function(rest)` and lets the constructor | |
| 34 | // and named-argument checks reject the forms `|>` disallows. | |
| 35 | is_thread_first: bool public; | |
| 36 | ||
| 37 | super(location); | |
| 38 | ||
| 39 | set_expected_type(expected_type: Semantic.Types.Type?, error_message: string?) is | |
| 40 | compile_expressions_state.set_expected_type(expected_type, error_message); | |
| 41 | si | |
| 42 | ||
| 43 | // `f(a = 1, b = 2)` parses with each argument as a `VARIABLE`: | |
| 44 | // the shared list parser treats `name = value` like a | |
| 45 | // tuple-element precursor. Detect that shape, record the | |
| 46 | // names, and unwrap each argument to its value expression so | |
| 47 | // every later pass sees an ordinary positional list. A call | |
| 48 | // must be all-named or all-positional. | |
| 49 | rewrite_named_arguments(logger: Logger) is | |
| 50 | let elements = arguments.expressions; | |
| 51 | ||
| 52 | if elements.count == 0 then | |
| 53 | return; | |
| 54 | fi | |
| 55 | ||
| 56 | let named_count = elements |> filter(e => _is_named_argument(e)) |> count(); | |
| 57 | ||
| 58 | if named_count == 0 then | |
| 59 | return; | |
| 60 | fi | |
| 61 | ||
| 62 | if named_count < elements.count then | |
| 63 | logger.error(location, "cannot mix named and positional arguments"); | |
| 64 | ||
| 65 | for index in 0..elements.count do | |
| 66 | if _is_named_argument(elements[index]) then | |
| 67 | elements[index] = (cast VARIABLE?(elements[index])!).initializer!; | |
| 68 | fi | |
| 69 | od | |
| 70 | ||
| 71 | return; | |
| 72 | fi | |
| 73 | ||
| 74 | let names = Collections.LIST[Identifiers.Identifier](); | |
| 75 | ||
| 76 | for index in 0..elements.count do | |
| 77 | let variable = cast VARIABLE?(elements[index])!; | |
| 78 | ||
| 79 | if names |> any(seen => seen.name =~ variable.name.name) then | |
| 80 | logger.error(variable.name.location, "named argument {variable.name.name} supplied more than once"); | |
| 81 | fi | |
| 82 | ||
| 83 | names.add(variable.name); | |
| 84 | elements[index] = variable.initializer!; | |
| 85 | od | |
| 86 | ||
| 87 | argument_names = names; | |
| 88 | si | |
| 89 | ||
| 90 | // A named argument is `name = value`: a VARIABLE node carrying | |
| 91 | // an initializer. A bare `name: type` (a VARIABLE with no | |
| 92 | // initializer) is not a named argument - it only arises from | |
| 93 | // parser error recovery, and is left for the normal error | |
| 94 | // paths rather than reported here. | |
| 95 | _is_named_argument(e: Expression) -> bool => | |
| 96 | isa VARIABLE(e) /\ (cast VARIABLE(e)).initializer?; | |
| 97 | ||
| 98 | accept(visitor: Visitor) is | |
| 99 | visitor.visit(self); | |
| 100 | si | |
| 101 | ||
| 102 | walk(visitor: Visitor) is | |
| 103 | // enter/leave_node wrap the whole visit so the ambient | |
| 104 | // location covers Values built by `pre()` overrides that | |
| 105 | // take control of the walk — `pre(CALL)` returns true and | |
| 106 | // `visit_call` does its own resolver-driven walk, skipping | |
| 107 | // the default recursion below. | |
| 108 | visitor.enter_node(self); | |
| 109 | try | |
| 110 | if !visitor.pre(self) then | |
| 111 | arguments.walk(visitor); | |
| 112 | function.walk(visitor); | |
| 113 | fi | |
| 114 | ||
| 115 | accept(visitor); | |
| 116 | finally | |
| 117 | visitor.leave_node(self); | |
| 118 | yrt | |
| 119 | si | |
| 120 | ||
| 121 | replace_child(old: Expression, replacement: Expression) is | |
| 122 | if function == old then | |
| 123 | function = replacement; | |
| 124 | fi | |
| 125 | // Argument elements are replaced via arguments.replace_element | |
| 126 | // by callers that have the index — no parent-driven lookup | |
| 127 | // here (it would require a linear scan; the SPILL_AWAITS | |
| 128 | // pass already has the index). | |
| 129 | si | |
| 130 | si | |
| 131 | si |