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 | ||
| 21 | // Definition and statement walks: pragmas, attributes, functions, let, for, destructuring | |
| 22 | // strategy, let-in, assert-in and assignments. | |
| 23 | partial COMPILE_EXPRESSIONS is | |
| 24 | pre(pragma: Trees.Definitions.PRAGMA) -> bool is | |
| 25 | _pragma_scope_stack.enter(pragma.pragma) | |
| 26 | ||
| 27 | return false | |
| 28 | si | |
| 29 | ||
| 30 | visit(pragma: Trees.Definitions.PRAGMA) is | |
| 31 | _pragma_scope_stack.leave(pragma.pragma) | |
| 32 | ||
| 33 | resolve_attribute(pragma) | |
| 34 | si | |
| 35 | ||
| 36 | // A statement pragma has no definition to attach an attribute | |
| 37 | // to; the resolver reports the pragma names it rejects. | |
| 38 | visit(pragma: Trees.Statements.PRAGMA) is | |
| 39 | _attribute_resolver.resolve(pragma.pragma, null) | |
| 40 | si | |
| 41 | ||
| 42 | // An attribute pragma applies to the definition it wraps; unwrap | |
| 43 | // any nested pragmas to reach that definition's symbol. | |
| 44 | resolve_attribute(pragma: Trees.Definitions.PRAGMA) is | |
| 45 | let definition: Trees.Definitions.Definition mut = pragma.definition | |
| 46 | ||
| 47 | while isa Trees.Definitions.PRAGMA(definition) do | |
| 48 | definition = cast Trees.Definitions.PRAGMA(definition).definition | |
| 49 | od | |
| 50 | ||
| 51 | _attribute_resolver.resolve(pragma.pragma, symbol_for(definition)) | |
| 52 | si | |
| 53 | ||
| 54 | // Iterative body walk. Returning true here suppresses the | |
| 55 | // walk framework's default child traversal so visit() can walk | |
| 56 | // arguments once and the body up to N times. Constraints set | |
| 57 | // on AST nodes during a walk persist across iterations | |
| 58 | // (TypeConstrained.upgrade_constraint is narrowing-only), so | |
| 59 | // each pass either narrows the constraint set or stays put; | |
| 60 | // convergence is _logger.is_clean (no errors and no flagged | |
| 61 | // wild/inferred type consumption). | |
| 62 | pre(function: Trees.Definitions.FUNCTION) -> bool is | |
| 63 | super.pre(function) | |
| 64 | return true | |
| 65 | si | |
| 66 | ||
| 67 | visit(function: Trees.Definitions.FUNCTION) is | |
| 68 | let symbol = symbol_for(function) | |
| 69 | ||
| 70 | // Declare-symbols rejected the declaration outright — a | |
| 71 | // generator or async function in a context that has no such | |
| 72 | // kind — so there is no function symbol to compile the body | |
| 73 | // against, and walking it anyway trips assertions that assume | |
| 74 | // an enclosing function. The rejection has already been | |
| 75 | // reported. | |
| 76 | if !isa Semantic.Symbols.Function(symbol) then | |
| 77 | super.visit(function) | |
| 78 | ||
| 79 | return | |
| 80 | fi | |
| 81 | ||
| 82 | let state_machine: Semantic.Symbols.STATE_MACHINE? mut = null | |
| 83 | let async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE? mut = null | |
| 84 | ||
| 85 | // Generator / async: realise the state-machine frame's | |
| 86 | // argument fields and `_outer_self` field before the body | |
| 87 | // walks. A closure inside the body captures loaded values | |
| 88 | // at compile-expressions time, and | |
| 89 | // the captured `Load.LOCAL_ARGUMENT.gen` only redirects | |
| 90 | // through `ldarg.0; ldfld _arg_<name>` when | |
| 91 | // `state_machine_field` is populated on the symbol — | |
| 92 | // populating it later (generate-il) would be too late. | |
| 93 | // | |
| 94 | // Also install the function-T → class-T gen_type override | |
| 95 | // so that any closure captured inside the body — and | |
| 96 | // any IR.Values it captures that ref a function-T | |
| 97 | // symbol — emits `!N` (class-level on the state machine) | |
| 98 | // rather than `!!N` (method-level, which has no meaning | |
| 99 | // inside MoveNext). The override is uninstalled after the | |
| 100 | // body walk; generate-il re-installs it around its own | |
| 101 | // body / state-machine emission paths. | |
| 102 | if let function_symbol: Semantic.Symbols.Function = symbol then | |
| 103 | // A closure body compiled during this walk re-marks any | |
| 104 | // argument it captures; a mark left by an earlier walk is | |
| 105 | // stale once an edit removes the capturing lambda, and | |
| 106 | // would wrongly reject assignments to a mut argument. | |
| 107 | // Re-derive from scratch on each walk of the owning body. | |
| 108 | for argument_name in function_symbol.argument_names do | |
| 109 | if let argument: Semantic.Symbols.LOCAL_ARGUMENT = function_symbol.find_direct(argument_name) then | |
| 110 | argument.is_captured = false | |
| 111 | fi | |
| 112 | od | |
| 113 | ||
| 114 | state_machine = Semantic.Symbols.state_machine_for(function_symbol) | |
| 115 | async_state_machine = Semantic.Symbols.async_state_machine_for(function_symbol) | |
| 116 | ||
| 117 | if state_machine? /\ state_machine.frame? then | |
| 118 | state_machine.frame!.declare() | |
| 119 | state_machine.install_body_emission_overrides() | |
| 120 | elif async_state_machine? /\ async_state_machine.frame? then | |
| 121 | async_state_machine.frame!.declare() | |
| 122 | async_state_machine.install_body_emission_overrides() | |
| 123 | fi | |
| 124 | fi | |
| 125 | ||
| 126 | _lambdas.visit_function_definition(function) | |
| 127 | ||
| 128 | if state_machine? /\ state_machine.frame? then | |
| 129 | state_machine.uninstall_body_emission_overrides() | |
| 130 | elif async_state_machine? /\ async_state_machine.frame? then | |
| 131 | async_state_machine.uninstall_body_emission_overrides() | |
| 132 | fi | |
| 133 | ||
| 134 | // Yield inside try/catch/finally needs the fault-block / | |
| 135 | // state-machine-finalisation dance C# uses and isn't | |
| 136 | // implemented in v1. The "yield + await in same body" | |
| 137 | // diagnostic fires earlier in declare-symbols, where | |
| 138 | // both AST forms are still observable. | |
| 139 | if state_machine? /\ function.body? then | |
| 140 | YIELD_IN_TRY_SCANNER(_logger).scan(function.body!) | |
| 141 | fi | |
| 142 | ||
| 143 | if async_state_machine? /\ function.body? then | |
| 144 | AWAIT_IN_PROTECTED_SCANNER(_logger).scan(function.body!) | |
| 145 | fi | |
| 146 | ||
| 147 | super.visit(function) | |
| 148 | si | |
| 149 | ||
| 150 | pre(`let: Trees.Statements.LET) -> bool is | |
| 151 | if `let.is_poisoned then | |
| 152 | return true | |
| 153 | fi | |
| 154 | ||
| 155 | super.pre(`let) | |
| 156 | return _bindings.pre_let(`let) | |
| 157 | si | |
| 158 | ||
| 159 | visit(`let: Trees.Statements.LET) is | |
| 160 | super.visit(`let) | |
| 161 | _bindings.visit_let(`let) | |
| 162 | si | |
| 163 | ||
| 164 | pre(`for: Trees.Statements.FOR) -> bool is | |
| 165 | super.pre(`for) | |
| 166 | ||
| 167 | _loops.push_loop_value_frame( | |
| 168 | `for, | |
| 169 | take_pending_label(), | |
| 170 | `for.want_value, | |
| 171 | `for.expected_type, | |
| 172 | `for.expected_type_error_message | |
| 173 | ) | |
| 174 | ||
| 175 | try | |
| 176 | _pre(`for) | |
| 177 | catch ex: Exception | |
| 178 | _logger.exception(`for.location, ex, "exception compiling for") | |
| 179 | yrt | |
| 180 | ||
| 181 | _loops.pop_and_settle_loop_value(`for) | |
| 182 | ||
| 183 | return true | |
| 184 | si | |
| 185 | ||
| 186 | _pre(`for: Trees.Statements.FOR) -> bool is | |
| 187 | let symbol: Semantic.Symbols.Symbol mut | |
| 188 | ||
| 189 | let type: Type mut = Semantic.Types.ERROR() | |
| 190 | ||
| 191 | let expression = `for.expression | |
| 192 | let variable = `for.variable | |
| 193 | ||
| 194 | if expression? /\ !expression.is_poisoned then | |
| 195 | expression.walk(self) | |
| 196 | ||
| 197 | record_iterable_constraint(expression) | |
| 198 | ||
| 199 | if let ev = expression.value /\ set_iterator_for(`for, ev.type!, false) then | |
| 200 | check_receiver_present(expression) | |
| 201 | ||
| 202 | if !variable? then | |
| 203 | type = `for.read_current!.return_type! | |
| 204 | elif let | |
| 205 | te = variable.type_expression, | |
| 206 | te_type = te.type /\ | |
| 207 | !isa Trees.TypeExpressions.INFER(te) | |
| 208 | then | |
| 209 | if te_type.is_assignable_from(`for.read_current!.return_type!) then | |
| 210 | type = te_type | |
| 211 | else | |
| 212 | _logger.error(variable.location, "type mismatch") | |
| 213 | fi | |
| 214 | else | |
| 215 | type = `for.read_current!.return_type! | |
| 216 | fi | |
| 217 | fi | |
| 218 | fi | |
| 219 | ||
| 220 | `for.fusion = _recognize_pipe_fusion(`for) | |
| 221 | ||
| 222 | if variable? /\ !variable.is_poisoned then | |
| 223 | set_symbol_type(variable.left, type) | |
| 224 | ||
| 225 | // Generator: register the per-iteration loop variable | |
| 226 | // on the state-machine frame so closures inside the | |
| 227 | // body that capture it freeze correctly. Same | |
| 228 | // rationale as the LET path above; `for.variable` is | |
| 229 | // not walked through the visitor framework here, so | |
| 230 | // we call the helper directly. | |
| 231 | declare_state_machine_local_fields(variable.left) | |
| 232 | fi | |
| 233 | ||
| 234 | let body = `for.body | |
| 235 | ||
| 236 | if body? then | |
| 237 | // Loop kill-set narrowing: narrows on variables the | |
| 238 | // loop writes are dropped (the back-edge could | |
| 239 | // invalidate them); narrows on variables it never | |
| 240 | // writes survive the loop. | |
| 241 | let kept = _loops.loop_kept_env(`for) | |
| 242 | let epoch = _flow.heap_epoch | |
| 243 | let mark = _flow.crossing_mark | |
| 244 | ||
| 245 | _flow.set_env(kept) | |
| 246 | body.walk(self) | |
| 247 | ||
| 248 | // The assignment kill-set covers direct writes but | |
| 249 | // not member stores inside the body, so a store | |
| 250 | // during the walk drops the kept environment's heap | |
| 251 | // facts before restoration; the body's calls attach | |
| 252 | // as crossings. | |
| 253 | if _flow.heap_killed_since(epoch) then | |
| 254 | kept.drop_heap_facts() | |
| 255 | else | |
| 256 | _flow.adopt_crossings_since(kept, mark) | |
| 257 | fi | |
| 258 | ||
| 259 | _flow.set_env(kept) | |
| 260 | fi | |
| 261 | return false | |
| 262 | si | |
| 263 | ||
| 264 | pre(left: Trees.Expressions.SIMPLE_LEFT_EXPRESSION) -> bool is | |
| 265 | super.pre(left) | |
| 266 | return _bindings.pre_simple_left(left) | |
| 267 | si | |
| 268 | ||
| 269 | visit(left: Trees.Expressions.SIMPLE_LEFT_EXPRESSION) is | |
| 270 | super.visit(left) | |
| 271 | _bindings.visit_simple_left(left) | |
| 272 | si | |
| 273 | ||
| 274 | // True iff `value` is statically known to hold a value — its | |
| 275 | // type is settled and is not an optional / null type. | |
| 276 | is_non_optional_value(value: IR.Values.Value?) -> bool => | |
| 277 | value? /\ value.type? /\ value.type.is_settled /\ | |
| 278 | !value.type!.is_null /\ !value.type!.is_optional | |
| 279 | ||
| 280 | // Resolve the destructure strategy (see DESTRUCTURE_RESOLVER) | |
| 281 | // and log an error against `location` for any unresolvable | |
| 282 | // case. When `field_names` is null the destructure is | |
| 283 | // positional and the failure is reported as a count | |
| 284 | // mismatch if the source has positional members, otherwise | |
| 285 | // as a not-destructurable source. When `field_names` is | |
| 286 | // non-null the destructure is by-name and each missed name | |
| 287 | // is reported individually. | |
| 288 | resolve_destructure_strategy( | |
| 289 | location: LOCATION, | |
| 290 | from_type: Type?, | |
| 291 | element_count: int, | |
| 292 | field_names: Collections.List[string?]? | |
| 293 | ) -> DESTRUCTURE_STRATEGY => | |
| 294 | DESTRUCTURE_RESOLVER.resolve_strategy_reporting(_logger, location, from_type, element_count, field_names) | |
| 295 | ||
| 296 | pre(left: Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION) -> bool is | |
| 297 | super.pre(left) | |
| 298 | ||
| 299 | let from = left.value | |
| 300 | ||
| 301 | if !from? then | |
| 302 | return true | |
| 303 | fi | |
| 304 | ||
| 305 | let from_type = from.type | |
| 306 | ||
| 307 | if !from_type? then | |
| 308 | return true | |
| 309 | fi | |
| 310 | ||
| 311 | if from_type.is_error then | |
| 312 | return true | |
| 313 | fi | |
| 314 | ||
| 315 | let elements = left.elements | |
| 316 | ||
| 317 | // Assignment-style destructure `(a, b) = expr` is | |
| 318 | // positional-only — by-name destructure uses the `let | |
| 319 | // (local = field, …) = expr` form, which goes through | |
| 320 | // the VariableLeft path instead. | |
| 321 | let strategy = resolve_destructure_strategy(left.location, from_type, elements.count, null) | |
| 322 | ||
| 323 | let block = IR.Values.BLOCK(_innate_symbol_lookup.get_void_type()) | |
| 324 | ||
| 325 | if strategy.is_deconstruct then | |
| 326 | let deconstruct = strategy.deconstruct_function! | |
| 327 | let arg_temps = Collections.LIST[IR.TEMP]() | |
| 328 | let call_args = Collections.LIST[IR.Values.Value]() | |
| 329 | ||
| 330 | for i in 0..deconstruct.arguments.count do | |
| 331 | let ref_type = deconstruct.arguments[i] | |
| 332 | let element_type = ref_type.get_element_type()! | |
| 333 | let arg_temp = IR.TEMP(block, "destructure_arg", i, element_type) | |
| 334 | ||
| 335 | arg_temps.add(arg_temp) | |
| 336 | call_args.add(IR.Values.ADDRESS(arg_temp.load(), ref_type)) | |
| 337 | od | |
| 338 | ||
| 339 | let call_value = | |
| 340 | deconstruct.call( | |
| 341 | left.location, | |
| 342 | from, | |
| 343 | call_args, | |
| 344 | null, | |
| 345 | _function_caller | |
| 346 | ) | |
| 347 | ||
| 348 | block.add(call_value) | |
| 349 | ||
| 350 | for i in 0..elements.count do | |
| 351 | let element = elements[i] | |
| 352 | ||
| 353 | element.compile_expressions_state.value = arg_temps[i].load() | |
| 354 | ||
| 355 | element.walk(self) | |
| 356 | ||
| 357 | if element.value? then | |
| 358 | block.add(element.value) | |
| 359 | fi | |
| 360 | od | |
| 361 | else | |
| 362 | let members = strategy.members | |
| 363 | let get_from = from.get_temp_copier(block, "destructure") | |
| 364 | ||
| 365 | for i in 0..elements.count do | |
| 366 | let element = elements[i] | |
| 367 | let member = members[i] | |
| 368 | ||
| 369 | if member? then | |
| 370 | element.compile_expressions_state.value = member.load(LOCATION.internal, get_from(), _symbol_loader) | |
| 371 | ||
| 372 | element.walk(self) | |
| 373 | ||
| 374 | if element.value? then | |
| 375 | block.add(element.value) | |
| 376 | fi | |
| 377 | fi | |
| 378 | od | |
| 379 | fi | |
| 380 | ||
| 381 | block.close() | |
| 382 | left.compile_expressions_state.value = block | |
| 383 | ||
| 384 | return true | |
| 385 | si | |
| 386 | ||
| 387 | pre(let_in: Trees.Expressions.LET_IN) -> bool is | |
| 388 | super.pre(let_in) | |
| 389 | ||
| 390 | return false | |
| 391 | si | |
| 392 | ||
| 393 | visit(let_in: Trees.Expressions.LET_IN) is | |
| 394 | super.visit(let_in) | |
| 395 | _bindings.visit_let_in(let_in) | |
| 396 | si | |
| 397 | ||
| 398 | pre(assert_in: Trees.Expressions.ASSERT_IN) -> bool is | |
| 399 | super.pre(assert_in) | |
| 400 | ||
| 401 | let epoch = _flow.heap_epoch | |
| 402 | let mark = _flow.crossing_mark | |
| 403 | ||
| 404 | assert_in.condition.walk(self) | |
| 405 | ||
| 406 | // A store during the condition's own walk means its | |
| 407 | // derived heap facts cannot be kept (`assert _f? /\ | |
| 408 | // mutate() in …`); its calls attach as crossings. The | |
| 409 | // message's walk is outside the span: it only runs on | |
| 410 | // the failure path, so its effects don't invalidate what | |
| 411 | // the passing condition proves. | |
| 412 | let condition_killed = _flow.heap_killed_since(epoch) | |
| 413 | let condition_mark_end = _flow.crossing_mark | |
| 414 | ||
| 415 | // Walk the message *before* installing the condition-holds | |
| 416 | // narrowing, because the message expression is only | |
| 417 | // evaluated on the failure path — where the condition does | |
| 418 | // not hold — and must type-check against the unnarrowed env. | |
| 419 | if assert_in.message? then | |
| 420 | assert_in.message.walk(self) | |
| 421 | _check_assertion_message(assert_in.message!) | |
| 422 | fi | |
| 423 | ||
| 424 | if _check_assertion_condition(assert_in.condition) then | |
| 425 | let facts = _condition_analyzer.analyze_condition(assert_in.condition, _flow.current_env) | |
| 426 | ||
| 427 | if condition_killed then | |
| 428 | facts.then_env.drop_heap_facts() | |
| 429 | else | |
| 430 | _flow.adopt_crossings_between(facts.then_env, mark, condition_mark_end) | |
| 431 | fi | |
| 432 | ||
| 433 | _flow.set_env(facts.then_env) | |
| 434 | fi | |
| 435 | ||
| 436 | assert_in.expression.walk(self) | |
| 437 | ||
| 438 | return true | |
| 439 | si | |
| 440 | ||
| 441 | visit(assert_in: Trees.Expressions.ASSERT_IN) is | |
| 442 | let value = assert_in.expression.value | |
| 443 | ||
| 444 | if | |
| 445 | !value? \/ | |
| 446 | !value.check_is_consumable(_logger, assert_in.expression.location) | |
| 447 | then | |
| 448 | assert_in.compile_expressions_state.value = IR.Values.DUMMY(Semantic.Types.ERROR(), assert_in.location) | |
| 449 | return | |
| 450 | fi | |
| 451 | ||
| 452 | assert_in.compile_expressions_state.value = value | |
| 453 | si | |
| 454 | ||
| 455 | pre(assignment: Trees.Statements.ASSIGNMENT) -> bool => | |
| 456 | _bindings.pre_assignment(assignment) | |
| 457 | ||
| 458 | pre(expression: Trees.Statements.EXPRESSION) -> bool is | |
| 459 | super.pre(expression) | |
| 460 | ||
| 461 | // A STATEMENT- or VAL_BLOCK-shaped expression wrapped in | |
| 462 | // an expression-statement is value-required only if the | |
| 463 | // surrounding list demands a value at this slot. Push | |
| 464 | // that down so a void-tail block / `if`-arm with no value | |
| 465 | // in expression-statement position is accepted silently. | |
| 466 | if let statement_expression: Trees.Expressions.STATEMENT = expression.expression then | |
| 467 | statement_expression.want_value = expression.want_value | |
| 468 | statement_expression.void_tolerated = expression.void_tolerated | |
| 469 | elif let val_block: Trees.Expressions.VAL_BLOCK = expression.expression then | |
| 470 | val_block.want_value = expression.want_value | |
| 471 | val_block.void_tolerated = expression.void_tolerated | |
| 472 | fi | |
| 473 | ||
| 474 | return false | |
| 475 | si | |
| 476 | ||
| 477 | visit(expression: Trees.Statements.EXPRESSION) is | |
| 478 | super.visit(expression) | |
| 479 | _bindings.visit_expression_statement(expression) | |
| 480 | ||
| 481 | if let val_block: Trees.Expressions.VAL_BLOCK = expression.expression then | |
| 482 | if _is_redundant_val_block(val_block) then | |
| 483 | _logger.warn( | |
| 484 | val_block.location, | |
| 485 | "redundant-val-block", | |
| 486 | if val_block.is_parenthesised then | |
| 487 | "parenthesised block is redundant" | |
| 488 | else | |
| 489 | "val block is redundant" | |
| 490 | fi | |
| 491 | ) | |
| 492 | fi | |
| 493 | fi | |
| 494 | si | |
| 495 | ||
| 496 | // True when this val-block is in expression-statement position | |
| 497 | // and could be inlined: the surrounding statement list already | |
| 498 | // accepts the same shape of body. Two refusals: a return inside | |
| 499 | // targets this block (inlining would re-route the return to the | |
| 500 | // enclosing function), or the body declares a local (inlining | |
| 501 | // would widen its scope). | |
| 502 | _is_redundant_val_block(block: Trees.Expressions.VAL_BLOCK) -> bool is | |
| 503 | if block.has_targeted_return then | |
| 504 | return false | |
| 505 | fi | |
| 506 | for s in block.body.statements do | |
| 507 | if isa Trees.Statements.LET(s) then | |
| 508 | return false | |
| 509 | fi | |
| 510 | od | |
| 511 | return true | |
| 512 | si | |
| 513 | ||
| 514 | pre(r: Trees.Statements.RETURN) -> bool is | |
| 515 | super.pre(r) | |
| 516 | return _bindings.pre_return(r) | |
| 517 | si | |
| 518 | ||
| 519 | visit(r: Trees.Statements.RETURN) is | |
| 520 | super.visit(r) | |
| 521 | _bindings.visit_return(r) | |
| 522 | si | |
| 523 | ||
| 524 | si | |
| 525 | si |