Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use System.Exception | |
| 3 | use Ghul.Disposable | |
| 4 | ||
| 5 | use IO.Std | |
| 6 | ||
| 7 | use Logging | |
| 8 | use Source | |
| 9 | ||
| 10 | use IR.Values | |
| 11 | use IR.VALUE_CONVERTER | |
| 12 | use IR.VALUE_BOXER | |
| 13 | ||
| 14 | use Semantic.LEAST_UPPER_BOUND_MAP | |
| 15 | use Semantic.Types.Type | |
| 16 | ||
| 17 | use Syntax.Trees.Definitions.PRAGMA | |
| 18 | ||
| 19 | use Ghul.Pipes | |
| 20 | ||
| 21 | ||
| 22 | // Name and reference walks: calls, identifiers, presence tests, unwraps, references, null and default values. | |
| 23 | partial COMPILE_EXPRESSIONS is | |
| 24 | pre(call: Trees.Expressions.CALL) -> bool => true | |
| 25 | visit(call: Trees.Expressions.CALL) is | |
| 26 | let mark = _logger.mark() | |
| 27 | ||
| 28 | try | |
| 29 | super.pre(call) | |
| 30 | ||
| 31 | // Push the call's expected type into the callee | |
| 32 | // expression. A unit-variant `Option.NONE` sitting at | |
| 33 | // call.function reads it in visit_member and asks | |
| 34 | // OWNER_CONSTRAINT_SPECIALIZER for the specialised | |
| 35 | // owner — the same binding the resolve_constructor | |
| 36 | // path performs for the parenthesised form. Non-variant | |
| 37 | // callees ignore the field. | |
| 38 | // | |
| 39 | // A callee that takes its own type from its context is | |
| 40 | // the exception: the call's expected type is what the | |
| 41 | // callee returns, not what it is, so pushing it here | |
| 42 | // would have `cast(v)` in callee position target the | |
| 43 | // result type and then report that the result cannot be | |
| 44 | // called. Such a callee is left until the arguments have | |
| 45 | // been walked and the whole function type is known. | |
| 46 | let defer_callee = call.function.awaits_context_type | |
| 47 | ||
| 48 | if call.expected_type? /\ !defer_callee then | |
| 49 | call.function.set_expected_type(call.expected_type, call.expected_type_error_message) | |
| 50 | fi | |
| 51 | ||
| 52 | // Mark the callee so a reflected TYPE_GROUP resolves to its | |
| 53 | // generic member for construction rather than collapsing to | |
| 54 | // the (often non-constructible) arity-0 member. | |
| 55 | call.function.mark_call_target() | |
| 56 | ||
| 57 | // Function walks outside the speculation level that | |
| 58 | // `visit_call` rolls back during constraint-push | |
| 59 | // retry, so diagnostics from sub-walks of the function | |
| 60 | // expression aren't scrubbed alongside first-pass | |
| 61 | // overload noise. | |
| 62 | if !defer_callee then | |
| 63 | call.function.walk(self) | |
| 64 | fi | |
| 65 | ||
| 66 | RETRY_SITE_STATS.note("references.call_arguments") | |
| 67 | _logger.speculate() | |
| 68 | ||
| 69 | // Snapshot the pre-argument narrowing facts so an | |
| 70 | // overload-retry re-walk resets to what the first walk | |
| 71 | // saw; on success the final walk's facts are kept. | |
| 72 | let use flow_speculation = _flow.speculate_then_commit() | |
| 73 | ||
| 74 | call.arguments.walk(self) | |
| 75 | ||
| 76 | if defer_callee then | |
| 77 | _walk_deferred_callee(call) | |
| 78 | fi | |
| 79 | ||
| 80 | _calls.visit_call(call) | |
| 81 | ||
| 82 | // A terminal Pipe consumer (`.count()`, ...) over a | |
| 83 | // fusible chain lowers to an inline driving loop instead | |
| 84 | // of building the pipe objects and iterating them. | |
| 85 | let fused = _recognize_consumer_fusion(call) | |
| 86 | ||
| 87 | if fused? then | |
| 88 | call.compile_expressions_state.value = fused | |
| 89 | fi | |
| 90 | ||
| 91 | _check_pure_slots(call.location, call.value, call.arguments) | |
| 92 | _note_call(call.location, call.value) | |
| 93 | ||
| 94 | _logger.commit() | |
| 95 | catch e: Exception | |
| 96 | call.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), call.location) | |
| 97 | _logger.release(mark) | |
| 98 | ||
| 99 | _logger.exception(call.location, e, "exception compiling call") | |
| 100 | yrt | |
| 101 | si | |
| 102 | ||
| 103 | // A callee whose own type comes from its context - `cast(v)` | |
| 104 | // with the target elided - can only take it from the call it | |
| 105 | // heads, and the call determines it completely: the arguments | |
| 106 | // give the parameter types and the call's own expected type | |
| 107 | // gives the return type. That is why such a callee is walked | |
| 108 | // here rather than before the arguments: the type it is waiting | |
| 109 | // for is not known until they have been. With no expected type | |
| 110 | // on the call there is no return type to supply, so the callee | |
| 111 | // walks unaided and reports that it cannot infer one. | |
| 112 | _walk_deferred_callee(call: Trees.Expressions.CALL) is | |
| 113 | let function_type = _try_get_callee_function_type(call) | |
| 114 | ||
| 115 | if function_type? then | |
| 116 | call.function.set_expected_type( | |
| 117 | function_type, | |
| 118 | "{{0}} is not assignable to {{1}}" | |
| 119 | ) | |
| 120 | fi | |
| 121 | ||
| 122 | call.function.walk(self) | |
| 123 | si | |
| 124 | ||
| 125 | // The function type a call determines for its own callee: | |
| 126 | // parameters from the walked arguments, return type from the | |
| 127 | // call's expected type. Null when either is unavailable. | |
| 128 | _try_get_callee_function_type(call: Trees.Expressions.CALL) -> Semantic.Types.Type? is | |
| 129 | let return_type = call.expected_type | |
| 130 | ||
| 131 | if !return_type? then | |
| 132 | return null | |
| 133 | fi | |
| 134 | ||
| 135 | let types = Collections.LIST[Semantic.Types.Type]() | |
| 136 | ||
| 137 | for a in call.arguments.expressions do | |
| 138 | let value = a.value | |
| 139 | ||
| 140 | if !value? then | |
| 141 | return null | |
| 142 | fi | |
| 143 | ||
| 144 | let type = value.type | |
| 145 | ||
| 146 | if !type? \/ type.is_error \/ !type.is_settled then | |
| 147 | return null | |
| 148 | fi | |
| 149 | ||
| 150 | types.add(type) | |
| 151 | od | |
| 152 | ||
| 153 | types.add(return_type) | |
| 154 | ||
| 155 | return _innate_symbol_lookup.get_function_type(types) | |
| 156 | si | |
| 157 | ||
| 158 | visit(identifier: Trees.Expressions.IDENTIFIER) is | |
| 159 | let mark = _logger.mark() | |
| 160 | ||
| 161 | try | |
| 162 | _access.visit_identifier(identifier) | |
| 163 | _note_call(identifier.location, identifier.value) | |
| 164 | ||
| 165 | catch ex: Exception | |
| 166 | _logger.exception(identifier.location, ex, "something went wrong with identifier") | |
| 167 | ||
| 168 | finally | |
| 169 | _logger.release(mark) | |
| 170 | yrt | |
| 171 | si | |
| 172 | ||
| 173 | visit(has_value: Trees.Expressions.HAS_VALUE) is | |
| 174 | // Stamp the log position the test is walked at, so facts | |
| 175 | // formed from it skip the calls that ran before it. | |
| 176 | _flow.note_test_site(has_value) | |
| 177 | ||
| 178 | _access.visit_has_value(has_value) | |
| 179 | _note_call(has_value.location, has_value.value) | |
| 180 | si | |
| 181 | ||
| 182 | visit(unwrap: Trees.Expressions.UNWRAP) is | |
| 183 | _access.visit_unwrap(unwrap) | |
| 184 | _note_call(unwrap.location, unwrap.value) | |
| 185 | si | |
| 186 | ||
| 187 | // An operand of `ref` is an address, not a value read. Suppress | |
| 188 | // the operand's definite-assignment check by recording its target | |
| 189 | // for the identifier load to skip, and reset the write flag so it | |
| 190 | // is re-derived from scratch this walk (robust to speculative | |
| 191 | // retry and inference iteration, which the flag does not otherwise | |
| 192 | // participate in). The resolved call does the real read-check and | |
| 193 | // assignment in `note_reference_arguments`. | |
| 194 | pre(reference: Trees.Expressions.REFERENCE) -> bool is | |
| 195 | reference.writes_target = false | |
| 196 | _reference_operand_target = try_get_narrowing_target(reference.left) | |
| 197 | ||
| 198 | return false | |
| 199 | si | |
| 200 | ||
| 201 | visit(reference: Trees.Expressions.REFERENCE) is | |
| 202 | _reference_operand_target = null | |
| 203 | _access.visit_reference(reference) | |
| 204 | si | |
| 205 | ||
| 206 | // True while walking the operand of the `ref` that writes to | |
| 207 | // `symbol` — consulted by the identifier load to skip the | |
| 208 | // definite-assignment check on an address-of operand. | |
| 209 | is_reference_operand_target(symbol: Semantic.Symbols.Symbol) -> bool => | |
| 210 | _reference_operand_target? /\ symbol == _reference_operand_target | |
| 211 | ||
| 212 | // Definite assignment for `ref` arguments, run once the call is | |
| 213 | // resolved so the matched parameter's direction is known. A slot | |
| 214 | // the callee reads (any by-ref except pure `out`) requires the | |
| 215 | // target to be assigned already; a slot it writes (any by-ref | |
| 216 | // except pure `in`) assigns it. The write flag is recorded on the | |
| 217 | // REFERENCE so the condition analyzer can carry that assignment | |
| 218 | // onto the branch edges a `ref` in a condition reaches. | |
| 219 | note_reference_arguments(call: Trees.Expressions.CALL, function: Semantic.Symbols.Function) is | |
| 220 | if call.argument_names? then | |
| 221 | return | |
| 222 | fi | |
| 223 | ||
| 224 | for i in 0..call.arguments.count do | |
| 225 | let expr = call.arguments.expressions[i] | |
| 226 | ||
| 227 | if !isa Trees.Expressions.REFERENCE(expr) then | |
| 228 | continue | |
| 229 | fi | |
| 230 | ||
| 231 | let reference = expr | |
| 232 | let target = try_get_narrowing_target(reference.left) | |
| 233 | ||
| 234 | if !target? then | |
| 235 | continue | |
| 236 | fi | |
| 237 | ||
| 238 | if | |
| 239 | function.argument_reads(i) /\ | |
| 240 | !_build_flags.no_warn_definite_assignment /\ | |
| 241 | isa Semantic.Symbols.Variable(target) /\ | |
| 242 | _flow.is_tracked(target) /\ | |
| 243 | !_flow.is_assigned(target) | |
| 244 | then | |
| 245 | _logger.warn(reference.location, "definite-assignment", "{target.name} may be used before it is assigned", target.location, "variable declared here") | |
| 246 | fi | |
| 247 | ||
| 248 | reference.writes_target = function.argument_writes(i) | |
| 249 | ||
| 250 | if reference.writes_target then | |
| 251 | _flow.mark_assigned(target) | |
| 252 | fi | |
| 253 | od | |
| 254 | si | |
| 255 | ||
| 256 | visit(`null: Trees.Expressions.NULL) is | |
| 257 | // For a value-type `T?` target (NULLABLE[T]) `null` is the | |
| 258 | // empty Nullable — a zeroed value, not a reference. Lower | |
| 259 | // it as an IR.Values.DEFAULT; a plain ldnull would be | |
| 260 | // invalid IL against a value-type slot. Reference-type | |
| 261 | // targets keep the ordinary null reference. | |
| 262 | if let `null.expected_type? /\ expected_type.is_value_type /\ expected_type.is_optional then | |
| 263 | `null.compile_expressions_state.value = IR.Values.DEFAULT(expected_type) | |
| 264 | else | |
| 265 | `null.compile_expressions_state.value = NULL(Semantic.Types.NULL()) | |
| 266 | fi | |
| 267 | ||
| 268 | si | |
| 269 | ||
| 270 | visit(`default: Trees.Expressions.DEFAULT) is | |
| 271 | _literals.visit_default(`default) | |
| 272 | ||
| 273 | // Non-optional-by-default: `default` reaching a non-optional | |
| 274 | // reference slot resolves to null at runtime, the same hole | |
| 275 | // as a bare `null` literal. Same warning category and flag | |
| 276 | // as visit(NULL) — one migration, one flip. | |
| 277 | if | |
| 278 | !_build_flags.no_warn_non_optional /\ | |
| 279 | `default.value? /\ | |
| 280 | _is_non_optional_reference(`default.value.type) | |
| 281 | then | |
| 282 | let target = `default.value!.type | |
| 283 | _logger.warn(`default.location, "non-optional", "default where non-optional {target} expected") | |
| 284 | fi | |
| 285 | si | |
| 286 | ||
| 287 | si | |
| 288 | si |