Skip to content
← Back

src/syntax/process/compile_expressions.ghul

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