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 a `|>` thread-first call. The subject has been inserted | |
| 25 | // as the first element of `arguments`, so the call resolves and | |
| 26 | // emits like any other; the flag only tells the formatter to | |
| 27 | // render `arguments[0] |> function(rest)` and lets the constructor | |
| 28 | // and named-argument checks reject the forms `|>` disallows. | |
| 29 | is_thread_first: bool public | |
| 30 | ||
| 31 | // True for a `~>` thread-first call. The subject was threaded | |
| 32 | // in as with `|>`, but the call is lowered inside a presence | |
| 33 | // test on it: an absent subject skips the call - arguments | |
| 34 | // included - and yields the absent value widened to the | |
| 35 | // optional shape of the call's result. Plain `|>` leaves | |
| 36 | // this false. | |
| 37 | propagates_absence: bool public | |
| 38 | ||
| 39 | // For a `|>` call, the span running from the operator through the | |
| 40 | // called name - what the completer treats as the position whose | |
| 41 | // candidates are the functions the subject can be threaded into. | |
| 42 | // Empty on every other call. | |
| 43 | completion_target: LOCATION? public | |
| 44 | ||
| 45 | super(location) | |
| 46 | ||
| 47 | set_expected_type(expected_type: Semantic.Types.Type?, error_message: string?) is | |
| 48 | compile_expressions_state.set_expected_type(expected_type, error_message) | |
| 49 | si | |
| 50 | ||
| 51 | // `f(a = 1, b = 2)` parses with each argument as a `VARIABLE`: | |
| 52 | // the shared list parser treats `name = value` like a | |
| 53 | // tuple-element precursor. Detect that shape, record the | |
| 54 | // names, and unwrap each argument to its value expression so | |
| 55 | // every later pass sees an ordinary positional list. A call | |
| 56 | // must be all-named or all-positional. | |
| 57 | rewrite_named_arguments(logger: Logger) is | |
| 58 | let elements = arguments.expressions | |
| 59 | ||
| 60 | if elements.count == 0 then | |
| 61 | return | |
| 62 | fi | |
| 63 | ||
| 64 | let named_count = elements |> filter(e => _is_named_argument(e)) |> count() | |
| 65 | ||
| 66 | if named_count == 0 then | |
| 67 | return | |
| 68 | fi | |
| 69 | ||
| 70 | if named_count < elements.count then | |
| 71 | logger.error(location, "cannot mix named and positional arguments") | |
| 72 | ||
| 73 | for index in 0..elements.count do | |
| 74 | if _is_named_argument(elements[index]) then | |
| 75 | elements[index] = (cast VARIABLE?(elements[index])!).initializer! | |
| 76 | fi | |
| 77 | od | |
| 78 | ||
| 79 | return | |
| 80 | fi | |
| 81 | ||
| 82 | let names = Collections.LIST[Identifiers.Identifier]() | |
| 83 | ||
| 84 | for index in 0..elements.count do | |
| 85 | let variable = cast VARIABLE?(elements[index])! | |
| 86 | ||
| 87 | if let first = names |> find(seen => seen.name =~ variable.name.name) then | |
| 88 | logger.error(variable.name.location, "named argument {variable.name.name} supplied more than once", first.location, "first supplied here") | |
| 89 | fi | |
| 90 | ||
| 91 | names.add(variable.name) | |
| 92 | elements[index] = variable.initializer! | |
| 93 | od | |
| 94 | ||
| 95 | argument_names = names | |
| 96 | si | |
| 97 | ||
| 98 | // A named argument is `name = value`: a VARIABLE node carrying | |
| 99 | // an initializer. A bare `name: type` (a VARIABLE with no | |
| 100 | // initializer) is not a named argument - it only arises from | |
| 101 | // parser error recovery, and is left for the normal error | |
| 102 | // paths rather than reported here. | |
| 103 | _is_named_argument(e: Expression) -> bool => | |
| 104 | isa VARIABLE(e) /\ (cast VARIABLE(e)).initializer? | |
| 105 | ||
| 106 | accept(visitor: Visitor) is | |
| 107 | visitor.visit(self) | |
| 108 | si | |
| 109 | ||
| 110 | walk(visitor: Visitor) is | |
| 111 | // enter/leave_node wrap the whole visit so the ambient | |
| 112 | // location covers Values built by `pre()` overrides that | |
| 113 | // take control of the walk — `pre(CALL)` returns true and | |
| 114 | // `visit_call` does its own resolver-driven walk, skipping | |
| 115 | // the default recursion below. | |
| 116 | visitor.enter_node(self) | |
| 117 | try | |
| 118 | if !visitor.pre(self) then | |
| 119 | arguments.walk(visitor) | |
| 120 | function.walk(visitor) | |
| 121 | fi | |
| 122 | ||
| 123 | accept(visitor) | |
| 124 | finally | |
| 125 | visitor.leave_node(self) | |
| 126 | yrt | |
| 127 | si | |
| 128 | ||
| 129 | replace_child(old: Expression, replacement: Expression) is | |
| 130 | if function == old then | |
| 131 | function = replacement | |
| 132 | fi | |
| 133 | // Argument elements are replaced via arguments.replace_element | |
| 134 | // by callers that have the index — no parent-driven lookup | |
| 135 | // here (it would require a linear scan; the SPILL_AWAITS | |
| 136 | // pass already has the index). | |
| 137 | si | |
| 138 | si | |
| 139 | si |