Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use System.Exception; | |
| 3 | ||
| 4 | use IO.Std; | |
| 5 | ||
| 6 | use Logging; | |
| 7 | use Source; | |
| 8 | ||
| 9 | use IR.Values; | |
| 10 | use IR.VALUE_CONVERTER; | |
| 11 | use IR.VALUE_BOXER; | |
| 12 | ||
| 13 | use Semantic.LEAST_UPPER_BOUND_MAP; | |
| 14 | use Semantic.Types.Type; | |
| 15 | ||
| 16 | use Syntax.Trees.Definitions.PRAGMA; | |
| 17 | ||
| 18 | use Ghul.Pipes; | |
| 19 | ||
| 20 | class COMPILE_EXPRESSIONS: ScopedVisitor is | |
| 21 | _logger: Logger; | |
| 22 | _symbol_table: Semantic.SYMBOL_TABLE; | |
| 23 | _int_type: Semantic.Types.NAMED; | |
| 24 | _symbol_loader: Semantic.SYMBOL_LOADER; | |
| 25 | _innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup; | |
| 26 | _function_caller: Semantic.FUNCTION_CALLER; | |
| 27 | _overload_resolver: Semantic.OVERLOAD_RESOLVER; | |
| 28 | _owner_type_arg_specializer: Semantic.OWNER_TYPE_ARG_SPECIALIZER; | |
| 29 | _owner_constraint_specializer: Semantic.OWNER_CONSTRAINT_SPECIALIZER; | |
| 30 | _unit_variant_constructor: Semantic.UNIT_VARIANT_CONSTRUCTOR; | |
| 31 | _under_determination_detector: Semantic.UNDER_DETERMINATION_DETECTOR; | |
| 32 | _type_arg_placeholder_registry: Semantic.TYPE_ARG_PLACEHOLDER_REGISTRY; | |
| 33 | _closure_arg_resolver: Semantic.CLOSURE_ARG_RESOLVER; | |
| 34 | _numeric_literal_classifier: NUMERIC_LITERAL_CLASSIFIER; | |
| 35 | _literals: COMPILE_LITERALS; | |
| 36 | _tuples: COMPILE_TUPLES; | |
| 37 | _operators: COMPILE_OPERATORS; | |
| 38 | _access: COMPILE_ACCESS; | |
| 39 | _generic_application: COMPILE_GENERIC_APPLICATION; | |
| 40 | _calls: COMPILE_CALLS; | |
| 41 | _conditionals: COMPILE_CONDITIONALS; | |
| 42 | _loops: COMPILE_LOOPS_AND_EXCEPTIONS; | |
| 43 | _bindings: COMPILE_BINDINGS; | |
| 44 | ||
| 45 | _pure_slots: PURE_SLOT_CHECK; | |
| 46 | _lambdas: COMPILE_LAMBDAS; | |
| 47 | _type_caster: Semantic.TYPE_CASTER; | |
| 48 | _symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS; | |
| 49 | _value_converter: VALUE_CONVERTER; | |
| 50 | _value_boxer: VALUE_BOXER; | |
| 51 | ||
| 52 | // The immutable, set-once command-line build flags. The | |
| 53 | // GLOBAL_BUILD_FLAGS object is created with the IoC container; | |
| 54 | // its fields are populated by the driver afterwards, so this | |
| 55 | // captures the reference in init and reads fields at walk time. | |
| 56 | // The `--no-warn-*` opt-outs for the definite-return / | |
| 57 | // definite-assignment / possible-null-dereference / non- | |
| 58 | // optional-by-default flow warnings are read straight off it. | |
| 59 | _build_flags: Compiler.GLOBAL_BUILD_FLAGS; | |
| 60 | _variable_left_state: VARIABLE_LEFT_STATE_STORE; | |
| 61 | ||
| 62 | _pragma_scope_stack: PRAGMA_SCOPE_STACK; | |
| 63 | _attribute_resolver: ATTRIBUTE_RESOLVER; | |
| 64 | ||
| 65 | // Depth-counter for nested `let await` walks. When > 0, | |
| 66 | // Flow-sensitive narrowing — holds the narrowing environment | |
| 67 | // in force at the current walk point and keeps each narrowed | |
| 68 | // variable's `.type` reconciled to it. See | |
| 69 | // `docs/claude/flow-sensitive-narrowing.md`. | |
| 70 | _flow: NARROWING_FLOW; | |
| 71 | ||
| 72 | // The target of the `ref` whose operand is currently being | |
| 73 | // walked. An operand of `ref` is an address, not a value read, | |
| 74 | // so its identifier load must skip the definite-assignment | |
| 75 | // check: the read-or-write decision belongs to the resolved | |
| 76 | // call, in `note_reference_arguments`. Set in `pre(REFERENCE)`, | |
| 77 | // cleared in `visit(reference)`. | |
| 78 | _reference_operand_target: Semantic.Symbols.Symbol?; | |
| 79 | ||
| 80 | // Pure analysis of boolean conditions into then/else | |
| 81 | // narrowing environments, plus the variant-type helpers. | |
| 82 | _condition_analyzer: CONDITION_ANALYZER; | |
| 83 | ||
| 84 | // Stack of per-IF frames, one per IF currently being walked. | |
| 85 | // pre(IF) pushes; each branch's controlled walk records its | |
| 86 | // exit environment; visit(IF) joins them and pops. | |
| 87 | _if_flow_stack: Collections.LIST[IF_FLOW_FRAME]; | |
| 88 | ||
| 89 | // Stack of loop kill-set environments, one per `while`/`do` | |
| 90 | // currently being walked. pre(DO) computes and pushes; | |
| 91 | // visit(DO) pops to restore the after-loop environment. | |
| 92 | _loop_kept_stack: Collections.LIST[NARROW_ENV]; | |
| 93 | ||
| 94 | // Per-ASSERT flag from the controlled walk in pre(ASSERT): | |
| 95 | // true when the condition's own walk killed heap facts, so | |
| 96 | // visit(ASSERT) drops them from the fall-through narrowing. | |
| 97 | // A stack because a block expression inside an assert's | |
| 98 | // condition or message can contain another assert. | |
| 99 | _assert_condition_killed_stack: Collections.LIST[bool]; | |
| 100 | ||
| 101 | // Stack of per-`try` frames, one per `try` currently being | |
| 102 | // walked. pre(TRY) pushes; visit(TRY) pops. The | |
| 103 | // definite-assignment facts established before a try survive | |
| 104 | // it; the try / catch bodies' own narrowing is discarded | |
| 105 | // conservatively (an exception can leave the body anywhere). | |
| 106 | _try_flow_stack: Collections.LIST[TRY_FLOW_FRAME]; | |
| 107 | ||
| 108 | // Stack of `val ... lav` blocks currently being walked. A | |
| 109 | // `return E` inside a val-block targets the innermost — top | |
| 110 | // of stack — block rather than the enclosing function. The | |
| 111 | // block accumulates return types into its `return_types` | |
| 112 | // list; visit(VAL_BLOCK) LUBs them with the tail expression's | |
| 113 | // value type to settle the block's own value type. | |
| 114 | // | |
| 115 | // A `null` entry is a function-literal boundary marker: | |
| 116 | // returns inside a nested function literal must target that | |
| 117 | // function, not an outer val-block, so pre(FUNCTION) pushes | |
| 118 | // null before walking the literal's body and visit(FUNCTION) | |
| 119 | // pops it. The lookup at the top returns null on encounter, | |
| 120 | // so the return falls through to the function-return path. | |
| 121 | _val_block_stack: Collections.LIST[Trees.Expressions.VAL_BLOCK?]; | |
| 122 | ||
| 123 | // Public accessor used by compile_bindings.pre_return / | |
| 124 | // visit_return to decide whether a `return` targets a | |
| 125 | // val-block (top of stack) or the enclosing function (empty | |
| 126 | // stack, or the top is a function-boundary null marker). | |
| 127 | innermost_val_block: Trees.Expressions.VAL_BLOCK? => | |
| 128 | if _val_block_stack.count > 0 then | |
| 129 | _val_block_stack[_val_block_stack.count - 1] | |
| 130 | else | |
| 131 | null | |
| 132 | fi; | |
| 133 | ||
| 134 | current_statement_list: Trees.Statements.LIST; | |
| 135 | ||
| 136 | init( | |
| 137 | logger: Logger, | |
| 138 | symbol_table: Semantic.SYMBOL_TABLE, | |
| 139 | namespaces: Semantic.NAMESPACES, | |
| 140 | symbol_loader: Semantic.SYMBOL_LOADER, | |
| 141 | innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup, | |
| 142 | function_caller: Semantic.FUNCTION_CALLER, | |
| 143 | type_caster: Semantic.TYPE_CASTER, | |
| 144 | task_conversion: Semantic.TASK_CONVERSION, | |
| 145 | overload_resolver: Semantic.OVERLOAD_RESOLVER, | |
| 146 | symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS, | |
| 147 | context: IR.CONTEXT, | |
| 148 | value_converter: VALUE_CONVERTER, | |
| 149 | value_boxer: VALUE_BOXER, | |
| 150 | build_flags: Compiler.GLOBAL_BUILD_FLAGS, | |
| 151 | variable_left_state: VARIABLE_LEFT_STATE_STORE | |
| 152 | ) | |
| 153 | is | |
| 154 | super.init(logger, symbol_table, namespaces); | |
| 155 | ||
| 156 | _logger = logger; | |
| 157 | _symbol_table = symbol_table; | |
| 158 | _symbol_loader = symbol_loader; | |
| 159 | _innate_symbol_lookup = innate_symbol_lookup; | |
| 160 | _function_caller = function_caller; | |
| 161 | _type_caster = type_caster; | |
| 162 | _overload_resolver = overload_resolver; | |
| 163 | _owner_type_arg_specializer = Semantic.OWNER_TYPE_ARG_SPECIALIZER(); | |
| 164 | _owner_constraint_specializer = Semantic.OWNER_CONSTRAINT_SPECIALIZER(); | |
| 165 | _under_determination_detector = Semantic.UNDER_DETERMINATION_DETECTOR(); | |
| 166 | _type_arg_placeholder_registry = Semantic.TYPE_ARG_PLACEHOLDER_REGISTRY(symbol_table); | |
| 167 | _unit_variant_constructor = Semantic.UNIT_VARIANT_CONSTRUCTOR(_owner_constraint_specializer, _type_arg_placeholder_registry, function_caller); | |
| 168 | _closure_arg_resolver = Semantic.CLOSURE_ARG_RESOLVER(logger); | |
| 169 | _numeric_literal_classifier = NUMERIC_LITERAL_CLASSIFIER(logger, innate_symbol_lookup); | |
| 170 | _literals = COMPILE_LITERALS(logger, innate_symbol_lookup, _numeric_literal_classifier); | |
| 171 | _symbol_use_locations = symbol_use_locations; | |
| 172 | _value_converter = value_converter; | |
| 173 | _value_boxer = value_boxer; | |
| 174 | _build_flags = build_flags; | |
| 175 | _variable_left_state = variable_left_state; | |
| 176 | ||
| 177 | _tuples = COMPILE_TUPLES(logger, innate_symbol_lookup, value_boxer, self); | |
| 178 | ||
| 179 | _flow = NARROWING_FLOW(logger); | |
| 180 | _condition_analyzer = CONDITION_ANALYZER( | |
| 181 | expr => try_get_narrowing_target(expr), | |
| 182 | expr => try_build_access_path(expr), | |
| 183 | logger | |
| 184 | ); | |
| 185 | ||
| 186 | _access = COMPILE_ACCESS( | |
| 187 | logger, | |
| 188 | symbol_table, | |
| 189 | symbol_loader, | |
| 190 | symbol_use_locations, | |
| 191 | innate_symbol_lookup, | |
| 192 | overload_resolver, | |
| 193 | function_caller, | |
| 194 | _unit_variant_constructor, | |
| 195 | _flow, | |
| 196 | _condition_analyzer, | |
| 197 | _build_flags, | |
| 198 | self | |
| 199 | ); | |
| 200 | ||
| 201 | _calls = COMPILE_CALLS( | |
| 202 | logger, | |
| 203 | symbol_table, | |
| 204 | symbol_use_locations, | |
| 205 | innate_symbol_lookup, | |
| 206 | overload_resolver, | |
| 207 | function_caller, | |
| 208 | _owner_constraint_specializer, | |
| 209 | _owner_type_arg_specializer, | |
| 210 | _under_determination_detector, | |
| 211 | _type_arg_placeholder_registry, | |
| 212 | _access, | |
| 213 | self, | |
| 214 | _flow, | |
| 215 | symbol_loader | |
| 216 | ); | |
| 217 | ||
| 218 | _operators = COMPILE_OPERATORS( | |
| 219 | logger, | |
| 220 | symbol_table, | |
| 221 | innate_symbol_lookup, | |
| 222 | overload_resolver, | |
| 223 | symbol_use_locations, | |
| 224 | function_caller, | |
| 225 | _calls, | |
| 226 | _flow, | |
| 227 | _condition_analyzer, | |
| 228 | self | |
| 229 | ); | |
| 230 | ||
| 231 | _generic_application = COMPILE_GENERIC_APPLICATION( | |
| 232 | logger, | |
| 233 | symbol_use_locations, | |
| 234 | symbol_loader, | |
| 235 | _unit_variant_constructor, | |
| 236 | self | |
| 237 | ); | |
| 238 | ||
| 239 | _if_flow_stack = Collections.LIST[IF_FLOW_FRAME](); | |
| 240 | _loop_kept_stack = Collections.LIST[NARROW_ENV](); | |
| 241 | _assert_condition_killed_stack = Collections.LIST[bool](); | |
| 242 | _try_flow_stack = Collections.LIST[TRY_FLOW_FRAME](); | |
| 243 | _val_block_stack = Collections.LIST[Trees.Expressions.VAL_BLOCK?](); | |
| 244 | ||
| 245 | _conditionals = COMPILE_CONDITIONALS( | |
| 246 | logger, | |
| 247 | innate_symbol_lookup, | |
| 248 | _flow, | |
| 249 | _condition_analyzer, | |
| 250 | _if_flow_stack, | |
| 251 | self, | |
| 252 | build_flags, | |
| 253 | variable_left_state | |
| 254 | ); | |
| 255 | ||
| 256 | _loops = COMPILE_LOOPS_AND_EXCEPTIONS( | |
| 257 | logger, | |
| 258 | innate_symbol_lookup, | |
| 259 | _flow, | |
| 260 | _condition_analyzer, | |
| 261 | _conditionals, | |
| 262 | self, | |
| 263 | _try_flow_stack, | |
| 264 | _loop_kept_stack | |
| 265 | ); | |
| 266 | ||
| 267 | _pure_slots = PURE_SLOT_CHECK(logger, _flow, _build_flags); | |
| 268 | ||
| 269 | _bindings = COMPILE_BINDINGS( | |
| 270 | logger, | |
| 271 | symbol_table, | |
| 272 | symbol_use_locations, | |
| 273 | innate_symbol_lookup, | |
| 274 | task_conversion, | |
| 275 | _flow, | |
| 276 | _build_flags, | |
| 277 | value_boxer, | |
| 278 | _pure_slots, | |
| 279 | self | |
| 280 | ); | |
| 281 | ||
| 282 | _attribute_resolver = ATTRIBUTE_RESOLVER(logger, innate_symbol_lookup, overload_resolver, self); | |
| 283 | ||
| 284 | _lambdas = COMPILE_LAMBDAS( | |
| 285 | logger, | |
| 286 | symbol_table, | |
| 287 | symbol_use_locations, | |
| 288 | symbol_loader, | |
| 289 | innate_symbol_lookup, | |
| 290 | task_conversion, | |
| 291 | _closure_arg_resolver, | |
| 292 | _type_arg_placeholder_registry, | |
| 293 | _flow, | |
| 294 | _build_flags, | |
| 295 | self, | |
| 296 | _attribute_resolver | |
| 297 | ); | |
| 298 | ||
| 299 | _pragma_scope_stack = PRAGMA_SCOPE_STACK(); | |
| 300 | si | |
| 301 | ||
| 302 | // If `expr` is an unqualified identifier resolving (in the | |
| 303 | // current scope) to a narrowing subject, return that symbol; | |
| 304 | // null otherwise. Subjects are local variables, fields, and | |
| 305 | // properties whose getter is proven store-free — for those, | |
| 306 | // re-reading under an unchanged heap repeats the same | |
| 307 | // presence and dynamic-type answers, and the flow transfers | |
| 308 | // (on_call / on_heap_store) forget them the moment the heap | |
| 309 | // may have changed. A property with an unproven getter never | |
| 310 | // narrows. Qualified and member-access targets are deferred. | |
| 311 | // Warn when a value is dereferenced through an optional receiver | |
| 312 | // not proven to hold a value here. Optionality is one concept | |
| 313 | // regardless of representation — a reference `T?`, a value-type | |
| 314 | // NULLABLE[T], or an unconstrained MAYBE[T] all answer is_optional | |
| 315 | // and are treated alike. Member access, indexing and `for ... in` | |
| 316 | // all reach the receiver's members off its non-optional shape, so | |
| 317 | // an un-narrowed optional receiver may be absent at the | |
| 318 | // dereference. A receiver proven present by flow narrowing is | |
| 319 | // safe. On by default; `--no-warn-null-deref` opts out. | |
| 320 | check_receiver_present(receiver: Trees.Expressions.Expression) is | |
| 321 | if _build_flags.no_warn_null_deref then | |
| 322 | return; | |
| 323 | fi | |
| 324 | ||
| 325 | let value = receiver.value; | |
| 326 | ||
| 327 | if !value? then | |
| 328 | return; | |
| 329 | fi | |
| 330 | ||
| 331 | let type = value.type; | |
| 332 | ||
| 333 | if !type? \/ !type.is_optional then | |
| 334 | return; | |
| 335 | fi | |
| 336 | ||
| 337 | let target = try_get_narrowing_target(receiver); | |
| 338 | ||
| 339 | if target? /\ _flow.is_non_null(target) then | |
| 340 | return; | |
| 341 | fi | |
| 342 | ||
| 343 | let subject = if target? then target.name else "receiver" fi; | |
| 344 | ||
| 345 | _logger.warn(receiver.location, "null-deref", "{subject} may not hold a value here"); | |
| 346 | si | |
| 347 | ||
| 348 | try_get_narrowing_target(expr: Trees.Expressions.Expression?) -> Semantic.Symbols.Symbol? is | |
| 349 | if !expr? then | |
| 350 | return null; | |
| 351 | fi | |
| 352 | ||
| 353 | // `self` narrows like a local, keyed on its instance-context | |
| 354 | // symbol. It can't be reassigned and an object's concrete type | |
| 355 | // is fixed for its lifetime, so a narrowing on `self` is never | |
| 356 | // killed by a call - it is sounder to narrow than a local. | |
| 357 | if isa Trees.Expressions.SELF(expr) then | |
| 358 | return current_instance_context; | |
| 359 | fi | |
| 360 | ||
| 361 | if !isa Trees.Expressions.IDENTIFIER(expr) then | |
| 362 | return null; | |
| 363 | fi | |
| 364 | ||
| 365 | let identifier_expr = expr; | |
| 366 | ||
| 367 | if identifier_expr.identifier.is_qualified then | |
| 368 | return null; | |
| 369 | fi | |
| 370 | ||
| 371 | let symbol = find(identifier_expr.identifier); | |
| 372 | ||
| 373 | if !symbol? then | |
| 374 | return null; | |
| 375 | fi | |
| 376 | ||
| 377 | if isa Semantic.Symbols.Variable(symbol) then | |
| 378 | return symbol; | |
| 379 | fi | |
| 380 | ||
| 381 | if isa Semantic.Symbols.Property(symbol) then | |
| 382 | let property = cast Semantic.Symbols.Property(symbol); | |
| 383 | ||
| 384 | if property.read_function? /\ property.read_function.is_store_free then | |
| 385 | return property; | |
| 386 | fi | |
| 387 | fi | |
| 388 | ||
| 389 | return null; | |
| 390 | si | |
| 391 | ||
| 392 | // Build the re-readable access path an expression names, or | |
| 393 | // null when it isn't one: a local- or field-rooted chain of | |
| 394 | // instance field / store-free-getter reads (`receiver.prop`, | |
| 395 | // `receiver.a.b`). Calls, indexers, `?.`, qualified names, | |
| 396 | // self / super roots, struct receivers and properties with | |
| 397 | // unproven getters are all excluded — the result must be a | |
| 398 | // location that re-reads to the same presence answer under an | |
| 399 | // unchanged heap, and the flow transfers drop its facts the | |
| 400 | // moment the heap may have changed. Each hop is re-resolved | |
| 401 | // through `find` / `find_member` so the check site (`x.y?`) | |
| 402 | // and every use site (`x.y`) agree on the same root Variable | |
| 403 | // and member Symbols by identity, and so key the same | |
| 404 | // presence fact. | |
| 405 | try_build_access_path(expr: Trees.Expressions.Expression?) -> ACCESS_PATH? is | |
| 406 | if !expr? \/ !isa Trees.Expressions.MEMBER(expr) then | |
| 407 | return null; | |
| 408 | fi | |
| 409 | ||
| 410 | let member = cast Trees.Expressions.MEMBER(expr); | |
| 411 | ||
| 412 | if member.is_coalesce \/ member.identifier.is_qualified then | |
| 413 | return null; | |
| 414 | fi | |
| 415 | ||
| 416 | let root: Semantic.Symbols.Variable? mut = null; | |
| 417 | let members = Collections.LIST[Semantic.Symbols.Symbol](); | |
| 418 | ||
| 419 | if isa Trees.Expressions.IDENTIFIER(member.left) then | |
| 420 | let id = cast Trees.Expressions.IDENTIFIER(member.left); | |
| 421 | ||
| 422 | if id.identifier.is_qualified then | |
| 423 | return null; | |
| 424 | fi | |
| 425 | ||
| 426 | let symbol = find(id.identifier); | |
| 427 | ||
| 428 | if !symbol? \/ !isa Semantic.Symbols.Variable(symbol) then | |
| 429 | return null; | |
| 430 | fi | |
| 431 | ||
| 432 | root = cast Semantic.Symbols.Variable(symbol); | |
| 433 | else | |
| 434 | let left_path = try_build_access_path(member.left); | |
| 435 | ||
| 436 | if !left_path? then | |
| 437 | return null; | |
| 438 | fi | |
| 439 | ||
| 440 | root = left_path.root; | |
| 441 | ||
| 442 | for m in left_path.members do | |
| 443 | members.add(m); | |
| 444 | od | |
| 445 | fi | |
| 446 | ||
| 447 | let left_value = member.left.value; | |
| 448 | ||
| 449 | if !left_value? \/ !left_value.type? then | |
| 450 | return null; | |
| 451 | fi | |
| 452 | ||
| 453 | let left_type = left_value.type; | |
| 454 | ||
| 455 | if !isa Semantic.Types.NAMED(left_type) \/ left_type.is_value_type \/ left_type.scope == null then | |
| 456 | return null; | |
| 457 | fi | |
| 458 | ||
| 459 | let hop = left_type.find_member(member.identifier.name); | |
| 460 | ||
| 461 | if !hop? \/ !hop.is_instance then | |
| 462 | return null; | |
| 463 | fi | |
| 464 | ||
| 465 | if !hop.is_field then | |
| 466 | let property = cast Semantic.Symbols.Property?(hop); | |
| 467 | ||
| 468 | if !property? \/ !property.read_function? \/ !property.read_function.is_store_free then | |
| 469 | return null; | |
| 470 | fi | |
| 471 | fi | |
| 472 | ||
| 473 | members.add(hop); | |
| 474 | ||
| 475 | return ACCESS_PATH(root, members); | |
| 476 | si | |
| 477 | ||
| 478 | _check_pure_slots(location: LOCATION, value: IR.Values.Value?) is | |
| 479 | _pure_slots.check_call(location, value); | |
| 480 | si | |
| 481 | ||
| 482 | // Drop field narrows once an expression that lowers to a real | |
| 483 | // call or a construction has been compiled — its receiver and | |
| 484 | // arguments are already read, and the callee may have reassigned | |
| 485 | // a field through an aliased receiver. `location` is the source | |
| 486 | // site of the expression: a Call value carries no ambient | |
| 487 | // location of its own, so the kill hint must be anchored to the | |
| 488 | // syntax node rather than to `value.location`. | |
| 489 | _note_call(location: LOCATION, value: IR.Values.Value?) is | |
| 490 | if value? /\ value.is_state_changing_call then | |
| 491 | _flow.on_call(location); | |
| 492 | fi | |
| 493 | si | |
| 494 | ||
| 495 | // True iff `type` is a non-optional reference type — the kind | |
| 496 | // of slot the non-optional-by-default check guards. Value | |
| 497 | // types, `T?`, type variables, void and error/placeholder | |
| 498 | // types are all excluded. | |
| 499 | _is_non_optional_reference(type: Semantic.Types.Type?) -> bool => | |
| 500 | type? /\ type.is_named /\ | |
| 501 | !type.is_value_type /\ !type.is_optional /\ | |
| 502 | !type.is_type_variable /\ !type.is_void /\ | |
| 503 | !type.is_error /\ !type.is_inferred; | |
| 504 | ||
| 505 | // True iff a value of `source` static type is guaranteed | |
| 506 | // castable to `target` at runtime. The plain assignability | |
| 507 | // check covers the direct subtype case. For an INTERSECTION | |
| 508 | // source (produced by class+trait flow narrowing — the | |
| 509 | // `if isa T(x) then cast T(x)` idiom when T is a trait | |
| 510 | // cross-cutting x's declared type), the intersection value | |
| 511 | // IS every member, so a target assignable from any member | |
| 512 | // is guaranteed to succeed. | |
| 513 | _cast_target_covers_source(target: Semantic.Types.Type, source: Semantic.Types.Type) -> bool is | |
| 514 | if target.is_assignable_from(source) then | |
| 515 | return true; | |
| 516 | fi | |
| 517 | ||
| 518 | if isa Semantic.Types.INTERSECTION(source) then | |
| 519 | let intersection = source; | |
| 520 | ||
| 521 | for m in intersection.members do | |
| 522 | if target.is_assignable_from(m) then | |
| 523 | return true; | |
| 524 | fi | |
| 525 | od | |
| 526 | fi | |
| 527 | ||
| 528 | return false; | |
| 529 | si | |
| 530 | ||
| 531 | // Non-optional-by-default: warn when a `T?` local the flow | |
| 532 | // analysis has not proven present reaches a non-optional | |
| 533 | // reference slot. `x?` / `isa` / `if let` clear it; an | |
| 534 | // explicit `x!` is exempt — the user took responsibility. A | |
| 535 | // non-variable `T?` source — a call result, a field — is not | |
| 536 | // flow-tracked, so it stays silent rather than risk a false | |
| 537 | // positive. The bare `null` literal is handled separately, in | |
| 538 | // visit(NULL), via its constraint. On by default; | |
| 539 | // `--no-warn-non-optional` opts out. A warning, not an error, | |
| 540 | // during the migration; the end state is `T?` not | |
| 541 | // assignment-compatible with `T`. | |
| 542 | check_non_optional(target: Semantic.Types.Type?, source: Trees.Expressions.Expression?, location: Source.LOCATION) is | |
| 543 | if _build_flags.no_warn_non_optional \/ !target? \/ !source? then | |
| 544 | return; | |
| 545 | fi | |
| 546 | ||
| 547 | if !_is_non_optional_reference(target) then | |
| 548 | return; | |
| 549 | fi | |
| 550 | ||
| 551 | let value = source.value; | |
| 552 | ||
| 553 | if !value? \/ !value.type? then | |
| 554 | return; | |
| 555 | fi | |
| 556 | ||
| 557 | if value.type!.is_optional /\ !isa Trees.Expressions.UNWRAP(source) then | |
| 558 | let v = try_get_narrowing_target(source); | |
| 559 | ||
| 560 | if v? /\ !_flow.is_non_null(v) then | |
| 561 | _logger.warn(location, "non-optional", "{target} expected but {v.name} may not hold a value"); | |
| 562 | fi | |
| 563 | fi | |
| 564 | si | |
| 565 | ||
| 566 | // Statements.LIST.walk calls these around every child. We | |
| 567 | // push the child's source location onto the workspace | |
| 568 | // LOCATION_STACK; every IR Value constructed while that | |
| 569 | // statement is being walked picks it up as its ambient | |
| 570 | // location in Value.init(). Generic at the Node level — the | |
| 571 | // hook fires before any subtype dispatch. | |
| 572 | // | |
| 573 | // Skipped when `--debug` is off (the only consumer is the | |
| 574 | // .line emission gated on the same flag): leaves the stack | |
| 575 | // permanently empty so Value.init() captures null and the | |
| 576 | // ambient-location bookkeeping costs nothing. | |
| 577 | enter_node(node: Trees.Node) is | |
| 578 | if !_build_flags.want_debug then | |
| 579 | return; | |
| 580 | fi | |
| 581 | IoC.CONTAINER.instance.location_stack.push(node.location); | |
| 582 | si | |
| 583 | ||
| 584 | leave_node(node: Trees.Node) is | |
| 585 | if !_build_flags.want_debug then | |
| 586 | return; | |
| 587 | fi | |
| 588 | IoC.CONTAINER.instance.location_stack.pop(); | |
| 589 | si | |
| 590 | ||
| 591 | apply(root: Trees.Node) is | |
| 592 | assert _pragma_scope_stack.is_balanced; | |
| 593 | ||
| 594 | IR.LABEL.reset_id(); | |
| 595 | IR.LABEL.set_pass("E"); | |
| 596 | ||
| 597 | // Phantom Variables for unbound owner type-args at constructor | |
| 598 | // sites are cached on this visitor (not the AST), keyed by AST | |
| 599 | // node reference. The visitor instance is long-lived — one per | |
| 600 | // IoC container, not one per build — so cache entries from a | |
| 601 | // previous pass would still hit on the same AST nodes after a | |
| 602 | // re-compile, returning phantoms whose `_lub_map` references | |
| 603 | // Type instances from the previous build's symbol table (now | |
| 604 | // wiped by clear_symbols). Body-retry iterations of one | |
| 605 | // function need stable phantoms, but those happen inside | |
| 606 | // visit(function: FUNCTION) below this point, so clearing at | |
| 607 | // the start of apply is safe. | |
| 608 | _type_arg_placeholder_registry.clear(); | |
| 609 | ||
| 610 | // Narrowing stack must be empty at apply() boundaries — | |
| 611 | // every push has to be balanced by a release before its | |
| 612 | // owning AST exits. Reset defensively so a narrowing | |
| 613 | // leaked by an aborted earlier walk (early return / | |
| 614 | // exception) doesn't poison subsequent runs — restores | |
| 615 | // symbol types and empties the environment. | |
| 616 | _flow.reset(); | |
| 617 | _if_flow_stack.clear(); | |
| 618 | _loop_kept_stack.clear(); | |
| 619 | _try_flow_stack.clear(); | |
| 620 | _assert_condition_killed_stack.clear(); | |
| 621 | ||
| 622 | root.walk(self); | |
| 623 | ||
| 624 | assert _pragma_scope_stack.is_balanced; | |
| 625 | assert _if_flow_stack.count == 0 | |
| 626 | else "if-flow frame stack leaked: {_if_flow_stack.count} unreleased"; | |
| 627 | assert _loop_kept_stack.count == 0 | |
| 628 | else "loop-kept env stack leaked: {_loop_kept_stack.count} unreleased"; | |
| 629 | assert _try_flow_stack.count == 0 | |
| 630 | else "try-flow frame stack leaked: {_try_flow_stack.count} unreleased"; | |
| 631 | assert _assert_condition_killed_stack.count == 0 | |
| 632 | else "assert-condition flag stack leaked: {_assert_condition_killed_stack.count} unreleased"; | |
| 633 | si | |
| 634 | ||
| 635 | get_zero_argument_function(type: Type, name: string) -> Semantic.Symbols.Function? is | |
| 636 | let symbol = type.find_member(name); | |
| 637 | ||
| 638 | if symbol? /\ isa Semantic.Symbols.FUNCTION_GROUP(symbol) then | |
| 639 | let function_group = symbol; | |
| 640 | ||
| 641 | for f in function_group.functions do | |
| 642 | if f.arguments.count == 0 then | |
| 643 | return f; | |
| 644 | fi | |
| 645 | od | |
| 646 | fi | |
| 647 | return null; | |
| 648 | si | |
| 649 | ||
| 650 | set_iterator_for(`for: Trees.Statements.FOR, type: Type, recursing: bool) -> bool is | |
| 651 | let expression = `for.expression!; | |
| 652 | ||
| 653 | expression.value!.check_is_consumable(_logger, expression.location); | |
| 654 | ||
| 655 | if type.is_error then | |
| 656 | return false; | |
| 657 | fi | |
| 658 | ||
| 659 | let move_next = get_zero_argument_function(type, "move_next"); | |
| 660 | ||
| 661 | if move_next? then | |
| 662 | let read_current mut = get_zero_argument_function(type, "$get_current"); | |
| 663 | ||
| 664 | if !read_current? then | |
| 665 | read_current = get_zero_argument_function(type, "$get_Current"); | |
| 666 | fi | |
| 667 | ||
| 668 | if !read_current? then | |
| 669 | _logger.error(expression.location, "incomplete iterator type (has move_next method but no current property)"); | |
| 670 | return false; | |
| 671 | fi | |
| 672 | ||
| 673 | `for.move_next = move_next; | |
| 674 | `for.read_current = read_current; | |
| 675 | ||
| 676 | return true; | |
| 677 | elif !recursing then | |
| 678 | let read_iterator = get_zero_argument_function(type, "$get_iterator"); | |
| 679 | ||
| 680 | if read_iterator? then | |
| 681 | `for.read_iterator = read_iterator; | |
| 682 | ||
| 683 | return set_iterator_for(`for, read_iterator.return_type!, true); | |
| 684 | fi | |
| 685 | fi | |
| 686 | ||
| 687 | _logger.error(expression.location, "not iterable"); | |
| 688 | ||
| 689 | return false; | |
| 690 | si | |
| 691 | ||
| 692 | // Recognise a fusible Pipe[T] chain on the loop expression and, | |
| 693 | // if found, resolve the source's own iterator members (exactly | |
| 694 | // as a plain `for x in source` would) so the IL pass can drive | |
| 695 | // them directly, skipping the pipe objects entirely. Returns | |
| 696 | // null - fall back to the normal iterator loop - when the chain | |
| 697 | // isn't fusible or the source doesn't expose a complete iterator | |
| 698 | // surface. | |
| 699 | _recognize_pipe_fusion(`for: Trees.Statements.FOR) -> PIPE_FUSION? is | |
| 700 | // `@suppress("pipe-fusion")` disables fusion for a scope, so the | |
| 701 | // loop runs the ordinary pipe-object lowering - used where the | |
| 702 | // real Pipe implementations must be exercised (the Ghul.Pipes | |
| 703 | // unit tests) rather than the fused equivalent. | |
| 704 | if _logger.is_suppressed("pipe-fusion", `for.location) then | |
| 705 | return null; | |
| 706 | fi | |
| 707 | ||
| 708 | let fusion = PIPE_FUSION_RECOGNIZER(_innate_symbol_lookup).recognize(`for); | |
| 709 | ||
| 710 | if !_resolve_chain_fusion(fusion) then | |
| 711 | return null; | |
| 712 | fi | |
| 713 | ||
| 714 | return fusion; | |
| 715 | si | |
| 716 | ||
| 717 | // Finish a recognised chain plan: resolve the source's own iterator | |
| 718 | // members, build the `isa Pipe` guard type for a non-sealed source, | |
| 719 | // and resolve each stage (index constructor, or inline a literal | |
| 720 | // lambda). Returns false when any of that fails, in which case the | |
| 721 | // chain must not be fused. Shared by the `for` and consumer paths. | |
| 722 | _resolve_chain_fusion(fusion: PIPE_FUSION?) -> bool => | |
| 723 | _resolve_chain_fusion(fusion, true); | |
| 724 | ||
| 725 | // `inline_stages` false skips the inline re-walk of map/filter lambda | |
| 726 | // bodies - consumer fusion calls the stage delegates rather than | |
| 727 | // inlining them, and the re-walk can disturb a stage lambda whose | |
| 728 | // body nests a capturing closure. | |
| 729 | _resolve_chain_fusion(fusion: PIPE_FUSION?, inline_stages: bool) -> bool is | |
| 730 | if !fusion? then | |
| 731 | return false; | |
| 732 | fi | |
| 733 | ||
| 734 | let source_value = fusion.source.value; | |
| 735 | ||
| 736 | if !source_value? \/ !source_value.type? then | |
| 737 | return false; | |
| 738 | fi | |
| 739 | ||
| 740 | if !_resolve_fusion_source_iterator(fusion, source_value.type!, false) then | |
| 741 | return false; | |
| 742 | fi | |
| 743 | ||
| 744 | if fusion.needs_guard then | |
| 745 | let guard_type = _fusion_guard_type(fusion); | |
| 746 | ||
| 747 | if !guard_type? then | |
| 748 | return false; | |
| 749 | fi | |
| 750 | ||
| 751 | fusion.guard_isa_type = guard_type; | |
| 752 | fi | |
| 753 | ||
| 754 | for stage in fusion.stages_outermost_first do | |
| 755 | if stage.is_countdown then | |
| 756 | // take/skip carry an int count evaluated once into the | |
| 757 | // running counter; nothing to inline or resolve, but the | |
| 758 | // count must have compiled to a value. | |
| 759 | let count_value = stage.argument!.value; | |
| 760 | ||
| 761 | if !count_value? \/ !count_value.type? then | |
| 762 | return false; | |
| 763 | fi | |
| 764 | elif stage.is_index then | |
| 765 | stage.indexed_value_constructor = _resolve_index_constructor(stage.indexed_value_type!); | |
| 766 | ||
| 767 | if !stage.is_index_ready then | |
| 768 | return false; | |
| 769 | fi | |
| 770 | elif inline_stages then | |
| 771 | _try_inline_stage(stage); | |
| 772 | fi | |
| 773 | od | |
| 774 | ||
| 775 | return true; | |
| 776 | si | |
| 777 | ||
| 778 | // Determine whether `call` is a terminal Pipe consumer, written | |
| 779 | // either as a `Pipe[T]` method call (`chain.count()`) or as a | |
| 780 | // `Ghul.Pipes` free-function call reached via |> thread-first | |
| 781 | // desugaring (`chain |> count()`, indistinguishable by the time | |
| 782 | // this pass runs from a plain `count(chain)` global-function | |
| 783 | // call). Returns the consumer's own name, the chain expression | |
| 784 | // feeding it, and how many of `call`'s own arguments belong to the | |
| 785 | // chain rather than the consumer (0 for the method form; 1 for the | |
| 786 | // free-function form, where the chain is spliced in as argument 0). | |
| 787 | _consumer_call_shape(call: Trees.Expressions.CALL) -> (name: string, chain_before: Trees.Expressions.Expression, arg_offset: int)? is | |
| 788 | if isa Trees.Expressions.MEMBER(call.function) then | |
| 789 | let member = cast Trees.Expressions.MEMBER(call.function); | |
| 790 | ||
| 791 | return (member.identifier.name, member.left, 0); | |
| 792 | fi | |
| 793 | ||
| 794 | let value = call.value; | |
| 795 | ||
| 796 | if !isa IR.Values.Call.GLOBAL(value) then | |
| 797 | return null; | |
| 798 | fi | |
| 799 | ||
| 800 | let global_call = cast IR.Values.Call.GLOBAL(value); | |
| 801 | let qualified_name = global_call.function.qualified_name; | |
| 802 | ||
| 803 | if !qualified_name.starts_with("Ghul.Pipes.") then | |
| 804 | return null; | |
| 805 | fi | |
| 806 | ||
| 807 | if call.arguments.expressions.count == 0 then | |
| 808 | return null; | |
| 809 | fi | |
| 810 | ||
| 811 | let short_name = qualified_name.substring("Ghul.Pipes.".length); | |
| 812 | ||
| 813 | return (short_name, call.arguments.expressions[0], 1); | |
| 814 | si | |
| 815 | ||
| 816 | // Recognise a terminal Pipe consumer (`chain.count()`, or the | |
| 817 | // free-function equivalent `chain |> count()`) whose chain is | |
| 818 | // fusible, and build the FUSED_CONSUMER value the call lowers to. | |
| 819 | // Every map/filter/take/skip stage fuses (its delegate is called, | |
| 820 | // not inlined); an `index` stage in the chain falls back to the | |
| 821 | // pipe objects. | |
| 822 | _recognize_consumer_fusion(call: Trees.Expressions.CALL) -> IR.Values.FUSED_CONSUMER? is | |
| 823 | if _logger.is_suppressed("pipe-fusion", call.location) then | |
| 824 | return null; | |
| 825 | fi | |
| 826 | ||
| 827 | let shape = _consumer_call_shape(call); | |
| 828 | ||
| 829 | if !shape? then | |
| 830 | return null; | |
| 831 | fi | |
| 832 | ||
| 833 | let name = shape.name; | |
| 834 | let chain_before = shape.chain_before; | |
| 835 | let arg_offset = shape.arg_offset; | |
| 836 | let arg_count = call.arguments.expressions.count - arg_offset; | |
| 837 | ||
| 838 | let consumer_kind: string mut; | |
| 839 | ||
| 840 | if name =~ "count" /\ arg_count == 0 then | |
| 841 | consumer_kind = "count"; | |
| 842 | elif name =~ "any" /\ arg_count == 1 then | |
| 843 | consumer_kind = "any"; | |
| 844 | elif name =~ "all" /\ arg_count == 1 then | |
| 845 | consumer_kind = "all"; | |
| 846 | elif name =~ "for_each" /\ arg_count == 1 then | |
| 847 | consumer_kind = "for_each"; | |
| 848 | elif name =~ "reduce" /\ arg_count == 2 then | |
| 849 | consumer_kind = "reduce"; | |
| 850 | elif name =~ "find" /\ arg_count == 1 then | |
| 851 | consumer_kind = "find"; | |
| 852 | elif name =~ "first" /\ arg_count == 0 then | |
| 853 | consumer_kind = "first"; | |
| 854 | elif name =~ "collect_list" /\ arg_count == 0 then | |
| 855 | consumer_kind = "collect"; | |
| 856 | else | |
| 857 | return null; | |
| 858 | fi | |
| 859 | ||
| 860 | if !call.value? \/ !call.value.type? then | |
| 861 | return null; | |
| 862 | fi | |
| 863 | ||
| 864 | let fusion = PIPE_FUSION_RECOGNIZER(_innate_symbol_lookup).recognize_any(chain_before); | |
| 865 | ||
| 866 | if !_resolve_chain_fusion(fusion, false) then | |
| 867 | return null; | |
| 868 | fi | |
| 869 | ||
| 870 | let plan = fusion!; | |
| 871 | ||
| 872 | // Fuse map/filter/take/skip stages; an index stage in the chain | |
| 873 | // still falls back to the pipe objects (its INDEXED_VALUE build | |
| 874 | // is only wired into the `for` path). | |
| 875 | for stage in plan.stages_outermost_first do | |
| 876 | if stage.is_index then | |
| 877 | return null; | |
| 878 | fi | |
| 879 | od | |
| 880 | ||
| 881 | let id = _next_fused_local_id(); | |
| 882 | ||
| 883 | let iterator_il_name = "'.fc_iter.{id}'"; | |
| 884 | let element_il_name = "'.fc_element.{id}'"; | |
| 885 | ||
| 886 | let source_read_iterator = plan.source_read_iterator; | |
| 887 | ||
| 888 | let iterator_type = | |
| 889 | if source_read_iterator? then | |
| 890 | source_read_iterator.return_type! | |
| 891 | else | |
| 892 | plan.source.value!.type! | |
| 893 | fi; | |
| 894 | ||
| 895 | let iterator_init = | |
| 896 | if source_read_iterator? then | |
| 897 | source_read_iterator.call(plan.source.location, plan.source.value!, Collections.LIST[IR.Values.Value](0), null, _function_caller) | |
| 898 | else | |
| 899 | plan.source.value! | |
| 900 | fi; | |
| 901 | ||
| 902 | let iterator_load = cast IR.Values.Value(IR.Values.Load.TEMP(iterator_il_name, iterator_type)); | |
| 903 | ||
| 904 | let move_next = plan.source_move_next!.call(plan.source.location, iterator_load, Collections.LIST[IR.Values.Value](0), null, _function_caller); | |
| 905 | let read_current = plan.source_read_current!.call(plan.source.location, iterator_load, Collections.LIST[IR.Values.Value](0), null, _function_caller); | |
| 906 | ||
| 907 | let element_type = plan.source_read_current!.return_type!; | |
| 908 | ||
| 909 | let built = _build_consumer_stages(plan, element_il_name, element_type); | |
| 910 | ||
| 911 | let result_il_name = "'.fc_result.{id}'"; | |
| 912 | ||
| 913 | let fused = IR.Values.FUSED_CONSUMER( | |
| 914 | call.value!.type!, | |
| 915 | plan.source.value!, | |
| 916 | call.value!, | |
| 917 | iterator_init, | |
| 918 | source_read_iterator?, | |
| 919 | iterator_il_name, | |
| 920 | iterator_type, | |
| 921 | move_next, | |
| 922 | read_current, | |
| 923 | element_il_name, | |
| 924 | element_type, | |
| 925 | built.ops, | |
| 926 | built.final_il_name, | |
| 927 | built.final_type, | |
| 928 | consumer_kind, | |
| 929 | result_il_name | |
| 930 | ); | |
| 931 | ||
| 932 | let final_element_load = cast IR.Values.Value(IR.Values.Load.TEMP(built.final_il_name, built.final_type)); | |
| 933 | ||
| 934 | if consumer_kind =~ "any" \/ consumer_kind =~ "all" \/ consumer_kind =~ "for_each" \/ consumer_kind =~ "find" then | |
| 935 | // Hoist the predicate / action delegate and apply it to the | |
| 936 | // surviving element per iteration. | |
| 937 | let function = call.arguments.expressions[arg_offset].value!; | |
| 938 | let func_type = function.type!; | |
| 939 | ||
| 940 | let arg_il_name = "'.fc_arg.{id}'"; | |
| 941 | ||
| 942 | let result_type = | |
| 943 | if func_type.is_action then | |
| 944 | _innate_symbol_lookup.get_void_type(); | |
| 945 | else | |
| 946 | func_type.arguments[func_type.arguments.count - 1]; | |
| 947 | fi; | |
| 948 | ||
| 949 | let call_arguments = Collections.LIST[IR.Values.Value](); | |
| 950 | call_arguments.add(final_element_load); | |
| 951 | ||
| 952 | fused.consumer_arg_il_name = arg_il_name; | |
| 953 | fused.consumer_arg_type = func_type; | |
| 954 | fused.consumer_arg_init = function; | |
| 955 | fused.consumer_apply = IR.Values.Call.CLOSURE( | |
| 956 | IR.Values.Load.TEMP(arg_il_name, func_type), result_type, func_type.is_action, func_type, call_arguments); | |
| 957 | elif consumer_kind =~ "reduce" then | |
| 958 | // running = accumulator(running, element). | |
| 959 | let seed = call.arguments.expressions[arg_offset].value!; | |
| 960 | let accumulator = call.arguments.expressions[arg_offset + 1].value!; | |
| 961 | let func_type = accumulator.type!; | |
| 962 | ||
| 963 | let arg_il_name = "'.fc_arg.{id}'"; | |
| 964 | let running_type = call.value!.type!; | |
| 965 | ||
| 966 | let call_arguments = Collections.LIST[IR.Values.Value](); | |
| 967 | call_arguments.add(cast IR.Values.Value(IR.Values.Load.TEMP(result_il_name, running_type))); | |
| 968 | call_arguments.add(final_element_load); | |
| 969 | ||
| 970 | fused.seed_init = seed; | |
| 971 | fused.consumer_arg_il_name = arg_il_name; | |
| 972 | fused.consumer_arg_type = func_type; | |
| 973 | fused.consumer_arg_init = accumulator; | |
| 974 | fused.consumer_apply = IR.Values.Call.CLOSURE( | |
| 975 | IR.Values.Load.TEMP(arg_il_name, func_type), running_type, false, func_type, call_arguments); | |
| 976 | fi | |
| 977 | ||
| 978 | // find / first return a MAYBE: seed with the empty MAYBE, and | |
| 979 | // wrap the surviving element with MAYBE(element). | |
| 980 | if consumer_kind =~ "find" \/ consumer_kind =~ "first" then | |
| 981 | let maybe_type = call.value!.type!; | |
| 982 | ||
| 983 | let empty_ctor = _resolve_ctor_by_arity(maybe_type, 0); | |
| 984 | let wrap_ctor = _resolve_ctor_by_arity(maybe_type, 1); | |
| 985 | ||
| 986 | if !empty_ctor? \/ !wrap_ctor? then | |
| 987 | return null; | |
| 988 | fi | |
| 989 | ||
| 990 | fused.seed_init = IR.Values.NEW(maybe_type, empty_ctor, Collections.LIST[IR.Values.Value](0)); | |
| 991 | ||
| 992 | let wrap_args = Collections.LIST[IR.Values.Value](); | |
| 993 | wrap_args.add(final_element_load); | |
| 994 | ||
| 995 | fused.wrap_element = IR.Values.NEW(maybe_type, wrap_ctor, wrap_args); | |
| 996 | fi | |
| 997 | ||
| 998 | // collect_list returns a fresh LIST[element]; seed it empty and | |
| 999 | // add the surviving element each iteration. | |
| 1000 | if consumer_kind =~ "collect" then | |
| 1001 | let list_type = call.value!.type!; | |
| 1002 | ||
| 1003 | let ctor = _resolve_ctor_by_arity(list_type, 0); | |
| 1004 | let add_fn = _resolve_member_by_arity(list_type, "add", 1); | |
| 1005 | ||
| 1006 | if !ctor? \/ !add_fn? then | |
| 1007 | return null; | |
| 1008 | fi | |
| 1009 | ||
| 1010 | fused.seed_init = IR.Values.NEW(list_type, ctor, Collections.LIST[IR.Values.Value](0)); | |
| 1011 | ||
| 1012 | let add_args = Collections.LIST[IR.Values.Value](); | |
| 1013 | add_args.add(final_element_load); | |
| 1014 | ||
| 1015 | fused.add_element = add_fn.call( | |
| 1016 | call.location, IR.Values.Load.TEMP(result_il_name, list_type), add_args, null, _function_caller); | |
| 1017 | fi | |
| 1018 | ||
| 1019 | return fused; | |
| 1020 | si | |
| 1021 | ||
| 1022 | // A member of `type` named `name` taking `arity` arguments (or null). | |
| 1023 | _resolve_member_by_arity(type: Type, name: string, arity: int) -> Semantic.Symbols.Function? is | |
| 1024 | let member = type.find_member(name); | |
| 1025 | ||
| 1026 | if !member? \/ !isa Semantic.Symbols.FUNCTION_GROUP(member) then | |
| 1027 | return null; | |
| 1028 | fi | |
| 1029 | ||
| 1030 | let group = cast Semantic.Symbols.FUNCTION_GROUP(member); | |
| 1031 | ||
| 1032 | for f in group.functions do | |
| 1033 | if f.argument_names.count == arity then | |
| 1034 | return f; | |
| 1035 | fi | |
| 1036 | od | |
| 1037 | ||
| 1038 | return null; | |
| 1039 | si | |
| 1040 | ||
| 1041 | // The constructor of `type` taking `arity` arguments (or null). | |
| 1042 | _resolve_ctor_by_arity(type: Type, arity: int) -> Semantic.Symbols.Function? => | |
| 1043 | _resolve_member_by_arity(type, "init", arity); | |
| 1044 | ||
| 1045 | // Pre-build the per-stage application for a consumer loop, threading | |
| 1046 | // the element through freshly-named locals (a map produces a new | |
| 1047 | // typed local the next stage reads). Every stage is an inlinable | |
| 1048 | // lambda: assign the current element to the lambda's parameter local, | |
| 1049 | // then the harvested inline body is the applied value. | |
| 1050 | _build_consumer_stages(fusion: PIPE_FUSION, start_il_name: string, start_type: Type) -> (ops: Collections.LIST[IR.Values.FUSED_CONSUMER_STAGE], final_il_name: string, final_type: Type) is | |
| 1051 | let ops = Collections.LIST[IR.Values.FUSED_CONSUMER_STAGE](); | |
| 1052 | ||
| 1053 | let current_il_name mut = start_il_name; | |
| 1054 | let current_type mut = start_type; | |
| 1055 | ||
| 1056 | let index mut = fusion.stages_outermost_first.count - 1; | |
| 1057 | ||
| 1058 | while index >= 0 do | |
| 1059 | let stage = fusion.stages_outermost_first[index]; | |
| 1060 | ||
| 1061 | if stage.is_countdown then | |
| 1062 | // take/skip: a running counter, no delegate and no | |
| 1063 | // output local - the element passes through unchanged. | |
| 1064 | let counter_il_name = "'.fc_countdown.{_next_fused_local_id()}'"; | |
| 1065 | ||
| 1066 | ops.add(IR.Values.FUSED_CONSUMER_STAGE(stage.is_take, stage.is_skip, counter_il_name, stage.argument!.value!)); | |
| 1067 | ||
| 1068 | index = index - 1; | |
| 1069 | ||
| 1070 | continue; | |
| 1071 | fi | |
| 1072 | ||
| 1073 | let delegate = stage.argument!.value!; | |
| 1074 | let func_type = delegate.type!; | |
| 1075 | ||
| 1076 | let delegate_il_name = "'.fc_stage.{_next_fused_local_id()}'"; | |
| 1077 | ||
| 1078 | let result_type = | |
| 1079 | if func_type.is_action then | |
| 1080 | _innate_symbol_lookup.get_void_type(); | |
| 1081 | else | |
| 1082 | func_type.arguments[func_type.arguments.count - 1]; | |
| 1083 | fi; | |
| 1084 | ||
| 1085 | let call_arguments = Collections.LIST[IR.Values.Value](); | |
| 1086 | call_arguments.add(cast IR.Values.Value(IR.Values.Load.TEMP(current_il_name, current_type))); | |
| 1087 | ||
| 1088 | let apply = cast IR.Values.Value(IR.Values.Call.CLOSURE( | |
| 1089 | IR.Values.Load.TEMP(delegate_il_name, func_type), result_type, func_type.is_action, func_type, call_arguments)); | |
| 1090 | ||
| 1091 | if stage.is_filter then | |
| 1092 | ops.add(IR.Values.FUSED_CONSUMER_STAGE(true, delegate_il_name, func_type, delegate, apply, "", null)); | |
| 1093 | else | |
| 1094 | ops.add(IR.Values.FUSED_CONSUMER_STAGE(false, delegate_il_name, func_type, delegate, apply, "'.fc_map.{_next_fused_local_id()}'", result_type)); | |
| 1095 | ||
| 1096 | current_il_name = ops[ops.count - 1].output_il_name; | |
| 1097 | current_type = result_type; | |
| 1098 | fi | |
| 1099 | ||
| 1100 | index = index - 1; | |
| 1101 | od | |
| 1102 | ||
| 1103 | return (ops, current_il_name, current_type); | |
| 1104 | si | |
| 1105 | ||
| 1106 | // The single INDEXED_VALUE[element] constructor `init(int, element)`, | |
| 1107 | // specialized to the stage's element type, that the fused index stage | |
| 1108 | // calls per element in place of building an INDEX_PIPE. | |
| 1109 | _resolve_index_constructor(indexed_value_type: Type) -> Semantic.Symbols.Function? is | |
| 1110 | let init_symbol = indexed_value_type.find_member("init"); | |
| 1111 | ||
| 1112 | if !init_symbol? \/ !isa Semantic.Symbols.FUNCTION_GROUP(init_symbol) then | |
| 1113 | return null; | |
| 1114 | fi | |
| 1115 | ||
| 1116 | let group = cast Semantic.Symbols.FUNCTION_GROUP(init_symbol); | |
| 1117 | ||
| 1118 | if group.count != 1 then | |
| 1119 | return null; | |
| 1120 | fi | |
| 1121 | ||
| 1122 | return group.functions[0]; | |
| 1123 | si | |
| 1124 | ||
| 1125 | // Build the `Pipe[source-element]` type the runtime guard tests the | |
| 1126 | // source against. The element type is the source's own iterator | |
| 1127 | // element (`source_read_current.return_type`), matching the T that | |
| 1128 | // `pipe()` uses to decide its short-circuit - so guard and pipe() | |
| 1129 | // agree by construction. | |
| 1130 | _fusion_guard_type(fusion: PIPE_FUSION) -> Type? is | |
| 1131 | if !fusion.source_read_current? \/ !fusion.source_read_current.return_type? then | |
| 1132 | return null; | |
| 1133 | fi | |
| 1134 | ||
| 1135 | let element_type = fusion.source_read_current.return_type; | |
| 1136 | ||
| 1137 | let pipe_type = _innate_symbol_lookup.get_unspecialized_pipe_type(); | |
| 1138 | ||
| 1139 | if !pipe_type? then | |
| 1140 | return null; | |
| 1141 | fi | |
| 1142 | ||
| 1143 | let pipe_symbol = pipe_type.symbol; | |
| 1144 | ||
| 1145 | let pipe_classy: Semantic.Symbols.Classy? mut = | |
| 1146 | if isa Semantic.Symbols.GENERIC(pipe_symbol) then | |
| 1147 | pipe_symbol.symbol; | |
| 1148 | elif isa Semantic.Symbols.Classy(pipe_symbol) then | |
| 1149 | pipe_symbol; | |
| 1150 | else | |
| 1151 | null; | |
| 1152 | fi; | |
| 1153 | ||
| 1154 | if !pipe_classy? then | |
| 1155 | return null; | |
| 1156 | fi | |
| 1157 | ||
| 1158 | let arguments = Collections.LIST[Type](); | |
| 1159 | arguments.add(element_type); | |
| 1160 | ||
| 1161 | return Semantic.Types.GENERIC(fusion.source.location, pipe_classy, arguments); | |
| 1162 | si | |
| 1163 | ||
| 1164 | _fused_local_id_counter: int static; | |
| 1165 | ||
| 1166 | _next_fused_local_id() -> int static is | |
| 1167 | let result = _fused_local_id_counter; | |
| 1168 | _fused_local_id_counter = _fused_local_id_counter + 1; | |
| 1169 | return result; | |
| 1170 | si | |
| 1171 | ||
| 1172 | // Attempt to inline a stage's `map`/`filter` lambda so the fused | |
| 1173 | // loop runs its body directly, with no delegate or closure | |
| 1174 | // frame. Only literal, single-parameter, expression-bodied | |
| 1175 | // lambdas qualify; anything else keeps the delegate path | |
| 1176 | // (`stage` left unmodified). | |
| 1177 | // | |
| 1178 | // Mechanism: declare a synthetic local, owned by the enclosing | |
| 1179 | // method, for the parameter, then re-walk the body with that | |
| 1180 | // local bound. Because the current function is the enclosing | |
| 1181 | // method (not the closure), the parameter and any captured outer | |
| 1182 | // locals resolve as ordinary local loads. The re-walk's | |
| 1183 | // diagnostics and symbol-use records are discarded (the first, | |
| 1184 | // normal walk already recorded them); the harvested IR is kept. | |
| 1185 | // A second, restoring walk of the whole lambda puts the | |
| 1186 | // closure-context IR back on the shared AST nodes so the (now | |
| 1187 | // unused) closure method body still emits valid IL. | |
| 1188 | _try_inline_stage(stage: PIPE_FUSION_STAGE) is | |
| 1189 | if !isa Trees.Expressions.FUNCTION(stage.argument) then | |
| 1190 | return; | |
| 1191 | fi | |
| 1192 | ||
| 1193 | let function = cast Trees.Expressions.FUNCTION(stage.argument); | |
| 1194 | ||
| 1195 | if | |
| 1196 | function.is_recursive \/ | |
| 1197 | function.contains_let_await \/ | |
| 1198 | function.arguments.expressions.count != 1 \/ | |
| 1199 | !isa Trees.Bodies.EXPRESSION(function.body) | |
| 1200 | then | |
| 1201 | return; | |
| 1202 | fi | |
| 1203 | ||
| 1204 | let param = function.arguments.expressions[0]; | |
| 1205 | ||
| 1206 | if !isa Trees.Expressions.VARIABLE(param) then | |
| 1207 | return; | |
| 1208 | fi | |
| 1209 | ||
| 1210 | let param_name = (cast Trees.Expressions.VARIABLE(param)).name.name; | |
| 1211 | ||
| 1212 | let closure = cast Semantic.Symbols.Closure?(_symbol_table.scope_for(function)); | |
| 1213 | ||
| 1214 | if !closure? then | |
| 1215 | return; | |
| 1216 | fi | |
| 1217 | ||
| 1218 | let param_symbol = closure.find_direct(param_name); | |
| 1219 | ||
| 1220 | if !param_symbol? \/ !isa Semantic.Types.Typed(param_symbol) then | |
| 1221 | return; | |
| 1222 | fi | |
| 1223 | ||
| 1224 | let param_type = (cast Semantic.Types.Typed(param_symbol)).type; | |
| 1225 | ||
| 1226 | if !param_type? \/ param_type.is_sentinel \/ param_type.is_error then | |
| 1227 | return; | |
| 1228 | fi | |
| 1229 | ||
| 1230 | let body = cast Trees.Bodies.EXPRESSION(function.body); | |
| 1231 | ||
| 1232 | if INLINE_DISQUALIFYING_SCANNER().contains_cast(body) then | |
| 1233 | return; | |
| 1234 | fi | |
| 1235 | ||
| 1236 | // Harvest: re-walk the body with the parameter bound to a | |
| 1237 | // fresh enclosing-method local. | |
| 1238 | let scope = Semantic.BLOCK_SCOPE(_symbol_table.current_scope); | |
| 1239 | ||
| 1240 | _symbol_table.enter_scope(scope); | |
| 1241 | ||
| 1242 | // The local-id generator is only in a function frame during | |
| 1243 | // the declare pass; push one so the LOCAL_VARIABLE ctor can | |
| 1244 | // mint an id, then give the local a globally-unique IL slot | |
| 1245 | // name so it can't collide with an enclosing local. | |
| 1246 | let local_id_generator = IoC.CONTAINER.instance.local_id_generator; | |
| 1247 | ||
| 1248 | local_id_generator.enter_function(); | |
| 1249 | ||
| 1250 | let local = scope.declare_variable(function.location, param_name, false, null); | |
| 1251 | ||
| 1252 | local_id_generator.leave_function(); | |
| 1253 | ||
| 1254 | if !isa Semantic.Symbols.Variable(local) \/ !isa Semantic.Types.SettableTyped(local) then | |
| 1255 | _symbol_table.leave_scope(scope); | |
| 1256 | return; | |
| 1257 | fi | |
| 1258 | ||
| 1259 | local.il_name_override = "'.fused_param.{_next_fused_local_id()}'"; | |
| 1260 | ||
| 1261 | (cast Semantic.Symbols.Variable(local)).define(); | |
| 1262 | (cast Semantic.Types.SettableTyped(local)).set_type(param_type); | |
| 1263 | ||
| 1264 | _logger.speculate(); | |
| 1265 | _symbol_use_locations.speculate(); | |
| 1266 | ||
| 1267 | body.expression.walk(self); | |
| 1268 | ||
| 1269 | let harvested = body.expression.value; | |
| 1270 | ||
| 1271 | _logger.roll_back(); | |
| 1272 | _symbol_use_locations.roll_back(); | |
| 1273 | ||
| 1274 | _symbol_table.leave_scope(scope); | |
| 1275 | ||
| 1276 | // Restore the closure-context IR on the shared AST nodes. | |
| 1277 | _logger.speculate(); | |
| 1278 | _symbol_use_locations.speculate(); | |
| 1279 | ||
| 1280 | function.walk(self); | |
| 1281 | ||
| 1282 | _logger.roll_back(); | |
| 1283 | _symbol_use_locations.roll_back(); | |
| 1284 | ||
| 1285 | if !harvested? then | |
| 1286 | return; | |
| 1287 | fi | |
| 1288 | ||
| 1289 | let harvested_type = harvested.type; | |
| 1290 | ||
| 1291 | if !harvested_type? \/ harvested_type.is_error then | |
| 1292 | return; | |
| 1293 | fi | |
| 1294 | ||
| 1295 | stage.param_local = cast Semantic.Symbols.Variable(local); | |
| 1296 | stage.inline_body = harvested; | |
| 1297 | si | |
| 1298 | ||
| 1299 | // Fill the fusion plan's iterator slots for `type`, mirroring | |
| 1300 | // set_iterator_for: bind move_next / read_current directly when | |
| 1301 | // `type` is itself an iterator, else follow $get_iterator once | |
| 1302 | // and recurse. Returns false if no complete iterator surface is | |
| 1303 | // found (fall back to the normal loop). | |
| 1304 | _resolve_fusion_source_iterator(fusion: PIPE_FUSION, type: Type, recursing: bool) -> bool is | |
| 1305 | let move_next = get_zero_argument_function(type, "move_next"); | |
| 1306 | ||
| 1307 | if move_next? then | |
| 1308 | let read_current mut = get_zero_argument_function(type, "$get_current"); | |
| 1309 | ||
| 1310 | if !read_current? then | |
| 1311 | read_current = get_zero_argument_function(type, "$get_Current"); | |
| 1312 | fi | |
| 1313 | ||
| 1314 | if !read_current? then | |
| 1315 | return false; | |
| 1316 | fi | |
| 1317 | ||
| 1318 | fusion.source_move_next = move_next; | |
| 1319 | fusion.source_read_current = read_current; | |
| 1320 | ||
| 1321 | return true; | |
| 1322 | elif !recursing then | |
| 1323 | let read_iterator = get_zero_argument_function(type, "$get_iterator"); | |
| 1324 | ||
| 1325 | if read_iterator? then | |
| 1326 | fusion.source_read_iterator = read_iterator; | |
| 1327 | ||
| 1328 | return _resolve_fusion_source_iterator(fusion, read_iterator.return_type!, true); | |
| 1329 | fi | |
| 1330 | fi | |
| 1331 | ||
| 1332 | return false; | |
| 1333 | si | |
| 1334 | ||
| 1335 | pre(pragma: Trees.Definitions.PRAGMA) -> bool is | |
| 1336 | _pragma_scope_stack.enter(pragma.pragma); | |
| 1337 | ||
| 1338 | return false; | |
| 1339 | si | |
| 1340 | ||
| 1341 | visit(pragma: Trees.Definitions.PRAGMA) is | |
| 1342 | _pragma_scope_stack.leave(pragma.pragma); | |
| 1343 | ||
| 1344 | resolve_attribute(pragma); | |
| 1345 | si | |
| 1346 | ||
| 1347 | // An attribute pragma applies to the definition it wraps; unwrap | |
| 1348 | // any nested pragmas to reach that definition's symbol. | |
| 1349 | resolve_attribute(pragma: Trees.Definitions.PRAGMA) is | |
| 1350 | let definition: Trees.Definitions.Definition mut = pragma.definition; | |
| 1351 | ||
| 1352 | while isa Trees.Definitions.PRAGMA(definition) do | |
| 1353 | definition = cast Trees.Definitions.PRAGMA(definition).definition; | |
| 1354 | od | |
| 1355 | ||
| 1356 | _attribute_resolver.resolve(pragma.pragma, symbol_for(definition)); | |
| 1357 | si | |
| 1358 | ||
| 1359 | // Iterative body walk. Returning true here suppresses the | |
| 1360 | // walk framework's default child traversal so visit() can walk | |
| 1361 | // arguments once and the body up to N times. Constraints set | |
| 1362 | // on AST nodes during a walk persist across iterations | |
| 1363 | // (TypeConstrained.upgrade_constraint is narrowing-only), so | |
| 1364 | // each pass either narrows the constraint set or stays put; | |
| 1365 | // convergence is _logger.is_clean (no errors and no flagged | |
| 1366 | // wild/inferred type consumption). See PENDING-PRS.md | |
| 1367 | // "Long-running local branches" — port of the body-retry | |
| 1368 | // mechanism from origin/degory/iterative-type-inference-2. | |
| 1369 | pre(function: Trees.Definitions.FUNCTION) -> bool is | |
| 1370 | super.pre(function); | |
| 1371 | return true; | |
| 1372 | si | |
| 1373 | ||
| 1374 | visit(function: Trees.Definitions.FUNCTION) is | |
| 1375 | let symbol = symbol_for(function); | |
| 1376 | ||
| 1377 | // Declare-symbols rejected the declaration outright — a | |
| 1378 | // generator or async function in a context that has no such | |
| 1379 | // kind — so there is no function symbol to compile the body | |
| 1380 | // against, and walking it anyway trips assertions that assume | |
| 1381 | // an enclosing function. The rejection has already been | |
| 1382 | // reported. | |
| 1383 | if !isa Semantic.Symbols.Function(symbol) then | |
| 1384 | super.visit(function); | |
| 1385 | ||
| 1386 | return; | |
| 1387 | fi | |
| 1388 | ||
| 1389 | let state_machine: Semantic.Symbols.STATE_MACHINE? mut = null; | |
| 1390 | let async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE? mut = null; | |
| 1391 | ||
| 1392 | // Generator / async: realise the state-machine frame's | |
| 1393 | // argument fields and `_outer_self` field before the body | |
| 1394 | // walks. A closure inside the body freezes captured loads | |
| 1395 | // via `Value.freeze()` at compile-expressions time, and | |
| 1396 | // the captured `Load.LOCAL_ARGUMENT.gen` only redirects | |
| 1397 | // through `ldarg.0; ldfld _arg_<name>` when | |
| 1398 | // `state_machine_field` is populated on the symbol — | |
| 1399 | // populating it later (generate-il) would be too late. | |
| 1400 | // | |
| 1401 | // Also install the function-T → class-T gen_type override | |
| 1402 | // so that any closure that freezes inside the body — and | |
| 1403 | // any IR.Values inside that freeze that ref a function-T | |
| 1404 | // symbol — emits `!N` (class-level on the state machine) | |
| 1405 | // rather than `!!N` (method-level, which has no meaning | |
| 1406 | // inside MoveNext). The override is uninstalled after the | |
| 1407 | // body walk; generate-il re-installs it around its own | |
| 1408 | // body / state-machine emission paths. | |
| 1409 | if let function_symbol: Semantic.Symbols.Function = symbol then | |
| 1410 | // A closure body compiled during this walk re-marks any | |
| 1411 | // argument it captures; a mark left by an earlier walk is | |
| 1412 | // stale once an edit removes the capturing lambda, and | |
| 1413 | // would wrongly reject assignments to a mut argument. | |
| 1414 | // Re-derive from scratch on each walk of the owning body. | |
| 1415 | for argument_name in function_symbol.argument_names do | |
| 1416 | if let argument: Semantic.Symbols.LOCAL_ARGUMENT = function_symbol.find_direct(argument_name) then | |
| 1417 | argument.is_captured = false; | |
| 1418 | fi | |
| 1419 | od | |
| 1420 | ||
| 1421 | state_machine = Semantic.Symbols.state_machine_for(function_symbol); | |
| 1422 | async_state_machine = Semantic.Symbols.async_state_machine_for(function_symbol); | |
| 1423 | ||
| 1424 | if state_machine? /\ state_machine.frame? then | |
| 1425 | state_machine.frame!.declare(); | |
| 1426 | state_machine.install_body_emission_overrides(); | |
| 1427 | elif async_state_machine? /\ async_state_machine.frame? then | |
| 1428 | async_state_machine.frame!.declare(); | |
| 1429 | async_state_machine.install_body_emission_overrides(); | |
| 1430 | fi | |
| 1431 | fi | |
| 1432 | ||
| 1433 | _lambdas.visit_function_definition(function); | |
| 1434 | ||
| 1435 | if state_machine? /\ state_machine.frame? then | |
| 1436 | state_machine.uninstall_body_emission_overrides(); | |
| 1437 | elif async_state_machine? /\ async_state_machine.frame? then | |
| 1438 | async_state_machine.uninstall_body_emission_overrides(); | |
| 1439 | fi | |
| 1440 | ||
| 1441 | // Yield inside try/catch/finally needs the fault-block / | |
| 1442 | // state-machine-finalisation dance C# uses and isn't | |
| 1443 | // implemented in v1. The "yield + await in same body" | |
| 1444 | // diagnostic fires earlier in declare-symbols, where | |
| 1445 | // both AST forms are still observable. | |
| 1446 | if state_machine? /\ function.body? then | |
| 1447 | YIELD_IN_TRY_SCANNER(_logger).scan(function.body!); | |
| 1448 | fi | |
| 1449 | ||
| 1450 | if async_state_machine? /\ function.body? then | |
| 1451 | AWAIT_IN_PROTECTED_SCANNER(_logger).scan(function.body!); | |
| 1452 | fi | |
| 1453 | ||
| 1454 | super.visit(function); | |
| 1455 | si | |
| 1456 | ||
| 1457 | pre(`let: Trees.Statements.LET) -> bool is | |
| 1458 | super.pre(`let); | |
| 1459 | return _bindings.pre_let(`let); | |
| 1460 | si | |
| 1461 | ||
| 1462 | visit(`let: Trees.Statements.LET) is | |
| 1463 | super.visit(`let); | |
| 1464 | _bindings.visit_let(`let); | |
| 1465 | si | |
| 1466 | ||
| 1467 | pre(`for: Trees.Statements.FOR) -> bool is | |
| 1468 | super.pre(`for); | |
| 1469 | ||
| 1470 | try | |
| 1471 | _pre(`for); | |
| 1472 | catch ex: Exception | |
| 1473 | _logger.exception(`for.location, ex, "exception compiling for"); | |
| 1474 | yrt | |
| 1475 | ||
| 1476 | return true; | |
| 1477 | si | |
| 1478 | ||
| 1479 | _pre(`for: Trees.Statements.FOR) -> bool is | |
| 1480 | let symbol: Semantic.Symbols.Symbol mut; | |
| 1481 | ||
| 1482 | let type: Type mut = Semantic.Types.ERROR(); | |
| 1483 | ||
| 1484 | let expression = `for.expression; | |
| 1485 | let variable = `for.variable; | |
| 1486 | ||
| 1487 | if expression? /\ !expression.is_poisoned then | |
| 1488 | expression.walk(self); | |
| 1489 | ||
| 1490 | // Iterable inference: a still-placeholder for-loop | |
| 1491 | // expression records an ITERABLE_CONSTRAINT on its | |
| 1492 | // origin so the body-retry loop can filter candidate | |
| 1493 | // types to those that actually iterate. set_iterator_for | |
| 1494 | // below will still emit "not iterable" on this iter — | |
| 1495 | // the speculate/roll-back wrapper drops the error on | |
| 1496 | // retry once the placeholder resolves. | |
| 1497 | if let | |
| 1498 | ev = expression.value, | |
| 1499 | ev_type = ev.type | |
| 1500 | /\ isa Semantic.Types.INFERRED_VARIABLE_TYPE(ev_type) | |
| 1501 | then | |
| 1502 | let placeholder = cast Semantic.Types.INFERRED_VARIABLE_TYPE(ev_type); | |
| 1503 | ||
| 1504 | _logger.mark_consumed_any_if(placeholder.origin.add_constraint(Semantic.ITERABLE_CONSTRAINT())); | |
| 1505 | fi | |
| 1506 | ||
| 1507 | if let ev = expression.value /\ set_iterator_for(`for, ev.type!, false) then | |
| 1508 | check_receiver_present(expression); | |
| 1509 | ||
| 1510 | if !variable? then | |
| 1511 | type = `for.read_current!.return_type!; | |
| 1512 | elif let | |
| 1513 | te = variable.type_expression, | |
| 1514 | te_type = te.type | |
| 1515 | /\ !isa Trees.TypeExpressions.INFER(te) | |
| 1516 | then | |
| 1517 | if te_type.is_assignable_from(`for.read_current!.return_type!) then | |
| 1518 | type = te_type; | |
| 1519 | else | |
| 1520 | _logger.error(variable.location, "type mismatch"); | |
| 1521 | fi | |
| 1522 | else | |
| 1523 | type = `for.read_current!.return_type!; | |
| 1524 | fi | |
| 1525 | fi | |
| 1526 | fi | |
| 1527 | ||
| 1528 | `for.fusion = _recognize_pipe_fusion(`for); | |
| 1529 | ||
| 1530 | if variable? /\ !variable.is_poisoned then | |
| 1531 | set_symbol_type(variable.left, type); | |
| 1532 | ||
| 1533 | // Generator: register the per-iteration loop variable | |
| 1534 | // on the state-machine frame so closures inside the | |
| 1535 | // body that capture it freeze correctly. Same | |
| 1536 | // rationale as the LET path above; `for.variable` is | |
| 1537 | // not walked through the visitor framework here, so | |
| 1538 | // we call the helper directly. | |
| 1539 | _declare_state_machine_local_fields(variable.left); | |
| 1540 | fi | |
| 1541 | ||
| 1542 | let body = `for.body; | |
| 1543 | ||
| 1544 | if body? then | |
| 1545 | // Loop kill-set narrowing: narrows on variables the | |
| 1546 | // loop writes are dropped (the back-edge could | |
| 1547 | // invalidate them); narrows on variables it never | |
| 1548 | // writes survive the loop. | |
| 1549 | let kept = _loops.loop_kept_env(`for); | |
| 1550 | let epoch = _flow.heap_epoch; | |
| 1551 | ||
| 1552 | _flow.set_env(kept); | |
| 1553 | body.walk(self); | |
| 1554 | ||
| 1555 | // The assignment kill-set covers direct writes but | |
| 1556 | // not calls or member stores inside the body, so a | |
| 1557 | // kill during the walk drops the kept environment's | |
| 1558 | // heap facts before restoration. | |
| 1559 | if _flow.heap_killed_since(epoch) then | |
| 1560 | kept.drop_heap_facts(); | |
| 1561 | fi | |
| 1562 | ||
| 1563 | _flow.set_env(kept); | |
| 1564 | fi | |
| 1565 | return false; | |
| 1566 | si | |
| 1567 | ||
| 1568 | pre(left: Trees.Expressions.SIMPLE_LEFT_EXPRESSION) -> bool is | |
| 1569 | super.pre(left); | |
| 1570 | return _bindings.pre_simple_left(left); | |
| 1571 | si | |
| 1572 | ||
| 1573 | visit(left: Trees.Expressions.SIMPLE_LEFT_EXPRESSION) is | |
| 1574 | super.visit(left); | |
| 1575 | _bindings.visit_simple_left(left); | |
| 1576 | si | |
| 1577 | ||
| 1578 | // True iff `value` is statically known to hold a value — its | |
| 1579 | // type is settled and is not an optional / null type. | |
| 1580 | is_non_optional_value(value: IR.Values.Value?) -> bool => | |
| 1581 | value? /\ value.type? /\ value.type!.is_settled /\ | |
| 1582 | !value.type!.is_null /\ !value.type!.is_optional; | |
| 1583 | ||
| 1584 | // Resolve the destructure strategy (see DESTRUCTURE_RESOLVER) | |
| 1585 | // and log an error against `location` for any unresolvable | |
| 1586 | // case. When `field_names` is null the destructure is | |
| 1587 | // positional and the failure is reported as a count | |
| 1588 | // mismatch if the source has positional members, otherwise | |
| 1589 | // as a not-destructurable source. When `field_names` is | |
| 1590 | // non-null the destructure is by-name and each missed name | |
| 1591 | // is reported individually. | |
| 1592 | resolve_destructure_strategy( | |
| 1593 | location: LOCATION, | |
| 1594 | from_type: Type?, | |
| 1595 | element_count: int, | |
| 1596 | field_names: Collections.List[string?]? | |
| 1597 | ) -> DESTRUCTURE_STRATEGY => | |
| 1598 | DESTRUCTURE_RESOLVER.resolve_strategy_reporting(_logger, location, from_type, element_count, field_names); | |
| 1599 | ||
| 1600 | pre(left: Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION) -> bool is | |
| 1601 | super.pre(left); | |
| 1602 | ||
| 1603 | let from = left.value; | |
| 1604 | ||
| 1605 | if !from? then | |
| 1606 | return true; | |
| 1607 | fi | |
| 1608 | ||
| 1609 | let from_type = from.type; | |
| 1610 | ||
| 1611 | if !from_type? then | |
| 1612 | return true; | |
| 1613 | fi | |
| 1614 | ||
| 1615 | if from_type.is_error then | |
| 1616 | return true; | |
| 1617 | fi | |
| 1618 | ||
| 1619 | let elements = left.elements; | |
| 1620 | ||
| 1621 | // Assignment-style destructure `(a, b) = expr` is | |
| 1622 | // positional-only — by-name destructure uses the `let | |
| 1623 | // (local = field, …) = expr` form, which goes through | |
| 1624 | // the VariableLeft path instead. | |
| 1625 | let strategy = resolve_destructure_strategy(left.location, from_type, elements.count, null); | |
| 1626 | ||
| 1627 | let block = IR.Values.BLOCK(); | |
| 1628 | ||
| 1629 | if strategy.is_deconstruct then | |
| 1630 | let deconstruct = strategy.deconstruct_function!; | |
| 1631 | let arg_temps = Collections.LIST[IR.TEMP](); | |
| 1632 | let call_args = Collections.LIST[IR.Values.Value](); | |
| 1633 | ||
| 1634 | for i in 0..deconstruct.arguments.count do | |
| 1635 | let ref_type = deconstruct.arguments[i]; | |
| 1636 | let element_type = ref_type.get_element_type()!; | |
| 1637 | let arg_temp = IR.TEMP(block, "destructure_arg", i, element_type); | |
| 1638 | ||
| 1639 | arg_temps.add(arg_temp); | |
| 1640 | call_args.add(IR.Values.ADDRESS(arg_temp.load(), ref_type)); | |
| 1641 | od | |
| 1642 | ||
| 1643 | let call_value = | |
| 1644 | deconstruct.call( | |
| 1645 | left.location, | |
| 1646 | from, | |
| 1647 | call_args, | |
| 1648 | null, | |
| 1649 | _function_caller | |
| 1650 | ); | |
| 1651 | ||
| 1652 | block.add(call_value); | |
| 1653 | ||
| 1654 | for i in 0..elements.count do | |
| 1655 | let element = elements[i]; | |
| 1656 | ||
| 1657 | element.compile_expressions_state.value = arg_temps[i].load(); | |
| 1658 | ||
| 1659 | element.walk(self); | |
| 1660 | ||
| 1661 | if element.value? then | |
| 1662 | block.add(element.value); | |
| 1663 | fi | |
| 1664 | od | |
| 1665 | else | |
| 1666 | let members = strategy.members; | |
| 1667 | let get_from = from.get_temp_copier(block, "destructure"); | |
| 1668 | ||
| 1669 | for i in 0..elements.count do | |
| 1670 | let element = elements[i]; | |
| 1671 | let member = members[i]; | |
| 1672 | ||
| 1673 | if member? then | |
| 1674 | element.compile_expressions_state.value = member.load(LOCATION.internal, get_from(), _symbol_loader); | |
| 1675 | ||
| 1676 | element.walk(self); | |
| 1677 | ||
| 1678 | if element.value? then | |
| 1679 | block.add(element.value); | |
| 1680 | fi | |
| 1681 | fi | |
| 1682 | od | |
| 1683 | fi | |
| 1684 | ||
| 1685 | block.close(); | |
| 1686 | left.compile_expressions_state.value = block; | |
| 1687 | ||
| 1688 | return true; | |
| 1689 | si | |
| 1690 | ||
| 1691 | pre(let_in: Trees.Expressions.LET_IN) -> bool is | |
| 1692 | super.pre(let_in); | |
| 1693 | ||
| 1694 | return false; | |
| 1695 | si | |
| 1696 | ||
| 1697 | visit(let_in: Trees.Expressions.LET_IN) is | |
| 1698 | super.visit(let_in); | |
| 1699 | _bindings.visit_let_in(let_in); | |
| 1700 | si | |
| 1701 | ||
| 1702 | pre(assert_in: Trees.Expressions.ASSERT_IN) -> bool is | |
| 1703 | super.pre(assert_in); | |
| 1704 | ||
| 1705 | let epoch = _flow.heap_epoch; | |
| 1706 | ||
| 1707 | assert_in.condition.walk(self); | |
| 1708 | ||
| 1709 | // A kill during the condition's own walk means its | |
| 1710 | // derived heap facts cannot be kept (`assert _f? /\ | |
| 1711 | // mutate() in …`). The message's walk is outside the | |
| 1712 | // span: it only runs on the failure path, so its kills | |
| 1713 | // don't invalidate what the passing condition proves. | |
| 1714 | let condition_killed = _flow.heap_killed_since(epoch); | |
| 1715 | ||
| 1716 | // Walk the message *before* installing the condition-holds | |
| 1717 | // narrowing, because the message expression is only | |
| 1718 | // evaluated on the failure path — where the condition does | |
| 1719 | // not hold — and must type-check against the unnarrowed env. | |
| 1720 | if assert_in.message? then | |
| 1721 | assert_in.message.walk(self); | |
| 1722 | _check_assertion_message(assert_in.message!); | |
| 1723 | fi | |
| 1724 | ||
| 1725 | if _check_assertion_condition(assert_in.condition) then | |
| 1726 | let facts = _condition_analyzer.analyze_condition(assert_in.condition, _flow.current_env); | |
| 1727 | ||
| 1728 | if condition_killed then | |
| 1729 | facts.then_env.drop_heap_facts(); | |
| 1730 | fi | |
| 1731 | ||
| 1732 | _flow.set_env(facts.then_env); | |
| 1733 | fi | |
| 1734 | ||
| 1735 | assert_in.expression.walk(self); | |
| 1736 | ||
| 1737 | return true; | |
| 1738 | si | |
| 1739 | ||
| 1740 | visit(assert_in: Trees.Expressions.ASSERT_IN) is | |
| 1741 | let value = assert_in.expression.value; | |
| 1742 | ||
| 1743 | if | |
| 1744 | !value? \/ | |
| 1745 | !value.check_is_consumable(_logger, assert_in.expression.location) | |
| 1746 | then | |
| 1747 | assert_in.compile_expressions_state.value = IR.Values.DUMMY(Semantic.Types.ERROR(), assert_in.location); | |
| 1748 | return; | |
| 1749 | fi | |
| 1750 | ||
| 1751 | assert_in.compile_expressions_state.value = value; | |
| 1752 | si | |
| 1753 | ||
| 1754 | pre(assignment: Trees.Statements.ASSIGNMENT) -> bool => | |
| 1755 | _bindings.pre_assignment(assignment); | |
| 1756 | ||
| 1757 | pre(expression: Trees.Statements.EXPRESSION) -> bool is | |
| 1758 | super.pre(expression); | |
| 1759 | ||
| 1760 | // A STATEMENT- or VAL_BLOCK-shaped expression wrapped in | |
| 1761 | // an expression-statement is value-required only if the | |
| 1762 | // surrounding list demands a value at this slot. Push | |
| 1763 | // that down so a void-tail block / `if`-arm with no value | |
| 1764 | // in expression-statement position is accepted silently. | |
| 1765 | if let statement_expression: Trees.Expressions.STATEMENT = expression.expression then | |
| 1766 | statement_expression.want_value = expression.want_value; | |
| 1767 | elif let val_block: Trees.Expressions.VAL_BLOCK = expression.expression then | |
| 1768 | val_block.want_value = expression.want_value; | |
| 1769 | fi | |
| 1770 | ||
| 1771 | return false; | |
| 1772 | si | |
| 1773 | ||
| 1774 | visit(expression: Trees.Statements.EXPRESSION) is | |
| 1775 | super.visit(expression); | |
| 1776 | _bindings.visit_expression_statement(expression); | |
| 1777 | ||
| 1778 | if let val_block: Trees.Expressions.VAL_BLOCK = expression.expression then | |
| 1779 | if _is_redundant_val_block(val_block) then | |
| 1780 | _logger.warn( | |
| 1781 | val_block.location, | |
| 1782 | "redundant-val-block", | |
| 1783 | "val block is redundant" | |
| 1784 | ); | |
| 1785 | fi | |
| 1786 | fi | |
| 1787 | si | |
| 1788 | ||
| 1789 | // True when this val-block is in expression-statement position | |
| 1790 | // and could be inlined: the surrounding statement list already | |
| 1791 | // accepts the same shape of body. Two refusals: a return inside | |
| 1792 | // targets this block (inlining would re-route the return to the | |
| 1793 | // enclosing function), or the body declares a local (inlining | |
| 1794 | // would widen its scope). | |
| 1795 | _is_redundant_val_block(block: Trees.Expressions.VAL_BLOCK) -> bool is | |
| 1796 | if block.has_targeted_return then | |
| 1797 | return false; | |
| 1798 | fi | |
| 1799 | for s in block.body.statements do | |
| 1800 | if isa Trees.Statements.LET(s) then | |
| 1801 | return false; | |
| 1802 | fi | |
| 1803 | od | |
| 1804 | return true; | |
| 1805 | si | |
| 1806 | ||
| 1807 | pre(r: Trees.Statements.RETURN) -> bool is | |
| 1808 | super.pre(r); | |
| 1809 | return _bindings.pre_return(r); | |
| 1810 | si | |
| 1811 | ||
| 1812 | visit(r: Trees.Statements.RETURN) is | |
| 1813 | super.visit(r); | |
| 1814 | _bindings.visit_return(r); | |
| 1815 | si | |
| 1816 | ||
| 1817 | visit(`throw: Trees.Statements.THROW) is | |
| 1818 | super.visit(`throw); | |
| 1819 | ||
| 1820 | // Control does not fall through a throw. | |
| 1821 | _flow.set_unreachable(); | |
| 1822 | ||
| 1823 | if !`throw.expression? then | |
| 1824 | return; | |
| 1825 | fi | |
| 1826 | ||
| 1827 | if | |
| 1828 | !Value.check_is_consumable(_logger, `throw.expression.location, `throw.expression.value) | |
| 1829 | then | |
| 1830 | return; | |
| 1831 | fi | |
| 1832 | ||
| 1833 | let exception_type = _innate_symbol_lookup.get_exception_type(); | |
| 1834 | ||
| 1835 | if !exception_type.is_assignable_from(`throw.expression!.value!.type!) then | |
| 1836 | _logger.warn(`throw.expression!.location, "non-exception-throw", "thrown value is not derived from System.Exception"); | |
| 1837 | fi | |
| 1838 | ||
| 1839 | // FIXME: need to signal to any enclosing expression if statement that this is a throw so | |
| 1840 | // if all branches are throws, an error can be reported | |
| 1841 | si | |
| 1842 | ||
| 1843 | pre(`yield: Trees.Statements.YIELD) -> bool is | |
| 1844 | super.pre(`yield); | |
| 1845 | ||
| 1846 | // The yielded expression's type must match the element | |
| 1847 | // type T (drawn from the enclosing function's | |
| 1848 | // `Iterable[T]` / `Iterator[T]` return type). Setting | |
| 1849 | // the constraint here — before walking the expression — | |
| 1850 | // lets the expression participate in inference / overload | |
| 1851 | // resolution against the expected type, like RETURN's | |
| 1852 | // value does in compile_bindings.pre_return. | |
| 1853 | let function = _symbol_table.current_function; | |
| 1854 | ||
| 1855 | assert function? else "yield outside a function"; | |
| 1856 | ||
| 1857 | let state_machine = Semantic.Symbols.state_machine_for(function); | |
| 1858 | ||
| 1859 | if state_machine? /\ function.return_type? then | |
| 1860 | let element_type = _yield_element_type_for(function); | |
| 1861 | ||
| 1862 | if element_type? /\ !element_type.is_inferred then | |
| 1863 | `yield.expression.set_expected_type( | |
| 1864 | element_type, | |
| 1865 | "yielded value of type {{0}} is not assignable to element type {{1}}" | |
| 1866 | ); | |
| 1867 | fi | |
| 1868 | fi | |
| 1869 | ||
| 1870 | return false; | |
| 1871 | si | |
| 1872 | ||
| 1873 | visit(`yield: Trees.Statements.YIELD) is | |
| 1874 | super.visit(`yield); | |
| 1875 | ||
| 1876 | let function = _symbol_table.current_function; | |
| 1877 | let state_machine = Semantic.Symbols.state_machine_for(function); | |
| 1878 | ||
| 1879 | if !state_machine? then | |
| 1880 | if function? /\ function.is_closure then | |
| 1881 | _logger.error( | |
| 1882 | `yield.location, | |
| 1883 | "cannot yield in function literal" | |
| 1884 | ); | |
| 1885 | else | |
| 1886 | _logger.error( | |
| 1887 | `yield.location, | |
| 1888 | "generator must return Pipe[T]" | |
| 1889 | ); | |
| 1890 | fi | |
| 1891 | ||
| 1892 | return; | |
| 1893 | fi | |
| 1894 | ||
| 1895 | assert function? else "state_machine_for returned non-null for null function"; | |
| 1896 | ||
| 1897 | if !Value.check_is_consumable(_logger, `yield.expression.location, `yield.expression.value) then | |
| 1898 | return; | |
| 1899 | fi | |
| 1900 | ||
| 1901 | // Verify the function's return type is actually an | |
| 1902 | // Iterable[T] / Iterator[T]. The yield-presence test in | |
| 1903 | // declare-symbols routes here without inspecting the | |
| 1904 | // return type, so a body with `yield` and a non-iterable | |
| 1905 | // return type lands here as a diagnostic. | |
| 1906 | let element_type = _yield_element_type_for(function); | |
| 1907 | ||
| 1908 | if !element_type? then | |
| 1909 | _logger.error( | |
| 1910 | `yield.location, | |
| 1911 | "generator must return Pipe[T]" | |
| 1912 | ); | |
| 1913 | fi | |
| 1914 | si | |
| 1915 | ||
| 1916 | // Extract `T` from a generator's `Pipe[T]` return type, or | |
| 1917 | // null if the return type is not a Pipe[T] (a generator must | |
| 1918 | // return Ghul.Pipes.Pipe[T]). T is taken from the Pipe itself, | |
| 1919 | // not its Iterable[T] base, so the concrete element type — not | |
| 1920 | // Pipe's own type parameter — is recovered. | |
| 1921 | // | |
| 1922 | // When the Pipe trait is unavailable (compiling ghul-runtime | |
| 1923 | // itself, where the assembly is not yet loadable) fall back to | |
| 1924 | // the bare Iterable[T] / Iterator[T] forms. | |
| 1925 | _yield_element_type_for(function: Semantic.Symbols.Function) -> Semantic.Types.Type? is | |
| 1926 | if !function.return_type? then | |
| 1927 | return null; | |
| 1928 | fi | |
| 1929 | ||
| 1930 | let return_type = function.return_type; | |
| 1931 | ||
| 1932 | let pipe = _innate_symbol_lookup.get_unspecialized_pipe_type(); | |
| 1933 | ||
| 1934 | if pipe? then | |
| 1935 | return Semantic.Symbols.TYPE_ARGUMENT_EXTRACTOR.extract(return_type, pipe); | |
| 1936 | fi | |
| 1937 | ||
| 1938 | let iterator = _innate_symbol_lookup.get_unspecialized_iterator_type(); | |
| 1939 | let iterable = _innate_symbol_lookup.get_unspecialized_iterable_type(); | |
| 1940 | ||
| 1941 | let candidates = Collections.LIST[Semantic.Types.Type](); | |
| 1942 | candidates.add(iterator); | |
| 1943 | candidates.add(iterable); | |
| 1944 | ||
| 1945 | return Semantic.Symbols.TYPE_ARGUMENT_EXTRACTOR.extract_from_any(return_type, candidates); | |
| 1946 | si | |
| 1947 | ||
| 1948 | // The postfix `|` operator's operand is already a Pipe[T] — so | |
| 1949 | // wrapping it again is redundant and `x |` should be a no-op. | |
| 1950 | // Decided purely from the operand's static type. | |
| 1951 | _pipe_wrap_operand_already_pipe(call: Trees.Expressions.CALL) -> bool is | |
| 1952 | if call.arguments.count != 1 then | |
| 1953 | return false; | |
| 1954 | fi | |
| 1955 | ||
| 1956 | let operand = call.arguments.expressions[0]; | |
| 1957 | ||
| 1958 | let value = operand.value; | |
| 1959 | ||
| 1960 | if !value? \/ !value.type? then | |
| 1961 | return false; | |
| 1962 | fi | |
| 1963 | ||
| 1964 | let pipe = _innate_symbol_lookup.get_unspecialized_pipe_type(); | |
| 1965 | ||
| 1966 | if !pipe? then | |
| 1967 | return false; | |
| 1968 | fi | |
| 1969 | ||
| 1970 | return Semantic.Symbols.TYPE_ARGUMENT_EXTRACTOR.extract(value.type, pipe)?; | |
| 1971 | si | |
| 1972 | ||
| 1973 | visit(`break: Trees.Statements.BREAK) is | |
| 1974 | super.visit(`break); | |
| 1975 | ||
| 1976 | // Control does not fall through a break. | |
| 1977 | _flow.set_unreachable(); | |
| 1978 | si | |
| 1979 | ||
| 1980 | visit(`continue: Trees.Statements.CONTINUE) is | |
| 1981 | super.visit(`continue); | |
| 1982 | ||
| 1983 | // Control does not fall through a continue. | |
| 1984 | _flow.set_unreachable(); | |
| 1985 | si | |
| 1986 | ||
| 1987 | _check_assertion_condition(condition: Trees.Expressions.Expression) -> bool is | |
| 1988 | let value = condition.value; | |
| 1989 | ||
| 1990 | if | |
| 1991 | !value? \/ | |
| 1992 | !value.type? \/ | |
| 1993 | !value.check_is_consumable(_logger, condition.location) | |
| 1994 | then | |
| 1995 | return false; | |
| 1996 | fi | |
| 1997 | ||
| 1998 | if | |
| 1999 | !_innate_symbol_lookup | |
| 2000 | .get_bool_type() | |
| 2001 | .is_assignable_from(value.type!) | |
| 2002 | then | |
| 2003 | _logger.error(condition.location, "assertion expression must be bool"); | |
| 2004 | fi | |
| 2005 | ||
| 2006 | return true; | |
| 2007 | si | |
| 2008 | ||
| 2009 | _check_assertion_message(message: Trees.Expressions.Expression?) is | |
| 2010 | if !message? then | |
| 2011 | return; | |
| 2012 | fi | |
| 2013 | ||
| 2014 | let value = message.value; | |
| 2015 | ||
| 2016 | if !value? \/ !value.type? then | |
| 2017 | return; | |
| 2018 | fi | |
| 2019 | ||
| 2020 | value.check_is_consumable(_logger, message.location); | |
| 2021 | ||
| 2022 | if | |
| 2023 | !_innate_symbol_lookup | |
| 2024 | .get_exception_type() | |
| 2025 | .is_assignable_from(value.type!) | |
| 2026 | /\ | |
| 2027 | !_innate_symbol_lookup | |
| 2028 | .get_string_type() | |
| 2029 | .is_assignable_from(value.type!) | |
| 2030 | then | |
| 2031 | _logger.error(message.location, "assertion else must be string or System.Exception"); | |
| 2032 | fi | |
| 2033 | si | |
| 2034 | ||
| 2035 | pre(`assert: Trees.Statements.ASSERT) -> bool is | |
| 2036 | super.pre(`assert); | |
| 2037 | ||
| 2038 | // Controlled walk so the epoch span covers the condition | |
| 2039 | // alone: a kill during its walk means its derived heap | |
| 2040 | // facts cannot survive (`assert _f? /\ mutate();`), but | |
| 2041 | // the message only runs on the failure path, so its kills | |
| 2042 | // don't invalidate what the passing condition proves. | |
| 2043 | let epoch = _flow.heap_epoch; | |
| 2044 | ||
| 2045 | _access.assert_condition_depth = _access.assert_condition_depth + 1; | |
| 2046 | ||
| 2047 | `assert.expression.walk(self); | |
| 2048 | ||
| 2049 | _access.assert_condition_depth = _access.assert_condition_depth - 1; | |
| 2050 | ||
| 2051 | _assert_condition_killed_stack.add(_flow.heap_killed_since(epoch)); | |
| 2052 | ||
| 2053 | if `assert.message? then | |
| 2054 | `assert.message.walk(self); | |
| 2055 | fi | |
| 2056 | ||
| 2057 | return true; | |
| 2058 | si | |
| 2059 | ||
| 2060 | visit(`assert: Trees.Statements.ASSERT) is | |
| 2061 | super.visit(`assert); | |
| 2062 | ||
| 2063 | let condition_killed = _assert_condition_killed_stack[_assert_condition_killed_stack.count - 1]; | |
| 2064 | _assert_condition_killed_stack.remove_at(_assert_condition_killed_stack.count - 1); | |
| 2065 | ||
| 2066 | // `assert false` always throws — control does not | |
| 2067 | // continue past it. | |
| 2068 | if | |
| 2069 | isa Trees.Expressions.Literals.BOOLEAN(`assert.expression) /\ | |
| 2070 | (cast Trees.Expressions.Literals.BOOLEAN(`assert.expression)).value_string =~ "false" | |
| 2071 | then | |
| 2072 | _flow.set_unreachable(); | |
| 2073 | fi | |
| 2074 | ||
| 2075 | if !_check_assertion_condition(`assert.expression) then | |
| 2076 | return; | |
| 2077 | fi | |
| 2078 | ||
| 2079 | // Apply the assert's narrowing to the fall-through: | |
| 2080 | // since a failed assert throws, only the then-branch of | |
| 2081 | // the condition reaches subsequent code. So `assert x?` | |
| 2082 | // narrows x to non-optional and `assert isa T(x)` to T | |
| 2083 | // in the rest of the enclosing scope, the same way | |
| 2084 | // `if !cond then throw ... fi` would. | |
| 2085 | let facts = _condition_analyzer.analyze_condition(`assert.expression, _flow.current_env); | |
| 2086 | ||
| 2087 | if condition_killed then | |
| 2088 | facts.then_env.drop_heap_facts(); | |
| 2089 | fi | |
| 2090 | ||
| 2091 | _flow.set_env(facts.then_env); | |
| 2092 | ||
| 2093 | _check_assertion_message(`assert.message); | |
| 2094 | si | |
| 2095 | ||
| 2096 | pre(`try: Trees.Statements.TRY) -> bool is | |
| 2097 | super.pre(`try); | |
| 2098 | return _loops.pre_try(`try); | |
| 2099 | si | |
| 2100 | ||
| 2101 | visit(`try: Trees.Statements.TRY) is | |
| 2102 | super.visit(`try); | |
| 2103 | _loops.visit_try(`try); | |
| 2104 | si | |
| 2105 | ||
| 2106 | pre(`catch: Trees.Statements.CATCH) -> bool is | |
| 2107 | super.pre(`catch); | |
| 2108 | return _loops.pre_catch(`catch); | |
| 2109 | si | |
| 2110 | ||
| 2111 | visit(`catch: Trees.Statements.CATCH) is | |
| 2112 | super.visit(`catch); | |
| 2113 | _loops.visit_catch(`catch); | |
| 2114 | si | |
| 2115 | ||
| 2116 | pre(`do: Trees.Statements.DO) -> bool is | |
| 2117 | super.pre(`do); | |
| 2118 | return _loops.pre_do(`do); | |
| 2119 | si | |
| 2120 | ||
| 2121 | visit(`do: Trees.Statements.DO) is | |
| 2122 | if | |
| 2123 | let `do?.condition? /\ | |
| 2124 | Value.check_is_consumable(_logger, condition.location, condition.value) | |
| 2125 | then | |
| 2126 | if !condition.value!.type!.matches(_innate_symbol_lookup.get_bool_type()) then | |
| 2127 | _logger.error(condition.location, "while condition must be bool"); | |
| 2128 | fi | |
| 2129 | fi | |
| 2130 | ||
| 2131 | super.visit(`do); | |
| 2132 | ||
| 2133 | _loops.visit_do(`do); | |
| 2134 | si | |
| 2135 | ||
| 2136 | pre(`if: Trees.Statements.IF_BRANCH) -> bool is | |
| 2137 | super.pre(`if); | |
| 2138 | return _conditionals.pre_if_branch(`if); | |
| 2139 | si | |
| 2140 | ||
| 2141 | visit(`if: Trees.Statements.IF_BRANCH) is | |
| 2142 | _conditionals.visit_if_branch(`if); | |
| 2143 | super.visit(`if); | |
| 2144 | si | |
| 2145 | ||
| 2146 | pre(expression: Trees.Bodies.EXPRESSION) -> bool is | |
| 2147 | super.pre(expression); | |
| 2148 | ||
| 2149 | let function = current_function; | |
| 2150 | ||
| 2151 | if | |
| 2152 | !function? \/ | |
| 2153 | !function.return_type? \/ | |
| 2154 | function.return_type.is_sentinel | |
| 2155 | then | |
| 2156 | return false; | |
| 2157 | fi | |
| 2158 | ||
| 2159 | expression.expression.set_expected_type(function.return_type, "cannot return value of type {{0}} where {{1}} expected"); | |
| 2160 | ||
| 2161 | // A void-returning `=> body` doesn't need its body to yield | |
| 2162 | // a value. Push that down into any STATEMENT- / VAL_BLOCK- | |
| 2163 | // shaped body so a non-value-providing tail is accepted | |
| 2164 | // silently. | |
| 2165 | if function.return_type!.is_void then | |
| 2166 | if let statement_expression: Trees.Expressions.STATEMENT = expression.expression then | |
| 2167 | statement_expression.want_value = false; | |
| 2168 | elif let val_block: Trees.Expressions.VAL_BLOCK = expression.expression then | |
| 2169 | val_block.want_value = false; | |
| 2170 | fi | |
| 2171 | fi | |
| 2172 | ||
| 2173 | return false; | |
| 2174 | ||
| 2175 | si | |
| 2176 | ||
| 2177 | visit(expression: Trees.Bodies.EXPRESSION) is | |
| 2178 | let function = current_function; | |
| 2179 | ||
| 2180 | // A diverging body — `=> throw E`, or an `=> if/case` whose | |
| 2181 | // every arm diverges — yields no value but is valid. Keep an | |
| 2182 | // explicitly declared return type; settle an inferred one as | |
| 2183 | // void since there is nothing to infer from. | |
| 2184 | if | |
| 2185 | function? /\ | |
| 2186 | !expression.expression.value? /\ | |
| 2187 | _flow.is_unreachable | |
| 2188 | then | |
| 2189 | super.visit(expression); | |
| 2190 | ||
| 2191 | if DIVERGING_VALUE_POSITION.settles_inferred_return_to_void(function.return_type) then | |
| 2192 | function.set_return_type(_innate_symbol_lookup.get_void_type()); | |
| 2193 | fi | |
| 2194 | ||
| 2195 | return; | |
| 2196 | fi | |
| 2197 | ||
| 2198 | let value = expression.expression?.value; | |
| 2199 | ||
| 2200 | if | |
| 2201 | !function? \/ | |
| 2202 | !function.return_type? \/ | |
| 2203 | !value? \/ | |
| 2204 | !value.type? | |
| 2205 | then | |
| 2206 | super.visit(expression); | |
| 2207 | ||
| 2208 | expression.expression.compile_expressions_state.value = IR.Values.DUMMY(Semantic.Types.ERROR(), expression.expression.location); | |
| 2209 | ||
| 2210 | if function? /\ (!function.return_type? \/ function.return_type.is_wild) then | |
| 2211 | function.set_return_type(Semantic.Types.ERROR()); | |
| 2212 | fi | |
| 2213 | ||
| 2214 | return; | |
| 2215 | fi | |
| 2216 | ||
| 2217 | let void_type = _innate_symbol_lookup.get_void_type(); | |
| 2218 | ||
| 2219 | if function.return_type!.is_inferred then | |
| 2220 | if Value.check_is_consumable_allow_void(_logger, expression.expression.location, value) then | |
| 2221 | // Body contains let-await → wrap the bare-T body | |
| 2222 | // expression as `Tasks.TASK.from_result(expr)` and | |
| 2223 | // settle the inferred return as Task[T]. Values | |
| 2224 | // already typed Task[?] are pinned as-is. | |
| 2225 | // Mirrors the wrap in visit_return. | |
| 2226 | let value_type mut = value.type; | |
| 2227 | ||
| 2228 | if | |
| 2229 | function.wrap_inferred_return_as_task /\ | |
| 2230 | value_type? /\ | |
| 2231 | value_type.is_settled /\ | |
| 2232 | !_bindings.task_conversion.is_task_type(value_type) | |
| 2233 | then | |
| 2234 | let task_type = _innate_symbol_lookup.get_task_type(value_type); | |
| 2235 | ||
| 2236 | if task_type? then | |
| 2237 | let wrapped = _bindings.task_conversion.try_wrap_value_as_task_expression(expression.expression, task_type, self); | |
| 2238 | if wrapped? then | |
| 2239 | expression.expression = wrapped; | |
| 2240 | value_type = expression.expression.value!.type; | |
| 2241 | fi | |
| 2242 | fi | |
| 2243 | fi | |
| 2244 | ||
| 2245 | function.set_return_type(value_type); | |
| 2246 | elif value.type? /\ value.type!.is_error then | |
| 2247 | function.set_return_type(Semantic.Types.ERROR()); | |
| 2248 | fi | |
| 2249 | elif function.return_type!.matches(void_type) /\ !function.return_type!.is_type_variable then | |
| 2250 | if !value.type!.matches(void_type) then | |
| 2251 | _logger | |
| 2252 | .error( | |
| 2253 | expression.location, | |
| 2254 | "cannot return value from function of void type" | |
| 2255 | ); | |
| 2256 | fi | |
| 2257 | elif !function.return_type!.is_assignable_from(value.type!) then | |
| 2258 | // Implicit T → TASK[T] widening at expression-body return | |
| 2259 | // position. Same mechanism as visit_return: synthesise | |
| 2260 | // Tasks.TASK.from_result(orig) and re-resolve. | |
| 2261 | let wrapped = _bindings.task_conversion.try_wrap_value_as_task_expression(expression.expression, function.return_type!, self); | |
| 2262 | if wrapped? then | |
| 2263 | expression.expression = wrapped; | |
| 2264 | else | |
| 2265 | _logger | |
| 2266 | .error( | |
| 2267 | expression.location, | |
| 2268 | "cannot return value of type {value.type} where {function.return_type} expected" | |
| 2269 | ); | |
| 2270 | fi | |
| 2271 | else | |
| 2272 | Value.check_is_consumable_allow_void(_logger, expression.expression.location, value); | |
| 2273 | fi | |
| 2274 | ||
| 2275 | check_non_optional(function.return_type, expression.expression, expression.expression.location); | |
| 2276 | ||
| 2277 | _pure_slots.check_store(expression.expression.location, function.return_type, expression.expression.value); | |
| 2278 | ||
| 2279 | super.visit(expression); | |
| 2280 | si | |
| 2281 | ||
| 2282 | pre(function: Trees.Expressions.FUNCTION) -> bool is | |
| 2283 | // Push a function-literal boundary marker on the val- | |
| 2284 | // block stack. A `return` inside this lambda's body | |
| 2285 | // looks up the innermost val-block via the top of the | |
| 2286 | // stack; the null marker reports "no val-block target" | |
| 2287 | // and the return falls through to the function-return | |
| 2288 | // path — exiting the lambda, not the enclosing val- | |
| 2289 | // block. | |
| 2290 | _val_block_stack.add(null); | |
| 2291 | ||
| 2292 | return true; | |
| 2293 | si | |
| 2294 | ||
| 2295 | visit(function: Trees.Expressions.FUNCTION) is | |
| 2296 | let mark = _logger.mark(); | |
| 2297 | let use uses_guard = _symbol_use_locations.mark_then_release(); | |
| 2298 | ||
| 2299 | // The lambda body walks under the enclosing method's | |
| 2300 | // environment (a closure may soundly observe a narrow | |
| 2301 | // in force at its construction point), but its own ifs / | |
| 2302 | // assignments / divergence must not leak back out — so | |
| 2303 | // the enclosing environment is saved and restored. | |
| 2304 | // | |
| 2305 | // The tracked deferred-init locals are deliberately NOT | |
| 2306 | // swapped out. Closures capture by value at construction | |
| 2307 | // time, so the closure body walking under `saved_env` | |
| 2308 | // observes exactly the definite-assignment facts in | |
| 2309 | // force where the closure is built — reading a captured | |
| 2310 | // local that is unassigned there is a genuine | |
| 2311 | // use-before-assignment (the closure captured a | |
| 2312 | // not-yet-assigned value), not a false positive. | |
| 2313 | let saved_env = _flow.current_env.copy(); | |
| 2314 | ||
| 2315 | try | |
| 2316 | _logger.speculate(); | |
| 2317 | _symbol_use_locations.speculate(); | |
| 2318 | ||
| 2319 | super.pre(function); | |
| 2320 | ||
| 2321 | _lambdas.visit_function(function); | |
| 2322 | ||
| 2323 | super.visit(function); | |
| 2324 | ||
| 2325 | _logger.commit(); | |
| 2326 | _symbol_use_locations.commit(); | |
| 2327 | catch e: Exception | |
| 2328 | function.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), function.location); | |
| 2329 | _logger.release(mark); | |
| 2330 | ||
| 2331 | _logger.exception(function.location, e, "exception compiling function literal"); | |
| 2332 | yrt | |
| 2333 | ||
| 2334 | _flow.set_env(saved_env); | |
| 2335 | ||
| 2336 | // Pop the function-literal boundary marker pushed by | |
| 2337 | // pre(FUNCTION). After this point an outer val-block | |
| 2338 | // (if any) is once again the innermost return target. | |
| 2339 | assert _val_block_stack.count > 0 else "val_block_stack underflow at function literal exit"; | |
| 2340 | assert !_val_block_stack[_val_block_stack.count - 1]? else "val_block_stack head is not the function-literal marker"; | |
| 2341 | _val_block_stack.remove_at(_val_block_stack.count - 1); | |
| 2342 | si | |
| 2343 | ||
| 2344 | visit(recurse: Trees.Expressions.RECURSE) is | |
| 2345 | _lambdas.visit_recurse(recurse); | |
| 2346 | si | |
| 2347 | ||
| 2348 | pre(tuple: Trees.Expressions.TUPLE) -> bool => | |
| 2349 | _tuples.pre_tuple(tuple); | |
| 2350 | ||
| 2351 | visit(tuple: Trees.Expressions.TUPLE) is | |
| 2352 | _tuples.visit_tuple(tuple); | |
| 2353 | si | |
| 2354 | ||
| 2355 | pre(sequence: Trees.Expressions.SEQUENCE) -> bool => true; | |
| 2356 | visit(sequence: Trees.Expressions.SEQUENCE) is | |
| 2357 | let mark = _logger.mark(); | |
| 2358 | ||
| 2359 | try | |
| 2360 | _logger.speculate(); | |
| 2361 | ||
| 2362 | super.pre(sequence); | |
| 2363 | ||
| 2364 | sequence.type_expression.walk(self); | |
| 2365 | ||
| 2366 | // If the sequence is going to compile against a known | |
| 2367 | // list/array type — either an inline type annotation | |
| 2368 | // (`[1, 2, 3]: int[]`) or a constraint pushed by the | |
| 2369 | // surrounding context (typed initializer / list-of- | |
| 2370 | // lambdas) — push the corresponding element type down | |
| 2371 | // to each element as its own constraint. Constraint- | |
| 2372 | // aware element types (notably function literals) use | |
| 2373 | // it to infer their argument types; element types that | |
| 2374 | // ignore constraint (most literals) are unaffected. | |
| 2375 | let element_constraint: Type? mut = _; | |
| 2376 | ||
| 2377 | if let sequence.type_expression? /\ !isa Trees.TypeExpressions.INFER(type_expression), type_expression.type? then | |
| 2378 | element_constraint = type.get_element_type(); | |
| 2379 | elif let sequence.expected_type? then | |
| 2380 | element_constraint = expected_type.get_element_type(); | |
| 2381 | fi | |
| 2382 | ||
| 2383 | for e in sequence.elements do | |
| 2384 | if element_constraint? then | |
| 2385 | e.set_expected_type(element_constraint, "element type {{0}} not compatible with inferred list type {{1}}"); | |
| 2386 | else | |
| 2387 | e.clear_expected_type(); | |
| 2388 | fi | |
| 2389 | od | |
| 2390 | ||
| 2391 | sequence.elements.walk(self); | |
| 2392 | ||
| 2393 | _tuples.visit_sequence(sequence); | |
| 2394 | ||
| 2395 | _logger.commit(); | |
| 2396 | catch e: Exception | |
| 2397 | sequence.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), sequence.location); | |
| 2398 | _logger.release(mark); | |
| 2399 | ||
| 2400 | _logger.exception(sequence.location, e, "exception compiling call"); | |
| 2401 | yrt | |
| 2402 | si | |
| 2403 | ||
| 2404 | visit(`self: Trees.Expressions.SELF) is | |
| 2405 | let s = current_instance_context; | |
| 2406 | ||
| 2407 | let type: Type? mut = null; | |
| 2408 | ||
| 2409 | if s? then | |
| 2410 | let f = current_function; | |
| 2411 | ||
| 2412 | if !f? \/ !f.is_instance then | |
| 2413 | _logger.error(`self.location, "cannot access self from non-instance context"); | |
| 2414 | fi | |
| 2415 | ||
| 2416 | // A closure body that reads `self` is self-dependent even | |
| 2417 | // if it captures no values (e.g. `() => self`), so its | |
| 2418 | // delegate must not be memoized and shared across receivers. | |
| 2419 | if let closure: Semantic.Symbols.Closure = f then | |
| 2420 | closure.note_delegate_body_loads_self(); | |
| 2421 | fi | |
| 2422 | ||
| 2423 | if s.argument_names.count > 0 then | |
| 2424 | let arguments = Collections.LIST[Type](); | |
| 2425 | ||
| 2426 | for n in s.argument_names do | |
| 2427 | let argument = s.find_member(n); | |
| 2428 | let argument_type = if argument? then argument.type else null fi; | |
| 2429 | ||
| 2430 | if argument? /\ argument.is_type_variable /\ argument_type? then | |
| 2431 | arguments.add(argument_type); | |
| 2432 | fi | |
| 2433 | od | |
| 2434 | ||
| 2435 | if arguments.count == s.argument_names.count then | |
| 2436 | type = Semantic.Types.GENERIC( | |
| 2437 | `self.location, | |
| 2438 | cast Semantic.Symbols.Classy(s), | |
| 2439 | arguments); | |
| 2440 | fi | |
| 2441 | fi | |
| 2442 | ||
| 2443 | // A flow narrowing on `self` (keyed on its instance | |
| 2444 | // context) presents `self` at the narrowed type, so | |
| 2445 | // `isa`/destructure and member access see the variant. | |
| 2446 | // A bare variant narrow (`CONS`) is specialised against | |
| 2447 | // `self`'s closed type (`List[T]`) so it resolves to its | |
| 2448 | // closed-generic form (`CONS[T]`) for a loadable reference. | |
| 2449 | if let narrowed = _flow.current_env.narrowed_type_of(s) then | |
| 2450 | if type? then | |
| 2451 | type = _condition_analyzer.specialize_variant_for_receiver(type, narrowed); | |
| 2452 | else | |
| 2453 | type = narrowed; | |
| 2454 | fi | |
| 2455 | fi | |
| 2456 | ||
| 2457 | // Generator instance methods read `self` from the | |
| 2458 | // frame's _outer_self field — the state-machine's | |
| 2459 | // ldarg.0 is the state machine itself, not the | |
| 2460 | // user instance. | |
| 2461 | let state_machine = Semantic.Symbols.state_machine_for(current_function); | |
| 2462 | ||
| 2463 | if state_machine? /\ state_machine.frame? /\ !s.is_value_type then | |
| 2464 | let frame = state_machine.frame; | |
| 2465 | ||
| 2466 | assert frame? else "state_machine.frame? was true but field is null"; | |
| 2467 | ||
| 2468 | frame.declare(); | |
| 2469 | ||
| 2470 | if frame.outer_self_field? then | |
| 2471 | `self.compile_expressions_state.value = Load.OUTER_SELF(s, type, frame.outer_self_field); | |
| 2472 | fi | |
| 2473 | fi | |
| 2474 | ||
| 2475 | if !`self.value? then | |
| 2476 | if s.is_value_type then | |
| 2477 | `self.compile_expressions_state.value = | |
| 2478 | Load.VALUE_SELF( | |
| 2479 | s, type | |
| 2480 | ); | |
| 2481 | else | |
| 2482 | `self.compile_expressions_state.value = | |
| 2483 | Load.REFERENCE_SELF( | |
| 2484 | s, type | |
| 2485 | ); | |
| 2486 | fi | |
| 2487 | fi | |
| 2488 | ||
| 2489 | // FIXME: this breaks VSCode rename symbol: | |
| 2490 | // _symbol_use_locations.add_symbol_use(`self.location, s); | |
| 2491 | else | |
| 2492 | _logger.error(`self.location, "cannot access self from non-instance context"); | |
| 2493 | fi | |
| 2494 | si | |
| 2495 | ||
| 2496 | visit(`super: Trees.Expressions.SUPER) is | |
| 2497 | // FIXME: | |
| 2498 | let s = _symbol_table.current_instance_context; | |
| 2499 | ||
| 2500 | if s? then | |
| 2501 | if s.is_trait then | |
| 2502 | _logger.error(`super.location, "{s.short_description} does not have a super class"); | |
| 2503 | ||
| 2504 | return; | |
| 2505 | fi | |
| 2506 | ||
| 2507 | let `classy = cast Semantic.Symbols.Classy(s); | |
| 2508 | let super_type mut = `classy.ancestors[0]; | |
| 2509 | ||
| 2510 | // If the enclosing method overrides exactly one trait method, | |
| 2511 | // prefer that trait as the super type so super.foo() resolves | |
| 2512 | // to the trait's default body and emits as a non-virtual | |
| 2513 | // `call` (.NET DIM). Class-chain super stays on `ancestors[0]` | |
| 2514 | // because that already specialises generic base types — using | |
| 2515 | // the overridee's owner type would lose the specialisation. | |
| 2516 | let current = current_function; | |
| 2517 | if current? /\ current.overridees? then | |
| 2518 | let overridee_count = current.overridees |> count(); | |
| 2519 | if overridee_count == 1 then | |
| 2520 | let overridee = current.overridees |> first(); | |
| 2521 | if overridee? /\ isa Semantic.Symbols.Classy(overridee.owner) then | |
| 2522 | let owner = cast Semantic.Symbols.Classy(overridee.owner); | |
| 2523 | let owner_type = owner.type; | |
| 2524 | ||
| 2525 | if owner.is_trait /\ owner_type? then | |
| 2526 | super_type = owner_type; | |
| 2527 | fi | |
| 2528 | fi | |
| 2529 | fi | |
| 2530 | fi | |
| 2531 | ||
| 2532 | `super.compile_expressions_state.value = Load.SUPER(s, super_type); | |
| 2533 | ||
| 2534 | // FIXME: breaks VSCode rename symbol: | |
| 2535 | // _symbol_use_locations.add_symbol_use(`super.location, s); | |
| 2536 | fi | |
| 2537 | si | |
| 2538 | ||
| 2539 | // SPILL is synthesised by the SPILL_AWAITS pass *after* | |
| 2540 | // compile-expressions for the spiller's runtime path, so it | |
| 2541 | // generally won't be seen here. The implementation is included | |
| 2542 | // for completeness — types as a transparent wrapper over the | |
| 2543 | // operand. Generate-IL handles the actual eager-emission + | |
| 2544 | // frame-field-store at visit time. | |
| 2545 | visit(spill: Trees.Expressions.SPILL) is | |
| 2546 | spill.compile_expressions_state.value = null; | |
| 2547 | ||
| 2548 | let operand_value = spill.operand.value; | |
| 2549 | ||
| 2550 | if !operand_value? \/ !operand_value.type? then | |
| 2551 | spill.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), spill.location); | |
| 2552 | return; | |
| 2553 | fi | |
| 2554 | ||
| 2555 | spill.compile_expressions_state.value = IR.Values.WRAPPER(DUMMY(operand_value.type!, spill.location)); | |
| 2556 | si | |
| 2557 | ||
| 2558 | visit(`await: Trees.Expressions.AWAIT) is | |
| 2559 | `await.compile_expressions_state.value = null; | |
| 2560 | ||
| 2561 | let operand_value = `await.operand.value; | |
| 2562 | ||
| 2563 | if !operand_value? \/ !operand_value.type? then | |
| 2564 | `await.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), `await.location); | |
| 2565 | return; | |
| 2566 | fi | |
| 2567 | ||
| 2568 | let operand_type = operand_value.type!; | |
| 2569 | ||
| 2570 | // Element type for a constructed Tasks.TASK[T]; for the | |
| 2571 | // non-generic Tasks.TASK the result is void. | |
| 2572 | let element_type: Semantic.Types.Type? mut = | |
| 2573 | _bindings.task_conversion.try_get_task_element_type(operand_type); | |
| 2574 | ||
| 2575 | if !element_type? then | |
| 2576 | if _bindings.task_conversion.is_task_type(operand_type) then | |
| 2577 | // Non-generic Tasks.TASK → void result. | |
| 2578 | element_type = _innate_symbol_lookup.get_void_type(); | |
| 2579 | else | |
| 2580 | _logger.error( | |
| 2581 | `await.location, | |
| 2582 | "await requires Tasks.TASK or Tasks.TASK[T] but found {operand_type}" | |
| 2583 | ); | |
| 2584 | `await.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), `await.location); | |
| 2585 | return; | |
| 2586 | fi | |
| 2587 | fi | |
| 2588 | ||
| 2589 | // Generate-il fills the wrapper with IR.Values.AWAIT_SUSPEND | |
| 2590 | // when emitting the SM body. The wrapper carries the result | |
| 2591 | // type so surrounding expressions / let-bindings see the | |
| 2592 | // right type at compile-expressions time. | |
| 2593 | `await.compile_expressions_state.value = IR.Values.WRAPPER(DUMMY(element_type, `await.location)); | |
| 2594 | si | |
| 2595 | ||
| 2596 | visit(`cast: Trees.Expressions.CAST) is | |
| 2597 | `cast.compile_expressions_state.value = null; | |
| 2598 | ||
| 2599 | let type mut = `cast.type_expression.type; | |
| 2600 | ||
| 2601 | if !type? then | |
| 2602 | `cast.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), `cast.location); | |
| 2603 | _logger.error(`cast.type_expression.location, "cast has no type"); | |
| 2604 | return; | |
| 2605 | fi | |
| 2606 | ||
| 2607 | `cast.type_expression.check_is_not_reference(_logger, "cannot cast to a reference type"); | |
| 2608 | ||
| 2609 | let right_value = `cast.right.value; | |
| 2610 | ||
| 2611 | if !right_value? then | |
| 2612 | _logger.poison(`cast.right.location, "cast has no value"); | |
| 2613 | ||
| 2614 | return; | |
| 2615 | fi | |
| 2616 | ||
| 2617 | if right_value.type? then | |
| 2618 | let specialized = _condition_analyzer.specialize_variant_for_receiver( | |
| 2619 | right_value.type!, | |
| 2620 | type | |
| 2621 | ); | |
| 2622 | ||
| 2623 | if specialized? then | |
| 2624 | type = specialized; | |
| 2625 | fi | |
| 2626 | fi | |
| 2627 | ||
| 2628 | // just check the cast is possible at this stage | |
| 2629 | _type_caster.check_cast_is_valid(`cast.location, right_value.type!, type); | |
| 2630 | ||
| 2631 | // Warn when the cast can never succeed at runtime: the source's | |
| 2632 | // statically-known type is sealed enough that we can rule out | |
| 2633 | // any subtype that satisfies the target. The conservative form | |
| 2634 | // is a value-type source against a *reference* target that | |
| 2635 | // isn't one of its boxed-form ancestors — a value type's | |
| 2636 | // runtime type is its declared type, so `cast string(42)` and | |
| 2637 | // similar are unconditionally a null result. Value→value | |
| 2638 | // casts go through the numeric-conversion path in TYPE_CASTER | |
| 2639 | // and are left to that check; reference-source casts stay | |
| 2640 | // unwarned because the runtime value may be a subtype | |
| 2641 | // unrelated to the declared source. | |
| 2642 | // | |
| 2643 | // Skip the warning when the source is optional — `T?` to | |
| 2644 | // a reference target lowers to boxing the Nullable<T> | |
| 2645 | // (null when absent, the boxed T when present), which the | |
| 2646 | // CLR supports unconditionally. The strict non-nullable- | |
| 2647 | // by-default rule rejects `T? → T` at slot assignment, but | |
| 2648 | // an explicit cast expressing the boxing is sound and the | |
| 2649 | // warning would be a false positive. | |
| 2650 | let source_type = right_value.type; | |
| 2651 | ||
| 2652 | let is_impossible_cast = | |
| 2653 | source_type? /\ | |
| 2654 | source_type.is_settled /\ !source_type.is_type_variable /\ | |
| 2655 | !source_type.is_sentinel /\ !source_type.is_error /\ | |
| 2656 | type.is_settled /\ !type.is_type_variable /\ | |
| 2657 | !type.is_sentinel /\ !type.is_error /\ | |
| 2658 | source_type.is_value_type /\ !type.is_value_type /\ | |
| 2659 | !source_type.is_optional /\ | |
| 2660 | !type.is_assignable_from(source_type) /\ | |
| 2661 | !source_type.is_assignable_from(type) /\ | |
| 2662 | !_type_caster.find_user_defined_conversion(source_type, type)?; | |
| 2663 | ||
| 2664 | if !_build_flags.no_warn_impossible_cast /\ is_impossible_cast then | |
| 2665 | _logger.warn( | |
| 2666 | `cast.location, | |
| 2667 | "impossible-cast", | |
| 2668 | "cast from {source_type} to {type} can never succeed" | |
| 2669 | ); | |
| 2670 | fi | |
| 2671 | ||
| 2672 | // Migration nudge: cast to a non-optional reference target can | |
| 2673 | // return null today (silent) and will throw once the semantics | |
| 2674 | // flip. Either way the call site is unchecked; rewriting to | |
| 2675 | // cast T?(...) makes the null-on-failure intent explicit at | |
| 2676 | // the type level, and the strict-optional slot check then | |
| 2677 | // catches misuses at assignment sites. Skip when the target is | |
| 2678 | // not a non-optional reference (cast T?(...) itself is the fix, | |
| 2679 | // not a target of the warning), when the source is statically | |
| 2680 | // assignable to the target (the cast is provably safe), and | |
| 2681 | // when the impossible-cast form already warned about this site | |
| 2682 | // (that message subsumes this one). | |
| 2683 | if | |
| 2684 | !_build_flags.no_warn_cast_may_throw /\ | |
| 2685 | !is_impossible_cast /\ | |
| 2686 | source_type? /\ | |
| 2687 | source_type.is_settled /\ !source_type.is_type_variable /\ | |
| 2688 | !source_type.is_sentinel /\ !source_type.is_error /\ | |
| 2689 | _is_non_optional_reference(type) /\ | |
| 2690 | !_cast_target_covers_source(type, source_type) | |
| 2691 | then | |
| 2692 | let target = type; | |
| 2693 | _logger.warn( | |
| 2694 | `cast.location, | |
| 2695 | "cast-may-throw", | |
| 2696 | "cast to non-optional {target} may throw; use cast {target}? for null on failure" | |
| 2697 | ); | |
| 2698 | fi | |
| 2699 | ||
| 2700 | // we will fill this wrapper with the actual code to cast in the generate IL pass: | |
| 2701 | `cast.compile_expressions_state.value = | |
| 2702 | IR.Values.WRAPPER(DUMMY(type, `cast.location)); | |
| 2703 | si | |
| 2704 | ||
| 2705 | visit(`isa: Trees.Expressions.ISA) is | |
| 2706 | `isa.compile_expressions_state.value = null; | |
| 2707 | ||
| 2708 | let isa_type mut = `isa.type_expression.type; | |
| 2709 | ||
| 2710 | if isa_type == null then | |
| 2711 | _logger.error(`isa.type_expression.location, "isa has no type"); | |
| 2712 | `isa.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), `isa.location); | |
| 2713 | return; | |
| 2714 | fi | |
| 2715 | ||
| 2716 | if let `isa.right?, right.value?, value.type? then | |
| 2717 | let specialized = _condition_analyzer.specialize_variant_for_receiver( | |
| 2718 | type, | |
| 2719 | isa_type | |
| 2720 | ); | |
| 2721 | ||
| 2722 | if specialized? then | |
| 2723 | isa_type = specialized; | |
| 2724 | fi | |
| 2725 | fi | |
| 2726 | ||
| 2727 | let bool_type = _innate_symbol_lookup.get_bool_type(); | |
| 2728 | ||
| 2729 | `isa.compile_expressions_state.value = | |
| 2730 | ISA( | |
| 2731 | bool_type, | |
| 2732 | isa_type, | |
| 2733 | `isa.right.value! | |
| 2734 | ); | |
| 2735 | si | |
| 2736 | ||
| 2737 | visit(`typeof: Trees.Expressions.TYPEOF) is | |
| 2738 | `typeof.compile_expressions_state.value = null; | |
| 2739 | ||
| 2740 | let typeof_type = `typeof.type_expression.type; | |
| 2741 | ||
| 2742 | if !typeof_type? then | |
| 2743 | _logger.error(`typeof.type_expression.location, "typeof has no type"); | |
| 2744 | `typeof.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), `typeof.location); | |
| 2745 | return; | |
| 2746 | fi | |
| 2747 | ||
| 2748 | let type_type = _innate_symbol_lookup.get_type_type(); | |
| 2749 | ||
| 2750 | `typeof.compile_expressions_state.value = | |
| 2751 | TYPEOF( | |
| 2752 | type_type, | |
| 2753 | typeof_type | |
| 2754 | ); | |
| 2755 | si | |
| 2756 | ||
| 2757 | visit(`new: Trees.Expressions.NEW) is | |
| 2758 | _calls.visit_new(`new); | |
| 2759 | _check_pure_slots(`new.location, `new.value); | |
| 2760 | _note_call(`new.location, `new.value); | |
| 2761 | si | |
| 2762 | ||
| 2763 | visit(unary: Trees.Expressions.UNARY) is | |
| 2764 | _operators.visit_unary(unary); | |
| 2765 | _check_pure_slots(unary.location, unary.value); | |
| 2766 | _note_call(unary.location, unary.value); | |
| 2767 | si | |
| 2768 | ||
| 2769 | pre(binary: Trees.Expressions.BINARY) -> bool => | |
| 2770 | _operators.pre_binary(binary); | |
| 2771 | ||
| 2772 | visit(binary: Trees.Expressions.BINARY) is | |
| 2773 | try | |
| 2774 | _operators.visit_binary(binary); | |
| 2775 | _check_pure_slots(binary.location, binary.value); | |
| 2776 | _note_call(binary.location, binary.value); | |
| 2777 | catch ex: Exception | |
| 2778 | _logger.exception(binary.location, ex, "exception compiling binary operator (called from {System.Diagnostics.StackTrace().to_string().replace_line_endings(" ")})"); | |
| 2779 | yrt | |
| 2780 | si | |
| 2781 | ||
| 2782 | visit(index: Trees.Expressions.INDEX) is | |
| 2783 | try | |
| 2784 | _operators.visit_index(index); | |
| 2785 | check_receiver_present(index.left); | |
| 2786 | _check_pure_slots(index.location, index.value); | |
| 2787 | _note_call(index.location, index.value); | |
| 2788 | catch e: Exception | |
| 2789 | index.compile_expressions_state.value = null; | |
| 2790 | ||
| 2791 | _logger.exception(index.location, e, "exception compiling index"); | |
| 2792 | yrt | |
| 2793 | si | |
| 2794 | ||
| 2795 | visit(member: Trees.Expressions.MEMBER) is | |
| 2796 | _access.visit_member(member); | |
| 2797 | _note_call(member.location, member.value); | |
| 2798 | si | |
| 2799 | ||
| 2800 | visit(explicit_specialization: Trees.Expressions.EXPLICIT_SPECIALIZATION) is | |
| 2801 | _generic_application.visit_explicit_specialization(explicit_specialization); | |
| 2802 | si | |
| 2803 | ||
| 2804 | pre(ambiguous_expression: Trees.Expressions.AMBIGUOUS_EXPRESSION) -> bool => | |
| 2805 | _generic_application.pre_ambiguous_expression(ambiguous_expression); | |
| 2806 | ||
| 2807 | visit(ambiguous_expression: Trees.Expressions.AMBIGUOUS_EXPRESSION) is | |
| 2808 | si | |
| 2809 | ||
| 2810 | pre(generic_application: Trees.Expressions.GENERIC_APPLICATION) -> bool => | |
| 2811 | _generic_application.pre_generic_application(generic_application); | |
| 2812 | ||
| 2813 | visit(generic_application: Trees.Expressions.GENERIC_APPLICATION) is | |
| 2814 | si | |
| 2815 | ||
| 2816 | pre(left: Trees.Variables.SIMPLE_VARIABLE_LEFT) -> bool => false; | |
| 2817 | visit(left: Trees.Variables.SIMPLE_VARIABLE_LEFT) is | |
| 2818 | let symbol = find(left.name); | |
| 2819 | ||
| 2820 | if symbol? /\ isa Semantic.Types.SettableTyped(symbol) then | |
| 2821 | let typed_symbol = cast Semantic.Types.SettableTyped(symbol); | |
| 2822 | let left_state = _variable_left_state.get_or_add(left); | |
| 2823 | ||
| 2824 | symbol.define(); | |
| 2825 | ||
| 2826 | let right_location = | |
| 2827 | if left_state.right_location? then | |
| 2828 | left_state.right_location; | |
| 2829 | else | |
| 2830 | left.location; | |
| 2831 | fi; | |
| 2832 | ||
| 2833 | // Iterative-inference re-narrowing: an iter-N type | |
| 2834 | // that's a sentinel or contains a placeholder may | |
| 2835 | // be overwritten on iter N+1 with the now-narrower | |
| 2836 | // value (e.g. Function[placeholder, int] → | |
| 2837 | // Function[int, int] once the lambda arg resolves). | |
| 2838 | // | |
| 2839 | // is_settled alone isn't enough: an overload candidate | |
| 2840 | // that loses to a sibling can still leave its own | |
| 2841 | // still-unbound method type-parameter committed as | |
| 2842 | // the right-hand value's type on an earlier iter - | |
| 2843 | // e.g. C from a losing >>[A,B,C] candidate that | |
| 2844 | // failed to bind C. That's a real, non-sentinel type, | |
| 2845 | // so is_settled reports it as final - but the | |
| 2846 | // parameter isn't one of this function's own generic | |
| 2847 | // parameters, so it can never mean anything here and | |
| 2848 | // must be re-derived rather than kept. | |
| 2849 | let current_type = typed_symbol.type; | |
| 2850 | ||
| 2851 | // A recursive literal's retry walk can re-derive its | |
| 2852 | // own self-reference at the pure shape once the body | |
| 2853 | // is proven store-free (see COMPILE_LAMBDAS), after | |
| 2854 | // this local already settled at the impure shape on | |
| 2855 | // an earlier walk. Without this, `is_settled` alone | |
| 2856 | // would keep the stale impure type and the local | |
| 2857 | // would never pick up the improved one. Scoped to a | |
| 2858 | // bare inferred `let` (no explicit type) with a | |
| 2859 | // same-shape right-hand value — an explicitly-typed | |
| 2860 | // local's assignability is checked by a sibling | |
| 2861 | // branch that only runs when `needs_set` is false, | |
| 2862 | // and this must never short-circuit that check by | |
| 2863 | // coincidentally matching on an unrelated pure | |
| 2864 | // function value of a different shape. | |
| 2865 | let right_value = left_state.right_value; | |
| 2866 | let upgrades_to_pure_function = | |
| 2867 | !left_state.explicit_type? /\ | |
| 2868 | current_type? /\ current_type.is_function /\ !current_type.is_pure_function /\ | |
| 2869 | right_value? /\ right_value.type? /\ right_value.type!.is_pure_function /\ | |
| 2870 | current_type.compare(right_value.type!) == Semantic.Types.MATCH.SAME; | |
| 2871 | ||
| 2872 | let needs_set mut = | |
| 2873 | !current_type? \/ | |
| 2874 | !current_type.is_settled \/ | |
| 2875 | current_type.has_function_generic_argument_foreign_to(current_function) \/ | |
| 2876 | upgrades_to_pure_function; | |
| 2877 | ||
| 2878 | // A refutable pattern's ascription is a narrowing | |
| 2879 | // target resolved against the scrutinee, which is | |
| 2880 | // only known here. An earlier pass types the symbol | |
| 2881 | // from the ascription as written, and for a union | |
| 2882 | // variant that spelling carries none of the union's | |
| 2883 | // arguments, so it has to be replaced rather than | |
| 2884 | // kept. | |
| 2885 | if !needs_set /\ left.is_refutable /\ left_state.explicit_type? then | |
| 2886 | needs_set = true; | |
| 2887 | fi | |
| 2888 | ||
| 2889 | if needs_set then | |
| 2890 | if left_state.explicit_type? then | |
| 2891 | typed_symbol.set_type(left_state.explicit_type); | |
| 2892 | elif left_state.right_value? then | |
| 2893 | let right_value = left_state.right_value; | |
| 2894 | ||
| 2895 | if Value.check_is_consumable(_logger, right_location, right_value) then | |
| 2896 | typed_symbol.set_type(right_value.type!); | |
| 2897 | else | |
| 2898 | typed_symbol.set_type(Semantic.Types.ERROR()); | |
| 2899 | fi | |
| 2900 | else | |
| 2901 | // No explicit type and no initializer | |
| 2902 | // (`let l;`). Try the LUB accumulated from | |
| 2903 | // later assignments; otherwise stand-in with | |
| 2904 | // an INFERRED_VARIABLE_TYPE placeholder so | |
| 2905 | // subsequent assignments can attach | |
| 2906 | // constraints and the body retry loop | |
| 2907 | // resolves on iteration N+1. | |
| 2908 | // | |
| 2909 | // !is_sentinel deliberate (not is_settled): | |
| 2910 | // if try_get_inferred_type returns a | |
| 2911 | // composite-with-placeholder LUB, we commit | |
| 2912 | // it on iter N. The needs_set path above | |
| 2913 | // uses is_settled, so iter N+1 picks the | |
| 2914 | // slot up again and may refresh with a | |
| 2915 | // more-resolved value. !is_sentinel accepts | |
| 2916 | // a one-iter delayed convergence in exchange | |
| 2917 | // for not re-deriving the LUB every iter | |
| 2918 | // while it's stable. | |
| 2919 | if isa Semantic.Symbols.Variable(symbol) then | |
| 2920 | let variable = symbol; | |
| 2921 | let inferred = variable.try_get_inferred_type(); | |
| 2922 | ||
| 2923 | if inferred? /\ !inferred.is_sentinel then | |
| 2924 | typed_symbol.set_type(inferred); | |
| 2925 | else | |
| 2926 | typed_symbol.set_type(Semantic.Types.INFERRED_VARIABLE_TYPE(variable)); | |
| 2927 | fi | |
| 2928 | fi | |
| 2929 | fi | |
| 2930 | elif | |
| 2931 | left_state.explicit_type? /\ | |
| 2932 | left_state.right_value? /\ | |
| 2933 | Value.check_is_consumable(_logger, right_location, left_state.right_value) | |
| 2934 | then | |
| 2935 | let explicit_type = left_state.explicit_type!; | |
| 2936 | let right_value = left_state.right_value!; | |
| 2937 | ||
| 2938 | if | |
| 2939 | !left.is_refutable /\ | |
| 2940 | !explicit_type.is_assignable_from(right_value.type!) | |
| 2941 | then | |
| 2942 | // Refutable bindings (`if let p: T = e`) skip | |
| 2943 | // this check: the type ascription is a | |
| 2944 | // runtime narrowing test, the scrutinee is | |
| 2945 | // typically wider than `T`, and the cast may | |
| 2946 | // fail — that's the whole point of the | |
| 2947 | // construct. | |
| 2948 | _logger.error( | |
| 2949 | left_state.variable_location!, | |
| 2950 | "{right_value.type} is not assignable to {explicit_type}"); | |
| 2951 | fi | |
| 2952 | fi | |
| 2953 | ||
| 2954 | if left_state.right_value? then | |
| 2955 | left_state.value = symbol.store(left.location, null, left_state.right_value, _symbol_loader, true); | |
| 2956 | ||
| 2957 | // A non-optional initializer leaves the local | |
| 2958 | // known to hold a value — even where its declared | |
| 2959 | // type is `T?` — so a following dereference does | |
| 2960 | // not warn. Skipped when the local's declared | |
| 2961 | // type is already non-optional (the presence bit | |
| 2962 | // is redundant and the hint would be noise). | |
| 2963 | if | |
| 2964 | isa Semantic.Symbols.Variable(symbol) /\ | |
| 2965 | is_non_optional_value(left_state.right_value) | |
| 2966 | then | |
| 2967 | let variable = cast Semantic.Symbols.Variable(symbol); | |
| 2968 | let variable_type = variable.type; | |
| 2969 | ||
| 2970 | if variable_type? /\ variable_type.is_optional then | |
| 2971 | _flow.mark_non_null(variable); | |
| 2972 | _flow.report_narrowing_site( | |
| 2973 | left.location, | |
| 2974 | "narrowing-assign", | |
| 2975 | "►", | |
| 2976 | INLAY_TYPE.render(variable_type.as_non_optional()) | |
| 2977 | ); | |
| 2978 | fi | |
| 2979 | fi | |
| 2980 | fi | |
| 2981 | else | |
| 2982 | _logger.error(left.name.location, "couldn't find typed symbol for variable {left.name}"); | |
| 2983 | fi | |
| 2984 | si | |
| 2985 | ||
| 2986 | pre(left: Trees.Variables.DESTRUCTURING_VARIABLE_LEFT) -> bool is | |
| 2987 | // A destructured formal argument has no initializer to walk | |
| 2988 | // a value from - its leaves' types are already assigned by | |
| 2989 | // resolve-explicit-types from the parameter's aggregate | |
| 2990 | // type, and generate-il sources the unpack directly from | |
| 2991 | // the synthesised parameter symbol. Nothing here applies. | |
| 2992 | if left.is_argument_left then | |
| 2993 | return true; | |
| 2994 | fi | |
| 2995 | ||
| 2996 | // Per-element type ascription: each element of a destructure | |
| 2997 | // pattern can carry its own `: T` (e.g. `(c: Cat, d: Dog)`, | |
| 2998 | // or recursively `((x: int, y: int): Point, c: Color)`). | |
| 2999 | // Walk the type_expression and pin the element's | |
| 3000 | // `explicit_type` so the bound symbol gets the declared | |
| 3001 | // type rather than the raw source-member type. The runtime | |
| 3002 | // narrowing test for the refutable path is emitted later | |
| 3003 | // by `gen_destructuring_initialize` in generate_il. | |
| 3004 | for e in left.elements do | |
| 3005 | if let e.type_expression? then | |
| 3006 | type_expression.walk(self); | |
| 3007 | ||
| 3008 | if let type_expression.type? then | |
| 3009 | _variable_left_state.get_or_add(e).explicit_type = type; | |
| 3010 | fi | |
| 3011 | fi | |
| 3012 | od | |
| 3013 | ||
| 3014 | let left_state = _variable_left_state.get_or_add(left); | |
| 3015 | ||
| 3016 | let from mut = left_state.right_value; | |
| 3017 | ||
| 3018 | if !from? then | |
| 3019 | _logger.error(left.location, "cannot destructure without initializer"); | |
| 3020 | return true; | |
| 3021 | fi | |
| 3022 | ||
| 3023 | let from_type mut = | |
| 3024 | if left_state.explicit_type? then | |
| 3025 | left_state.explicit_type; | |
| 3026 | else | |
| 3027 | from.type; | |
| 3028 | fi; | |
| 3029 | ||
| 3030 | if !from_type? then | |
| 3031 | _logger.error(left.location, "oops: null type"); | |
| 3032 | ||
| 3033 | from_type = Semantic.Types.ERROR(); | |
| 3034 | fi | |
| 3035 | ||
| 3036 | // Refutable destructure on an optional source: the `if let` | |
| 3037 | // presence test has already excluded null at this point, so | |
| 3038 | // resolve members against the unwrapped type and load them | |
| 3039 | // from the unwrapped value. A value-type `T?` unwraps via | |
| 3040 | // its synthesised `value` member; a reference `T?` keeps | |
| 3041 | // the same backing value and just drops the optional flag. | |
| 3042 | if left.is_refutable /\ from_type.is_optional then | |
| 3043 | if from_type.is_value_type then | |
| 3044 | let value_member = from_type.find_member("value"); | |
| 3045 | ||
| 3046 | if value_member? then | |
| 3047 | from = value_member.load(LOCATION.internal, from, _symbol_loader); | |
| 3048 | from_type = from.type!; | |
| 3049 | fi | |
| 3050 | else | |
| 3051 | from_type = from_type.as_non_optional(); | |
| 3052 | from = IR.Values.TYPE_WRAPPER(from_type, from); | |
| 3053 | fi | |
| 3054 | fi | |
| 3055 | ||
| 3056 | // Destructure on a still-unresolved placeholder: emit a | |
| 3057 | // DESTRUCTURE_CONSTRAINT(member_count) on the | |
| 3058 | // placeholder's origin so the body-retry loop can filter | |
| 3059 | // candidate types against it, then defer rather than | |
| 3060 | // hard-erroring "cannot destructure". Without this the | |
| 3061 | // body-retry never gets a useful signal — the destructure | |
| 3062 | // type is fixed and the lambda's RHS placeholder is | |
| 3063 | // forced to resolve via some other use. | |
| 3064 | if isa Semantic.Types.INFERRED_VARIABLE_TYPE(from_type) then | |
| 3065 | let placeholder = from_type; | |
| 3066 | let constraint = Semantic.DESTRUCTURE_CONSTRAINT(left.elements.count); | |
| 3067 | ||
| 3068 | _logger.mark_consumed_any_if(placeholder.origin.add_constraint(constraint)); | |
| 3069 | ||
| 3070 | return true; | |
| 3071 | fi | |
| 3072 | ||
| 3073 | let elements = left.elements; | |
| 3074 | ||
| 3075 | // The parser enforces all-or-nothing per group: either | |
| 3076 | // every element carries a source_field_name (named | |
| 3077 | // group) or none do (positional group). Sample the | |
| 3078 | // first to decide which strategy entry point to call. | |
| 3079 | let is_named_group = elements.count > 0 /\ elements[0].source_field_name?; | |
| 3080 | ||
| 3081 | let field_names: Collections.List[string?]? mut = null; | |
| 3082 | if is_named_group then | |
| 3083 | let names = Collections.LIST[string?](); | |
| 3084 | for element in elements do | |
| 3085 | // is_named_group: the parser enforces all-or-nothing | |
| 3086 | // source field names per destructure group | |
| 3087 | names.add(element.source_field_name!.name); | |
| 3088 | od | |
| 3089 | field_names = names; | |
| 3090 | fi | |
| 3091 | ||
| 3092 | let strategy = resolve_destructure_strategy(left.location, from_type, elements.count, field_names); | |
| 3093 | ||
| 3094 | let block = IR.Values.BLOCK(); | |
| 3095 | ||
| 3096 | if strategy.is_deconstruct then | |
| 3097 | let deconstruct = strategy.deconstruct_function!; | |
| 3098 | ||
| 3099 | for i in 0..elements.count do | |
| 3100 | let element = elements[i]; | |
| 3101 | let element_type = deconstruct.arguments[i].get_element_type()!; | |
| 3102 | ||
| 3103 | let element_state = _variable_left_state.get_or_add(element); | |
| 3104 | ||
| 3105 | element_state.right_value = IR.Values.DUMMY(element_type, element.location); | |
| 3106 | ||
| 3107 | element_state.variable_location = left_state.variable_location; | |
| 3108 | element_state.right_location = left_state.right_location; | |
| 3109 | ||
| 3110 | element.walk(self); | |
| 3111 | ||
| 3112 | if element_state.value? then | |
| 3113 | block.add(element_state.value); | |
| 3114 | fi | |
| 3115 | od | |
| 3116 | else | |
| 3117 | let members = strategy.members; | |
| 3118 | let get_from = from.get_temp_copier(block, "destructure"); | |
| 3119 | ||
| 3120 | for i in 0..elements.count do | |
| 3121 | let element = elements[i]; | |
| 3122 | let member = members[i]; | |
| 3123 | ||
| 3124 | if member? then | |
| 3125 | let element_state = _variable_left_state.get_or_add(element); | |
| 3126 | ||
| 3127 | element_state.right_value = member.load(LOCATION.internal, get_from(), _symbol_loader); | |
| 3128 | ||
| 3129 | element_state.variable_location = left_state.variable_location; | |
| 3130 | element_state.right_location = left_state.right_location; | |
| 3131 | ||
| 3132 | element.walk(self); | |
| 3133 | ||
| 3134 | if element_state.value? then | |
| 3135 | block.add(element_state.value); | |
| 3136 | fi | |
| 3137 | fi | |
| 3138 | od | |
| 3139 | fi | |
| 3140 | ||
| 3141 | block.close(); | |
| 3142 | ||
| 3143 | left_state.value = block; | |
| 3144 | ||
| 3145 | return true; | |
| 3146 | si | |
| 3147 | ||
| 3148 | visit(destructure_left: Trees.Variables.DESTRUCTURING_VARIABLE_LEFT) is | |
| 3149 | si | |
| 3150 | ||
| 3151 | // A literal leaf inside a destructure pattern — a runtime | |
| 3152 | // equality test, not a binding. The expression carries its | |
| 3153 | // own type (literal kinds map to fixed types; an enum-member | |
| 3154 | // name expression types to its enum). The expression is | |
| 3155 | // walked via default descent; the source position's type is | |
| 3156 | // pushed down as a constraint so a `null` leaf picks up the | |
| 3157 | // source position's nullable type (other literal kinds | |
| 3158 | // inherit the constraint as a no-op, since their type is | |
| 3159 | // fixed by their token kind). Literal-vs-source mismatches | |
| 3160 | // are diagnosed eagerly by `visit(LITERAL_VARIABLE_LEFT)` | |
| 3161 | // below, not via the constraint message. | |
| 3162 | pre(left: Trees.Variables.LITERAL_VARIABLE_LEFT) -> bool is | |
| 3163 | let right_value = _variable_left_state.get_or_add(left).right_value; | |
| 3164 | ||
| 3165 | if right_value? /\ right_value.type? then | |
| 3166 | left.expression.set_expected_type( | |
| 3167 | right_value.type!, | |
| 3168 | "literal pattern type {{0}} is not comparable to source position type {{1}}" | |
| 3169 | ); | |
| 3170 | fi | |
| 3171 | return false; | |
| 3172 | si | |
| 3173 | ||
| 3174 | visit(left: Trees.Variables.LITERAL_VARIABLE_LEFT) is | |
| 3175 | // Literal-leaf in a non-refutable context (plain `let`) | |
| 3176 | // is a silent no-op at runtime — the pattern would never | |
| 3177 | // actually test the source value, so subsequent bindings | |
| 3178 | // run as if the literal were a wildcard. Reject it loudly | |
| 3179 | // here so the user is forced to write either `if let` | |
| 3180 | // / `case`-when (where the literal becomes an actual | |
| 3181 | // equality test) or remove the literal. | |
| 3182 | if !left.is_refutable then | |
| 3183 | _logger.error( | |
| 3184 | left.location, | |
| 3185 | "literal pattern is only allowed inside a refutable binding (if let or case-when arm)" | |
| 3186 | ); | |
| 3187 | ||
| 3188 | return; | |
| 3189 | fi | |
| 3190 | ||
| 3191 | // A literal whose type is incompatible with the source | |
| 3192 | // position's type can never match — the runtime equality | |
| 3193 | // test is statically dead. Reject with a clear diagnostic | |
| 3194 | // here rather than letting the IL emit and surface as a | |
| 3195 | // less helpful comparison-operator error later. | |
| 3196 | if let | |
| 3197 | rv = _variable_left_state.get_or_add(left).right_value, source_type = rv.type, | |
| 3198 | ev = left.expression.value, literal_type = ev.type | |
| 3199 | then | |
| 3200 | if | |
| 3201 | !source_type.is_assignable_from(literal_type) /\ | |
| 3202 | !literal_type.is_assignable_from(source_type) | |
| 3203 | then | |
| 3204 | _logger.error( | |
| 3205 | left.location, | |
| 3206 | "literal of type {literal_type} cannot match source position of type {source_type}" | |
| 3207 | ); | |
| 3208 | fi | |
| 3209 | fi | |
| 3210 | si | |
| 3211 | ||
| 3212 | pre(variable: Trees.Variables.VARIABLE) -> bool is | |
| 3213 | // Attribute pragmas on a formal-argument parameter — walk | |
| 3214 | // their argument expressions here since this override | |
| 3215 | // suppresses VARIABLE's own default child walk (returns | |
| 3216 | // true below). | |
| 3217 | if variable.pragmas? then | |
| 3218 | for pragma in variable.pragmas do | |
| 3219 | pragma.walk(self); | |
| 3220 | od | |
| 3221 | fi | |
| 3222 | ||
| 3223 | variable.type_expression.walk(self); | |
| 3224 | ||
| 3225 | // push explicit type down into the variable left | |
| 3226 | if let te_type = variable.type_expression.type /\ variable.is_explicit_type then | |
| 3227 | _variable_left_state.get_or_add(variable.left).explicit_type = te_type; | |
| 3228 | fi | |
| 3229 | ||
| 3230 | if variable.is_refutable then | |
| 3231 | variable.left.mark_refutable_recursive(); | |
| 3232 | fi | |
| 3233 | ||
| 3234 | // A bare `_` initializer of a simple local with no | |
| 3235 | // explicit type is treated exactly like a no-initializer | |
| 3236 | // `let`: the type is inferred from later assignments. | |
| 3237 | // `_` only contributes the definite-assignment fact — the | |
| 3238 | // LET deferred-init tracking keys off `initializer?`, | |
| 3239 | // which is true here, so the variable is not warned. | |
| 3240 | let bare_default = | |
| 3241 | isa Trees.Expressions.DEFAULT(variable.initializer) /\ | |
| 3242 | !(cast Trees.Expressions.DEFAULT(variable.initializer)).type_expression? /\ | |
| 3243 | !variable.is_explicit_type /\ | |
| 3244 | variable.left.is_simple_name; | |
| 3245 | ||
| 3246 | if let init = variable.initializer /\ !bare_default /\ !variable.is_argument then | |
| 3247 | if let te = variable.type_expression, te_type = te.type /\ !isa Trees.TypeExpressions.INFER(te) then | |
| 3248 | // if we have both an explicit type and an initializer, we | |
| 3249 | // can push a type constraint down into the initializer | |
| 3250 | init.set_expected_type(te_type, "{{0}} is not assignable to {{1}}"); | |
| 3251 | fi | |
| 3252 | ||
| 3253 | init.walk(self); | |
| 3254 | ||
| 3255 | // push the initializer value down into the variable left | |
| 3256 | let left_state = _variable_left_state.get_or_add(variable.left); | |
| 3257 | ||
| 3258 | if let init_value = init.value then | |
| 3259 | left_state.right_value = init_value; | |
| 3260 | else | |
| 3261 | left_state.right_value = IR.Values.DUMMY(Semantic.Types.ERROR(), init.location); | |
| 3262 | fi | |
| 3263 | ||
| 3264 | left_state.variable_location = variable.location; | |
| 3265 | left_state.right_location = init.location; | |
| 3266 | fi | |
| 3267 | ||
| 3268 | // No-type, no-initializer is now allowed: the variable's | |
| 3269 | // type is inferred from later assignments via the | |
| 3270 | // INFERRED_VARIABLE_TYPE placeholder + LUB accumulator | |
| 3271 | // (#1174 — see visit(SIMPLE_VARIABLE_LEFT) below). If | |
| 3272 | // no assignment ever fires, the placeholder remains | |
| 3273 | // and the variable's first use will produce a | |
| 3274 | // "cannot infer" error. | |
| 3275 | ||
| 3276 | variable.left.walk(self); | |
| 3277 | ||
| 3278 | // Generator: register the new local on the state-machine | |
| 3279 | // frame so a closure later in the body that captures it | |
| 3280 | // freezes IL referencing the frame field rather than a | |
| 3281 | // CLR-local slot that doesn't exist inside MoveNext. | |
| 3282 | // No-op outside a generator function; idempotent on the | |
| 3283 | // field (declare_local_field returns the existing field | |
| 3284 | // and only refreshes its type on subsequent calls). | |
| 3285 | _declare_state_machine_local_fields(variable.left); | |
| 3286 | ||
| 3287 | return true; | |
| 3288 | si | |
| 3289 | ||
| 3290 | visit(variable: Trees.Variables.VARIABLE) is | |
| 3291 | // A typed `let x: T = e` — check the initializer against | |
| 3292 | // the declared type. An untyped `let` infers its type | |
| 3293 | // from the initializer, so there is no slot to violate. | |
| 3294 | let type_expression = variable.type_expression; | |
| 3295 | let initializer = variable.initializer; | |
| 3296 | ||
| 3297 | if initializer? then | |
| 3298 | check_non_optional( | |
| 3299 | type_expression.type, | |
| 3300 | initializer, | |
| 3301 | initializer.location | |
| 3302 | ); | |
| 3303 | ||
| 3304 | _pure_slots.check_store(initializer.location, type_expression.type, initializer.value); | |
| 3305 | fi | |
| 3306 | ||
| 3307 | if let pragmas = variable.pragmas then | |
| 3308 | let target = symbol_for(variable); | |
| 3309 | ||
| 3310 | for pragma in pragmas do | |
| 3311 | _attribute_resolver.resolve(pragma, target); | |
| 3312 | od | |
| 3313 | fi | |
| 3314 | si | |
| 3315 | ||
| 3316 | // Iterate the names on a variable-left pattern and, when the | |
| 3317 | // enclosing function is a generator, register each LOCAL_VARIABLE | |
| 3318 | // with the state-machine frame. LOCAL_ARGUMENTs are filtered out | |
| 3319 | // — those are already wired by `frame.declare()` at function | |
| 3320 | // entry — and non-generator functions are a no-op. | |
| 3321 | _declare_state_machine_local_fields(left: Trees.Variables.VariableLeft) is | |
| 3322 | ||
| 3323 | let function = current_function; | |
| 3324 | ||
| 3325 | if !function? then | |
| 3326 | return; | |
| 3327 | fi | |
| 3328 | ||
| 3329 | // Try generator first, then async — they're mutually | |
| 3330 | // exclusive (declare-symbols enforces, see the | |
| 3331 | // generator-and-async-not-allowed diagnostic). | |
| 3332 | let state_machine = Semantic.Symbols.state_machine_for(function); | |
| 3333 | let async_state_machine = Semantic.Symbols.async_state_machine_for(function); | |
| 3334 | ||
| 3335 | let names_into = Collections.LIST[Trees.Identifiers.Identifier](); | |
| 3336 | left.get_names_into(names_into); | |
| 3337 | ||
| 3338 | if state_machine? /\ state_machine.frame? then | |
| 3339 | let frame = state_machine.frame; | |
| 3340 | ||
| 3341 | assert frame? else "state_machine.frame? was true but field is null"; | |
| 3342 | ||
| 3343 | for name in names_into do | |
| 3344 | let symbol = find(name); | |
| 3345 | if let local: Semantic.Symbols.LOCAL_VARIABLE = symbol then | |
| 3346 | frame.declare_local_field(local); | |
| 3347 | fi | |
| 3348 | od | |
| 3349 | elif async_state_machine? /\ async_state_machine.frame? then | |
| 3350 | let frame = async_state_machine.frame; | |
| 3351 | ||
| 3352 | assert frame? else "async_state_machine.frame? was true but field is null"; | |
| 3353 | ||
| 3354 | for name in names_into do | |
| 3355 | let symbol = find(name); | |
| 3356 | if let local: Semantic.Symbols.LOCAL_VARIABLE = symbol then | |
| 3357 | frame.declare_local_field(local); | |
| 3358 | fi | |
| 3359 | od | |
| 3360 | fi | |
| 3361 | si | |
| 3362 | ||
| 3363 | // TODO used by for loop - needs removing | |
| 3364 | set_symbol_type(left: Trees.Variables.VariableLeft, type: Type) is | |
| 3365 | ||
| 3366 | if left.is_simple_name then | |
| 3367 | // is_simple_name => SIMPLE_VARIABLE_LEFT, whose name is non-null | |
| 3368 | let symbol = find(left.name!); | |
| 3369 | ||
| 3370 | if symbol? /\ isa Semantic.Types.SettableTyped(symbol) then | |
| 3371 | let typed_symbol = cast Semantic.Types.SettableTyped(symbol); | |
| 3372 | ||
| 3373 | symbol.define(); | |
| 3374 | ||
| 3375 | typed_symbol.set_type(type); | |
| 3376 | else | |
| 3377 | _logger.error(left.location, "couldn't find typed symbol for variable {left.name}"); | |
| 3378 | fi | |
| 3379 | else | |
| 3380 | set_symbol_destructure_types(left, type); | |
| 3381 | fi | |
| 3382 | si | |
| 3383 | ||
| 3384 | // TODO used by for loop - needs removing | |
| 3385 | set_symbol_destructure_types(left: Trees.Variables.VariableLeft, from_type: Type) is | |
| 3386 | let elements = left.elements; | |
| 3387 | ||
| 3388 | if !elements? then | |
| 3389 | return; | |
| 3390 | fi | |
| 3391 | ||
| 3392 | let is_named_group = elements.count > 0 /\ elements[0].source_field_name?; | |
| 3393 | ||
| 3394 | let field_names: Collections.List[string?]? mut = null; | |
| 3395 | if is_named_group then | |
| 3396 | let names = Collections.LIST[string?](); | |
| 3397 | for element in elements do | |
| 3398 | // is_named_group: the parser enforces all-or-nothing | |
| 3399 | // source field names per destructure group | |
| 3400 | names.add(element.source_field_name!.name); | |
| 3401 | od | |
| 3402 | field_names = names; | |
| 3403 | fi | |
| 3404 | ||
| 3405 | let strategy = resolve_destructure_strategy(left.location, from_type, elements.count, field_names); | |
| 3406 | ||
| 3407 | if strategy.is_deconstruct then | |
| 3408 | let deconstruct = strategy.deconstruct_function!; | |
| 3409 | ||
| 3410 | for i in 0..elements.count do | |
| 3411 | let element = elements[i]; | |
| 3412 | ||
| 3413 | let element_type = deconstruct.arguments[i].get_element_type()!; | |
| 3414 | ||
| 3415 | if element.is_simple_name then | |
| 3416 | let symbol = find(element.name!); | |
| 3417 | ||
| 3418 | if symbol? /\ isa Semantic.Types.SettableTyped(symbol) then | |
| 3419 | let typed_symbol = cast Semantic.Types.SettableTyped(symbol); | |
| 3420 | ||
| 3421 | symbol.define(); | |
| 3422 | ||
| 3423 | typed_symbol.set_type(element_type); | |
| 3424 | else | |
| 3425 | _logger.error(element.location, "couldn't find typed symbol for destructuring element {element.name}"); | |
| 3426 | fi | |
| 3427 | else | |
| 3428 | set_symbol_destructure_types(element, element_type); | |
| 3429 | fi | |
| 3430 | od | |
| 3431 | ||
| 3432 | return; | |
| 3433 | fi | |
| 3434 | ||
| 3435 | let members = strategy.members; | |
| 3436 | ||
| 3437 | for i in 0..elements.count do | |
| 3438 | let element = elements[i]; | |
| 3439 | let member = members[i]; | |
| 3440 | ||
| 3441 | if member? then | |
| 3442 | let member_type = member.type; | |
| 3443 | ||
| 3444 | if element.is_simple_name then | |
| 3445 | let symbol = find(element.name!); | |
| 3446 | ||
| 3447 | if symbol? /\ isa Semantic.Types.SettableTyped(symbol) then | |
| 3448 | let typed_symbol = cast Semantic.Types.SettableTyped(symbol); | |
| 3449 | ||
| 3450 | symbol.define(); | |
| 3451 | ||
| 3452 | if member_type? then | |
| 3453 | typed_symbol.set_type(member_type); | |
| 3454 | fi | |
| 3455 | else | |
| 3456 | _logger.error(element.location, "couldn't find typed symbol for destructuring element {element.name}"); | |
| 3457 | fi | |
| 3458 | elif member_type? then | |
| 3459 | set_symbol_destructure_types(element, member_type); | |
| 3460 | fi | |
| 3461 | fi | |
| 3462 | od | |
| 3463 | si | |
| 3464 | ||
| 3465 | // TODO used by for loop - needs removing | |
| 3466 | get_destructure_types(type: Type?) -> Collections.List[Type] is | |
| 3467 | let result = Collections.LIST[Type](); | |
| 3468 | ||
| 3469 | if !type? \/ !type.is_value_tuple then | |
| 3470 | return result; | |
| 3471 | fi | |
| 3472 | ||
| 3473 | get_destructure_types_into(type, result); | |
| 3474 | ||
| 3475 | return result; | |
| 3476 | si | |
| 3477 | ||
| 3478 | // TODO used by for loop - needs removing | |
| 3479 | get_destructure_types_into(type: Type, into: Collections.MutableList[Type]) is | |
| 3480 | let result = Collections.LIST[Type](); | |
| 3481 | ||
| 3482 | for t in type.arguments do | |
| 3483 | if t.is_value_tuple then | |
| 3484 | get_destructure_types_into(t, into); | |
| 3485 | else | |
| 3486 | into.add(t); | |
| 3487 | fi | |
| 3488 | od | |
| 3489 | si | |
| 3490 | ||
| 3491 | visit(integer: Trees.Expressions.Literals.INTEGER) is | |
| 3492 | _literals.visit_integer(integer); | |
| 3493 | si | |
| 3494 | ||
| 3495 | visit(float: Trees.Expressions.Literals.FLOAT) is | |
| 3496 | _literals.visit_float(float); | |
| 3497 | si | |
| 3498 | ||
| 3499 | visit(interpolation: Trees.Expressions.STRING_INTERPOLATION) is | |
| 3500 | _literals.visit_interpolation(interpolation); | |
| 3501 | ||
| 3502 | // Interpolation formats each fragment through to_string | |
| 3503 | // at run time, but those calls are synthesised at | |
| 3504 | // emission and never surface as call values here — so | |
| 3505 | // the call transfer must fire now unless every | |
| 3506 | // fragment's to_string dispatch is provably store-free. | |
| 3507 | if !_all_fragments_format_store_free(interpolation) then | |
| 3508 | _flow.on_call(interpolation.location); | |
| 3509 | fi | |
| 3510 | si | |
| 3511 | ||
| 3512 | _all_fragments_format_store_free(interpolation: Trees.Expressions.STRING_INTERPOLATION) -> bool is | |
| 3513 | for fragment in interpolation.values do | |
| 3514 | if fragment.is_expression then | |
| 3515 | if fragment.format? then | |
| 3516 | // a format specifier selects a different, | |
| 3517 | // culture-aware formatting path | |
| 3518 | return false; | |
| 3519 | fi | |
| 3520 | ||
| 3521 | let expression = fragment.expression; | |
| 3522 | ||
| 3523 | if !expression.value? then | |
| 3524 | return false; | |
| 3525 | fi | |
| 3526 | ||
| 3527 | let fragment_type = expression.value.type; | |
| 3528 | ||
| 3529 | if !fragment_type? then | |
| 3530 | return false; | |
| 3531 | fi | |
| 3532 | ||
| 3533 | let member = fragment_type.find_member("to_string"); | |
| 3534 | ||
| 3535 | if !_is_store_free_to_string(member) then | |
| 3536 | return false; | |
| 3537 | fi | |
| 3538 | fi | |
| 3539 | od | |
| 3540 | ||
| 3541 | return true; | |
| 3542 | si | |
| 3543 | ||
| 3544 | // True when the zero-argument to_string a fragment formats | |
| 3545 | // through — including every override a call could dispatch | |
| 3546 | // to — is proven store-free. | |
| 3547 | _is_store_free_to_string(member: Semantic.Symbols.Symbol?) -> bool is | |
| 3548 | if !member? then | |
| 3549 | return false; | |
| 3550 | fi | |
| 3551 | ||
| 3552 | if isa Semantic.Symbols.FUNCTION_GROUP(member) then | |
| 3553 | for function in (cast Semantic.Symbols.FUNCTION_GROUP(member)).functions do | |
| 3554 | if !function.are_arguments_declared \/ function.arguments.count == 0 then | |
| 3555 | return function.is_store_free; | |
| 3556 | fi | |
| 3557 | od | |
| 3558 | ||
| 3559 | return false; | |
| 3560 | fi | |
| 3561 | ||
| 3562 | if isa Semantic.Symbols.Function(member) then | |
| 3563 | let function = cast Semantic.Symbols.Function(member); | |
| 3564 | ||
| 3565 | if !function.are_arguments_declared \/ function.arguments.count == 0 then | |
| 3566 | return function.is_store_free; | |
| 3567 | fi | |
| 3568 | fi | |
| 3569 | ||
| 3570 | return false; | |
| 3571 | si | |
| 3572 | ||
| 3573 | visit(`string: Trees.Expressions.Literals.STRING) is | |
| 3574 | _literals.visit_string(`string); | |
| 3575 | si | |
| 3576 | ||
| 3577 | visit(character: Trees.Expressions.Literals.CHARACTER) is | |
| 3578 | _literals.visit_character(character); | |
| 3579 | si | |
| 3580 | ||
| 3581 | visit(boolean: Trees.Expressions.Literals.BOOLEAN) is | |
| 3582 | _literals.visit_boolean(boolean); | |
| 3583 | si | |
| 3584 | ||
| 3585 | pre(call: Trees.Expressions.CALL) -> bool => true; | |
| 3586 | visit(call: Trees.Expressions.CALL) is | |
| 3587 | let mark = _logger.mark(); | |
| 3588 | ||
| 3589 | try | |
| 3590 | super.pre(call); | |
| 3591 | ||
| 3592 | // Push the call's expected type into the callee | |
| 3593 | // expression. A unit-variant `Option.NONE` sitting at | |
| 3594 | // call.function reads it in visit_member and asks | |
| 3595 | // OWNER_CONSTRAINT_SPECIALIZER for the specialised | |
| 3596 | // owner — the same binding the resolve_constructor | |
| 3597 | // path performs for the parenthesised form. Non-variant | |
| 3598 | // callees ignore the field. | |
| 3599 | if call.expected_type? then | |
| 3600 | call.function.set_expected_type(call.expected_type, call.expected_type_error_message); | |
| 3601 | fi | |
| 3602 | ||
| 3603 | // Mark the callee so a reflected TYPE_GROUP resolves to its | |
| 3604 | // generic member for construction rather than collapsing to | |
| 3605 | // the (often non-constructible) arity-0 member. | |
| 3606 | call.function.mark_call_target(); | |
| 3607 | ||
| 3608 | // Function walks outside the speculation level that | |
| 3609 | // `visit_call` rolls back during constraint-push | |
| 3610 | // retry, so diagnostics from sub-walks of the function | |
| 3611 | // expression aren't scrubbed alongside first-pass | |
| 3612 | // overload noise. | |
| 3613 | call.function.walk(self); | |
| 3614 | ||
| 3615 | _logger.speculate(); | |
| 3616 | ||
| 3617 | // Snapshot the pre-argument narrowing facts so an | |
| 3618 | // overload-retry re-walk resets to what the first walk | |
| 3619 | // saw; on success the final walk's facts are kept. | |
| 3620 | let use flow_speculation = _flow.speculate_then_commit(); | |
| 3621 | ||
| 3622 | call.arguments.walk(self); | |
| 3623 | ||
| 3624 | if call.is_pipe_wrap /\ _pipe_wrap_operand_already_pipe(call) then | |
| 3625 | // `x |` where x is already a Pipe[T] is a no-op: | |
| 3626 | // yield the operand directly rather than wrapping it | |
| 3627 | // in another adaptor. A compile-time decision on the | |
| 3628 | // operand's static type. | |
| 3629 | call.compile_expressions_state.value = call.arguments.expressions[0].value; | |
| 3630 | else | |
| 3631 | _calls.visit_call(call); | |
| 3632 | ||
| 3633 | // A terminal Pipe consumer (`.count()`, ...) over a | |
| 3634 | // fusible chain lowers to an inline driving loop instead | |
| 3635 | // of building the pipe objects and iterating them. | |
| 3636 | let fused = _recognize_consumer_fusion(call); | |
| 3637 | ||
| 3638 | if fused? then | |
| 3639 | call.compile_expressions_state.value = fused; | |
| 3640 | fi | |
| 3641 | fi | |
| 3642 | ||
| 3643 | _check_pure_slots(call.location, call.value); | |
| 3644 | _note_call(call.location, call.value); | |
| 3645 | ||
| 3646 | _logger.commit(); | |
| 3647 | catch e: Exception | |
| 3648 | call.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), call.location); | |
| 3649 | _logger.release(mark); | |
| 3650 | ||
| 3651 | _logger.exception(call.location, e, "exception compiling call"); | |
| 3652 | yrt | |
| 3653 | si | |
| 3654 | ||
| 3655 | visit(identifier: Trees.Expressions.IDENTIFIER) is | |
| 3656 | let mark = _logger.mark(); | |
| 3657 | ||
| 3658 | try | |
| 3659 | _access.visit_identifier(identifier); | |
| 3660 | _note_call(identifier.location, identifier.value); | |
| 3661 | ||
| 3662 | catch ex: Exception | |
| 3663 | _logger.exception(identifier.location, ex, "something went wrong with identifier"); | |
| 3664 | ||
| 3665 | finally | |
| 3666 | _logger.release(mark); | |
| 3667 | yrt | |
| 3668 | si | |
| 3669 | ||
| 3670 | visit(has_value: Trees.Expressions.HAS_VALUE) is | |
| 3671 | _access.visit_has_value(has_value); | |
| 3672 | _note_call(has_value.location, has_value.value); | |
| 3673 | si | |
| 3674 | ||
| 3675 | visit(unwrap: Trees.Expressions.UNWRAP) is | |
| 3676 | _access.visit_unwrap(unwrap); | |
| 3677 | _note_call(unwrap.location, unwrap.value); | |
| 3678 | si | |
| 3679 | ||
| 3680 | // An operand of `ref` is an address, not a value read. Suppress | |
| 3681 | // the operand's definite-assignment check by recording its target | |
| 3682 | // for the identifier load to skip, and reset the write flag so it | |
| 3683 | // is re-derived from scratch this walk (robust to speculative | |
| 3684 | // retry and inference iteration, which the flag does not otherwise | |
| 3685 | // participate in). The resolved call does the real read-check and | |
| 3686 | // assignment in `note_reference_arguments`. | |
| 3687 | pre(reference: Trees.Expressions.REFERENCE) -> bool is | |
| 3688 | reference.writes_target = false; | |
| 3689 | _reference_operand_target = try_get_narrowing_target(reference.left); | |
| 3690 | ||
| 3691 | return false; | |
| 3692 | si | |
| 3693 | ||
| 3694 | visit(reference: Trees.Expressions.REFERENCE) is | |
| 3695 | _reference_operand_target = null; | |
| 3696 | _access.visit_reference(reference); | |
| 3697 | si | |
| 3698 | ||
| 3699 | // True while walking the operand of the `ref` that writes to | |
| 3700 | // `symbol` — consulted by the identifier load to skip the | |
| 3701 | // definite-assignment check on an address-of operand. | |
| 3702 | is_reference_operand_target(symbol: Semantic.Symbols.Symbol) -> bool => | |
| 3703 | _reference_operand_target? /\ symbol == _reference_operand_target; | |
| 3704 | ||
| 3705 | // Definite assignment for `ref` arguments, run once the call is | |
| 3706 | // resolved so the matched parameter's direction is known. A slot | |
| 3707 | // the callee reads (any by-ref except pure `out`) requires the | |
| 3708 | // target to be assigned already; a slot it writes (any by-ref | |
| 3709 | // except pure `in`) assigns it. The write flag is recorded on the | |
| 3710 | // REFERENCE so the condition analyzer can carry that assignment | |
| 3711 | // onto the branch edges a `ref` in a condition reaches. | |
| 3712 | note_reference_arguments(call: Trees.Expressions.CALL, function: Semantic.Symbols.Function) is | |
| 3713 | if call.argument_names? then | |
| 3714 | return; | |
| 3715 | fi | |
| 3716 | ||
| 3717 | for i in 0..call.arguments.count do | |
| 3718 | let expr = call.arguments.expressions[i]; | |
| 3719 | ||
| 3720 | if !isa Trees.Expressions.REFERENCE(expr) then | |
| 3721 | continue; | |
| 3722 | fi | |
| 3723 | ||
| 3724 | let reference = expr; | |
| 3725 | let target = try_get_narrowing_target(reference.left); | |
| 3726 | ||
| 3727 | if !target? then | |
| 3728 | continue; | |
| 3729 | fi | |
| 3730 | ||
| 3731 | if | |
| 3732 | function.argument_reads(i) /\ | |
| 3733 | !_build_flags.no_warn_definite_assignment /\ | |
| 3734 | isa Semantic.Symbols.Variable(target) /\ | |
| 3735 | _flow.is_tracked(target) /\ | |
| 3736 | !_flow.is_assigned(target) | |
| 3737 | then | |
| 3738 | _logger.warn(reference.location, "definite-assignment", "{target.name} may be used before it is assigned"); | |
| 3739 | fi | |
| 3740 | ||
| 3741 | reference.writes_target = function.argument_writes(i); | |
| 3742 | ||
| 3743 | if reference.writes_target then | |
| 3744 | _flow.mark_assigned(target); | |
| 3745 | fi | |
| 3746 | od | |
| 3747 | si | |
| 3748 | ||
| 3749 | visit(`null: Trees.Expressions.NULL) is | |
| 3750 | // For a value-type `T?` target (NULLABLE[T]) `null` is the | |
| 3751 | // empty Nullable — a zeroed value, not a reference. Lower | |
| 3752 | // it as an IR.Values.DEFAULT; a plain ldnull would be | |
| 3753 | // invalid IL against a value-type slot. Reference-type | |
| 3754 | // targets keep the ordinary null reference. | |
| 3755 | if let `null.expected_type? /\ expected_type.is_value_type /\ expected_type.is_optional then | |
| 3756 | `null.compile_expressions_state.value = IR.Values.DEFAULT(expected_type); | |
| 3757 | else | |
| 3758 | `null.compile_expressions_state.value = NULL(Semantic.Types.NULL()); | |
| 3759 | fi | |
| 3760 | ||
| 3761 | // Non-optional-by-default: the bare `null` literal flowing | |
| 3762 | // into a non-optional reference slot. The constraint is | |
| 3763 | // the expected type, pushed by the parent context — a | |
| 3764 | // typed `let`, an assignment, a `return`, a call argument, | |
| 3765 | // an `if`/`else` branch — so this one check covers every | |
| 3766 | // such site. A warning during the migration; `T?` not | |
| 3767 | // being assignment-compatible with `T` is the end state. | |
| 3768 | if !_build_flags.no_warn_non_optional /\ _is_non_optional_reference(`null.expected_type) then | |
| 3769 | _logger.warn(`null.location, "non-optional", "null where non-optional {`null.expected_type} expected"); | |
| 3770 | fi | |
| 3771 | si | |
| 3772 | ||
| 3773 | visit(`default: Trees.Expressions.DEFAULT) is | |
| 3774 | _literals.visit_default(`default); | |
| 3775 | ||
| 3776 | // Non-optional-by-default: `default` reaching a non-optional | |
| 3777 | // reference slot resolves to null at runtime, the same hole | |
| 3778 | // as a bare `null` literal. Same warning category and flag | |
| 3779 | // as visit(NULL) — one migration, one flip. | |
| 3780 | if | |
| 3781 | !_build_flags.no_warn_non_optional /\ | |
| 3782 | `default.value? /\ | |
| 3783 | _is_non_optional_reference(`default.value!.type) | |
| 3784 | then | |
| 3785 | let target = `default.value!.type; | |
| 3786 | _logger.warn(`default.location, "non-optional", "default where non-optional {target} expected"); | |
| 3787 | fi | |
| 3788 | si | |
| 3789 | ||
| 3790 | pre(statement: Trees.Expressions.STATEMENT) -> bool is | |
| 3791 | super.pre(statement); | |
| 3792 | ||
| 3793 | statement.statement.compile_expressions_state.want_value = statement.want_value; | |
| 3794 | ||
| 3795 | return false; | |
| 3796 | si | |
| 3797 | ||
| 3798 | visit(statement: Trees.Expressions.STATEMENT) is | |
| 3799 | // A diverging statement (`throw`, or an `if`/`case` whose | |
| 3800 | // every arm diverges) legitimately yields no value; the | |
| 3801 | // unreachable continuation needs none. | |
| 3802 | if DIVERGING_VALUE_POSITION.is_missing_value_reportable(statement.want_value, statement.statement.value?, _flow.is_unreachable) then | |
| 3803 | _logger.warn(statement.location, "statement-expression-no-value", "statement expression has no value"); | |
| 3804 | fi | |
| 3805 | ||
| 3806 | if statement.statement.value? then | |
| 3807 | statement.compile_expressions_state.value = statement.statement.value; | |
| 3808 | elif !statement.want_value then | |
| 3809 | // Void-tolerant position (expression-statement, void- | |
| 3810 | // returning `=>` body) with an `if`/`case` whose | |
| 3811 | // branches don't all provide values: synthesise a void | |
| 3812 | // block value so the consumer has something to thread | |
| 3813 | // through rather than a null that downstream passes | |
| 3814 | // treat as an error. | |
| 3815 | statement.compile_expressions_state.value = IR.Values.BLOCK( | |
| 3816 | _innate_symbol_lookup.get_void_type() | |
| 3817 | ); | |
| 3818 | fi | |
| 3819 | si | |
| 3820 | ||
| 3821 | pre(block: Trees.Expressions.VAL_BLOCK) -> bool is | |
| 3822 | super.pre(block); | |
| 3823 | ||
| 3824 | // The body's tail is the only fall-through value | |
| 3825 | // contributor. Push want_value down through the LIST to | |
| 3826 | // its last statement so it must provide a value when this | |
| 3827 | // block is consumed; the surrounding context (expression- | |
| 3828 | // statement, void-returning `=>` body) may also write | |
| 3829 | // false here to allow a void tail. | |
| 3830 | block.body.compile_expressions_state.want_value = block.want_value; | |
| 3831 | ||
| 3832 | // Push this block as the innermost return target before | |
| 3833 | // walking its body. Returns inside read the top of stack | |
| 3834 | // in pre_return / visit_return; nested val-blocks override | |
| 3835 | // the current top while their own body is walked. | |
| 3836 | _val_block_stack.add(block); | |
| 3837 | ||
| 3838 | return false; | |
| 3839 | si | |
| 3840 | ||
| 3841 | visit(block: Trees.Expressions.VAL_BLOCK) is | |
| 3842 | // Pop ourselves off the val-block stack regardless of | |
| 3843 | // outcome — every push in pre must be balanced by a pop | |
| 3844 | // here, otherwise an outer return-target lookup picks up | |
| 3845 | // a stale inner block. | |
| 3846 | assert _val_block_stack.count > 0 else "val_block_stack underflow"; | |
| 3847 | let top = _val_block_stack[_val_block_stack.count - 1]; | |
| 3848 | assert top? /\ top == block else "val_block_stack head is not the block being visited"; | |
| 3849 | _val_block_stack.remove_at(_val_block_stack.count - 1); | |
| 3850 | ||
| 3851 | if !block.want_value then | |
| 3852 | // Void-tolerant position: yield a void block value so | |
| 3853 | // the consumer (expression-statement, void-returning | |
| 3854 | // `=>` body) has something to thread through. Returns | |
| 3855 | // from inside the block are still emitted as branches | |
| 3856 | // by generate-il; the value here is the fall-through | |
| 3857 | // type, which is void in this branch. | |
| 3858 | block.compile_expressions_state.value = IR.Values.BLOCK( | |
| 3859 | _innate_symbol_lookup.get_void_type() | |
| 3860 | ); | |
| 3861 | return; | |
| 3862 | fi | |
| 3863 | ||
| 3864 | // LUB over every value-contributing source: returns | |
| 3865 | // targeting us (recorded by visit_return as we walked the | |
| 3866 | // body) and the tail expression's value type (if the tail | |
| 3867 | // provides one). When the tail itself diverges (every | |
| 3868 | // path returns from us), block.body.value may still be | |
| 3869 | // set by the LIST visitor — it conservatively types as | |
| 3870 | // BLOCK(last.value.type) — but the LUB shape below | |
| 3871 | // tolerates either presence. | |
| 3872 | let lub = LEAST_UPPER_BOUND_MAP(); | |
| 3873 | ||
| 3874 | for t in block.return_types do | |
| 3875 | if !t.is_error then | |
| 3876 | lub.add(t); | |
| 3877 | fi | |
| 3878 | od | |
| 3879 | ||
| 3880 | if let block.body.value?, value.type? /\ !type.is_error then | |
| 3881 | lub.add(type); | |
| 3882 | fi | |
| 3883 | ||
| 3884 | let lub_type = lub.get_result(); | |
| 3885 | ||
| 3886 | if lub_type? then | |
| 3887 | block.compile_expressions_state.value = IR.Values.BLOCK(lub_type); | |
| 3888 | elif block.body.value? then | |
| 3889 | // No usable LUB contributions but the body produced a | |
| 3890 | // value of some sentinel / error type — pass it | |
| 3891 | // through so downstream phases see a real Value and | |
| 3892 | // diagnose at the original location. | |
| 3893 | block.compile_expressions_state.value = block.body.value; | |
| 3894 | else | |
| 3895 | // Body's tail doesn't provide a value and there are | |
| 3896 | // no returns to LUB with. The body walk has already | |
| 3897 | // emitted "expected a value" at the offending | |
| 3898 | // statement (compile_expressions.visit(Statements | |
| 3899 | // .LIST) — the val-block contract is the same as | |
| 3900 | // the LIST contract there); add the parallel "no | |
| 3901 | // value" warning the if/case-in-expression form | |
| 3902 | // emits in the same situation, and stand up a DUMMY | |
| 3903 | // value so downstream walks have a typed Value. | |
| 3904 | _logger.warn( | |
| 3905 | block.location, | |
| 3906 | "statement-expression-no-value", | |
| 3907 | "statement expression has no value" | |
| 3908 | ); | |
| 3909 | ||
| 3910 | block.compile_expressions_state.value = IR.Values.DUMMY_BLOCK( | |
| 3911 | Semantic.Types.ERROR(), | |
| 3912 | block.location, | |
| 3913 | "val block produced no value" | |
| 3914 | ); | |
| 3915 | fi | |
| 3916 | si | |
| 3917 | ||
| 3918 | pre(list: Trees.Statements.LIST) -> bool is | |
| 3919 | super.pre(list); | |
| 3920 | return true; | |
| 3921 | si | |
| 3922 | ||
| 3923 | visit(list: Trees.Statements.LIST) is | |
| 3924 | let enclosing_statement_list = current_statement_list; | |
| 3925 | current_statement_list = list; | |
| 3926 | ||
| 3927 | try | |
| 3928 | if let list.last? then | |
| 3929 | last.compile_expressions_state.want_value = list.want_value; | |
| 3930 | fi | |
| 3931 | ||
| 3932 | for s in list.statements do | |
| 3933 | self.enter_node(s); | |
| 3934 | try | |
| 3935 | s.walk(self); | |
| 3936 | finally | |
| 3937 | self.leave_node(s); | |
| 3938 | yrt | |
| 3939 | od | |
| 3940 | ||
| 3941 | if !list.want_value then | |
| 3942 | return; | |
| 3943 | fi | |
| 3944 | ||
| 3945 | if list.is_empty then | |
| 3946 | _logger.error(list.location, "expected a value"); | |
| 3947 | return; | |
| 3948 | fi | |
| 3949 | ||
| 3950 | let last = list.last; | |
| 3951 | ||
| 3952 | if !last? then | |
| 3953 | return; | |
| 3954 | fi | |
| 3955 | ||
| 3956 | if !last.provides_value then | |
| 3957 | // Tolerate a non-value-providing tail when every | |
| 3958 | // path through the body has already diverged | |
| 3959 | // (returned / thrown). Fall-through is dead | |
| 3960 | // code; the consumer needs the IL emitted for | |
| 3961 | // its side effects but no value need be left | |
| 3962 | // on the stack. | |
| 3963 | if !_flow.is_unreachable then | |
| 3964 | _logger.error(last.location, "expected a value"); | |
| 3965 | fi | |
| 3966 | return; | |
| 3967 | fi | |
| 3968 | ||
| 3969 | if let last.value? then | |
| 3970 | list.compile_expressions_state.value = IR.Values.BLOCK(value.type!); | |
| 3971 | fi | |
| 3972 | finally | |
| 3973 | current_statement_list = enclosing_statement_list; | |
| 3974 | yrt | |
| 3975 | si | |
| 3976 | ||
| 3977 | pre(`if: Trees.Statements.IF) -> bool is | |
| 3978 | super.pre(`if); | |
| 3979 | return _conditionals.pre_if(`if); | |
| 3980 | si | |
| 3981 | ||
| 3982 | visit(`if: Trees.Statements.IF) is | |
| 3983 | _conditionals.visit_if(`if); | |
| 3984 | si | |
| 3985 | ||
| 3986 | pre(`case: Trees.Statements.CASE) -> bool is | |
| 3987 | super.pre(`case); | |
| 3988 | return _conditionals.pre_case(`case); | |
| 3989 | si | |
| 3990 | ||
| 3991 | visit(`case: Trees.Statements.CASE) is | |
| 3992 | _conditionals.visit_case(`case); | |
| 3993 | super.visit(`case); | |
| 3994 | si | |
| 3995 | ||
| 3996 | pre(arm: Trees.Statements.CASE_MATCH) -> bool is | |
| 3997 | super.pre(arm); | |
| 3998 | return _conditionals.pre_case_match(arm); | |
| 3999 | si | |
| 4000 | ||
| 4001 | visit(arm: Trees.Statements.CASE_MATCH) is | |
| 4002 | _conditionals.visit_case_match(arm); | |
| 4003 | super.visit(arm); | |
| 4004 | si | |
| 4005 | ||
| 4006 | // Do not descend into properties (otherwise accessor functions will be walked twice) | |
| 4007 | pre(property: Trees.Definitions.PROPERTY) -> bool => true; | |
| 4008 | ||
| 4009 | visit(property: Trees.Definitions.PROPERTY) is | |
| 4010 | si | |
| 4011 | si | |
| 4012 | si |