Skip to content
← Back

src/syntax/process/compile_access.ghul

1
namespace Syntax.Process is
2
use Logging;
3
use Source;
4
5
use Semantic.Types.Type;
6
7
use IR.Values;
8
9
use Ghul.Pipes;
10
11
// Compiles the member-access family of expressions: member access,
12
// identifier resolution, `?` (has-value), `!` (unwrap) and `ref`.
13
// Split out of COMPILE_EXPRESSIONS, which delegates the matching
14
// visit methods here. The exception-handling wrapper of
15
// visit(identifier) stays on the visitor; visit_identifier is the
16
// enclosed logic.
17
class COMPILE_ACCESS is
18
19
// Depth of enclosing assert conditions during the controlled
20
// walk in the expressions pass. A presence test on a
21
// never-optional value inside an assert is a deliberate
22
// runtime trap for laundered nulls, not a redundant check,
23
// so the static-type hint stays quiet there.
24
assert_condition_depth: int public;
25
_logger: Logger;
26
_symbol_table: Semantic.SYMBOL_TABLE;
27
_symbol_loader: Semantic.SYMBOL_LOADER;
28
_symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS;
29
_innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup;
30
_overload_resolver: Semantic.OVERLOAD_RESOLVER;
31
_function_caller: Semantic.FUNCTION_CALLER;
32
_unit_variant_constructor: Semantic.UNIT_VARIANT_CONSTRUCTOR;
33
_flow: NARROWING_FLOW;
34
_condition_analyzer: CONDITION_ANALYZER;
35
_build_flags: Compiler.GLOBAL_BUILD_FLAGS;
36
_visitor: COMPILE_EXPRESSIONS;
37
_function_reference_resolver: Semantic.FUNCTION_REFERENCE_RESOLVER;
38
39
init(
40
logger: Logger,
41
symbol_table: Semantic.SYMBOL_TABLE,
42
symbol_loader: Semantic.SYMBOL_LOADER,
43
symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS,
44
innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup,
45
overload_resolver: Semantic.OVERLOAD_RESOLVER,
46
function_caller: Semantic.FUNCTION_CALLER,
47
unit_variant_constructor: Semantic.UNIT_VARIANT_CONSTRUCTOR,
48
flow: NARROWING_FLOW,
49
condition_analyzer: CONDITION_ANALYZER,
50
build_flags: Compiler.GLOBAL_BUILD_FLAGS,
51
visitor: COMPILE_EXPRESSIONS
52
) is
53
super.init();
54
55
_logger = logger;
56
_symbol_table = symbol_table;
57
_symbol_loader = symbol_loader;
58
_symbol_use_locations = symbol_use_locations;
59
_innate_symbol_lookup = innate_symbol_lookup;
60
_overload_resolver = overload_resolver;
61
_function_caller = function_caller;
62
_unit_variant_constructor = unit_variant_constructor;
63
_flow = flow;
64
_condition_analyzer = condition_analyzer;
65
_build_flags = build_flags;
66
_visitor = visitor;
67
_function_reference_resolver = Semantic.FUNCTION_REFERENCE_RESOLVER(logger, symbol_table, symbol_loader, innate_symbol_lookup, overload_resolver);
68
si
69
70
visit_member(member: Trees.Expressions.MEMBER) is
71
let need_store = member.value? /\ isa Need.STORE(member.value);
72
let need_deref mut = false;
73
74
if member.identifier == null \/ member.identifier.is_poisoned then
75
member.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), member.location);
76
return;
77
fi
78
79
if member.left.value? then
80
let left_value mut = member.left.value;
81
let type mut = left_value.type;
82
83
if type == null then
84
_logger.poison(member.left.location, "member left has no type");
85
86
member.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), member.location);
87
return;
88
fi
89
90
// `x?.y` never dereferences a null receiver, and
91
// `x.has_value` is the optional protocol's presence
92
// query — asking is not dereferencing.
93
if !member.is_coalesce /\ !(member.identifier.name =~ "has_value") then
94
_visitor.check_receiver_present(member.left);
95
fi
96
97
// TODO we could loop here to handle multiple levels of dereferencing
98
if type.is_ref then
99
need_deref = true;
100
// is_ref implies an element type; the runtime
101
// invariant guarantees get_element_type() non-null.
102
type = type.get_element_type()!;
103
104
left_value = DEREF(left_value, type);
105
member.left.compile_expressions_state.value = left_value;
106
fi
107
108
// Operation-constraint inference: when the receiver
109
// is an INFERRED_VARIABLE_TYPE placeholder, record a
110
// MEMBER_CONSTRAINT on the placeholder's origin so
111
// the constraint-aware LUB can later filter candidate
112
// types to those that actually expose this member.
113
// mark_consumed_any drives the retry loop to re-walk
114
// the body with the resolved type (if any).
115
if isa Semantic.Types.INFERRED_VARIABLE_TYPE(type) then
116
let placeholder = type;
117
118
_logger.mark_consumed_any_if(placeholder.origin.add_constraint(Semantic.MEMBER_CONSTRAINT(member.identifier.name)));
119
120
member.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), member.location);
121
return;
122
fi
123
124
if !isa Semantic.Types.NAMED(type) then
125
if !isa Semantic.Types.ERROR(type) /\ !type.is_inferred then
126
// Suppress for ERROR (existing — error already
127
// reported, the contract says don't pile on)
128
// and for is_inferred placeholders (deferred-
129
// inference: the lambda's arg type is awaiting
130
// the call-site second-pass to fill it in,
131
// and emitting a fatal "type has no members"
132
// here breaks that flow). Both share the
133
// intent of "don't surface a member-resolution
134
// failure on a stand-in type".
135
_logger.poison(member.left.location, "type has no members: {type}");
136
fi
137
138
member.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), member.location);
139
return;
140
fi
141
142
let named_type mut = type;
143
144
// For coalescing access against any optional, look
145
// up the member on the unwrapped (inner) type so
146
// `a?.b` consistently means "if a is present, access
147
// b on the unwrapped a". A value-type optional like
148
// NULLABLE[T] / MAYBE[T] otherwise resolves `value`
149
// / `has_value` against the wrapper rather than the
150
// payload, surprising the user and producing IL that
151
// mixes wrapper-thissed calls with payload-typed
152
// stack values.
153
if member.is_coalesce /\ named_type.is_optional then
154
let inner = named_type.optional_inner_type;
155
156
if inner? /\ isa Semantic.Types.NAMED(inner) then
157
named_type = inner;
158
fi
159
fi
160
161
if named_type.scope == null then
162
_logger.poison(member.left.location, "member left type has no type: {type}");
163
164
member.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), member.location);
165
return;
166
fi
167
168
let symbol mut = named_type.find_member(member.identifier.name);
169
170
// A member that resolves to same-name types at several
171
// generic arities comes back as a TYPE_GROUP. In value
172
// position (`Foo.bar` with no `[...]`) collapse to the
173
// arity-0 member; `Foo[T].bar` is handled earlier by
174
// GENERIC_APPLICATION before reaching here.
175
let member_group = cast Semantic.Symbols.TYPE_GROUP?(symbol);
176
177
if member_group? then
178
let arity_zero = member_group.find_by_generic_arguments_count(0);
179
180
// A constructor call on the qualified name resolves to the
181
// group's generic member when there is no constructible
182
// arity-0 member (a static factory class shares the name
183
// with a generic type, `Collections.KeyValuePair`); when
184
// the arity-0 member is itself constructible, or this is a
185
// plain value-position access, collapse to it.
186
let for_call =
187
if member.is_call_target /\ (!arity_zero? \/ arity_zero.is_abstract) then
188
member_group.sole_generic_member()
189
else
190
null
191
fi;
192
193
if for_call? then
194
symbol = for_call;
195
elif arity_zero? then
196
symbol = arity_zero;
197
else
198
let counts = member_group.generic_arguments_counts |> map(a -> string => "{a}") |> join(" or ");
199
_logger.error(member.identifier.location, "type {member.identifier.name} requires {counts} type arguments");
200
member.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), member.location);
201
return;
202
fi
203
fi
204
205
if !symbol? then
206
let hint = try_describe_intersection_miss(named_type, member.identifier.name);
207
208
if hint? then
209
_logger.error(member.identifier.location, hint);
210
else
211
_logger.error(member.identifier.location, "member {member.identifier.name} not found in {named_type}");
212
fi
213
214
member.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), member.location);
215
return;
216
fi
217
218
_symbol_use_locations.add_symbol_use(member.identifier.right_location, symbol);
219
220
if symbol.is_instance /\ !left_value.is_consumable then
221
_logger.error(member.identifier.location, "cannot access instance member {member.identifier.name} in {named_type}");
222
fi
223
224
_symbol_loader.find_symbol =
225
(name: string) is
226
let result = named_type.find_member(name);
227
228
return result;
229
si;
230
231
let value: Value? mut = _;
232
233
if need_store then
234
if member.is_coalesce then
235
_logger.error(member.location, "cannot assign through coalescing member access");
236
fi
237
238
// A struct-typed receiver with no in-place address is a
239
// temporary copy - a property get, method result or
240
// indexer element - so a store through it would be
241
// silently lost. Field chains, locals, self and refs
242
// all carry an address and store in place.
243
if
244
symbol.is_instance /\
245
type.is_value_type /\
246
!left_value.has_address
247
then
248
_logger.error(member.location, "cannot assign through a copy of a struct value here");
249
250
member.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), member.location);
251
return;
252
fi
253
254
let store_value = cast Need.STORE?(member.value)!.value;
255
256
if check_store_access(member.location, left_value, symbol, named_type) then
257
value = symbol.store(member.location, left_value, store_value, _symbol_loader, false);
258
fi
259
260
// The written receiver may alias any receiver a
261
// fact was proven on, so the stored member's own
262
// facts and every path reading through it die —
263
// keyed on the member symbol, not the receiver.
264
_flow.on_member_store(symbol);
265
else
266
let variant = cast Semantic.Symbols.VARIANT?(symbol);
267
268
if variant? /\ variant.is_unit_variant then
269
if member.is_coalesce then
270
_logger.error(member.location, "coalescing member access does not apply to a unit-variant constructor");
271
fi
272
273
let lowered = _unit_variant_constructor.try_load(member.location, variant, member.expected_type, member);
274
275
if lowered? then
276
_symbol_use_locations.add_symbol_use(member.identifier.right_location, lowered.constructor);
277
member.compile_expressions_state.value = lowered.value;
278
return;
279
fi
280
fi
281
282
if check_load_access(member.location, left_value, symbol, named_type) then
283
if member.is_coalesce then
284
value = _build_coalesce_load(member, symbol);
285
286
if value? then
287
member.compile_expressions_state.value = value;
288
return;
289
fi
290
291
// Fallthrough on a build failure (e.g.
292
// unsupported receiver shape): proceed with
293
// a plain access so the user gets at most
294
// one diagnostic instead of a cascade.
295
value = symbol.load(member.location, left_value, _symbol_loader);
296
else
297
value =
298
_function_reference_resolver.try_load(member.location, symbol, left_value, member.expected_type, member.is_call_target) ??
299
symbol.load(member.location, left_value, _symbol_loader);
300
fi
301
fi
302
fi
303
304
member.compile_expressions_state.value = value;
305
306
// Path narrowing: when the flow analysis has recorded
307
// facts for this member-access path, surface the
308
// access at the narrower view type so
309
// `if isa Cat(x.y) then x.y.meow() fi` and
310
// `if x.y? then x.y.z fi` both type-check. Mirrors the
311
// local-variable path in visit_identifier. A reference
312
// optional shares IL with its non-optional form (a type
313
// view); NULLABLE[T] / MAYBE[T] get a `.value`
314
// projection.
315
if !need_store /\ member.value? then
316
let path = _visitor.try_build_access_path(member);
317
318
if path? then
319
// Step 1: type narrow (isa T on x.y). Wraps the
320
// loaded value at the recorded static subtype
321
// when the composition is sound.
322
if let recorded = _flow.narrowed_type_of_path(path) then
323
let loaded = member.value!;
324
325
if loaded.type? then
326
if let composed = _flow.compose_path_narrow(loaded.type, recorded) then
327
member.compile_expressions_state.value = IR.Values.NARROW_VIEW(loaded, composed);
328
_symbol_use_locations.replace_with_variable_use(member.identifier.right_location, symbol, member.value!);
329
fi
330
fi
331
fi
332
333
// Step 2: presence narrow (if x.y? then x.y).
334
// Reads member.value again in case step 1
335
// already wrapped it.
336
let loaded = member.value!;
337
338
if loaded.type? /\ loaded.type!.is_optional /\ _flow.is_non_null_path(path) then
339
let loaded_type = loaded.type!;
340
let narrowed = loaded_type.as_non_optional();
341
342
if !narrowed.is_optional then
343
member.compile_expressions_state.value = IR.Values.NARROW_VIEW(loaded, narrowed);
344
// Overwrite the earlier symbol-use entry so
345
// HOVER on the member surfaces the narrowed
346
// type — mirroring what visit_identifier does
347
// for a narrowed local. Same location; put
348
// wins for the last write.
349
_symbol_use_locations.replace_with_variable_use(member.identifier.right_location, symbol, member.value!);
350
elif loaded_type.is_value_type then
351
let value_member = loaded_type.find_member("value");
352
353
if value_member? then
354
let projected = value_member.load(member.location, loaded, _symbol_loader);
355
356
member.compile_expressions_state.value = IR.Values.NARROW_PROJECT(loaded, projected);
357
_symbol_use_locations.replace_with_variable_use(member.identifier.right_location, symbol, member.value!);
358
fi
359
fi
360
fi
361
fi
362
fi
363
fi
364
si
365
366
// True iff any receiver in `expr`'s member-access chain has
367
// been wrapped in a NARROW_VIEW by path narrowing. Used to
368
// recognise loads whose result type reflects a narrowed
369
// receiver — the declared receiver type is still optional at
370
// this position, so an `!` against the narrowed type is
371
// redundant rather than an error.
372
_has_narrowed_receiver(expr: Trees.Expressions.Expression) -> bool is
373
let cursor mut = expr;
374
375
while isa Trees.Expressions.MEMBER(cursor) do
376
let member = cast Trees.Expressions.MEMBER(cursor);
377
378
if member.left.value? /\ isa IR.Values.NARROW_VIEW(member.left.value) then
379
return true;
380
fi
381
382
cursor = member.left;
383
od
384
385
return false;
386
si
387
388
check_load_access(location: LOCATION, from: Value?, symbol: Semantic.Symbols.Symbol, type: Type) -> bool is
389
if !symbol.is_accessible_to(_symbol_table.current_accessor) then
390
_logger.error(location, "{symbol} is not accessible here");
391
// The member is assembly-reachable, so let the access through to
392
// avoid a cascade of downstream errors: the diagnostic stands but
393
// the generated IL is still valid.
394
return true;
395
fi
396
397
if symbol.is_public_readable then
398
return true;
399
fi
400
401
if from? /\ from.is_self then
402
return true;
403
fi
404
405
let instance_context = _symbol_table.current_instance_context;
406
let instance_context_type = if instance_context? then instance_context.type else null fi;
407
408
if instance_context_type? /\ instance_context_type.is_assignable_from(type) then
409
return true;
410
fi
411
412
_logger.error(location, "{symbol} is not publicly readable");
413
414
return false;
415
si
416
417
check_store_access(location: LOCATION, from: Value?, symbol: Semantic.Symbols.Symbol, type: Type) -> bool is
418
if !symbol.is_accessible_to(_symbol_table.current_accessor) then
419
_logger.error(location, "{symbol} is not accessible here");
420
// Assembly-reachable: allow the store so the diagnostic does not
421
// cascade into follow-on errors.
422
return true;
423
fi
424
425
if !symbol.is_private /\ !symbol.is_field then
426
return true;
427
fi
428
429
if from? /\ from.is_self then
430
return true;
431
fi
432
433
let instance_context = _symbol_table.current_instance_context;
434
let instance_context_type = if instance_context? then instance_context.type else null fi;
435
436
if instance_context_type? /\ instance_context_type.is_assignable_from(type) then
437
return true;
438
fi
439
440
_logger.error(location, "{symbol} is not publicly assignable");
441
442
return false;
443
si
444
445
visit_identifier(identifier: Trees.Expressions.IDENTIFIER) is
446
let need_store = identifier.value? /\ isa Need.STORE(identifier.value);
447
448
let symbol mut = _visitor.find(identifier.identifier);
449
450
if let shadowed = symbol, callable = _try_find_shadowed_callable(identifier, shadowed) then
451
symbol = callable;
452
fi
453
454
let group = cast Semantic.Symbols.TYPE_GROUP?(symbol);
455
456
if group? then
457
let bare = group.find_by_generic_arguments_count(0);
458
459
// A constructor call on the bare name resolves to the group's
460
// generic member when there is no constructible arity-0
461
// member — the reflected shape where a static factory class
462
// (abstract, so not constructible) shares a name with a
463
// generic type (`KeyValuePair` + `KeyValuePair[K,V]`). When
464
// the arity-0 member *is* constructible (a plain non-generic
465
// class overloaded with a generic one, `FOO` + `FOO[T]`), or
466
// this is a plain value-position reference, collapse to it.
467
let for_call =
468
if identifier.is_call_target /\ (!bare? \/ bare.is_abstract) then
469
group.sole_generic_member()
470
else
471
null
472
fi;
473
474
if for_call? then
475
symbol = for_call;
476
elif bare? then
477
symbol = bare;
478
else
479
let counts = group.generic_arguments_counts |> map(a -> string => "{a}") |> join(" or ");
480
_logger.error(identifier.location, "type {identifier.identifier} requires {counts} type arguments");
481
identifier.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), identifier.location);
482
return;
483
fi
484
fi
485
486
if symbol? then
487
if symbol.is_type then
488
_symbol_use_locations.add_symbol_use(identifier.right_location, symbol);
489
490
let variant = cast Semantic.Symbols.VARIANT?(symbol);
491
492
if variant? /\ variant.is_unit_variant then
493
let lowered = _unit_variant_constructor.try_load(identifier.location, variant, identifier.expected_type, identifier);
494
495
if lowered? then
496
// Anchor HOVER and find-references to the
497
// specialised init so the rendered owner
498
// carries the resolved type arguments —
499
// matching the parenthesised path's
500
// bookkeeping in resolve_constructor.
501
_symbol_use_locations.add_symbol_use(identifier.right_location, lowered.constructor);
502
identifier.compile_expressions_state.value = lowered.value;
503
return;
504
fi
505
fi
506
507
let symbol_type = symbol.type;
508
509
if symbol_type? then
510
identifier.compile_expressions_state.value = TYPE_EXPRESSION(symbol_type, identifier.location);
511
else
512
identifier.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), identifier.location);
513
fi
514
else
515
// Bare-identifier (implicit-self) access to a field, including
516
// an inherited underscore field, is subject to the same policy
517
// as explicit member access. Log but allow: the field is
518
// assembly-reachable, so blocking would only cascade.
519
if symbol.is_field /\ !symbol.is_accessible_to(_symbol_table.current_accessor) then
520
_logger.error(identifier.location, "{symbol} is not accessible here");
521
fi
522
523
_symbol_loader.find_symbol = (name: string) => _visitor.find(name);
524
525
if need_store then
526
let store_value = cast Need.STORE?(identifier.value)!.value;
527
identifier.compile_expressions_state.value = symbol.store(identifier.location, null, store_value, _symbol_loader, false);
528
529
// Optional-narrowing, write side: a
530
// non-optional right-hand side leaves a
531
// declared-optional target known to hold a
532
// value, so HOVER on the target describes
533
// the state the assignment leaves behind —
534
// agreeing with what a subsequent read
535
// reports rather than with the declared
536
// type. Gated on the same target shape the
537
// flow analysis narrows, so hover never
538
// claims a narrow that isn't applied.
539
let narrowed: Semantic.Types.Type? mut = null;
540
541
let target = _visitor.try_get_narrowing_target(identifier);
542
543
if target? /\ _visitor.is_non_optional_value(store_value) then
544
let declared = _flow.declared_type_of(target);
545
546
if declared? /\ declared.is_optional then
547
let inner = declared.optional_inner_type;
548
549
if inner? /\ !inner.is_optional then
550
narrowed = inner;
551
fi
552
fi
553
fi
554
555
if narrowed? then
556
_symbol_use_locations.add_variable_use(identifier.right_location, symbol, narrowed);
557
else
558
_symbol_use_locations.add_symbol_use(identifier.right_location, symbol);
559
fi
560
else
561
identifier.compile_expressions_state.value =
562
_function_reference_resolver.try_load(identifier.location, symbol, null, identifier.expected_type, identifier.is_call_target) ??
563
symbol.load(identifier.location, null, _symbol_loader);
564
565
// Optional-narrowing: when flow analysis has
566
// established this variable is non-null here,
567
// surface the use as the non-optional type.
568
// Reference `T?` is a NAMED-with-flag — IL
569
// representation matches `T`, so the load gets
570
// a type-view snapshot (mirrors how `isa`-
571
// narrowing reaches the load via a mutated
572
// `symbol.type`). The value-type lowerings
573
// (`NULLABLE[T]` / `MAYBE[T]`) are wrapper
574
// structs, so the load gets wrapped in
575
// `NARROW_PROJECT` — emits `.value` at gen
576
// time, but `visit_has_value` and
577
// `visit_unwrap` can peel back to the
578
// underlying wrapper for their own IR.
579
let loaded = identifier.value;
580
581
// the load's snapshot type is null for an inference placeholder
582
@suppress("presence-test-non-optional")
583
if
584
isa Semantic.Symbols.Variable(symbol) /\
585
isa IR.Values.Load.SYMBOL(loaded) /\
586
loaded.type? /\
587
loaded.type.is_optional
588
then
589
let variable = symbol;
590
591
if _flow.is_non_null(variable) then
592
let loaded_type = loaded.type;
593
let narrowed = loaded_type.as_non_optional();
594
595
if !narrowed.is_optional then
596
(cast IR.Values.Load.SYMBOL(loaded)).narrow_snapshot_type(narrowed);
597
elif loaded_type.is_value_type then
598
let value_member = loaded_type.find_member("value");
599
600
if value_member? then
601
let projected = value_member.load(identifier.location, loaded, _symbol_loader);
602
603
identifier.compile_expressions_state.value = IR.Values.NARROW_PROJECT(loaded, projected);
604
fi
605
fi
606
fi
607
fi
608
609
// HOVER reads a narrowed local's type off
610
// this use-site node, not the symbol — the
611
// symbol's `type` is restored after the walk.
612
_symbol_use_locations.add_variable_use(identifier.right_location, symbol, identifier.value!);
613
614
// Definite assignment: reading a tracked
615
// deferred-init local before it is assigned
616
// on every path reaching here. An address-of
617
// operand of `ref` is not a value read — its
618
// read-or-write is decided by the resolved call.
619
if
620
!_build_flags.no_warn_definite_assignment /\
621
isa Semantic.Symbols.Variable(symbol) /\
622
!_visitor.is_reference_operand_target(symbol)
623
then
624
let variable = symbol;
625
626
if _flow.is_tracked(variable) /\ !_flow.is_assigned(variable) then
627
_logger.warn(identifier.location, "definite-assignment", "{variable.name} may be used before it is assigned");
628
fi
629
fi
630
fi
631
fi
632
633
else
634
identifier.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), identifier.location);
635
fi
636
si
637
638
// A bare name in call position that resolved to something that
639
// cannot be called - a local, field or property that holds no
640
// function - looks past it for a callable of the same name
641
// further out, so a value does not hide a function it shares a
642
// name with. Returns null to leave resolution as it was, which
643
// is every case but the shadowed one.
644
//
645
// Deliberately narrow: only a symbol that is not callable at all
646
// qualifies. A function group whose overloads do not accept the
647
// supplied arguments is left alone, so a genuine argument
648
// mismatch still reports as one rather than silently calling
649
// something else.
650
_try_find_shadowed_callable(
651
identifier: Trees.Expressions.IDENTIFIER,
652
symbol: Semantic.Symbols.Symbol
653
) -> Semantic.Symbols.Symbol? is
654
if
655
!identifier.is_call_target \/
656
identifier.identifier.qualifier? \/
657
Semantic.SYMBOL_TABLE.is_callable_symbol(symbol)
658
then
659
return null;
660
fi
661
662
// A local read before its own initializer has completed
663
// holds no value to call, whatever type it ends up with -
664
// the case a `let` whose initializer calls the name it is
665
// declaring runs into. Its type is still an inference
666
// placeholder at this point, so it is settled here, ahead
667
// of the sentinel test below.
668
//
669
// Restricted to a reference in the same capture context as
670
// the declaration: inside a nested literal the same shape
671
// is a lambda referring to itself, where reaching a
672
// like-named function further out would be a surprise, so
673
// that keeps its existing error.
674
let is_uninitialized_local mut = false;
675
676
if let variable: Semantic.Symbols.Variable = symbol /\ !variable.is_defined then
677
if _symbol_table.current_capture_context != cast Semantic.Symbols.Symbol?(variable.owner) then
678
return null;
679
fi
680
681
is_uninitialized_local = true;
682
fi
683
684
// Otherwise a sentinel type says the slot's type is not
685
// known - inference has not settled it, or it failed and
686
// was poisoned. Neither is evidence that the symbol holds
687
// no function, and looking past a symbol whose declaration
688
// already reported an error would add a second diagnostic
689
// about the first one's consequence.
690
if
691
let typed: Semantic.Types.Typed = symbol /\
692
!is_uninitialized_local /\
693
(typed.type?.is_sentinel ?? false)
694
then
695
return null;
696
fi
697
698
let callable = _symbol_table.find_enclosing_callable(identifier.identifier.name);
699
700
if !callable? \/ callable == symbol then
701
return null;
702
fi
703
704
_logger.warn(
705
identifier.location,
706
"shadowed-non-callable",
707
"{identifier.identifier.name} is not callable here, calling the one from an enclosing scope instead"
708
);
709
710
return callable;
711
si
712
713
visit_has_value(has_value: Trees.Expressions.HAS_VALUE) is
714
715
if !has_value.left.value? then
716
_logger.poison(has_value.left.location, "has value expression has no value");
717
has_value.compile_expressions_state.value = DUMMY(_innate_symbol_lookup.get_bool_type(), has_value.location);
718
return;
719
fi
720
721
if
722
!has_value.left.value.check_is_consumable(_logger, has_value.left.location)
723
then
724
_logger.error(has_value.left.location, "cannot use this here");
725
has_value.compile_expressions_state.value = DUMMY(_innate_symbol_lookup.get_bool_type(), has_value.location);
726
return;
727
fi
728
729
// Peel a flow-narrowing projection back to its wrapper so
730
// `has_value` lookup hits the wrapper's bool field.
731
has_value.left.compile_expressions_state.value = IR.Values.NARROW_PROJECT.peel(has_value.left.value!);
732
733
let left_value = has_value.left.value!;
734
let bool_type = _innate_symbol_lookup.get_bool_type();
735
736
// Union `u?` → `isa Default(u)`. Both source-defined
737
// and cross-asm unions carry the default variant on the
738
// symbol; cross-asm unions get it via the
739
// `DEFAULT_VARIANT_ATTRIBUTE` marker read in
740
// `SYMBOL_FACTORY.materialize_variant`. A union without
741
// a default variant short-circuits to a bare null check
742
// — `value`/`has_value` are no longer the convention
743
// for unions.
744
if !left_value.type!.is_value_type then
745
let classy = _condition_analyzer.get_classy_for_narrowing(left_value.type);
746
747
if classy? /\ (classy.is_union \/ classy.is_variant) then
748
let default_variant = _get_default_variant_for_operand(classy);
749
750
if default_variant? then
751
has_value.compile_expressions_state.value =
752
_build_default_variant_isa(
753
has_value.location,
754
left_value,
755
default_variant,
756
bool_type
757
);
758
else
759
has_value.compile_expressions_state.value = HAS_VALUE(left_value, bool_type);
760
fi
761
762
return;
763
fi
764
fi
765
766
let has_value_member =
767
left_value.type!.find_member("has_value");
768
let has_value_valid mut = false;
769
770
if has_value_member? then
771
if !isa Semantic.Symbols.Property(has_value_member) then
772
_logger.error(
773
has_value.left.location,
774
"has_value member must be a bool property"
775
);
776
elif !has_value_member.type!.matches(bool_type) then
777
_logger.error(
778
has_value.left.location,
779
"has_value property must be bool"
780
);
781
else
782
has_value_valid = true;
783
fi
784
fi
785
786
let target = _visitor.try_get_narrowing_target(has_value.left);
787
788
if left_value.type!.is_optional then
789
// the test that itself proves presence is not
790
// redundant; one on an operand already proven to
791
// hold a value is
792
if target? /\ _flow.is_non_null(target) then
793
_logger.warn(
794
has_value.location,
795
"redundant-presence-test",
796
"'?' is redundant here"
797
);
798
fi
799
elif !has_value_member? then
800
// no live has_value layer to consult: a non-optional
801
// operand narrowed from optional gets the flow-specific
802
// "redundant here"; a never-optional value type is an
803
// error; a never-optional reference is redundant by its
804
// static type and gets the plain "redundant" - the type
805
// guarantees presence everywhere, not just here.
806
let declared = if target? then _flow.declared_type_of(target) else null fi;
807
808
if declared? /\ declared.is_optional then
809
_logger.warn(
810
has_value.location,
811
"redundant-presence-test",
812
"'?' is redundant here"
813
);
814
elif left_value.type!.is_value_type then
815
_logger.error(has_value.left.location, "not optional");
816
has_value.compile_expressions_state.value = DUMMY(bool_type, has_value.location);
817
return;
818
elif assert_condition_depth == 0 then
819
_logger.warn(
820
has_value.location,
821
"presence-test-non-optional",
822
"'?' is redundant"
823
);
824
fi
825
fi
826
827
if !left_value.type!.is_value_type then
828
has_value.compile_expressions_state.value =
829
get_reference_has_value(
830
has_value.location,
831
left_value,
832
if has_value_valid then has_value_member else null fi,
833
bool_type
834
);
835
elif has_value_member? then
836
if has_value_valid then
837
has_value.compile_expressions_state.value =
838
has_value_member.load(
839
has_value.location,
840
left_value,
841
_symbol_loader
842
);
843
else
844
has_value.compile_expressions_state.value = DUMMY(bool_type, has_value.location);
845
fi
846
fi
847
si
848
849
visit_unwrap(unwrap: Trees.Expressions.UNWRAP) is
850
let need_store = unwrap.value? /\ isa Need.STORE(unwrap.value);
851
852
// Peel a flow-narrowing projection back to its wrapper so
853
// `!` runs against the optional shape it was designed for
854
// (avoids "no value member" on the already-projected `T`).
855
unwrap.left.compile_expressions_state.value = IR.Values.NARROW_PROJECT.peel(unwrap.left.value);
856
857
let unwrap_left_value = unwrap.left.value;
858
859
if !unwrap_left_value? \/ !unwrap_left_value.type? then
860
_logger.poison(unwrap.left.location, "unwrap expression has no value");
861
unwrap.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), unwrap.location);
862
return;
863
fi
864
865
if
866
!unwrap_left_value.check_is_consumable(_logger, unwrap.left.location)
867
then
868
_logger.error(unwrap.left.location, "cannot use this here");
869
unwrap.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), unwrap.location);
870
return;
871
fi
872
873
if unwrap_left_value.type!.is_ref then
874
// is_ref implies element type per the runtime invariant
875
let element_type = unwrap_left_value.type!.get_element_type()!;
876
877
if need_store then
878
let store_value = cast Need.STORE?(unwrap.value)!.value;
879
unwrap.compile_expressions_state.value = IR.Values.Store.INDIRECT(unwrap_left_value, store_value, element_type);
880
else
881
unwrap.compile_expressions_state.value = DEREF(unwrap_left_value, element_type);
882
fi
883
884
return;
885
fi
886
887
let value_member = unwrap_left_value.type!.find_member("value");
888
889
if !unwrap_left_value.type!.is_value_type then
890
let classy = _condition_analyzer.get_classy_for_narrowing(unwrap_left_value.type);
891
let default_variant = _get_default_variant_for_operand(classy);
892
893
if classy? /\ default_variant? then
894
// Union `u!` → cast to the default variant (no
895
// IL cast when the operand is already narrowed
896
// to that variant), with single-field projection
897
// so the canonical "unwrap to the sole field"
898
// rule is preserved.
899
unwrap.compile_expressions_state.value =
900
_build_default_variant_unwrap(
901
unwrap.location,
902
unwrap_left_value,
903
classy,
904
default_variant
905
);
906
else
907
// a plain reference type: `!` only asserts non-null.
908
// The result type drops the `?` so an inferred `let`
909
// binding or a non-optional slot downstream sees a
910
// present value rather than a may-be-null one.
911
// TODO we could do an explicit null check here:
912
let left_value = unwrap_left_value;
913
914
if left_value.type!.is_optional then
915
unwrap.compile_expressions_state.value = TYPE_WRAPPER(left_value.type!.as_non_optional(), left_value);
916
elif value_member? /\ left_value.type!.find_member("has_value")? then
917
// an option-shaped reference type: flow
918
// analysis does not track its has_value
919
// layer, so `!` re-asserts it and is never
920
// redundant.
921
unwrap.compile_expressions_state.value = left_value;
922
else
923
let target = _visitor.try_get_narrowing_target(unwrap.left);
924
let declared = if target? then _flow.declared_type_of(target) else null fi;
925
926
// A NARROW_VIEW is a member-access path read
927
// at its non-optional type because a path
928
// fact holds here — the declared member is
929
// optional, so the unwrap is redundant, not
930
// an error. A load whose receiver chain
931
// contains a NARROW_VIEW is the same case one
932
// hop deeper: the load resolved a member on a
933
// narrowed receiver, so its result type
934
// reflects the narrow rather than the
935
// declared receiver type.
936
if declared? /\ declared.is_optional \/ isa IR.Values.NARROW_VIEW(left_value) \/ _has_narrowed_receiver(unwrap.left) then
937
// non-optional only because flow analysis
938
// has narrowed it at this point
939
if !_build_flags.no_warn_redundant_unwrap then
940
_logger.warn(
941
unwrap.location,
942
"redundant-unwrap",
943
"'!' is redundant here"
944
);
945
fi
946
947
unwrap.compile_expressions_state.value = left_value;
948
else
949
_logger.error(unwrap.left.location, "cannot unwrap this");
950
unwrap.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), unwrap.location);
951
fi
952
fi
953
fi
954
elif value_member? then
955
// TODO we could do an explicit has-value check here:
956
if
957
!_build_flags.no_warn_redundant_unwrap /\
958
unwrap_left_value.type!.is_optional
959
then
960
let unwrap_target = _visitor.try_get_narrowing_target(unwrap.left);
961
962
if unwrap_target? /\ _flow.is_non_null(unwrap_target) then
963
_logger.warn(
964
unwrap.location,
965
"redundant-unwrap",
966
"'!' is redundant here"
967
);
968
fi
969
fi
970
971
unwrap.compile_expressions_state.value = value_member.load(unwrap.location, unwrap_left_value, _symbol_loader);
972
else
973
_logger.error(unwrap.left.location, "cannot unwrap this");
974
unwrap.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), unwrap.location);
975
fi
976
977
// `x!` asserts `x` holds a value — record it, so a
978
// subsequent dereference of `x` is not flagged. Skipped
979
// when the target's current type is already non-optional
980
// (the redundant-unwrap warning fires separately at that
981
// site; recording the presence bit only grows every
982
// downstream env for nothing).
983
let target = _visitor.try_get_narrowing_target(unwrap.left);
984
let target_type = if target? then target.type else null fi;
985
986
if target? /\ target_type? /\ target_type.is_optional then
987
_flow.mark_non_null(target);
988
_flow.report_narrowing_site(
989
unwrap.location,
990
"narrowing-unwrap",
991
"►",
992
INLAY_TYPE.render(target_type.as_non_optional())
993
);
994
fi
995
996
// TODO, we could handle ref and ptr here as well
997
si
998
999
visit_reference(reference: Trees.Expressions.REFERENCE) is
1000
let left_value = reference.left.value;
1001
1002
if !left_value? \/ !left_value.type? then
1003
_logger.poison(reference.left.location, "reference expression has no value");
1004
reference.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), reference.location);
1005
return;
1006
elif !left_value.has_address then
1007
_logger.warn(reference.left.location, "reference-to-expression", "reference to an expression");
1008
fi
1009
1010
left_value.check_is_consumable(_logger, reference.left.location);
1011
1012
reference.compile_expressions_state.value = ADDRESS(left_value, _innate_symbol_lookup.get_reference_type(left_value.type!));
1013
si
1014
1015
get_reference_has_value(
1016
location: LOCATION,
1017
value: Value,
1018
member: Semantic.Symbols.Symbol?,
1019
bool_type: Type
1020
) -> Value is
1021
let null_check = HAS_VALUE(value, bool_type);
1022
1023
if member? then
1024
let member_check = member.load(location, value, _symbol_loader);
1025
1026
let and_identifier = Trees.Identifiers.Identifier(location, "/\\");
1027
let and_symbol = _visitor.find(and_identifier);
1028
1029
if and_symbol? /\ isa Semantic.Symbols.FUNCTION_GROUP(and_symbol) then
1030
_symbol_loader.find_symbol = (name: string) => _visitor.find(name);
1031
1032
let argument_types = Collections.LIST[Type]();
1033
argument_types.add(bool_type);
1034
argument_types.add(bool_type);
1035
1036
let overload_result =
1037
_overload_resolver.resolve(
1038
location,
1039
and_symbol,
1040
argument_types,
1041
false,
1042
false,
1043
false
1044
);
1045
1046
if overload_result? then
1047
let function = overload_result.function;
1048
if function.is_unsafe_constraints then
1049
_logger.warn(location, "unchecked-constraints", "call to {function} has unchecked constraints");
1050
fi
1051
1052
let arguments = Collections.LIST[Value]();
1053
arguments.add(null_check);
1054
arguments.add(member_check);
1055
1056
return function.call(
1057
location,
1058
null,
1059
arguments,
1060
bool_type,
1061
_function_caller
1062
);
1063
fi
1064
fi
1065
fi
1066
1067
return null_check;
1068
si
1069
1070
// For a `?` / `!` operand, identify the default variant to
1071
// dispatch against. Returns the union's `default_variant`
1072
// when the operand is the union (null when the union has
1073
// no default — a source union without one, or an older
1074
// cross-asm union compiled before the
1075
// `DEFAULT_VARIANT_ATTRIBUTE` marker existed); returns the
1076
// variant itself when the operand is already narrowed to
1077
// its parent union's default variant so the caller can
1078
// skip an extra cast. Null for any other shape — the
1079
// caller's branch then short-circuits `?` to a bare null
1080
// check and `!` to a non-null assert on the operand.
1081
_get_default_variant_for_operand(
1082
classy: Semantic.Symbols.Classy?
1083
) -> Semantic.Symbols.VARIANT? is
1084
if !classy? then
1085
return null;
1086
fi
1087
1088
if classy.is_union then
1089
let union_classy = cast Semantic.Symbols.UNION?(classy)!;
1090
return union_classy.default_variant;
1091
fi
1092
1093
if classy.is_variant then
1094
let variant_classy = cast Semantic.Symbols.VARIANT?(classy)!;
1095
1096
if !variant_classy.owner? \/ !isa Semantic.Symbols.UNION(variant_classy.owner) then
1097
return null;
1098
fi
1099
1100
let owner_union = cast Semantic.Symbols.UNION(variant_classy.owner);
1101
1102
if owner_union.default_variant == variant_classy then
1103
return variant_classy;
1104
fi
1105
fi
1106
1107
return null;
1108
si
1109
1110
// `u?` lowers to `isa Default(u)`. The receiver's type
1111
// (often a generic instantiation of the union) drives
1112
// specialisation of the variant target type so the emitted
1113
// `isinst` references a loadable closed-generic class.
1114
_build_default_variant_isa(
1115
location: LOCATION,
1116
operand: Value,
1117
default_variant: Semantic.Symbols.VARIANT,
1118
bool_type: Type
1119
) -> Value is
1120
let variant_type =
1121
_condition_analyzer.try_get_variant_type_for_classy(
1122
operand.type!,
1123
default_variant
1124
);
1125
1126
if !variant_type? then
1127
return DUMMY(bool_type, location);
1128
fi
1129
1130
return IR.Values.ISA(bool_type, variant_type, operand);
1131
si
1132
1133
// `u!` lowers to `cast Default(u)`, with single-field
1134
// projection so the existing rule "default variant with one
1135
// field unwraps to that field" rides on the new lowering.
1136
// When the operand is already narrowed to the default
1137
// variant the cast is elided — the CLR accepts loading the
1138
// field directly off the variant-shaped receiver.
1139
_build_default_variant_unwrap(
1140
location: LOCATION,
1141
operand: Value,
1142
classy: Semantic.Symbols.Classy,
1143
default_variant: Semantic.Symbols.VARIANT
1144
) -> Value is
1145
let variant_type =
1146
_condition_analyzer.try_get_variant_type_for_classy(
1147
operand.type!,
1148
default_variant
1149
);
1150
1151
if !variant_type? then
1152
return DUMMY(Semantic.Types.ERROR(), location);
1153
fi
1154
1155
let variant_value: Value mut = operand;
1156
1157
if !classy.is_variant then
1158
variant_value = IR.Values.CAST(variant_type, operand);
1159
fi
1160
1161
// Single-field unwrap rides on the cast: a default
1162
// variant declaring exactly one field — own or inherited
1163
// from the union's primary header — projects to that
1164
// field. Multi-field defaults return the variant itself
1165
// so callers can read every field via member access.
1166
if default_variant.field_count == 1 then
1167
let field_name = default_variant.get_destructure_member_name(0);
1168
1169
if field_name? then
1170
let field_member = variant_type.find_member(field_name);
1171
1172
if field_member? then
1173
return field_member.load(location, variant_value, _symbol_loader);
1174
fi
1175
fi
1176
fi
1177
1178
return variant_value;
1179
si
1180
1181
// Build a hinted "narrow further" message when a member
1182
// access on a ONE_OF fails to find the member on the root
1183
// but every in-set subtype has a same-named member. Returns
1184
// null when the hint doesn't apply (receiver isn't a ONE_OF,
1185
// or the name is missing on at least one in-set subtype).
1186
// Subtype-private fields can't be accessed through ONE_OF
1187
// without IL-side subtype-dispatching support — for now we
1188
// just direct the user to narrow further. Applies uniformly
1189
// to a union's variants and a closed class's subclasses.
1190
try_describe_intersection_miss(
1191
receiver_type: Type,
1192
member_name: string
1193
) -> string? is
1194
if !isa Semantic.Types.ONE_OF(receiver_type) then
1195
return null;
1196
fi
1197
1198
let one_of = receiver_type;
1199
1200
let any_subtype mut = false;
1201
for subtype in one_of.subtypes do
1202
if !subtype.find_member(member_name)? then
1203
return null;
1204
fi
1205
any_subtype = true;
1206
od
1207
1208
if !any_subtype then
1209
return null;
1210
fi
1211
1212
let names = System.Text.StringBuilder();
1213
let seen_any mut = false;
1214
for subtype in one_of.subtypes do
1215
if seen_any then
1216
names.append(" | ");
1217
fi
1218
names.append(subtype.name);
1219
seen_any = true;
1220
od
1221
1222
return "member {member_name} is ambiguous in {names}";
1223
si
1224
1225
// Build the IR value for an `a?.b` access. Returns the
1226
// COALESCE_LOAD wrap when the member is a readable field or
1227
// property; returns null (so the caller falls back to a plain
1228
// access) on shapes that aren't lowered here. Emits a
1229
// diagnostic for each unsupported shape so the user learns
1230
// why the `?.` had no effect.
1231
_build_coalesce_load(
1232
member: Trees.Expressions.MEMBER,
1233
symbol: Semantic.Symbols.Symbol
1234
) -> Value? is
1235
// A method selection is coalesced at the enclosing call -
1236
// the whole call, argument evaluation included, must
1237
// short-circuit on an absent receiver - so a function
1238
// group falls through silently to the plain load the call
1239
// path consumes. An uncalled method reference through
1240
// `?.` then fails downstream exactly as it does through
1241
// `.`.
1242
if isa Semantic.Symbols.FUNCTION_GROUP(symbol) then
1243
return null;
1244
fi
1245
1246
if
1247
!isa Semantic.Symbols.Field(symbol) /\
1248
!isa Semantic.Symbols.Property(symbol)
1249
then
1250
_logger.error(
1251
member.location,
1252
"coalescing member access supports fields, properties and method calls only"
1253
);
1254
return null;
1255
fi
1256
1257
return build_coalesce_wrap(
1258
member,
1259
symbol.is_instance,
1260
from => symbol.load(member.location, from, _symbol_loader)
1261
);
1262
si
1263
1264
// Shared lowering for `a?.b` accesses and `a?.m(...)` calls:
1265
// receiver presence diagnostics, unwrap of the optional
1266
// receiver, optional widening of the result and the
1267
// COALESCE_LOAD composition. `build_member_value` produces
1268
// the member load or call against the supplied receiver
1269
// stand-in: the original receiver value when it is statically
1270
// present, or a STACK_TOP of the unwrapped receiver type
1271
// inside the short-circuit arm. `receiver_consumed` is false
1272
// when the member is static - the present arm then pops the
1273
// tested receiver instead of feeding it to the member value.
1274
// Returns null, with any diagnostic already emitted, when the
1275
// shape can't be lowered.
1276
build_coalesce_wrap(
1277
member: Trees.Expressions.MEMBER,
1278
receiver_consumed: bool,
1279
build_member_value: (Value) -> Value?
1280
) -> Value? is
1281
let receiver = member.left.value;
1282
1283
if !receiver? \/ !receiver.type? then
1284
return null;
1285
fi
1286
1287
let receiver_type = receiver.type!;
1288
1289
// A non-optional receiver (declared so, or flow-narrowed
1290
// from `T?` to `T`) is statically present, so the null
1291
// test can never fail. One flow-narrowed from optional
1292
// gets a redundancy warning; a never-optional value type
1293
// is an error (a struct is never null, so the test has
1294
// no defensive value); a never-optional reference stays
1295
// legal with no diagnostic, as the null-defense idiom at
1296
// unsound boundaries (reflected APIs, staged tree
1297
// construction) where null can arrive despite the static
1298
// type. The access is emitted directly — the conditional
1299
// path is unsound for a value-type receiver (`dup;
1300
// brfalse` rejects a struct on the stack) and unnecessary
1301
// work for a reference receiver. The result is still
1302
// widened to the optional shape the user asked for with
1303
// `?.`.
1304
if !receiver_type.is_optional then
1305
let target = _visitor.try_get_narrowing_target(member.left);
1306
let declared = if target? then _flow.declared_type_of(target) else null fi;
1307
1308
if declared? /\ declared.is_optional then
1309
_logger.warn(
1310
member.location,
1311
"redundant-coalesce",
1312
"'?.' is redundant here"
1313
);
1314
elif receiver_type.is_value_type then
1315
_logger.error(member.left.location, "receiver is not optional");
1316
return null;
1317
fi
1318
1319
let direct = build_member_value(receiver);
1320
1321
if !direct? then
1322
return null;
1323
fi
1324
1325
return widen_coalesce_result(direct);
1326
fi
1327
1328
let receiver_is_value_optional = receiver_type.is_value_type;
1329
1330
// The member symbol lives on the receiver's underlying
1331
// type regardless of the `?` flag, so the member value is
1332
// built against a STACK_TOP of the non-optional type and
1333
// emits the expected access or call ops with no preceding
1334
// receiver gen.
1335
let inner_type =
1336
if receiver_is_value_optional then
1337
receiver_type.optional_inner_type;
1338
else
1339
receiver_type.as_non_optional();
1340
fi;
1341
1342
if !inner_type? then
1343
return null;
1344
fi
1345
1346
// STACK_TOP (has_address=false): the inner-receiver
1347
// value is on the stack, not its address. A struct
1348
// member access reads the value off the top, and the
1349
// existing call_struct_method path's `ADDRESS(from)`
1350
// spill takes its address as needed. A STACK_TOP_ADDRESS
1351
// here would tell that spill the address is already
1352
// present and skip it — producing a `call instance` on a
1353
// bare value, which segfaults at the JIT.
1354
let stack_top: IR.Values.Value = IR.Values.STACK_TOP(inner_type);
1355
1356
let raw = build_member_value(stack_top);
1357
1358
if !raw? then
1359
return null;
1360
fi
1361
1362
let arm = _widen_coalesce_arm(raw);
1363
1364
if !arm? then
1365
return null;
1366
fi
1367
1368
if receiver_is_value_optional then
1369
// Build presence test (get_has_value) and value
1370
// extract (get_value) against a STACK_TOP_ADDRESS so
1371
// both consume the address dup'd by COALESCE_LOAD.
1372
let address_stand_in = IR.Values.STACK_TOP_ADDRESS(receiver_type);
1373
let has_value_member = receiver_type.find_member("has_value");
1374
let value_member = receiver_type.find_member("value");
1375
1376
if !has_value_member? \/ !value_member? then
1377
_logger.poison(
1378
member.location,
1379
"coalescing member access receiver {receiver_type} missing has_value or value member"
1380
);
1381
return null;
1382
fi
1383
1384
let presence_test = has_value_member.load(member.location, address_stand_in, _symbol_loader);
1385
1386
let value_extract =
1387
if receiver_consumed then
1388
value_member.load(member.location, address_stand_in, _symbol_loader)
1389
else
1390
null
1391
fi;
1392
1393
return IR.Values.COALESCE_LOAD(
1394
receiver,
1395
presence_test,
1396
value_extract,
1397
arm.member_value,
1398
arm.null_value,
1399
arm.result_type,
1400
receiver_consumed
1401
);
1402
fi
1403
1404
return IR.Values.COALESCE_LOAD(receiver, arm.member_value, arm.null_value, arm.result_type, receiver_consumed);
1405
si
1406
1407
// Re-seat a coalescing member access whose loaded member is
1408
// itself then invoked - `a?.f(...)` where f is a
1409
// function-typed field or property. The invocation must sit
1410
// inside the short-circuit arm, so the original wrap's
1411
// receiver plumbing is reused around the call value, which
1412
// consumes the original member load from the same arm.
1413
rewrap_coalesce_call(
1414
original: IR.Values.COALESCE_LOAD,
1415
call_value: Value
1416
) -> Value? is
1417
let arm = _widen_coalesce_arm(call_value);
1418
1419
if !arm? then
1420
return null;
1421
fi
1422
1423
if original.receiver_is_value_type then
1424
return IR.Values.COALESCE_LOAD(
1425
original.receiver,
1426
original.presence_test!,
1427
original.value_extract,
1428
arm.member_value,
1429
arm.null_value,
1430
arm.result_type,
1431
true
1432
);
1433
fi
1434
1435
return IR.Values.COALESCE_LOAD(
1436
original.receiver,
1437
arm.member_value,
1438
arm.null_value,
1439
arm.result_type,
1440
true
1441
);
1442
si
1443
1444
// The direct result of a coalescing access or call whose
1445
// receiver is statically present, widened to the optional
1446
// shape the `?.` asked for. A void result stays void.
1447
widen_coalesce_result(direct: Value) -> Value? is
1448
let direct_type = direct.type;
1449
1450
if !direct_type? then
1451
return null;
1452
fi
1453
1454
if direct_type.is_void then
1455
return direct;
1456
fi
1457
1458
let direct_result = build_optional_type(direct_type, _innate_symbol_lookup);
1459
1460
if !direct_type.is_optional /\ direct_result.is_value_type then
1461
return IR.Values.WRAP_OPTIONAL(direct_result, direct);
1462
fi
1463
1464
// Reference T and T? share IL — re-type the load.
1465
if !direct_type.matches(direct_result) then
1466
return IR.Values.TYPE_WRAPPER(direct_result, direct);
1467
fi
1468
1469
return direct;
1470
si
1471
1472
// Widen a present-arm member value to the optional result
1473
// shape of the enclosing `?.`:
1474
// - void → no widening, no null sentinel.
1475
// - reference T → T flagged optional, IL identity.
1476
// - value T → NULLABLE[T] via WRAP_OPTIONAL (newobj
1477
// Nullable<T>::.ctor(T)).
1478
// - already-optional U → identity.
1479
_widen_coalesce_arm(raw: Value) -> COALESCE_ARM? is
1480
let raw_type = raw.type;
1481
1482
if !raw_type? then
1483
return null;
1484
fi
1485
1486
if raw_type.is_void then
1487
return COALESCE_ARM(raw, null, raw_type);
1488
fi
1489
1490
let result_type = build_optional_type(raw_type, _innate_symbol_lookup);
1491
1492
let member_value: Value =
1493
if !raw_type.is_optional /\ result_type.is_value_type then
1494
IR.Values.WRAP_OPTIONAL(result_type, raw)
1495
else
1496
raw
1497
fi;
1498
1499
let null_value = _build_null_sentinel(result_type);
1500
1501
if !null_value? then
1502
return null;
1503
fi
1504
1505
return COALESCE_ARM(member_value, null_value, result_type);
1506
si
1507
1508
// The null-arm sentinel for a coalescing access of result
1509
// type `result_type`: `ldnull` for a reference type, a fresh
1510
// DEFAULT (which hoists a `.locals init` and pushes the zero
1511
// value) for a value-type optional like NULLABLE[T] or
1512
// MAYBE[T].
1513
_build_null_sentinel(result_type: Type?) -> Value? is
1514
if !result_type? then
1515
return null;
1516
fi
1517
1518
if result_type.is_value_type then
1519
return IR.Values.DEFAULT(result_type);
1520
fi
1521
1522
return IR.RAW(result_type, "ldnull");
1523
si
1524
1525
// The `T?` form of `t`: NULLABLE[T] for a value type, the
1526
// flagged reference for a reference type. `as_optional()`
1527
// alone returns the receiver for a value type — leaving the
1528
// result mistyped as `T` — so this dispatches the way
1529
// `resolve_type_expressions` does for a written `T?`. Static
1530
// so the rule can be pinned without standing up a full
1531
// COMPILE_ACCESS instance.
1532
build_optional_type(t: Type, lookup: Semantic.Lookups.InnateSymbolLookup) -> Type static is
1533
// t may be null
1534
@suppress("presence-test-non-optional")
1535
if !t? then
1536
return t;
1537
fi
1538
1539
if t.is_optional then
1540
return t;
1541
fi
1542
1543
if t.is_value_type then
1544
return lookup.get_optional_type(t);
1545
fi
1546
1547
return t.as_optional();
1548
si
1549
si
1550
1551
// The present-arm pieces of a coalescing wrap: the member value
1552
// widened to the optional result shape, the null-arm sentinel
1553
// (absent when the member value is void) and the result type.
1554
class COALESCE_ARM(
1555
member_value: Value,
1556
null_value: Value?,
1557
result_type: Type
1558
) is
1559
si
1560
si