Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use Logging; | |
| 3 | ||
| 4 | use Semantic.Types.Type; | |
| 5 | ||
| 6 | use IR.Values; | |
| 7 | ||
| 8 | use Ghul.Pipes; | |
| 9 | ||
| 10 | // Compiles function bodies and function literals: the method-body | |
| 11 | // walk with its iterative type-inference retry loop, lambda | |
| 12 | // (closure) literals and `rec` references. Split out of | |
| 13 | // COMPILE_EXPRESSIONS, which delegates the matching visit methods | |
| 14 | // here. The `super.pre` / `super.visit` base-visitor calls and | |
| 15 | // the exception / environment-save wrapper of visit(function | |
| 16 | // literal) stay in the visitor; the methods here are the enclosed | |
| 17 | // logic. | |
| 18 | class COMPILE_LAMBDAS is | |
| 19 | _logger: Logger; | |
| 20 | _symbol_table: Semantic.SYMBOL_TABLE; | |
| 21 | _symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS; | |
| 22 | _symbol_loader: Semantic.SYMBOL_LOADER; | |
| 23 | _innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup; | |
| 24 | _task_conversion: Semantic.TASK_CONVERSION; | |
| 25 | _closure_arg_resolver: Semantic.CLOSURE_ARG_RESOLVER; | |
| 26 | _type_arg_placeholder_registry: Semantic.TYPE_ARG_PLACEHOLDER_REGISTRY; | |
| 27 | _flow: NARROWING_FLOW; | |
| 28 | _build_flags: Compiler.GLOBAL_BUILD_FLAGS; | |
| 29 | _visitor: ScopedVisitor; | |
| 30 | _delegate_shape: Semantic.DELEGATE_SHAPE; | |
| 31 | _attribute_resolver: ATTRIBUTE_RESOLVER; | |
| 32 | ||
| 33 | init( | |
| 34 | logger: Logger, | |
| 35 | symbol_table: Semantic.SYMBOL_TABLE, | |
| 36 | symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS, | |
| 37 | symbol_loader: Semantic.SYMBOL_LOADER, | |
| 38 | innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup, | |
| 39 | task_conversion: Semantic.TASK_CONVERSION, | |
| 40 | closure_arg_resolver: Semantic.CLOSURE_ARG_RESOLVER, | |
| 41 | type_arg_placeholder_registry: Semantic.TYPE_ARG_PLACEHOLDER_REGISTRY, | |
| 42 | flow: NARROWING_FLOW, | |
| 43 | build_flags: Compiler.GLOBAL_BUILD_FLAGS, | |
| 44 | visitor: ScopedVisitor, | |
| 45 | attribute_resolver: ATTRIBUTE_RESOLVER | |
| 46 | ) is | |
| 47 | super.init(); | |
| 48 | ||
| 49 | _logger = logger; | |
| 50 | _symbol_table = symbol_table; | |
| 51 | _symbol_use_locations = symbol_use_locations; | |
| 52 | _symbol_loader = symbol_loader; | |
| 53 | _innate_symbol_lookup = innate_symbol_lookup; | |
| 54 | _task_conversion = task_conversion; | |
| 55 | _closure_arg_resolver = closure_arg_resolver; | |
| 56 | _type_arg_placeholder_registry = type_arg_placeholder_registry; | |
| 57 | _flow = flow; | |
| 58 | _build_flags = build_flags; | |
| 59 | _visitor = visitor; | |
| 60 | _attribute_resolver = attribute_resolver; | |
| 61 | _delegate_shape = Semantic.DELEGATE_SHAPE(); | |
| 62 | si | |
| 63 | ||
| 64 | visit_function_definition(function: Trees.Definitions.FUNCTION) is | |
| 65 | if function.name? then | |
| 66 | function.name.walk(_visitor); | |
| 67 | fi | |
| 68 | ||
| 69 | function.type_expression.walk(_visitor); | |
| 70 | ||
| 71 | function.arguments.walk(_visitor); | |
| 72 | ||
| 73 | if function.body? then | |
| 74 | // Recorded symbol uses follow the diagnostics speculation | |
| 75 | // discipline through this walk: every roll_back below is | |
| 76 | // followed by a full body re-walk, so uses recorded | |
| 77 | // against not-yet-settled types are discarded with the | |
| 78 | // diagnostics of the walk that produced them rather than | |
| 79 | // surviving to shadow the settled walk's records. | |
| 80 | _logger.speculate(); | |
| 81 | _symbol_use_locations.speculate(); | |
| 82 | ||
| 83 | let retries = 20; | |
| 84 | ||
| 85 | for i in 1::retries do | |
| 86 | // Narrowing is method-local — start each body | |
| 87 | // walk (and each inference-retry re-walk) from | |
| 88 | // an empty environment. | |
| 89 | _flow.reset(); | |
| 90 | ||
| 91 | function.body!.walk(_visitor); | |
| 92 | ||
| 93 | // Convergence: clean or no progress signal raised. | |
| 94 | // Without a `has_consumed_any` flag from somewhere | |
| 95 | // in the walk, errors that did fire are real and | |
| 96 | // persistent — re-walking won't change them, and | |
| 97 | // re-walking AST nodes that hold partial state | |
| 98 | // produces *different* (often worse) error sets | |
| 99 | // because the second walk sees state left over | |
| 100 | // from the first. So retry only when something | |
| 101 | // explicitly flagged "I consumed an unresolved | |
| 102 | // type" — that's the case where iteration N+1 | |
| 103 | // might find narrower constraints and make | |
| 104 | // progress. | |
| 105 | if _logger.is_clean \/ !_logger.has_consumed_any then | |
| 106 | break; | |
| 107 | fi | |
| 108 | ||
| 109 | if i < retries then | |
| 110 | _logger.roll_back(); | |
| 111 | _logger.speculate(); | |
| 112 | ||
| 113 | _symbol_use_locations.roll_back(); | |
| 114 | _symbol_use_locations.speculate(); | |
| 115 | fi | |
| 116 | od | |
| 117 | ||
| 118 | // Any constructor-type-arg placeholder that didn't | |
| 119 | // acquire a concrete constraint from any usage has no | |
| 120 | // type to settle to - report rather than guess. This | |
| 121 | // is also the backstop that keeps such a placeholder | |
| 122 | // from surviving to IL emission, where its canary | |
| 123 | // comment form would fail the ilasm step. | |
| 124 | // | |
| 125 | // Only sweep when the retry loop failed to converge. | |
| 126 | // A walk that converged clean consumed no unresolved | |
| 127 | // types, so every phantom that matters has settled - | |
| 128 | // but the registry also holds phantom sets minted for | |
| 129 | // overload candidates that lost their resolution and | |
| 130 | // were rolled back, and those stay unresolved without | |
| 131 | // being errors. Non-convergence is the signal that an | |
| 132 | // unresolved phantom actually reached committed code. | |
| 133 | if _logger.has_consumed_any /\ !_logger.is_clean then | |
| 134 | _type_arg_placeholder_registry.report_unresolved(_logger); | |
| 135 | fi | |
| 136 | ||
| 137 | // Definite return: control can reach the end of a | |
| 138 | // non-void, block-bodied function's body without a | |
| 139 | // value having been returned. `_flow.is_unreachable` | |
| 140 | // is false here only when at least one path falls | |
| 141 | // off the end — every path that returns / throws | |
| 142 | // leaves the flow at `bottom`. | |
| 143 | let function_symbol = _symbol_table.current_function; | |
| 144 | ||
| 145 | // Generators "fall off the end" by design — that's | |
| 146 | // how the state machine signals end-of-stream. The | |
| 147 | // definite-return rule doesn't apply to them. | |
| 148 | let is_generator = Semantic.Symbols.state_machine_for(function_symbol)?; | |
| 149 | ||
| 150 | if | |
| 151 | !_build_flags.no_warn_definite_return /\ | |
| 152 | !is_generator /\ | |
| 153 | function.body!.is_block /\ | |
| 154 | !_flow.is_unreachable /\ | |
| 155 | function_symbol? /\ | |
| 156 | function_symbol.return_type? /\ | |
| 157 | !function_symbol.return_type!.is_sentinel /\ | |
| 158 | !function_symbol.return_type!.is_inferred /\ | |
| 159 | !function_symbol.return_type!.matches(_innate_symbol_lookup.get_void_type()) | |
| 160 | then | |
| 161 | _logger.warn(function.location, "definite-return", "function may not return a value on all paths"); | |
| 162 | fi | |
| 163 | ||
| 164 | _logger.commit(); | |
| 165 | _symbol_use_locations.commit(); | |
| 166 | fi | |
| 167 | si | |
| 168 | ||
| 169 | visit_function(function: Trees.Expressions.FUNCTION) is | |
| 170 | if !function.value? then | |
| 171 | function.compile_expressions_state.value = IR.Values.WRAPPER(); | |
| 172 | fi | |
| 173 | ||
| 174 | let closure = cast Semantic.Symbols.Closure?(_symbol_table.scope_for(function))!; | |
| 175 | ||
| 176 | closure.map_type_arguments(); | |
| 177 | ||
| 178 | let implied_type: Type mut; | |
| 179 | let implied_argument_types: Collections.List[Type]? mut = null; | |
| 180 | let constraint_return_type: Type? mut = null; | |
| 181 | ||
| 182 | // have we been passed expected arguments types? If so we'll use them | |
| 183 | // to argument types of any anonymous function literal arguments. | |
| 184 | // Single source of truth: function.expected_type, set by the parent | |
| 185 | // context — LHS of an assignment, typed variable initializer, or | |
| 186 | // overload-resolution second-pass when this lambda was an actual | |
| 187 | // arg whose chosen-formal type is now known (#1174). | |
| 188 | // A context expecting a named delegate constrains the literal | |
| 189 | // exactly as a function type does: the literal is compiled to | |
| 190 | // the delegate's call shape and the delegate itself is | |
| 191 | // constructed at the load site. | |
| 192 | let expected_type: Type? mut = function.expected_type; | |
| 193 | ||
| 194 | if let expected = expected_type then | |
| 195 | if !expected.is_function then | |
| 196 | let shape = _delegate_shape.try_get_function_type(expected, _innate_symbol_lookup); | |
| 197 | ||
| 198 | if shape? then | |
| 199 | expected_type = shape; | |
| 200 | closure.delegate_target_type = expected; | |
| 201 | fi | |
| 202 | fi | |
| 203 | fi | |
| 204 | ||
| 205 | if let effective = expected_type /\ effective.is_function then | |
| 206 | implied_argument_types = effective.arguments; | |
| 207 | ||
| 208 | // Func slots store the return type as the last entry in | |
| 209 | // `arguments`; Action slots strip the void return so | |
| 210 | // `arguments` holds only the input types. Reading | |
| 211 | // `arguments[count - 1]` on an Action would grab the | |
| 212 | // last input arg, not the return. | |
| 213 | if !effective.is_action then | |
| 214 | let argument_count = effective.arguments.count; | |
| 215 | if argument_count > 0 then | |
| 216 | // The slot type may embed placeholders whose | |
| 217 | // origins settled on an earlier iteration - | |
| 218 | // collapse them, or the stale composite gets | |
| 219 | // pushed onto the body and committed as the | |
| 220 | // closure's return type every iteration, | |
| 221 | // keeping the retry loop from converging. | |
| 222 | let candidate = | |
| 223 | Semantic.SETTLED_PLACEHOLDER_RESOLVER.instance.resolve( | |
| 224 | effective.arguments[argument_count - 1]); | |
| 225 | ||
| 226 | if Semantic.LAMBDA_RETURN_CONSTRAINT.should_push(candidate) then | |
| 227 | constraint_return_type = candidate; | |
| 228 | fi | |
| 229 | fi | |
| 230 | fi | |
| 231 | fi | |
| 232 | ||
| 233 | _symbol_table.current_closure_context.add_closure(closure); | |
| 234 | ||
| 235 | // we are walking the arguments here while speculating | |
| 236 | let arguments_resolved = _closure_arg_resolver.resolve(function.arguments.expressions, closure, implied_argument_types); | |
| 237 | ||
| 238 | _resolve_argument_attributes(function, closure); | |
| 239 | ||
| 240 | if arguments_resolved then | |
| 241 | let return_type: Type mut; | |
| 242 | ||
| 243 | if function.type_expression.type? then | |
| 244 | return_type = function.type_expression.type!; | |
| 245 | else | |
| 246 | return_type = Semantic.Types.INFERRED_RETURN_TYPE(); | |
| 247 | closure.return_type_was_inferred = true; | |
| 248 | fi | |
| 249 | ||
| 250 | // Body contains `await` → closure must eventually | |
| 251 | // return `Tasks.TASK[?]`. Void-async (no value | |
| 252 | // returns) pins to non-generic `Tasks.TASK`; value- | |
| 253 | // async stays INFERRED_RETURN_TYPE and pins to | |
| 254 | // `Tasks.TASK[T]` via the wrap-paths. | |
| 255 | if function.contains_let_await then | |
| 256 | if function.is_void_async then | |
| 257 | let void_task = _innate_symbol_lookup.get_void_task_type(); | |
| 258 | if void_task? then | |
| 259 | return_type = void_task; | |
| 260 | closure.return_type_was_inferred = false; | |
| 261 | closure.wrap_inferred_return_as_task = false; | |
| 262 | closure.is_void_async = true; | |
| 263 | fi | |
| 264 | else | |
| 265 | closure.wrap_inferred_return_as_task = true; | |
| 266 | fi | |
| 267 | elif | |
| 268 | closure.return_type_was_inferred /\ | |
| 269 | constraint_return_type? /\ | |
| 270 | _task_conversion.try_get_task_element_type(constraint_return_type)? | |
| 271 | then | |
| 272 | // Slot expects `Tasks.TASK[T]`; mark the closure | |
| 273 | // so a bare-T body return wraps to | |
| 274 | // `Tasks.TASK.from_result(orig)` via the same | |
| 275 | // inferred-return paths used for async closures. | |
| 276 | closure.wrap_inferred_return_as_task = true; | |
| 277 | fi | |
| 278 | ||
| 279 | closure.set_return_type(return_type); | |
| 280 | fi | |
| 281 | ||
| 282 | // Push the constraint's return-type slot onto the body's | |
| 283 | // last expression before walking. The implicit-return path | |
| 284 | // (pre(Bodies.EXPRESSION)) is gated on | |
| 285 | // `!function.return_type.is_sentinel` and skips when the closure | |
| 286 | // hasn't been given an explicit return-type annotation — | |
| 287 | // but that's exactly the case where a bare variant | |
| 288 | // constructor (`DONE()`) in the body would benefit from | |
| 289 | // knowing the expected type. Set it here so the body's | |
| 290 | // CALL nodes see the constraint; without setting | |
| 291 | // closure.return_type, the lambda's emitted return type | |
| 292 | // is still inferred from the body so covariant-return-via- | |
| 293 | // assignment is unaffected. | |
| 294 | if | |
| 295 | constraint_return_type? /\ | |
| 296 | !function.type_expression.type? /\ | |
| 297 | isa Trees.Bodies.EXPRESSION(function.body) | |
| 298 | then | |
| 299 | let body = cast Trees.Bodies.EXPRESSION?(function.body)!; | |
| 300 | ||
| 301 | body.expression.set_expected_type(constraint_return_type, "cannot return value of type {{0}} where {{1}} expected"); | |
| 302 | fi | |
| 303 | ||
| 304 | _logger.commit(); | |
| 305 | _logger.speculate(); | |
| 306 | ||
| 307 | _symbol_use_locations.commit(); | |
| 308 | _symbol_use_locations.speculate(); | |
| 309 | ||
| 310 | // Each body re-walk below must start from the narrowing | |
| 311 | // facts in force at the literal, not the ones the previous | |
| 312 | // walk recorded: a body ending in `return` leaves the | |
| 313 | // environment unreachable (a re-walk from there loses every | |
| 314 | // narrow in the body), and a body that unwraps its own | |
| 315 | // parameter leaves the recorded presence fact (a re-walk | |
| 316 | // over it misreports the unwrap redundant). | |
| 317 | let use flow_speculation = _flow.speculate_then_commit(); | |
| 318 | ||
| 319 | _walk_literal_body(function, closure); | |
| 320 | ||
| 321 | let reported_inference_failure = false; | |
| 322 | ||
| 323 | if closure.is_recursive then | |
| 324 | _logger.roll_back(); | |
| 325 | _logger.speculate(); | |
| 326 | ||
| 327 | _symbol_use_locations.roll_back(); | |
| 328 | _symbol_use_locations.speculate(); | |
| 329 | ||
| 330 | _flow.restore(); | |
| 331 | ||
| 332 | _walk_literal_body(function, closure); | |
| 333 | fi | |
| 334 | ||
| 335 | // A recursive literal's self-call goes through the | |
| 336 | // `$recurse` field, whose type mirrors the closure's own | |
| 337 | // (still-impure) type — so a genuinely store-free | |
| 338 | // recursive body poisons itself the moment it calls | |
| 339 | // itself. Retype `$recurse` to the pure shape and re-walk: | |
| 340 | // if nothing else in the body proves impure, the | |
| 341 | // optimistic assumption is self-consistent (the closure | |
| 342 | // really is store-free — by induction, every recursive | |
| 343 | // invocation is too) and stands; otherwise some other | |
| 344 | // operation is genuinely impure regardless of how the | |
| 345 | // self-call is typed, and reverting the retype and | |
| 346 | // re-walking once more reproduces the original verdict. | |
| 347 | if closure.is_recursive /\ closure.literal_body_impure /\ closure.frame? /\ closure.type? then | |
| 348 | let frame = closure.frame; | |
| 349 | let pure_recurse_type = Semantic.Types.PURE_FUNCTION_SHAPE.pure_shape_of(closure.type, _innate_symbol_lookup); | |
| 350 | ||
| 351 | if pure_recurse_type? then | |
| 352 | frame.try_update_recurse_type(pure_recurse_type); | |
| 353 | ||
| 354 | _logger.roll_back(); | |
| 355 | _logger.speculate(); | |
| 356 | ||
| 357 | _symbol_use_locations.roll_back(); | |
| 358 | _symbol_use_locations.speculate(); | |
| 359 | ||
| 360 | _flow.restore(); | |
| 361 | ||
| 362 | _walk_literal_body(function, closure); | |
| 363 | ||
| 364 | if closure.literal_body_impure then | |
| 365 | frame.try_update_recurse_type(closure.type!); | |
| 366 | ||
| 367 | _logger.roll_back(); | |
| 368 | _logger.speculate(); | |
| 369 | ||
| 370 | _symbol_use_locations.roll_back(); | |
| 371 | _symbol_use_locations.speculate(); | |
| 372 | ||
| 373 | _flow.restore(); | |
| 374 | ||
| 375 | _walk_literal_body(function, closure); | |
| 376 | fi | |
| 377 | fi | |
| 378 | fi | |
| 379 | ||
| 380 | if _build_flags.want_assembler /\ arguments_resolved /\ closure.could_be_delegate then | |
| 381 | _logger.roll_back(); | |
| 382 | _logger.speculate(); | |
| 383 | ||
| 384 | _symbol_use_locations.roll_back(); | |
| 385 | _symbol_use_locations.speculate(); | |
| 386 | ||
| 387 | _flow.restore(); | |
| 388 | ||
| 389 | closure.convert_to_delegate(); | |
| 390 | ||
| 391 | _walk_literal_body(function, closure); | |
| 392 | fi | |
| 393 | ||
| 394 | // Default an unresolved return type to void only when the | |
| 395 | // arguments are *fully* resolved — concrete types, not still | |
| 396 | // INFERRED_VARIABLE_TYPE placeholders. With placeholder args, | |
| 397 | // an unresolved return is the symptom of an identity-shaped | |
| 398 | // body (`a => a`) where the body offered no constraint and | |
| 399 | // we're depending on the call-site formal-arg-push to | |
| 400 | // resolve the args on the next outer iteration. Defaulting | |
| 401 | // the return to void here would lock in `*** -> void` and | |
| 402 | // poison the overload resolution that follows. | |
| 403 | if closure.return_type!.is_inferred /\ closure.arguments |> all(a => a.is_settled) then | |
| 404 | closure.set_return_type(_innate_symbol_lookup.get_void_type()); | |
| 405 | fi | |
| 406 | ||
| 407 | closure.unmap_type_arguments(); | |
| 408 | ||
| 409 | let value mut = closure.load(function.location, _symbol_loader).freeze(); | |
| 410 | ||
| 411 | // A literal whose body proved store-free surfaces at the | |
| 412 | // pure shape of its function type, so it satisfies a pure | |
| 413 | // function-typed slot on type alone — freezing to raw IL | |
| 414 | // has discarded the closure symbol by the time the slot | |
| 415 | // check sees the value. The shape is the literal's own | |
| 416 | // property, so it does not depend on the slot it is | |
| 417 | // heading for: a store-free literal reads as pure | |
| 418 | // wherever its type is shown. | |
| 419 | if | |
| 420 | !closure.literal_body_impure /\ value.type? | |
| 421 | then | |
| 422 | let pure_type = Semantic.Types.PURE_FUNCTION_SHAPE.pure_shape_of(value.type!, _innate_symbol_lookup); | |
| 423 | ||
| 424 | if pure_type? then | |
| 425 | value = IR.Values.TYPE_WRAPPER(pure_type, value); | |
| 426 | fi | |
| 427 | fi | |
| 428 | ||
| 429 | cast IR.Values.WRAPPER?(function.value)!.value = value; | |
| 430 | si | |
| 431 | ||
| 432 | // visit_function re-runs on every retry of the enclosing | |
| 433 | // function's body (a lambda literal is re-walked along with | |
| 434 | // everything else in the body until the retry loop converges), | |
| 435 | // so guard on custom_attributes already being populated — | |
| 436 | // otherwise the same attribute would be resolved and appended | |
| 437 | // again on each retry. | |
| 438 | _resolve_argument_attributes(function: Trees.Expressions.FUNCTION, closure: Semantic.Symbols.Closure) is | |
| 439 | for expr in function.arguments.expressions do | |
| 440 | if let argument: Trees.Expressions.VARIABLE = expr, pragmas = argument.pragmas then | |
| 441 | // Trees.Expressions.FUNCTION.pre() suppresses the | |
| 442 | // default child walk (so visit_function can walk | |
| 443 | // the body up to N times), so nothing else ever | |
| 444 | // walks a pragma's own argument expressions. | |
| 445 | for pragma in pragmas do | |
| 446 | pragma.walk(_visitor); | |
| 447 | od | |
| 448 | ||
| 449 | let symbol = closure.find_direct(argument.name.name); | |
| 450 | ||
| 451 | if symbol? /\ !symbol.custom_attributes? then | |
| 452 | for pragma in pragmas do | |
| 453 | _attribute_resolver.resolve(pragma, symbol); | |
| 454 | od | |
| 455 | fi | |
| 456 | fi | |
| 457 | od | |
| 458 | si | |
| 459 | ||
| 460 | // Walk a literal's body with a purity frame in force, so the | |
| 461 | // flow transfers record whether the body performed anything | |
| 462 | // possibly heap-visible. Re-walks (recursion, delegate | |
| 463 | // conversion) overwrite the recorded flag — the last walk is | |
| 464 | // the one whose compilation stands. | |
| 465 | _walk_literal_body(function: Trees.Expressions.FUNCTION, closure: Semantic.Symbols.Closure) is | |
| 466 | _flow.push_literal_frame(); | |
| 467 | ||
| 468 | closure.enter_literal_body(); | |
| 469 | function.body.walk(_visitor); | |
| 470 | closure.leave_literal_body(); | |
| 471 | ||
| 472 | closure.literal_body_impure = _flow.pop_literal_frame(); | |
| 473 | si | |
| 474 | ||
| 475 | visit_recurse(recurse: Trees.Expressions.RECURSE) is | |
| 476 | let function = _symbol_table.current_function; | |
| 477 | ||
| 478 | if !function? \/ !function.is_closure then | |
| 479 | recurse.compile_expressions_state.value = IR.Values.DUMMY(Semantic.Types.ERROR(), recurse.location); | |
| 480 | ||
| 481 | _logger.error(recurse.location, "rec can only be used in a function literal body"); | |
| 482 | return; | |
| 483 | fi | |
| 484 | ||
| 485 | let closure = cast Semantic.Symbols.Closure?(function)!; | |
| 486 | ||
| 487 | if closure.is_recursive then | |
| 488 | recurse.compile_expressions_state.value = closure.load_recurse(recurse.location, _symbol_loader); | |
| 489 | return; | |
| 490 | fi | |
| 491 | ||
| 492 | // Walk up enclosing closures looking for a recursive | |
| 493 | // ancestor whose $recurse we can capture. The chain in | |
| 494 | // between needs to capture it too so the value can be | |
| 495 | // threaded down through nested frame constructors. | |
| 496 | let stack = _symbol_table.stack; | |
| 497 | let index mut = stack.count - 1; | |
| 498 | let seen_self mut = false; | |
| 499 | let outer_recursive: Semantic.Symbols.Closure? mut = null; | |
| 500 | let intermediates = Collections.LIST[Semantic.Symbols.Closure](); | |
| 501 | ||
| 502 | while index >= 0 do | |
| 503 | let scope = stack[index]; | |
| 504 | ||
| 505 | if scope.is_closure then | |
| 506 | let c = cast Semantic.Symbols.Closure?(scope)!; | |
| 507 | ||
| 508 | if seen_self then | |
| 509 | if c.is_recursive then | |
| 510 | outer_recursive = c; | |
| 511 | break; | |
| 512 | fi | |
| 513 | intermediates.add(c); | |
| 514 | elif c == closure then | |
| 515 | seen_self = true; | |
| 516 | fi | |
| 517 | fi | |
| 518 | ||
| 519 | index = index - 1; | |
| 520 | od | |
| 521 | ||
| 522 | if !outer_recursive? then | |
| 523 | recurse.compile_expressions_state.value = IR.Values.DUMMY(Semantic.Types.ERROR(), recurse.location); | |
| 524 | ||
| 525 | _logger.error(recurse.location, "rec can only be used in a recursive function"); | |
| 526 | return; | |
| 527 | fi | |
| 528 | ||
| 529 | for intermediate in intermediates do | |
| 530 | intermediate.find_or_add_captured_outer_recurse(outer_recursive); | |
| 531 | od | |
| 532 | ||
| 533 | recurse.compile_expressions_state.value = closure.load_captured_outer_recurse(recurse.location, outer_recursive, _symbol_loader); | |
| 534 | si | |
| 535 | si | |
| 536 | si |