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 | _symbol_loader: Semantic.SYMBOL_LOADER | |
| 24 | _innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup | |
| 25 | _awaitable_resolver: Semantic.AWAITABLE_RESOLVER | |
| 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 | _composite_spill_state: COMPOSITE_SPILL_STORE | |
| 62 | _function_reference_adapter: FUNCTION_REFERENCE_ADAPTER | |
| 63 | ||
| 64 | _pragma_scope_stack: PRAGMA_SCOPE_STACK | |
| 65 | _attribute_resolver: ATTRIBUTE_RESOLVER | |
| 66 | ||
| 67 | // Depth-counter for nested `let await` walks. When > 0, | |
| 68 | // Flow-sensitive narrowing — holds the narrowing environment | |
| 69 | // in force at the current walk point and keeps each narrowed | |
| 70 | // variable's `.type` reconciled to it. See | |
| 71 | // `docs/claude/flow-sensitive-narrowing.md`. | |
| 72 | _flow: NARROWING_FLOW | |
| 73 | ||
| 74 | // Walk a subtree again for a retry. The retry has pushed what | |
| 75 | // it learned onto the subtree as inputs; what the attempt being | |
| 76 | // retried produced is dropped first, so the re-walk starts from | |
| 77 | // a first walk's inputs and none of the previous outputs, as a | |
| 78 | // whole-body re-walk does. | |
| 79 | rewalk(node: Trees.Node) is | |
| 80 | CLEAR_STATE_VISITOR(true, true).apply(node) | |
| 81 | node.walk(self) | |
| 82 | si | |
| 83 | ||
| 84 | // The target of the `ref` whose operand is currently being | |
| 85 | // walked. An operand of `ref` is an address, not a value read, | |
| 86 | // so its identifier load must skip the definite-assignment | |
| 87 | // check: the read-or-write decision belongs to the resolved | |
| 88 | // call, in `note_reference_arguments`. Set in `pre(REFERENCE)`, | |
| 89 | // cleared in `visit(reference)`. | |
| 90 | _reference_operand_target: Semantic.Symbols.Symbol? | |
| 91 | ||
| 92 | // Pure analysis of boolean conditions into then/else | |
| 93 | // narrowing environments, plus the variant-type helpers. | |
| 94 | _condition_analyzer: CONDITION_ANALYZER | |
| 95 | ||
| 96 | // Stack of per-IF frames, one per IF currently being walked. | |
| 97 | // pre(IF) pushes; each branch's controlled walk records its | |
| 98 | // exit environment; visit(IF) joins them and pops. | |
| 99 | _if_flow_stack: Collections.LIST[IF_FLOW_FRAME] | |
| 100 | ||
| 101 | // Stack of loop kill-set environments, one per `while`/`do` | |
| 102 | // currently being walked. pre(DO) computes and pushes; | |
| 103 | // visit(DO) pops to restore the after-loop environment. | |
| 104 | _loop_kept_stack: Collections.LIST[NARROW_ENV] | |
| 105 | ||
| 106 | // One open loop's value bookkeeping (see LOOP_VALUE_FRAME): | |
| 107 | // pushed by the DO / FOR pre handlers, popped when the loop's | |
| 108 | // walk settles its expression value. `break` resolves against | |
| 109 | // this stack — a bare identifier naming a label targets the | |
| 110 | // matching frame; anything else contributes to the innermost | |
| 111 | // frame. | |
| 112 | loop_value_frames: Collections.LIST[LOOP_VALUE_FRAME] | |
| 113 | ||
| 114 | // Labels seen since the last loop frame was pushed: the | |
| 115 | // LABELLED pre pushes, the wrapped loop's pre takes the name | |
| 116 | // into its frame. | |
| 117 | _pending_label_names: Collections.LIST[Trees.Identifiers.Identifier] | |
| 118 | ||
| 119 | // Per-ASSERT flag from the controlled walk in pre(ASSERT): | |
| 120 | // true when the condition's own walk killed heap facts, so | |
| 121 | // visit(ASSERT) drops them from the fall-through narrowing. | |
| 122 | // A stack because a block expression inside an assert's | |
| 123 | // condition or message can contain another assert. | |
| 124 | _assert_condition_killed_stack: Collections.LIST[bool] | |
| 125 | _assert_condition_marks_stack: Collections.LIST[(from_mark: int, to_mark: int)] | |
| 126 | ||
| 127 | // Stack of per-`try` frames, one per `try` currently being | |
| 128 | // walked. pre(TRY) pushes; visit(TRY) pops. The | |
| 129 | // definite-assignment facts established before a try survive | |
| 130 | // it; the try / catch bodies' own narrowing is discarded | |
| 131 | // conservatively (an exception can leave the body anywhere). | |
| 132 | _try_flow_stack: Collections.LIST[TRY_FLOW_FRAME] | |
| 133 | ||
| 134 | // Stack of `val ... lav` blocks currently being walked. A | |
| 135 | // `return E` inside a val-block targets the innermost — top | |
| 136 | // of stack — block rather than the enclosing function. The | |
| 137 | // block accumulates return types into its `return_types` | |
| 138 | // list; visit(VAL_BLOCK) LUBs them with the tail expression's | |
| 139 | // value type to settle the block's own value type. | |
| 140 | // | |
| 141 | // A `null` entry is a function-literal boundary marker: | |
| 142 | // returns inside a nested function literal must target that | |
| 143 | // function, not an outer val-block, so pre(FUNCTION) pushes | |
| 144 | // null before walking the literal's body and visit(FUNCTION) | |
| 145 | // pops it. The lookup at the top returns null on encounter, | |
| 146 | // so the return falls through to the function-return path. | |
| 147 | _val_block_stack: Collections.LIST[Trees.Expressions.VAL_BLOCK?] | |
| 148 | ||
| 149 | // Public accessor used by compile_bindings.pre_return / | |
| 150 | // visit_return to decide whether a `return` targets a | |
| 151 | // val-block (top of stack) or the enclosing function (empty | |
| 152 | // stack, or the top is a function-boundary null marker). | |
| 153 | innermost_val_block: Trees.Expressions.VAL_BLOCK? => | |
| 154 | if _val_block_stack.count > 0 then | |
| 155 | _val_block_stack[_val_block_stack.count - 1] | |
| 156 | else | |
| 157 | null | |
| 158 | fi | |
| 159 | ||
| 160 | current_statement_list: Trees.Statements.LIST | |
| 161 | ||
| 162 | // current_statement_list is walk state: it is set on entering a | |
| 163 | // statement list and restored on leaving one, and means nothing | |
| 164 | // outside a walk. | |
| 165 | @suppress("field-definite-assignment") | |
| 166 | init( | |
| 167 | logger: Logger, | |
| 168 | symbol_table: Semantic.SYMBOL_TABLE, | |
| 169 | namespaces: Semantic.NAMESPACES, | |
| 170 | symbol_loader: Semantic.SYMBOL_LOADER, | |
| 171 | innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup, | |
| 172 | function_caller: Semantic.FUNCTION_CALLER, | |
| 173 | type_caster: Semantic.TYPE_CASTER, | |
| 174 | task_conversion: Semantic.TASK_CONVERSION, | |
| 175 | overload_resolver: Semantic.OVERLOAD_RESOLVER, | |
| 176 | symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS, | |
| 177 | context: IR.CONTEXT, | |
| 178 | value_converter: VALUE_CONVERTER, | |
| 179 | value_boxer: VALUE_BOXER, | |
| 180 | build_flags: Compiler.GLOBAL_BUILD_FLAGS, | |
| 181 | variable_left_state: VARIABLE_LEFT_STATE_STORE, | |
| 182 | composite_spill_state: COMPOSITE_SPILL_STORE | |
| 183 | ) | |
| 184 | is | |
| 185 | super.init(logger, symbol_table, namespaces) | |
| 186 | ||
| 187 | _logger = logger | |
| 188 | _symbol_table = symbol_table | |
| 189 | _symbol_loader = symbol_loader | |
| 190 | _innate_symbol_lookup = innate_symbol_lookup | |
| 191 | _awaitable_resolver = Semantic.AWAITABLE_RESOLVER(innate_symbol_lookup, logger) | |
| 192 | _function_caller = function_caller | |
| 193 | _type_caster = type_caster | |
| 194 | _overload_resolver = overload_resolver | |
| 195 | _owner_type_arg_specializer = Semantic.OWNER_TYPE_ARG_SPECIALIZER() | |
| 196 | _owner_constraint_specializer = Semantic.OWNER_CONSTRAINT_SPECIALIZER() | |
| 197 | _under_determination_detector = Semantic.UNDER_DETERMINATION_DETECTOR() | |
| 198 | _type_arg_placeholder_registry = Semantic.TYPE_ARG_PLACEHOLDER_REGISTRY(symbol_table) | |
| 199 | _unit_variant_constructor = Semantic.UNIT_VARIANT_CONSTRUCTOR(_owner_constraint_specializer, _type_arg_placeholder_registry, function_caller) | |
| 200 | _closure_arg_resolver = Semantic.CLOSURE_ARG_RESOLVER(logger) | |
| 201 | _numeric_literal_classifier = NUMERIC_LITERAL_CLASSIFIER(logger, innate_symbol_lookup) | |
| 202 | _literals = COMPILE_LITERALS(logger, innate_symbol_lookup, _numeric_literal_classifier, symbol_use_locations) | |
| 203 | _symbol_use_locations = symbol_use_locations | |
| 204 | _value_converter = value_converter | |
| 205 | _value_boxer = value_boxer | |
| 206 | _build_flags = build_flags | |
| 207 | _variable_left_state = variable_left_state | |
| 208 | _composite_spill_state = composite_spill_state | |
| 209 | ||
| 210 | _tuples = COMPILE_TUPLES(logger, innate_symbol_lookup, value_boxer, self) | |
| 211 | ||
| 212 | _flow = NARROWING_FLOW(logger) | |
| 213 | _condition_analyzer = CONDITION_ANALYZER( | |
| 214 | expr => try_get_narrowing_target(expr), | |
| 215 | expr => try_build_access_path(expr), | |
| 216 | expr => _flow.test_site_mark_of(expr), | |
| 217 | logger | |
| 218 | ) | |
| 219 | ||
| 220 | _access = COMPILE_ACCESS( | |
| 221 | logger, | |
| 222 | symbol_table, | |
| 223 | symbol_loader, | |
| 224 | symbol_use_locations, | |
| 225 | innate_symbol_lookup, | |
| 226 | overload_resolver, | |
| 227 | function_caller, | |
| 228 | _unit_variant_constructor, | |
| 229 | _flow, | |
| 230 | _condition_analyzer, | |
| 231 | _build_flags, | |
| 232 | self | |
| 233 | ) | |
| 234 | ||
| 235 | _function_reference_adapter = FUNCTION_REFERENCE_ADAPTER(symbol_table) | |
| 236 | ||
| 237 | _calls = COMPILE_CALLS( | |
| 238 | logger, | |
| 239 | symbol_table, | |
| 240 | symbol_use_locations, | |
| 241 | innate_symbol_lookup, | |
| 242 | overload_resolver, | |
| 243 | function_caller, | |
| 244 | _owner_constraint_specializer, | |
| 245 | _owner_type_arg_specializer, | |
| 246 | _under_determination_detector, | |
| 247 | _type_arg_placeholder_registry, | |
| 248 | _access, | |
| 249 | self, | |
| 250 | _flow, | |
| 251 | symbol_loader, | |
| 252 | _function_reference_adapter | |
| 253 | ) | |
| 254 | ||
| 255 | _operators = COMPILE_OPERATORS( | |
| 256 | logger, | |
| 257 | symbol_table, | |
| 258 | innate_symbol_lookup, | |
| 259 | overload_resolver, | |
| 260 | symbol_use_locations, | |
| 261 | function_caller, | |
| 262 | _calls, | |
| 263 | _flow, | |
| 264 | _condition_analyzer, | |
| 265 | self | |
| 266 | ) | |
| 267 | ||
| 268 | _generic_application = COMPILE_GENERIC_APPLICATION( | |
| 269 | logger, | |
| 270 | symbol_table, | |
| 271 | symbol_use_locations, | |
| 272 | symbol_loader, | |
| 273 | innate_symbol_lookup, | |
| 274 | overload_resolver, | |
| 275 | _unit_variant_constructor, | |
| 276 | self | |
| 277 | ) | |
| 278 | ||
| 279 | _if_flow_stack = Collections.LIST[IF_FLOW_FRAME]() | |
| 280 | _loop_kept_stack = Collections.LIST[NARROW_ENV]() | |
| 281 | loop_value_frames = Collections.LIST[LOOP_VALUE_FRAME]() | |
| 282 | _pending_label_names = Collections.LIST[Trees.Identifiers.Identifier]() | |
| 283 | _assert_condition_killed_stack = Collections.LIST[bool]() | |
| 284 | _assert_condition_marks_stack = Collections.LIST[(from_mark: int, to_mark: int)]() | |
| 285 | _try_flow_stack = Collections.LIST[TRY_FLOW_FRAME]() | |
| 286 | _val_block_stack = Collections.LIST[Trees.Expressions.VAL_BLOCK?]() | |
| 287 | ||
| 288 | _conditionals = COMPILE_CONDITIONALS( | |
| 289 | logger, | |
| 290 | innate_symbol_lookup, | |
| 291 | _flow, | |
| 292 | _condition_analyzer, | |
| 293 | _if_flow_stack, | |
| 294 | self, | |
| 295 | build_flags, | |
| 296 | variable_left_state | |
| 297 | ) | |
| 298 | ||
| 299 | _loops = COMPILE_LOOPS_AND_EXCEPTIONS( | |
| 300 | logger, | |
| 301 | innate_symbol_lookup, | |
| 302 | _flow, | |
| 303 | _condition_analyzer, | |
| 304 | _conditionals, | |
| 305 | self, | |
| 306 | _try_flow_stack, | |
| 307 | _loop_kept_stack | |
| 308 | ) | |
| 309 | ||
| 310 | _pure_slots = PURE_SLOT_CHECK(_flow, _build_flags) | |
| 311 | ||
| 312 | _bindings = COMPILE_BINDINGS( | |
| 313 | logger, | |
| 314 | symbol_table, | |
| 315 | symbol_use_locations, | |
| 316 | innate_symbol_lookup, | |
| 317 | task_conversion, | |
| 318 | _awaitable_resolver, | |
| 319 | _flow, | |
| 320 | _build_flags, | |
| 321 | value_boxer, | |
| 322 | _pure_slots, | |
| 323 | self | |
| 324 | ) | |
| 325 | ||
| 326 | _attribute_resolver = ATTRIBUTE_RESOLVER(logger, innate_symbol_lookup, overload_resolver, self) | |
| 327 | ||
| 328 | _lambdas = COMPILE_LAMBDAS( | |
| 329 | logger, | |
| 330 | symbol_table, | |
| 331 | symbol_use_locations, | |
| 332 | symbol_loader, | |
| 333 | innate_symbol_lookup, | |
| 334 | task_conversion, | |
| 335 | _closure_arg_resolver, | |
| 336 | _type_arg_placeholder_registry, | |
| 337 | _flow, | |
| 338 | _build_flags, | |
| 339 | self, | |
| 340 | _attribute_resolver, | |
| 341 | _composite_spill_state, | |
| 342 | _function_reference_adapter | |
| 343 | ) | |
| 344 | ||
| 345 | _pragma_scope_stack = PRAGMA_SCOPE_STACK() | |
| 346 | si | |
| 347 | ||
| 348 | // Statements.LIST.walk calls these around every child. We | |
| 349 | // push the child's debug location onto the workspace | |
| 350 | // LOCATION_STACK; every IR Value constructed while that | |
| 351 | // statement is being walked picks it up as its ambient | |
| 352 | // location in Value.init(). Generic at the Node level — the | |
| 353 | // hook fires before any subtype dispatch. | |
| 354 | // | |
| 355 | // Skipped when `--debug` is off (the only consumer is the | |
| 356 | // .line emission gated on the same flag): leaves the stack | |
| 357 | // permanently empty so Value.init() captures null and the | |
| 358 | // ambient-location bookkeeping costs nothing. | |
| 359 | enter_node(node: Trees.Node) is | |
| 360 | if !_build_flags.want_debug then | |
| 361 | return | |
| 362 | fi | |
| 363 | IoC.CONTAINER.instance.location_stack.push(node.debug_location) | |
| 364 | si | |
| 365 | ||
| 366 | leave_node(node: Trees.Node) is | |
| 367 | if !_build_flags.want_debug then | |
| 368 | return | |
| 369 | fi | |
| 370 | IoC.CONTAINER.instance.location_stack.pop() | |
| 371 | si | |
| 372 | ||
| 373 | // Drop every piece of state this visitor carries between walks. | |
| 374 | // Callers that drive the visitor without going through `apply` — | |
| 375 | // the incremental body re-walk — must call this themselves, | |
| 376 | // because the visitor is one per IoC container rather than one | |
| 377 | // per build. | |
| 378 | // | |
| 379 | // Phantom Variables for unbound owner type-args at constructor | |
| 380 | // sites are cached on this visitor (not the AST), keyed by AST | |
| 381 | // node reference, so cache entries from a previous pass would | |
| 382 | // still hit on the same AST nodes after a re-compile, returning | |
| 383 | // phantoms whose `_lub_map` references Type instances from the | |
| 384 | // previous build's symbol table (now wiped by clear_symbols). | |
| 385 | // Body-retry iterations of one function need stable phantoms, | |
| 386 | // but those happen inside visit(function: FUNCTION), so clearing | |
| 387 | // before the walk starts is safe. | |
| 388 | // | |
| 389 | // The narrowing stack must be empty at a walk boundary — every | |
| 390 | // push has to be balanced by a release before its owning AST | |
| 391 | // exits. Reset defensively so a narrowing leaked by an aborted | |
| 392 | // earlier walk (early return / exception) doesn't poison | |
| 393 | // subsequent runs — restores symbol types and empties the | |
| 394 | // environment. | |
| 395 | reset_between_walks() is | |
| 396 | _type_arg_placeholder_registry.clear() | |
| 397 | ||
| 398 | _flow.reset() | |
| 399 | _if_flow_stack.clear() | |
| 400 | _loop_kept_stack.clear() | |
| 401 | loop_value_frames.clear() | |
| 402 | _pending_label_names.clear() | |
| 403 | _try_flow_stack.clear() | |
| 404 | _assert_condition_killed_stack.clear() | |
| 405 | _assert_condition_marks_stack.clear() | |
| 406 | si | |
| 407 | ||
| 408 | // Flushed by the build loop once the compile-expressions pass has | |
| 409 | // covered every file - see FIELD_ASSIGNMENT_CHECKER. | |
| 410 | report_field_assignments() is | |
| 411 | _lambdas.report_field_assignments() | |
| 412 | si | |
| 413 | ||
| 414 | // Public entry for the loop settle path (COMPILE_LOOPS_AND_ | |
| 415 | // EXCEPTIONS cannot reach the private helper across classes): | |
| 416 | // records the spill-vs-capture decision for a composite whose | |
| 417 | // body may suspend. See _declare_composite_spill_field. | |
| 418 | declare_composite_spill_field_for(node: Trees.Node, value: IR.Values.Value?) is | |
| 419 | _declare_composite_spill_field(node, value) | |
| 420 | si | |
| 421 | ||
| 422 | apply(root: Trees.Node) is | |
| 423 | assert _pragma_scope_stack.is_balanced | |
| 424 | ||
| 425 | IR.LABEL.reset_id() | |
| 426 | IR.LABEL.set_pass("E") | |
| 427 | ||
| 428 | reset_between_walks() | |
| 429 | ||
| 430 | KILL_LEDGER.current_file = root.location.file_name | |
| 431 | ||
| 432 | root.walk(self) | |
| 433 | ||
| 434 | assert _pragma_scope_stack.is_balanced | |
| 435 | assert _if_flow_stack.count == 0 | |
| 436 | else "if-flow frame stack leaked: {_if_flow_stack.count} unreleased" | |
| 437 | assert _loop_kept_stack.count == 0 | |
| 438 | else "loop-kept env stack leaked: {_loop_kept_stack.count} unreleased" | |
| 439 | assert _try_flow_stack.count == 0 | |
| 440 | else "try-flow frame stack leaked: {_try_flow_stack.count} unreleased" | |
| 441 | assert _assert_condition_killed_stack.count == 0 | |
| 442 | else "assert-condition flag stack leaked: {_assert_condition_killed_stack.count} unreleased" | |
| 443 | si | |
| 444 | ||
| 445 | get_zero_argument_function(type: Type, name: string) -> Semantic.Symbols.Function? is | |
| 446 | let symbol = type.find_member(name) | |
| 447 | ||
| 448 | if symbol? /\ isa Semantic.Symbols.FUNCTION_GROUP(symbol) then | |
| 449 | let function_group = symbol | |
| 450 | ||
| 451 | for f in function_group.functions do | |
| 452 | if f.arguments.count == 0 then | |
| 453 | return f | |
| 454 | fi | |
| 455 | od | |
| 456 | fi | |
| 457 | return null | |
| 458 | si | |
| 459 | ||
| 460 | si | |
| 461 | si |