Skip to content
← Back

src/syntax/process/compile-expressions/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
_shadowed_callable_finder: Semantic.SHADOWED_CALLABLE_FINDER
39
40
init(
41
logger: Logger,
42
symbol_table: Semantic.SYMBOL_TABLE,
43
symbol_loader: Semantic.SYMBOL_LOADER,
44
symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS,
45
innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup,
46
overload_resolver: Semantic.OVERLOAD_RESOLVER,
47
function_caller: Semantic.FUNCTION_CALLER,
48
unit_variant_constructor: Semantic.UNIT_VARIANT_CONSTRUCTOR,
49
flow: NARROWING_FLOW,
50
condition_analyzer: CONDITION_ANALYZER,
51
build_flags: Compiler.GLOBAL_BUILD_FLAGS,
52
visitor: COMPILE_EXPRESSIONS
53
) is
54
super.init()
55
56
_logger = logger
57
_symbol_table = symbol_table
58
_symbol_loader = symbol_loader
59
_symbol_use_locations = symbol_use_locations
60
_innate_symbol_lookup = innate_symbol_lookup
61
_overload_resolver = overload_resolver
62
_function_caller = function_caller
63
_unit_variant_constructor = unit_variant_constructor
64
_flow = flow
65
_condition_analyzer = condition_analyzer
66
_build_flags = build_flags
67
_visitor = visitor
68
_function_reference_resolver = Semantic.FUNCTION_REFERENCE_RESOLVER(logger, symbol_table, symbol_loader, innate_symbol_lookup, overload_resolver)
69
_shadowed_callable_finder = Semantic.SHADOWED_CALLABLE_FINDER(logger, symbol_table)
70
si
71
72
visit_member(member: Trees.Expressions.MEMBER) is
73
let need_store = member.value? /\ isa Need.STORE(member.value)
74
let need_deref mut = false
75
76
if member.identifier.is_poisoned then
77
member.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), member.location)
78
return
79
fi
80
81
if member.left.value? then
82
let left_value mut = member.left.value
83
let type mut = left_value.type
84
85
if type == null then
86
_logger.poison(member.left.location, "member left has no type")
87
88
member.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), member.location)
89
return
90
fi
91
92
// `x?.y` never dereferences a null receiver, and
93
// `x.has_value` is the optional protocol's presence
94
// query — asking is not dereferencing.
95
if !member.is_coalesce /\ !(member.identifier.name =~ "has_value") then
96
_visitor.check_receiver_present(member.left)
97
fi
98
99
// TODO we could loop here to handle multiple levels of dereferencing
100
if type.is_ref then
101
need_deref = true
102
// is_ref implies an element type; the runtime
103
// invariant guarantees get_element_type() non-null.
104
type = type.get_element_type()!
105
106
left_value = DEREF(left_value, type)
107
member.left.compile_expressions_state.value = left_value
108
fi
109
110
// Operation-constraint inference: when the receiver
111
// is an INFERRED_VARIABLE_TYPE placeholder, record a
112
// MEMBER_CONSTRAINT on the placeholder's origin so
113
// the constraint-aware LUB can later filter candidate
114
// types to those that actually expose this member.
115
// mark_consumed_any drives the retry loop to re-walk
116
// the body with the resolved type (if any).
117
if isa Semantic.Types.INFERRED_VARIABLE_TYPE(type) then
118
let placeholder = type
119
120
_logger.mark_consumed_any_if(Semantic.INFERENCE_TRACE.add_constraint("access.member", placeholder.origin, Semantic.MEMBER_CONSTRAINT(member.identifier.name)))
121
122
// The read waits with the receiver rather than
123
// standing as an error: a consumer that pins this
124
// value's type - a tuple a literal returns, say -
125
// would otherwise fix the failure into a type no
126
// later walk reaches.
127
member.compile_expressions_state.value =
128
if Semantic.OPERAND_WAIT.is_typed_by_the_call_site(placeholder) then
129
DUMMY(Semantic.Types.INFERRED_JOIN_TYPE(placeholder), member.location)
130
else
131
DUMMY(Semantic.Types.ERROR(), member.location)
132
fi
133
134
return
135
fi
136
137
if !isa Semantic.Types.NAMED(type) then
138
if !isa Semantic.Types.ERROR(type) /\ !type.is_inferred then
139
// Suppress for ERROR (existing — error already
140
// reported, the contract says don't pile on)
141
// and for is_inferred placeholders (deferred-
142
// inference: the lambda's arg type is awaiting
143
// the call-site second-pass to fill it in,
144
// and emitting a fatal "type has no members"
145
// here breaks that flow). Both share the
146
// intent of "don't surface a member-resolution
147
// failure on a stand-in type".
148
_logger.poison(member.left.location, "type has no members: {type}")
149
fi
150
151
member.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), member.location)
152
return
153
fi
154
155
let named_type mut = type
156
157
// For coalescing access against any optional, look
158
// up the member on the unwrapped (inner) type so
159
// `a?.b` consistently means "if a is present, access
160
// b on the unwrapped a". A value-type optional like
161
// NULLABLE[T] / MAYBE[T] otherwise resolves `value`
162
// / `has_value` against the wrapper rather than the
163
// payload, surprising the user and producing IL that
164
// mixes wrapper-thissed calls with payload-typed
165
// stack values.
166
if member.is_coalesce /\ named_type.is_optional then
167
let inner = named_type.optional_inner_type
168
169
if inner? /\ isa Semantic.Types.NAMED(inner) then
170
named_type = inner
171
fi
172
fi
173
174
let symbol mut = named_type.find_member(member.identifier.name)
175
176
// A member that resolves to same-name types at several
177
// generic arities comes back as a TYPE_GROUP. In value
178
// position (`Foo.bar` with no `[...]`) collapse to the
179
// arity-0 member; `Foo[T].bar` is handled earlier by
180
// GENERIC_APPLICATION before reaching here.
181
let member_group = cast Semantic.Symbols.TYPE_GROUP?(symbol)
182
183
if member_group? then
184
let arity_zero = member_group.find_by_generic_arguments_count(0)
185
186
// A constructor call on the qualified name resolves to the
187
// group's generic member when there is no constructible
188
// arity-0 member (a static factory class shares the name
189
// with a generic type, `Collections.KeyValuePair`); when
190
// the arity-0 member is itself constructible, or this is a
191
// plain value-position access, collapse to it.
192
let for_call =
193
if member.is_call_target /\ (!arity_zero? \/ arity_zero.is_abstract) then
194
member_group.sole_generic_member()
195
else
196
null
197
fi
198
199
if for_call? then
200
symbol = for_call
201
elif arity_zero? then
202
symbol = arity_zero
203
else
204
let counts = member_group.generic_arguments_counts |> map(a -> string => "{a}") |> join(" or ")
205
_logger.error(member.identifier.location, "type {member.identifier.name} requires {counts} type arguments")
206
member.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), member.location)
207
return
208
fi
209
fi
210
211
if !symbol? then
212
let hint = try_describe_intersection_miss(named_type, member.identifier.name)
213
214
if hint? then
215
_logger.error(member.identifier.location, hint)
216
else
217
// A member that the narrowed type did have, on a
218
// value whose narrowing a call ended, is missing
219
// for that reason: the type is the widened one
220
// again from the call onwards. The message is the
221
// ordinary one, with the call it ended at.
222
let dropped =
223
DROPPED_NARROWING.for_member(
224
_visitor.try_get_narrowing_target(member.left),
225
_visitor.try_build_access_path(member.left),
226
member.identifier.name,
227
member.identifier.location
228
)
229
230
if let kill = dropped then
231
_logger.error(
232
member.identifier.location,
233
"member {member.identifier.name} not found in {named_type}",
234
kill.crossing.location,
235
DROPPED_NARROWING.message(kill)
236
)
237
else
238
_logger.error(member.identifier.location, "member {member.identifier.name} not found in {named_type}")
239
fi
240
fi
241
242
member.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), member.location)
243
return
244
fi
245
246
if _is_indexer_accessor(symbol) then
247
_logger.error(
248
member.identifier.location,
249
"cannot call an indexer accessor directly",
250
member.identifier.location,
251
"help: index {named_type} with [ ] instead"
252
)
253
254
member.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), member.location)
255
return
256
fi
257
258
_symbol_use_locations.add_symbol_use(member.identifier.right_location, symbol)
259
260
if _is_instance_only(symbol) /\ !left_value.is_consumable then
261
_logger.error(member.identifier.location, "cannot access instance member {member.identifier.name} in {named_type}")
262
263
member.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), member.location)
264
return
265
fi
266
267
_symbol_loader.find_symbol =
268
(name: string) is
269
let result = named_type.find_member(name)
270
271
return result
272
si
273
274
let value: Value? mut = _
275
276
if need_store then
277
if member.is_coalesce then
278
_logger.error(member.location, "cannot assign through coalescing member access")
279
fi
280
281
// A struct-typed receiver with no in-place address is a
282
// temporary copy - a property get, method result or
283
// indexer element - so a store through it would be
284
// silently lost. Field chains, locals, self and refs
285
// all carry an address and store in place.
286
if
287
symbol.is_instance /\
288
type.is_value_type /\
289
!left_value.has_address
290
then
291
_logger.error(member.location, "cannot assign through a copy of a struct value here")
292
293
member.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), member.location)
294
return
295
fi
296
297
let store_value = cast Need.STORE?(member.value)!.value
298
299
if check_store_access(member.location, left_value, symbol, named_type) then
300
value = symbol.store(member.location, left_value, store_value, _symbol_loader, false)
301
fi
302
303
// The written receiver may alias any receiver a
304
// fact was proven on, so the stored member's own
305
// facts and every path reading through it die —
306
// keyed on the member symbol, not the receiver.
307
_flow.on_member_store(symbol)
308
else
309
if symbol.is_unit_variant then
310
if member.is_coalesce then
311
_logger.error(member.location, "coalescing member access does not apply to a unit-variant constructor")
312
fi
313
314
let lowered = _unit_variant_constructor.try_load(member.location, symbol, member.expected_type, member)
315
316
if lowered? then
317
_symbol_use_locations.add_symbol_use(member.identifier.right_location, lowered.constructor)
318
member.compile_expressions_state.value = lowered.value
319
return
320
fi
321
fi
322
323
if check_load_access(member.location, left_value, symbol, named_type) then
324
if member.is_coalesce then
325
value = _build_coalesce_load(member, symbol)
326
327
if value? then
328
member.compile_expressions_state.value = value
329
return
330
fi
331
332
// Fallthrough on a build failure (e.g.
333
// unsupported receiver shape): proceed with
334
// a plain access so the user gets at most
335
// one diagnostic instead of a cascade.
336
value = symbol.load(member.location, left_value, _symbol_loader)
337
else
338
value =
339
_function_reference_resolver.try_load(member.location, symbol, left_value, member.expected_type, member.is_call_target) ??
340
symbol.load(member.location, left_value, _symbol_loader)
341
fi
342
fi
343
fi
344
345
member.compile_expressions_state.value = value
346
347
// Path narrowing: when the flow analysis has recorded
348
// facts for this member-access path, surface the
349
// access at the narrower view type so
350
// `if isa Cat(x.y) then x.y.meow() fi` and
351
// `if x.y? then x.y.z fi` both type-check. Mirrors the
352
// local-variable path in visit_identifier. A reference
353
// optional shares IL with its non-optional form (a type
354
// view); NULLABLE[T] / MAYBE[T] get a `.value`
355
// projection.
356
if !need_store /\ member.value? then
357
let path = _visitor.try_build_access_path(member)
358
359
// `self.x` builds no path: it names the same
360
// location the bare `x` does, so the flow analysis
361
// keys their shared fact on the member symbol
362
// instead. Read back under whichever key the guard
363
// recorded, so both spellings see the same narrow.
364
let self_target =
365
if path? then
366
null
367
else
368
_visitor.try_get_narrowing_target(member)
369
fi
370
371
if path? \/ self_target? then
372
// The un-narrowed view of this load, for the
373
// consumption judge: a use the wide type
374
// already satisfies does not lean on the fact.
375
let wide_type =
376
if path? then
377
member.value!.type
378
else
379
_flow.declared_type_of(self_target!)
380
fi
381
382
// Step 1: type narrow (isa T on x.y). Wraps the
383
// loaded value at the recorded static subtype
384
// when the composition is sound. Path-keyed
385
// only — a symbol-keyed type narrow already
386
// reaches the load through the narrowed
387
// symbol's own type.
388
if let recorded = _flow.narrowed_type_of_path(path) then
389
let loaded = member.value!
390
391
if loaded.type? then
392
if let composed = _flow.compose_path_narrow(loaded.type, recorded) then
393
member.compile_expressions_state.value = IR.Values.NARROW_VIEW(loaded, composed)
394
_symbol_use_locations.replace_with_variable_use(member.identifier.right_location, symbol, member.value!)
395
fi
396
fi
397
fi
398
399
// Step 2: presence narrow (if x.y? then x.y).
400
// Reads member.value again in case step 1
401
// already wrapped it.
402
let loaded = member.value!
403
404
let is_present =
405
if path? then
406
_flow.is_non_null_path(path)
407
else
408
_flow.is_non_null(self_target!)
409
fi
410
411
if loaded.type? /\ loaded.type.is_optional /\ is_present then
412
let loaded_type = loaded.type
413
let narrowed = loaded_type.as_non_optional()
414
415
if !narrowed.is_optional then
416
member.compile_expressions_state.value = IR.Values.NARROW_VIEW(loaded, narrowed)
417
// Overwrite the earlier symbol-use entry so
418
// HOVER on the member surfaces the narrowed
419
// type — mirroring what visit_identifier does
420
// for a narrowed local. Same location; put
421
// wins for the last write.
422
_symbol_use_locations.replace_with_variable_use(member.identifier.right_location, symbol, member.value!)
423
elif loaded_type.is_value_type then
424
let value_member = loaded_type.find_member("value")
425
426
if value_member? then
427
let projected = value_member.load(member.location, loaded, _symbol_loader)
428
429
member.compile_expressions_state.value = IR.Values.NARROW_PROJECT(loaded, projected)
430
_symbol_use_locations.replace_with_variable_use(member.identifier.right_location, symbol, member.value!)
431
fi
432
fi
433
fi
434
435
// Record the fact load for the kill ledger,
436
// so the re-ask after the next solve covers it.
437
if path? then
438
_flow.record_load_of_path(member.location, path)
439
else
440
_flow.record_load_of_symbol(member.location, self_target!)
441
fi
442
fi
443
fi
444
fi
445
si
446
447
// Whether the fact behind an `!` operand has every crossing
448
// discharged. When it does not, the unwrap is the remedy the
449
// reliance error asks for, and must not be warned redundant.
450
_unwrap_fact_validated(operand: Trees.Expressions.Expression?) -> bool is
451
if !operand? then
452
return true
453
fi
454
455
let target = _visitor.try_get_narrowing_target(operand)
456
457
if target? then
458
return _flow.symbol_fact_validated(target)
459
fi
460
461
if let member: Trees.Expressions.MEMBER = operand then
462
let path = _visitor.try_build_access_path(member)
463
464
if path? then
465
return _flow.path_fact_validated(path)
466
fi
467
fi
468
469
return true
470
si
471
472
// True iff any receiver in `expr`'s member-access chain has
473
// been wrapped in a NARROW_VIEW by path narrowing. Used to
474
// recognise loads whose result type reflects a narrowed
475
// receiver — the declared receiver type is still optional at
476
// this position, so an `!` against the narrowed type is
477
// redundant rather than an error.
478
_has_narrowed_receiver(expr: Trees.Expressions.Expression) -> bool is
479
let cursor mut = expr
480
481
while isa Trees.Expressions.MEMBER(cursor) do
482
let member = cast Trees.Expressions.MEMBER(cursor)
483
484
if member.left.value? /\ isa IR.Values.NARROW_VIEW(member.left.value) then
485
return true
486
fi
487
488
cursor = member.left
489
od
490
491
return false
492
si
493
494
check_load_access(location: LOCATION, from: Value?, symbol: Semantic.Symbols.Symbol, type: Type) -> bool is
495
// A synthesized member reads what the type holds whatever
496
// its visibility: a variant's equality and hash read the
497
// private members of its own union.
498
if location.is_internal then
499
return true
500
fi
501
502
if !symbol.is_accessible_to(_symbol_table.current_accessor) then
503
_logger.error(location, "{symbol} is not accessible here")
504
// The member is assembly-reachable, so let the access through to
505
// avoid a cascade of downstream errors: the diagnostic stands but
506
// the generated IL is still valid.
507
return true
508
fi
509
510
if symbol.is_public_readable then
511
return true
512
fi
513
514
if from? /\ from.is_self then
515
return true
516
fi
517
518
let instance_context = _symbol_table.current_instance_context
519
let instance_context_type = if instance_context? then instance_context.type else null fi
520
521
if instance_context_type? /\ instance_context_type.is_assignable_from(type) then
522
return true
523
fi
524
525
// A member reached through a specialization is the same
526
// declaring type under a distinct symbol, so the
527
// assignability test above misses a receiver that is the
528
// current instance type specialized - reading a private
529
// member off a second instance of the same generic, as an
530
// equality or comparison method does. find_ancestor
531
// compares root unspecialized symbols along the ancestor
532
// chain, which holds for the specialized case and matches
533
// the assignability result for the ordinary one.
534
if instance_context_type? /\ type.symbol.find_ancestor(instance_context_type)? then
535
return true
536
fi
537
538
_logger.error(location, "{symbol} is not publicly readable")
539
540
return false
541
si
542
543
check_store_access(location: LOCATION, from: Value?, symbol: Semantic.Symbols.Symbol, type: Type) -> bool is
544
if !symbol.is_accessible_to(_symbol_table.current_accessor) then
545
_logger.error(location, "{symbol} is not accessible here")
546
// Assembly-reachable: allow the store so the diagnostic does not
547
// cascade into follow-on errors.
548
return true
549
fi
550
551
if !symbol.is_private /\ !symbol.is_field then
552
return true
553
fi
554
555
if from? /\ from.is_self then
556
return true
557
fi
558
559
let instance_context = _symbol_table.current_instance_context
560
let instance_context_type = if instance_context? then instance_context.type else null fi
561
562
if instance_context_type? /\ instance_context_type.is_assignable_from(type) then
563
return true
564
fi
565
566
// Same specialization-identity case as check_load_access
567
// above: a second instance of the same generic is the
568
// declaring type under a distinct symbol.
569
if instance_context_type? /\ type.symbol.find_ancestor(instance_context_type)? then
570
return true
571
fi
572
573
_logger.error(location, "{symbol} is not publicly assignable")
574
575
return false
576
si
577
578
visit_identifier(identifier: Trees.Expressions.IDENTIFIER) is
579
let need_store = identifier.value? /\ isa Need.STORE(identifier.value)
580
581
let symbol mut = _visitor.find(identifier.identifier)
582
583
if
584
let shadowed = symbol /\
585
identifier.is_call_target /\
586
!identifier.identifier.qualifier?,
587
callable = _shadowed_callable_finder.find(identifier.location, identifier.identifier.name, shadowed)
588
then
589
symbol = callable
590
fi
591
592
let group = cast Semantic.Symbols.TYPE_GROUP?(symbol)
593
594
if group? then
595
let bare = group.find_by_generic_arguments_count(0)
596
597
// A constructor call on the bare name resolves to the group's
598
// generic member when there is no constructible arity-0
599
// member — the reflected shape where a static factory class
600
// (abstract, so not constructible) shares a name with a
601
// generic type (`KeyValuePair` + `KeyValuePair[K,V]`). When
602
// the arity-0 member *is* constructible (a plain non-generic
603
// class overloaded with a generic one, `FOO` + `FOO[T]`), or
604
// this is a plain value-position reference, collapse to it.
605
let for_call =
606
if identifier.is_call_target /\ (!bare? \/ bare.is_abstract) then
607
group.sole_generic_member()
608
else
609
null
610
fi
611
612
if for_call? then
613
symbol = for_call
614
elif bare? then
615
symbol = bare
616
else
617
let counts = group.generic_arguments_counts |> map(a -> string => "{a}") |> join(" or ")
618
_logger.error(identifier.location, "type {identifier.identifier} requires {counts} type arguments")
619
identifier.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), identifier.location)
620
return
621
fi
622
fi
623
624
if symbol? then
625
if symbol.is_type then
626
_symbol_use_locations.add_symbol_use(identifier.right_location, symbol)
627
628
if symbol.is_unit_variant then
629
let lowered = _unit_variant_constructor.try_load(identifier.location, symbol, identifier.expected_type, identifier)
630
631
if lowered? then
632
// Anchor HOVER and find-references to the
633
// specialised init so the rendered owner
634
// carries the resolved type arguments —
635
// matching the parenthesised path's
636
// bookkeeping in resolve_constructor.
637
_symbol_use_locations.add_symbol_use(identifier.right_location, lowered.constructor)
638
identifier.compile_expressions_state.value = lowered.value
639
return
640
fi
641
fi
642
643
let symbol_type =
644
if let alias = cast Semantic.Symbols.TYPE_ALIAS?(symbol) /\ alias.argument_count > 0 then
645
_bare_generic_alias_type(identifier.location, alias)
646
else
647
symbol.type
648
fi
649
650
if symbol_type? then
651
identifier.compile_expressions_state.value = TYPE_EXPRESSION(symbol_type, identifier.location)
652
else
653
identifier.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), identifier.location)
654
fi
655
else
656
// A loop label resolves like any symbol in scope but
657
// is not a value; only `break` consumes the name.
658
if isa Semantic.Symbols.LABEL(symbol) then
659
_logger.error(identifier.location, "{symbol} names a loop label and has no value")
660
identifier.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), identifier.location)
661
return
662
fi
663
664
// Bare-identifier (implicit-self) access to a field, including
665
// an inherited underscore field, is subject to the same policy
666
// as explicit member access. Log but allow: the field is
667
// assembly-reachable, so blocking would only cascade.
668
if symbol.is_field /\ !symbol.is_accessible_to(_symbol_table.current_accessor) then
669
_logger.error(identifier.location, "{symbol} is not accessible here")
670
fi
671
672
_symbol_loader.find_symbol = (name: string) => _visitor.find(name)
673
674
// The function's own name, read from inside its own
675
// body. Serve it before the ordinary load, which would
676
// report the local as undefined - it is, and the
677
// recurse field is what holds the function there.
678
if !need_store then
679
if let recurse = _visitor.try_load_self_reference(identifier.location, symbol) then
680
identifier.compile_expressions_state.value = recurse
681
682
_symbol_use_locations.add_symbol_use(identifier.right_location, symbol)
683
684
return
685
fi
686
fi
687
688
if need_store then
689
let store_value = cast Need.STORE?(identifier.value)!.value
690
identifier.compile_expressions_state.value = symbol.store(identifier.location, null, store_value, _symbol_loader, false)
691
692
// Optional-narrowing, write side: a
693
// non-optional right-hand side leaves a
694
// declared-optional target known to hold a
695
// value, so HOVER on the target describes
696
// the state the assignment leaves behind —
697
// agreeing with what a subsequent read
698
// reports rather than with the declared
699
// type. Gated on the same target shape the
700
// flow analysis narrows, so hover never
701
// claims a narrow that isn't applied.
702
let narrowed: Semantic.Types.Type? mut = null
703
704
let target = _visitor.try_get_narrowing_target(identifier)
705
706
if target? /\ _visitor.is_non_optional_value(store_value) then
707
let declared = _flow.declared_type_of(target)
708
709
if declared? /\ declared.is_optional then
710
let inner = declared.optional_inner_type
711
712
if inner? /\ !inner.is_optional then
713
narrowed = inner
714
fi
715
fi
716
fi
717
718
if narrowed? then
719
_symbol_use_locations.add_variable_use(identifier.right_location, symbol, narrowed)
720
else
721
_symbol_use_locations.add_symbol_use(identifier.right_location, symbol)
722
fi
723
else
724
identifier.compile_expressions_state.value =
725
_function_reference_resolver.try_load(identifier.location, symbol, null, identifier.expected_type, identifier.is_call_target) ??
726
symbol.load(identifier.location, null, _symbol_loader)
727
728
// Optional-narrowing: when flow analysis has
729
// established this variable is non-null here,
730
// surface the use as the non-optional type.
731
// Reference `T?` is a NAMED-with-flag — IL
732
// representation matches `T`, so the load gets
733
// a type-view snapshot (mirrors how `isa`-
734
// narrowing reaches the load via a mutated
735
// `symbol.type`). The value-type lowerings
736
// (`NULLABLE[T]` / `MAYBE[T]`) are wrapper
737
// structs, so the load gets wrapped in
738
// `NARROW_PROJECT` — emits `.value` at gen
739
// time, but `visit_has_value` and
740
// `visit_unwrap` can peel back to the
741
// underlying wrapper for their own IR.
742
let loaded = identifier.value
743
744
// the load's snapshot type is null for an inference placeholder
745
@suppress("presence-test-non-optional")
746
if
747
isa Semantic.Symbols.Variable(symbol) /\
748
loaded? /\
749
loaded.type? /\
750
loaded.type.is_optional
751
then
752
let variable = symbol
753
754
if _flow.is_non_null(variable) then
755
let loaded_type = loaded.type
756
let narrowed = loaded_type.as_non_optional()
757
758
if isa IR.Values.Load.SYMBOL(loaded) then
759
if !narrowed.is_optional then
760
(cast IR.Values.Load.SYMBOL(loaded)).narrow_snapshot_type(narrowed)
761
elif loaded_type.is_value_type then
762
let value_member = loaded_type.find_member("value")
763
764
if value_member? then
765
let projected = value_member.load(identifier.location, loaded, _symbol_loader)
766
767
identifier.compile_expressions_state.value = IR.Values.NARROW_PROJECT(loaded, projected)
768
fi
769
fi
770
else
771
// A captured, reassigned local loads
772
// through its box's `value` getter
773
// rather than as a symbol load, so
774
// the presence fact is applied as a
775
// view over the plumbing call - the
776
// same shape a narrowed property
777
// read takes.
778
if !narrowed.is_optional then
779
identifier.compile_expressions_state.value = IR.Values.NARROW_VIEW(loaded, narrowed)
780
elif loaded_type.is_value_type then
781
let value_member = loaded_type.find_member("value")
782
783
if value_member? then
784
let projected = value_member.load(identifier.location, loaded, _symbol_loader)
785
786
identifier.compile_expressions_state.value = IR.Values.NARROW_PROJECT(loaded, projected)
787
fi
788
fi
789
fi
790
fi
791
elif let variable: Semantic.Symbols.Variable = symbol, load: IR.Values.Load.SYMBOL = loaded then
792
_project_narrowed_carrier(identifier, variable, load)
793
elif
794
isa Semantic.Symbols.Property(symbol) /\
795
loaded? /\
796
_visitor.try_get_narrowing_target(identifier) == symbol
797
then
798
// Same presence fact, read through a
799
// property rather than a field. The getter
800
// call is not a symbol load, so it takes a
801
// view wrapper instead of a snapshot —
802
// matching how an explicit `receiver.prop`
803
// is narrowed in visit_member. Gated on the
804
// property being the flow analysis's own
805
// narrowing target, so this reads the fact
806
// exactly where the target rule recorded
807
// one. The fact test sits in its own
808
// condition so the narrowing-target query
809
// above does not cross the fact the copy
810
// below leans on.
811
if
812
loaded.type? /\
813
loaded.type.is_optional /\
814
_flow.is_non_null(symbol)
815
then
816
let loaded_type = loaded.type
817
let narrowed = loaded_type.as_non_optional()
818
819
if !narrowed.is_optional then
820
identifier.compile_expressions_state.value = IR.Values.NARROW_VIEW(loaded, narrowed)
821
elif loaded_type.is_value_type then
822
let value_member = loaded_type.find_member("value")
823
824
if value_member? then
825
let projected = value_member.load(identifier.location, loaded, _symbol_loader)
826
827
identifier.compile_expressions_state.value = IR.Values.NARROW_PROJECT(loaded, projected)
828
fi
829
fi
830
fi
831
fi
832
833
// Record the fact load for the kill ledger,
834
// so the re-ask after the next solve covers it.
835
// Locals are filtered inside the recorder.
836
_flow.record_load_of_symbol(identifier.location, symbol)
837
838
// HOVER reads a narrowed local's type off
839
// this use-site node, not the symbol — the
840
// symbol's `type` is restored after the walk.
841
_symbol_use_locations.add_variable_use(identifier.right_location, symbol, identifier.value!)
842
843
// Definite assignment: reading a tracked
844
// deferred-init local before it is assigned
845
// on every path reaching here. An address-of
846
// operand of `ref` is not a value read — its
847
// read-or-write is decided by the resolved call.
848
if
849
!_build_flags.no_warn_definite_assignment /\
850
isa Semantic.Symbols.Variable(symbol) /\
851
!_visitor.is_reference_operand_target(symbol)
852
then
853
let variable = symbol
854
855
if _flow.is_tracked(variable) /\ !_flow.is_assigned(variable) then
856
_logger.warn(identifier.location, "definite-assignment", "{variable.name} may be used before it is assigned", variable.location, "variable declared here")
857
fi
858
fi
859
fi
860
fi
861
862
else
863
identifier.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), identifier.location)
864
fi
865
si
866
867
visit_has_value(has_value: Trees.Expressions.HAS_VALUE) is
868
869
if !has_value.left.value? then
870
_logger.poison(has_value.left.location, "has value expression has no value")
871
has_value.compile_expressions_state.value = DUMMY(_innate_symbol_lookup.get_bool_type(), has_value.location)
872
return
873
fi
874
875
if
876
!has_value.left.value.check_is_consumable(_logger, has_value.left.location)
877
then
878
_logger.error(has_value.left.location, "cannot use this here")
879
has_value.compile_expressions_state.value = DUMMY(_innate_symbol_lookup.get_bool_type(), has_value.location)
880
return
881
fi
882
883
// Peel a flow-narrowing projection back to its wrapper so
884
// `has_value` lookup hits the wrapper's bool field.
885
has_value.left.compile_expressions_state.value = IR.Values.NARROW_PROJECT.peel(has_value.left.value!)
886
887
let left_value = has_value.left.value!
888
let bool_type = _innate_symbol_lookup.get_bool_type()
889
890
// Union `u?` → `isa Default(u)`. Both source-defined
891
// and cross-asm unions carry the default variant on the
892
// symbol; cross-asm unions get it via the
893
// `DEFAULT_VARIANT_ATTRIBUTE` marker read in
894
// `SYMBOL_FACTORY.materialize_variant`. A union without
895
// a default variant short-circuits to a bare null check
896
// — `value`/`has_value` are no longer the convention
897
// for unions.
898
if !left_value.type!.is_value_type then
899
let classy = _condition_analyzer.get_classy_for_narrowing(left_value.type)
900
901
if classy? /\ (classy.is_union \/ classy.is_variant) then
902
let default_variant = _get_default_variant_for_operand(classy)
903
904
if default_variant? then
905
has_value.compile_expressions_state.value =
906
_build_default_variant_isa(
907
has_value.location,
908
left_value,
909
default_variant,
910
bool_type
911
)
912
else
913
has_value.compile_expressions_state.value = HAS_VALUE(left_value, bool_type)
914
fi
915
916
return
917
fi
918
fi
919
920
let has_value_member =
921
left_value.type!.find_member("has_value")
922
let has_value_valid mut = false
923
924
if has_value_member? then
925
if !isa Semantic.Symbols.Property(has_value_member) then
926
_logger.error(
927
has_value.left.location,
928
"has_value member must be a bool property"
929
)
930
elif !has_value_member.type!.matches(bool_type) then
931
_logger.error(
932
has_value.left.location,
933
"has_value property must be bool"
934
)
935
else
936
has_value_valid = true
937
fi
938
fi
939
940
941
let target = _visitor.try_get_narrowing_target(has_value.left)
942
943
if left_value.type!.is_optional then
944
// the test that itself proves presence is not
945
// redundant; one on an operand already proven to
946
// hold a value is
947
if target? /\ _flow.is_non_null(target) /\ _flow.symbol_fact_validated(target) then
948
_logger.warn(
949
has_value.location,
950
"redundant-presence-test",
951
"'?' is redundant here"
952
)
953
fi
954
elif !has_value_member? then
955
// no live has_value layer to consult: a non-optional
956
// operand narrowed from optional gets the flow-specific
957
// "redundant here"; a never-optional value type is an
958
// error; a never-optional reference is redundant by its
959
// static type and gets the plain "redundant" - the type
960
// guarantees presence everywhere, not just here.
961
let declared = if target? then _flow.declared_type_of(target) else null fi
962
963
if declared? /\ declared.is_optional then
964
if !target? \/ _flow.symbol_fact_validated(target) then
965
_logger.warn(
966
has_value.location,
967
"redundant-presence-test",
968
"'?' is redundant here"
969
)
970
fi
971
elif left_value.type!.is_value_type then
972
_logger.error(has_value.left.location, "not optional")
973
has_value.compile_expressions_state.value = DUMMY(bool_type, has_value.location)
974
return
975
elif assert_condition_depth == 0 /\ !left_value.type!.is_inferred then
976
// A member path reads as non-optional here when a
977
// presence fact is applied to the view; a re-test
978
// of one whose getters are unproven or whose
979
// crossings are undischarged is the very
980
// re-establishment the reliance judge asks for,
981
// never redundant.
982
let operand_path =
983
if isa Trees.Expressions.MEMBER(has_value.left) then
984
_visitor.try_build_access_path(cast Trees.Expressions.MEMBER(has_value.left))
985
else
986
null
987
fi
988
989
if !operand_path? \/ _flow.path_fact_validated(operand_path) then
990
_logger.warn(
991
has_value.location,
992
"presence-test-non-optional",
993
"'?' is redundant"
994
)
995
fi
996
fi
997
fi
998
999
if !left_value.type!.is_value_type then
1000
has_value.compile_expressions_state.value =
1001
get_reference_has_value(
1002
has_value.location,
1003
left_value,
1004
if has_value_valid then has_value_member else null fi,
1005
bool_type
1006
)
1007
elif has_value_member? then
1008
if has_value_valid then
1009
has_value.compile_expressions_state.value =
1010
has_value_member.load(
1011
has_value.location,
1012
left_value,
1013
_symbol_loader
1014
)
1015
else
1016
has_value.compile_expressions_state.value = DUMMY(bool_type, has_value.location)
1017
fi
1018
fi
1019
si
1020
1021
visit_unwrap(unwrap: Trees.Expressions.UNWRAP) is
1022
1023
let need_store = unwrap.value? /\ isa Need.STORE(unwrap.value)
1024
1025
// Peel a flow-narrowing projection back to its wrapper so
1026
// `!` runs against the optional shape it was designed for
1027
// (avoids "no value member" on the already-projected `T`).
1028
unwrap.left.compile_expressions_state.value = IR.Values.NARROW_PROJECT.peel(unwrap.left.value)
1029
1030
let unwrap_left_value = unwrap.left.value
1031
1032
if !unwrap_left_value? \/ !unwrap_left_value.type? then
1033
_logger.poison(unwrap.left.location, "unwrap expression has no value")
1034
unwrap.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), unwrap.location)
1035
return
1036
fi
1037
1038
if
1039
!unwrap_left_value.check_is_consumable(_logger, unwrap.left.location)
1040
then
1041
_logger.error(unwrap.left.location, "cannot use this here")
1042
unwrap.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), unwrap.location)
1043
return
1044
fi
1045
1046
if unwrap_left_value.type!.is_ref then
1047
// is_ref implies element type per the runtime invariant
1048
let element_type = unwrap_left_value.type!.get_element_type()!
1049
1050
if need_store then
1051
let store_value = cast Need.STORE?(unwrap.value)!.value
1052
unwrap.compile_expressions_state.value = IR.Values.Store.INDIRECT(unwrap_left_value, store_value, element_type)
1053
else
1054
unwrap.compile_expressions_state.value = DEREF(unwrap_left_value, element_type)
1055
fi
1056
1057
return
1058
fi
1059
1060
let value_member = unwrap_left_value.type!.find_member("value")
1061
1062
if !unwrap_left_value.type!.is_value_type then
1063
let classy = _condition_analyzer.get_classy_for_narrowing(unwrap_left_value.type)
1064
let default_variant = _get_default_variant_for_operand(classy)
1065
1066
if classy? /\ default_variant? then
1067
// Union `u!` → cast to the default variant (no
1068
// IL cast when the operand is already narrowed
1069
// to that variant), with single-field projection
1070
// so the canonical "unwrap to the sole field"
1071
// rule is preserved.
1072
unwrap.compile_expressions_state.value =
1073
_build_default_variant_unwrap(
1074
unwrap.location,
1075
unwrap_left_value,
1076
classy,
1077
default_variant
1078
)
1079
else
1080
// a plain reference type: `!` asserts non-null. The
1081
// result type drops the `?` so an inferred `let`
1082
// binding or a non-optional slot downstream sees a
1083
// present value rather than a may-be-null one. A
1084
// reference `T?` is already just `T` at IL, so `!`
1085
// has no dereference of its own to force a throw
1086
// with — wrap it so an absent value throws here
1087
// rather than silently reaching wherever the
1088
// now-non-optional value is next read.
1089
let left_value = unwrap_left_value
1090
1091
if left_value.type!.is_optional then
1092
let checked_value = REFERENCE_NULL_CHECK(left_value, left_value.type!)
1093
unwrap.compile_expressions_state.value = TYPE_WRAPPER(left_value.type!.as_non_optional(), checked_value)
1094
elif value_member? /\ left_value.type!.find_member("has_value")? then
1095
// an option-shaped reference type: flow
1096
// analysis does not track its has_value
1097
// layer, so `!` re-asserts it and is never
1098
// redundant.
1099
unwrap.compile_expressions_state.value = left_value
1100
else
1101
let target = _visitor.try_get_narrowing_target(unwrap.left)
1102
let declared = if target? then _flow.declared_type_of(target) else null fi
1103
1104
// A NARROW_VIEW is a member-access path read
1105
// at its non-optional type because a path
1106
// fact holds here — the declared member is
1107
// optional, so the unwrap is redundant, not
1108
// an error. A load whose receiver chain
1109
// contains a NARROW_VIEW is the same case one
1110
// hop deeper: the load resolved a member on a
1111
// narrowed receiver, so its result type
1112
// reflects the narrow rather than the
1113
// declared receiver type.
1114
if declared? /\ declared.is_optional \/ isa IR.Values.NARROW_VIEW(left_value) \/ _has_narrowed_receiver(unwrap.left) then
1115
// non-optional only because flow analysis
1116
// has narrowed it at this point; an `!`
1117
// covering an undischarged crossing is
1118
// load-bearing, not redundant
1119
if !_build_flags.no_warn_redundant_unwrap /\ _unwrap_fact_validated(unwrap.left) then
1120
_logger.warn(
1121
unwrap.location,
1122
"redundant-unwrap",
1123
"'!' is redundant here"
1124
)
1125
elif NARROWING_STUDY.enabled then
1126
// Study output: an `!` kept quiet
1127
// because its fact has an undischarged
1128
// crossing. The set of these is the
1129
// ceremony the analysis still requires;
1130
// grep the tag when measuring it.
1131
IO.Std.error.write_line("LOAD-BEARING-UNWRAP {unwrap.location}")
1132
fi
1133
1134
unwrap.compile_expressions_state.value = left_value
1135
else
1136
_logger.error(unwrap.left.location, "cannot unwrap this")
1137
unwrap.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), unwrap.location)
1138
fi
1139
fi
1140
fi
1141
elif value_member? then
1142
// The `value` getter this loads (Nullable[T].Value,
1143
// MAYBE[T].value) already throws when absent, so `!`
1144
// here needs no explicit has-value check of its own.
1145
if
1146
!_build_flags.no_warn_redundant_unwrap /\
1147
unwrap_left_value.type!.is_optional
1148
then
1149
let unwrap_target = _visitor.try_get_narrowing_target(unwrap.left)
1150
1151
if unwrap_target? /\ _flow.is_non_null(unwrap_target) /\ _flow.symbol_fact_validated(unwrap_target) then
1152
_logger.warn(
1153
unwrap.location,
1154
"redundant-unwrap",
1155
"'!' is redundant here"
1156
)
1157
fi
1158
fi
1159
1160
unwrap.compile_expressions_state.value = value_member.load(unwrap.location, unwrap_left_value, _symbol_loader)
1161
else
1162
_logger.error(unwrap.left.location, "cannot unwrap this")
1163
unwrap.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), unwrap.location)
1164
fi
1165
1166
// `x!` asserts `x` holds a value — record it, so a
1167
// subsequent dereference of `x` is not flagged. Skipped
1168
// when the target's current type is already non-optional
1169
// (the redundant-unwrap warning fires separately at that
1170
// site; recording the presence bit only grows every
1171
// downstream env for nothing).
1172
let target = _visitor.try_get_narrowing_target(unwrap.left)
1173
let target_type = if target? then target.type else null fi
1174
1175
if target? /\ target_type? /\ target_type.is_optional then
1176
_flow.mark_non_null(target)
1177
_flow.report_narrowing_site(
1178
unwrap.location,
1179
"narrowing-unwrap",
1180
"►",
1181
INLAY_TYPE.render(target_type.as_non_optional())
1182
)
1183
fi
1184
1185
// TODO, we could handle ref and ptr here as well
1186
si
1187
1188
visit_reference(reference: Trees.Expressions.REFERENCE) is
1189
let left_value = reference.left.value
1190
1191
if !left_value? \/ !left_value.type? then
1192
_logger.poison(reference.left.location, "reference expression has no value")
1193
reference.compile_expressions_state.value = DUMMY(Semantic.Types.ERROR(), reference.location)
1194
return
1195
elif !left_value.has_address then
1196
_logger.warn(reference.left.location, "reference-to-expression", "reference to an expression")
1197
fi
1198
1199
left_value.check_is_consumable(_logger, reference.left.location)
1200
1201
reference.compile_expressions_state.value = ADDRESS(left_value, _innate_symbol_lookup.get_reference_type(left_value.type!))
1202
si
1203
1204
get_reference_has_value(
1205
location: LOCATION,
1206
value: Value,
1207
member: Semantic.Symbols.Symbol?,
1208
bool_type: Type
1209
) -> Value is
1210
let null_check = HAS_VALUE(value, bool_type)
1211
1212
if member? then
1213
let member_check = member.load(location, value, _symbol_loader)
1214
1215
let and_identifier = Trees.Identifiers.Identifier(location, "/\\")
1216
let and_symbol = _visitor.find(and_identifier)
1217
1218
if and_symbol? /\ isa Semantic.Symbols.FUNCTION_GROUP(and_symbol) then
1219
_symbol_loader.find_symbol = (name: string) => _visitor.find(name)
1220
1221
let argument_types = Collections.LIST[Type]()
1222
argument_types.add(bool_type)
1223
argument_types.add(bool_type)
1224
1225
let overload_result =
1226
_overload_resolver.resolve(
1227
location,
1228
and_symbol,
1229
argument_types,
1230
false,
1231
false,
1232
false
1233
)
1234
1235
if overload_result? then
1236
let function = overload_result.function
1237
if function.is_unsafe_constraints then
1238
_logger.warn(location, "unchecked-constraints", "call to {function} has unchecked constraints")
1239
fi
1240
1241
let arguments = Collections.LIST[Value]()
1242
arguments.add(null_check)
1243
arguments.add(member_check)
1244
1245
return function.call(
1246
location,
1247
null,
1248
arguments,
1249
bool_type,
1250
_function_caller
1251
)
1252
fi
1253
fi
1254
fi
1255
1256
return null_check
1257
si
1258
1259
// For a `?` / `!` operand, identify the default variant to
1260
// dispatch against. Returns the union's `default_variant`
1261
// when the operand is the union (null when the union has
1262
// no default — a source union without one, or an older
1263
// cross-asm union compiled before the
1264
// `DEFAULT_VARIANT_ATTRIBUTE` marker existed); returns the
1265
// variant itself when the operand is already narrowed to
1266
// its parent union's default variant so the caller can
1267
// skip an extra cast. Null for any other shape — the
1268
// caller's branch then short-circuits `?` to a bare null
1269
// check and `!` to a non-null assert on the operand.
1270
_get_default_variant_for_operand(
1271
classy: Semantic.Symbols.Classy?
1272
) -> Semantic.Symbols.VARIANT? is
1273
if !classy? then
1274
return null
1275
fi
1276
1277
if classy.is_union then
1278
let union_classy = cast Semantic.Symbols.UNION?(classy)!
1279
return union_classy.default_variant
1280
fi
1281
1282
if classy.is_variant then
1283
let variant_classy = cast Semantic.Symbols.VARIANT?(classy)!
1284
1285
if !variant_classy.owner? \/ !isa Semantic.Symbols.UNION(variant_classy.owner) then
1286
return null
1287
fi
1288
1289
let owner_union = cast Semantic.Symbols.UNION(variant_classy.owner)
1290
1291
if owner_union.default_variant == variant_classy then
1292
return variant_classy
1293
fi
1294
fi
1295
1296
return null
1297
si
1298
1299
// A variable held in a value-type optional carrier - `Nullable[T]`
1300
// or `MAYBE[T]` - still holds the carrier where a type test has
1301
// narrowed it: the narrowing reaches the load as its type, but
1302
// what is stored is the struct. The payload is projected out and
1303
// viewed at the narrowed type, rather than the struct being read
1304
// as though it already were one.
1305
_project_narrowed_carrier(identifier: Trees.Expressions.IDENTIFIER, variable: Semantic.Symbols.Variable, load: IR.Values.Load.SYMBOL) is
1306
let declared = _flow.declared_type_of(variable)
1307
let narrowed = load.type
1308
1309
if !declared? \/ !narrowed? \/ !declared.is_value_type \/ !declared.is_optional \/ narrowed.is_optional then
1310
return
1311
fi
1312
1313
let inner = declared.optional_inner_type
1314
let value_member = declared.find_member("value")
1315
1316
if !inner? \/ !value_member? then
1317
return
1318
fi
1319
1320
load.narrow_snapshot_type(declared)
1321
1322
let projected: IR.Values.Value mut = value_member.load(identifier.location, load, _symbol_loader)
1323
1324
if !narrowed.is_equivalent_to(inner) then
1325
projected = IR.Values.CAST(narrowed, projected, false)
1326
1327
if narrowed.is_value_type then
1328
projected = IR.Values.UNBOX(projected)
1329
fi
1330
fi
1331
1332
identifier.compile_expressions_state.value = IR.Values.NARROW_PROJECT(load, projected)
1333
si
1334
1335
// `u?` lowers to `isa Default(u)`. The receiver's type
1336
// (often a generic instantiation of the union) drives
1337
// specialisation of the variant target type so the emitted
1338
// `isinst` references a loadable closed-generic class.
1339
_build_default_variant_isa(
1340
location: LOCATION,
1341
operand: Value,
1342
default_variant: Semantic.Symbols.VARIANT,
1343
bool_type: Type
1344
) -> Value is
1345
let variant_type =
1346
_condition_analyzer.try_get_variant_type_for_classy(
1347
operand.type!,
1348
default_variant
1349
)
1350
1351
if !variant_type? then
1352
return DUMMY(bool_type, location)
1353
fi
1354
1355
return IR.Values.ISA(bool_type, variant_type, operand)
1356
si
1357
1358
// `u!` lowers to `cast Default(u)`, with single-field
1359
// projection so the existing rule "default variant with one
1360
// field unwraps to that field" rides on the new lowering.
1361
// When the operand is already narrowed to the default
1362
// variant the cast is elided — the CLR accepts loading the
1363
// field directly off the variant-shaped receiver.
1364
_build_default_variant_unwrap(
1365
location: LOCATION,
1366
operand: Value,
1367
classy: Semantic.Symbols.Classy,
1368
default_variant: Semantic.Symbols.VARIANT
1369
) -> Value is
1370
let variant_type =
1371
_condition_analyzer.try_get_variant_type_for_classy(
1372
operand.type!,
1373
default_variant
1374
)
1375
1376
if !variant_type? then
1377
return DUMMY(Semantic.Types.ERROR(), location)
1378
fi
1379
1380
let variant_value: Value mut = operand
1381
1382
if !classy.is_variant then
1383
variant_value = IR.Values.CAST(variant_type, operand, false)
1384
fi
1385
1386
// Single-field unwrap rides on the cast: a default
1387
// variant declaring exactly one field — own or inherited
1388
// from the union's primary header — projects to that
1389
// field. Multi-field defaults return the variant itself
1390
// so callers can read every field via member access.
1391
if default_variant.field_count == 1 then
1392
let field_name = default_variant.get_destructure_member_name(0)
1393
1394
if field_name? then
1395
let field_member = variant_type.find_member(field_name)
1396
1397
if field_member? then
1398
return field_member.load(location, variant_value, _symbol_loader)
1399
fi
1400
fi
1401
fi
1402
1403
return variant_value
1404
si
1405
1406
// Build a hinted "narrow further" message when a member
1407
// access on a ONE_OF fails to find the member on the root
1408
// but every in-set subtype has a same-named member. Returns
1409
// null when the hint doesn't apply (receiver isn't a ONE_OF,
1410
// or the name is missing on at least one in-set subtype).
1411
// Subtype-private fields can't be accessed through ONE_OF
1412
// without IL-side subtype-dispatching support — for now we
1413
// just direct the user to narrow further. Applies uniformly
1414
// to a union's variants and a closed class's subclasses.
1415
try_describe_intersection_miss(
1416
receiver_type: Type,
1417
member_name: string
1418
) -> string? is
1419
if !isa Semantic.Types.ONE_OF(receiver_type) then
1420
return null
1421
fi
1422
1423
let one_of = receiver_type
1424
1425
let any_subtype mut = false
1426
for subtype in one_of.subtypes do
1427
if !subtype.find_member(member_name)? then
1428
return null
1429
fi
1430
any_subtype = true
1431
od
1432
1433
if !any_subtype then
1434
return null
1435
fi
1436
1437
let names = System.Text.StringBuilder()
1438
let seen_any mut = false
1439
for subtype in one_of.subtypes do
1440
if seen_any then
1441
names.append(" | ")
1442
fi
1443
names.append(subtype.name)
1444
seen_any = true
1445
od
1446
1447
return "member {member_name} is ambiguous in {names}"
1448
si
1449
1450
// Whether the member can only be reached through a receiver. A
1451
// method group reports nothing itself, so it is judged by its
1452
// overloads: a group holding a static one is reachable through
1453
// the type, and only a group that is entirely instance is not.
1454
_is_instance_only(symbol: Semantic.Symbols.Symbol) -> bool is
1455
if let group: Semantic.Symbols.FUNCTION_GROUP = symbol then
1456
return
1457
group.functions.count > 0 /\
1458
group.functions |> all(f => f.is_instance)
1459
fi
1460
1461
return symbol.is_instance
1462
si
1463
1464
// An indexer's accessors carry the CLR-required names, which are
1465
// not a member surface the language offers: an indexer is reached
1466
// by indexing. A name that resolves to one is refused rather than
1467
// left unfound, so the diagnostic can say what to write.
1468
_is_indexer_accessor(symbol: Semantic.Symbols.Symbol) -> bool is
1469
if let group: Semantic.Symbols.FUNCTION_GROUP = symbol then
1470
return group.functions |> any(f => f.is_indexer_accessor)
1471
fi
1472
1473
if let function: Semantic.Symbols.Function = symbol then
1474
return function.is_indexer_accessor
1475
fi
1476
1477
return false
1478
si
1479
1480
// Build the IR value for an `a?.b` access. Returns the
1481
// COALESCE_LOAD wrap when the member is a readable field or
1482
// property; returns null (so the caller falls back to a plain
1483
// access) on shapes that aren't lowered here. Emits a
1484
// diagnostic for each unsupported shape so the user learns
1485
// why the `?.` had no effect.
1486
_build_coalesce_load(
1487
member: Trees.Expressions.MEMBER,
1488
symbol: Semantic.Symbols.Symbol
1489
) -> Value? is
1490
// A method selection is coalesced at the enclosing call -
1491
// the whole call, argument evaluation included, must
1492
// short-circuit on an absent receiver - so a function
1493
// group falls through silently to the plain load the call
1494
// path consumes. An uncalled method reference through
1495
// `?.` then fails downstream exactly as it does through
1496
// `.`.
1497
if isa Semantic.Symbols.FUNCTION_GROUP(symbol) then
1498
return null
1499
fi
1500
1501
if
1502
!isa Semantic.Symbols.Field(symbol) /\
1503
!isa Semantic.Symbols.Property(symbol)
1504
then
1505
_logger.error(
1506
member.location,
1507
"coalescing member access supports fields, properties and method calls only"
1508
)
1509
return null
1510
fi
1511
1512
return build_coalesce_wrap(
1513
member,
1514
symbol.is_instance,
1515
from => symbol.load(member.location, from, _symbol_loader)
1516
)
1517
si
1518
1519
// Shared lowering for `a?.b` accesses and `a?.m(...)` calls:
1520
// receiver presence diagnostics, unwrap of the optional
1521
// receiver, optional widening of the result and the
1522
// COALESCE_LOAD composition. `build_member_value` produces
1523
// the member load or call against the supplied receiver
1524
// stand-in: the original receiver value when it is statically
1525
// present, or a STACK_TOP of the unwrapped receiver type
1526
// inside the short-circuit arm. `receiver_consumed` is false
1527
// when the member is static - the present arm then pops the
1528
// tested receiver instead of feeding it to the member value.
1529
// Returns null, with any diagnostic already emitted, when the
1530
// shape can't be lowered.
1531
build_coalesce_wrap(
1532
member: Trees.Expressions.MEMBER,
1533
receiver_consumed: bool,
1534
build_member_value: (Value) -> Value?
1535
) -> Value? is
1536
build_optional_subject_wrap(
1537
member.left,
1538
member.location,
1539
receiver_consumed,
1540
"'?.'",
1541
build_member_value
1542
)
1543
si
1544
1545
// The same lowering for a `~>` thread-first call, whose
1546
// subject is tested for presence the way a `?.` receiver is:
1547
// absent skips the call - argument evaluation included - and
1548
// yields the absent value; present runs it against the
1549
// unwrapped subject.
1550
build_propagating_subject_wrap(
1551
subject: Trees.Expressions.Expression,
1552
build_member_value: (Value) -> Value?
1553
) -> Value? is
1554
build_optional_subject_wrap(
1555
subject,
1556
subject.location,
1557
true,
1558
"'~>'",
1559
raw => (
1560
let raw_type = raw.type
1561
1562
if !raw_type? then
1563
null
1564
1565
// Inside the short-circuit arm the stand-in for
1566
// the unwrapped subject sits on the evaluation
1567
// stack, where only a value built to consume it
1568
// first can read it. A call through a stored
1569
// function value pushes its callee before its
1570
// arguments, so the subject cannot stay where the
1571
// wrap left it: spill it to a local and hand the
1572
// built value a load of that local instead.
1573
elif isa IR.Values.STACK_TOP(raw) then
1574
let name = ".propagate.{IR.TEMP.get_next_id()}"
1575
let built = build_member_value(IR.Values.Load.TEMP(name, raw_type))
1576
1577
if built? then
1578
IR.Values.SPILLED_SUBJECT(name, raw_type, built)
1579
else
1580
null
1581
fi
1582
1583
// A statically present subject is an ordinary
1584
// value; the built call generates it as its first
1585
// argument in the caller's own order.
1586
else
1587
build_member_value(raw)
1588
fi
1589
)
1590
)
1591
si
1592
1593
// Shared core of the coalescing wraps: an optional-typed
1594
// expression - a `?.` receiver or a `~>` subject - tested for
1595
// presence, with `build_member_value` producing the value
1596
// against the supplied receiver stand-in: the original value
1597
// when it is statically present, or a STACK_TOP of the
1598
// unwrapped type inside the short-circuit arm.
1599
// `receiver_consumed` is false when the present arm pops the
1600
// tested value rather than feeding it to the member value.
1601
// Returns null, with any diagnostic already emitted, when the
1602
// shape can't be lowered.
1603
build_optional_subject_wrap(
1604
subject: Trees.Expressions.Expression,
1605
diagnostic_location: Source.LOCATION,
1606
receiver_consumed: bool,
1607
spelling: string,
1608
build_member_value: (Value) -> Value?
1609
) -> Value? is
1610
let receiver = subject.value
1611
1612
if !receiver? \/ !receiver.type? then
1613
return null
1614
fi
1615
1616
let receiver_type = receiver.type
1617
1618
// A non-optional receiver (declared so, or flow-narrowed
1619
// from `T?` to `T`) is statically present, so the null
1620
// test can never fail. One flow-narrowed from optional
1621
// gets a redundancy warning; a never-optional value type
1622
// is an error (a struct is never null, so the test has
1623
// no defensive value); a never-optional reference stays
1624
// legal with no diagnostic, as the null-defense idiom at
1625
// unsound boundaries (reflected APIs, staged tree
1626
// construction) where null can arrive despite the static
1627
// type. The access is emitted directly — the conditional
1628
// path is unsound for a value-type receiver (`dup;
1629
// brfalse` rejects a struct on the stack) and unnecessary
1630
// work for a reference receiver. The result is still
1631
// widened to the optional shape the user asked for with
1632
// `?.`.
1633
if !receiver_type.is_optional then
1634
1635
let target = _visitor.try_get_narrowing_target(subject)
1636
let declared = if target? then _flow.declared_type_of(target) else null fi
1637
1638
if declared? /\ declared.is_optional then
1639
if !target? \/ _flow.symbol_fact_validated(target) then
1640
_logger.warn(
1641
diagnostic_location,
1642
"redundant-coalesce",
1643
"{spelling} is redundant here"
1644
)
1645
fi
1646
elif receiver_type.is_value_type then
1647
_logger.error(subject.location, "receiver is not optional")
1648
return null
1649
fi
1650
1651
let direct = build_member_value(receiver)
1652
1653
if !direct? then
1654
return null
1655
fi
1656
1657
return widen_coalesce_result(direct)
1658
fi
1659
1660
let receiver_is_value_optional = receiver_type.is_value_type
1661
1662
// The member symbol lives on the receiver's underlying
1663
// type regardless of the `?` flag, so the member value is
1664
// built against a STACK_TOP of the non-optional type and
1665
// emits the expected access or call ops with no preceding
1666
// receiver gen.
1667
let inner_type =
1668
if receiver_is_value_optional then
1669
receiver_type.optional_inner_type
1670
else
1671
receiver_type.as_non_optional()
1672
fi
1673
1674
if !inner_type? then
1675
return null
1676
fi
1677
1678
// STACK_TOP (has_address=false): the inner-receiver
1679
// value is on the stack, not its address. A struct
1680
// member access reads the value off the top, and the
1681
// existing call_struct_method path's `ADDRESS(from)`
1682
// spill takes its address as needed. A STACK_TOP_ADDRESS
1683
// here would tell that spill the address is already
1684
// present and skip it — producing a `call instance` on a
1685
// bare value, which segfaults at the JIT.
1686
let stack_top: IR.Values.Value = IR.Values.STACK_TOP(inner_type)
1687
1688
let raw = build_member_value(stack_top)
1689
1690
if !raw? then
1691
return null
1692
fi
1693
1694
let arm = _widen_coalesce_arm(raw)
1695
1696
if !arm? then
1697
return null
1698
fi
1699
1700
if receiver_is_value_optional then
1701
// Build presence test (get_has_value) and value
1702
// extract (get_value) against a STACK_TOP_ADDRESS so
1703
// both consume the address dup'd by COALESCE_LOAD.
1704
let address_stand_in = IR.Values.STACK_TOP_ADDRESS(receiver_type)
1705
let has_value_member = receiver_type.find_member("has_value")
1706
let value_member = receiver_type.find_member("value")
1707
1708
if !has_value_member? \/ !value_member? then
1709
_logger.poison(
1710
diagnostic_location,
1711
"coalescing member access receiver {receiver_type} missing has_value or value member"
1712
)
1713
return null
1714
fi
1715
1716
let presence_test = has_value_member.load(diagnostic_location, address_stand_in, _symbol_loader)
1717
1718
let value_extract =
1719
if receiver_consumed then
1720
value_member.load(diagnostic_location, address_stand_in, _symbol_loader)
1721
else
1722
null
1723
fi
1724
1725
return IR.Values.COALESCE_LOAD(
1726
receiver,
1727
presence_test,
1728
value_extract,
1729
arm.member_value,
1730
arm.null_value,
1731
arm.result_type,
1732
receiver_consumed
1733
)
1734
fi
1735
1736
return IR.Values.COALESCE_LOAD(receiver, arm.member_value, arm.null_value, arm.result_type, receiver_consumed)
1737
si
1738
1739
// Re-seat a coalescing member access whose loaded member is
1740
// itself then invoked - `a?.f(...)` where f is a
1741
// function-typed field or property. The invocation must sit
1742
// inside the short-circuit arm, so the original wrap's
1743
// receiver plumbing is reused around the call value, which
1744
// consumes the original member load from the same arm.
1745
rewrap_coalesce_call(
1746
original: IR.Values.COALESCE_LOAD,
1747
call_value: Value
1748
) -> Value? is
1749
let arm = _widen_coalesce_arm(call_value)
1750
1751
if !arm? then
1752
return null
1753
fi
1754
1755
if original.receiver_is_value_type then
1756
return IR.Values.COALESCE_LOAD(
1757
original.receiver,
1758
original.presence_test!,
1759
original.value_extract,
1760
arm.member_value,
1761
arm.null_value,
1762
arm.result_type,
1763
true
1764
)
1765
fi
1766
1767
return IR.Values.COALESCE_LOAD(
1768
original.receiver,
1769
arm.member_value,
1770
arm.null_value,
1771
arm.result_type,
1772
true
1773
)
1774
si
1775
1776
// The direct result of a coalescing access or call whose
1777
// receiver is statically present, widened to the optional
1778
// shape the `?.` asked for. A void result stays void.
1779
widen_coalesce_result(direct: Value) -> Value? is
1780
let direct_type = direct.type
1781
1782
if !direct_type? then
1783
return null
1784
fi
1785
1786
if direct_type.is_void then
1787
return direct
1788
fi
1789
1790
let direct_result = build_optional_type(direct_type, _innate_symbol_lookup)
1791
1792
if !direct_type.is_optional /\ direct_result.is_value_type then
1793
return IR.Values.WRAP_OPTIONAL(direct_result, direct)
1794
fi
1795
1796
// Reference T and T? share IL — re-type the load.
1797
if !direct_type.matches(direct_result) then
1798
return IR.Values.TYPE_WRAPPER(direct_result, direct)
1799
fi
1800
1801
return direct
1802
si
1803
1804
// Widen a present-arm member value to the optional result
1805
// shape of the enclosing `?.`:
1806
// - void → no widening, no null sentinel.
1807
// - reference T → T flagged optional, IL identity.
1808
// - value T → NULLABLE[T] via WRAP_OPTIONAL (newobj
1809
// Nullable<T>::.ctor(T)).
1810
// - already-optional U → identity.
1811
_widen_coalesce_arm(raw: Value) -> COALESCE_ARM? is
1812
let raw_type = raw.type
1813
1814
if !raw_type? then
1815
return null
1816
fi
1817
1818
if raw_type.is_void then
1819
return COALESCE_ARM(raw, null, raw_type)
1820
fi
1821
1822
let result_type = build_optional_type(raw_type, _innate_symbol_lookup)
1823
1824
let member_value: Value =
1825
if !raw_type.is_optional /\ result_type.is_value_type then
1826
IR.Values.WRAP_OPTIONAL(result_type, raw)
1827
else
1828
raw
1829
fi
1830
1831
let null_value = _build_null_sentinel(result_type)
1832
1833
if !null_value? then
1834
return null
1835
fi
1836
1837
return COALESCE_ARM(member_value, null_value, result_type)
1838
si
1839
1840
// The null-arm sentinel for a coalescing access of result
1841
// type `result_type`: `ldnull` for a reference type, a fresh
1842
// DEFAULT (which hoists a `.locals init` and pushes the zero
1843
// value) for a value-type optional like NULLABLE[T] or
1844
// MAYBE[T].
1845
_build_null_sentinel(result_type: Type?) -> Value? is
1846
if !result_type? then
1847
return null
1848
fi
1849
1850
if result_type.is_value_type then
1851
return IR.Values.DEFAULT(result_type)
1852
fi
1853
1854
return IR.Values.NULL(result_type)
1855
si
1856
1857
// The `T?` form of `t`: NULLABLE[T] for a value type, the
1858
// flagged reference for a reference type. `as_optional()`
1859
// alone returns the receiver for a value type — leaving the
1860
// result mistyped as `T` — so this dispatches the way
1861
// `resolve_type_expressions` does for a written `T?`. Static
1862
// so the rule can be pinned without standing up a full
1863
// COMPILE_ACCESS instance.
1864
build_optional_type(t: Type, lookup: Semantic.Lookups.InnateSymbolLookup) -> Type static is
1865
if t.is_optional then
1866
return t
1867
fi
1868
1869
if t.is_value_type then
1870
return lookup.get_optional_type(t)
1871
fi
1872
1873
return t.as_optional()
1874
si
1875
1876
_bare_generic_alias_type(location: Source.LOCATION, alias: Semantic.Symbols.TYPE_ALIAS) -> Semantic.Types.Type is
1877
if let class_type = BARE_GENERIC_ALIAS.class_type(alias) then
1878
return class_type
1879
fi
1880
1881
_logger.error(location, "type alias {alias.name} needs type arguments here")
1882
1883
return Semantic.Types.ERROR()
1884
si
1885
si
1886
1887
// The present-arm pieces of a coalescing wrap: the member value
1888
// widened to the optional result shape, the null-arm sentinel
1889
// (absent when the member value is void) and the result type.
1890
class COALESCE_ARM(
1891
member_value: Value,
1892
null_value: Value?,
1893
result_type: Type
1894
) is
1895
si
1896
si