Skip to content
← Back

src/semantic/symbol_loader.ghul

1
namespace Semantic is
2
use System.NotImplementedException
3
4
use Logging
5
6
use IR.Values
7
use IR.VALUE_BOXER
8
9
use Types.Type
10
11
class SYMBOL_LOADER is
12
_null_find_symbol: (string) -> Symbols.Symbol? static
13
14
_logger: Logger
15
_symbol_table: SYMBOL_TABLE
16
_function_caller: FUNCTION_CALLER
17
_value_boxer: IR.VALUE_BOXER
18
19
_innate_symbol_lookup: Lookups.InnateSymbolLookup
20
21
find_symbol: (string) -> Symbols.Symbol? public
22
23
// A call through a null anon function does not always produce a
24
// sane stack trace, so the unset state is a function that says so.
25
init() static is
26
_null_find_symbol =
27
(name: string) -> Symbols.Symbol? is
28
throw NotImplementedException("find_symbol is not set")
29
si
30
si
31
32
init(
33
logger: Logger,
34
symbol_table: SYMBOL_TABLE,
35
function_caller: FUNCTION_CALLER,
36
value_boxer: VALUE_BOXER,
37
innate_symbol_lookup: Lookups.InnateSymbolLookup
38
) is
39
super.init()
40
41
_logger = logger
42
_symbol_table = symbol_table
43
_function_caller = function_caller
44
_value_boxer = value_boxer
45
_innate_symbol_lookup = innate_symbol_lookup
46
47
find_symbol = _null_find_symbol
48
si
49
50
load_self(location: Source.LOCATION) -> Value is
51
let context = _symbol_table.current_function
52
53
if context? then
54
let result = context.load_self(location, self)
55
56
return result
57
fi
58
59
IoC.CONTAINER.instance.logger.error(location, "cannot access instance member from non-instance context")
60
61
return IR.Values.DUMMY(Types.ERROR(), Source.LOCATION.internal)
62
si
63
64
load_outer_self(location: Source.LOCATION) -> Value is
65
let context = _symbol_table.current_function
66
67
if context? then
68
let result = context.load_outer_self(location, self)
69
70
assert result? else "load outer self did not return a value: {context}"
71
72
return result
73
fi
74
75
IoC.CONTAINER.instance.logger.error(location, "cannot access instance member from non-instance context")
76
77
return IR.Values.DUMMY(Types.ERROR(), Source.LOCATION.internal)
78
si
79
80
load_namespace(symbol: Symbols.NAMESPACE) -> Value =>
81
Load.SYMBOL(null, symbol)
82
83
load_class(symbol: Symbols.CLASS) -> Value =>
84
TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal)
85
86
load_trait(symbol: Symbols.TRAIT) -> Value =>
87
TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal)
88
89
load_struct(symbol: Symbols.STRUCT) -> Value =>
90
TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal)
91
92
load_union(symbol: Symbols.UNION) -> Value =>
93
TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal)
94
95
load_variant(symbol: Symbols.VARIANT) -> Value =>
96
TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal)
97
98
load_enum_struct_member(symbol: Symbols.ENUM_STRUCT_MEMBER) -> Value =>
99
if let number = symbol.numeric_value then
100
Symbols.ENUM_CONSTANT.from_number(symbol.type, number, _innate_symbol_lookup)
101
else
102
Symbols.ENUM_CONSTANT.from_pattern(
103
symbol.type,
104
_wide_enum_pattern(symbol.emitted_value),
105
_innate_symbol_lookup)
106
fi
107
108
// A member of an enum wider than int32 has no int32 value to
109
// read and arrives as the text it was declared with. What the
110
// load takes is the bit pattern, so both signednesses are tried
111
// before giving up: `[Flags] enum : uint` with the high bit set
112
// reads as unsigned, a negative `enum : long` as signed.
113
_wide_enum_pattern(text: string) -> ulong static is
114
let unsigned: ulong mut = _
115
116
if ulong.try_parse(text, unsigned ref) then
117
return unsigned
118
fi
119
120
let signed: long mut = _
121
122
if long.try_parse(text, signed ref) then
123
return cast ulong(signed)
124
fi
125
126
return 0UL
127
si
128
129
load_instance_anonymous_function(symbol: Symbols.Symbol, func_type: Type) -> Value =>
130
Load.INSTANCE_ANONYMOUS_FUNCTION(symbol, func_type)
131
132
load_static_anonymous_function(symbol: Symbols.Symbol, func_type: Type) -> Value =>
133
Load.STATIC_ANONYMOUS_FUNCTION(symbol, func_type)
134
135
load_global_anonymous_function(symbol: Symbols.Symbol, func_type: Type) -> Value =>
136
Load.GLOBAL_ANONYMOUS_FUNCTION(symbol, func_type)
137
138
load_function_group(from: Value?, symbol: Symbols.Symbol) -> Value =>
139
Load.SYMBOL(from, symbol)
140
141
load_global_function(symbol: Symbols.Symbol) -> Value =>
142
Load.SYMBOL(null, symbol)
143
144
load_instance_method(location: Source.LOCATION, from: Value?, symbol: Symbols.Symbol) -> Value
145
=> Load.SYMBOL(from, symbol)
146
147
load_struct_method(location: Source.LOCATION, from: Value?, symbol: Symbols.Symbol) -> Value
148
=> Load.SYMBOL(from, symbol)
149
150
// FIXME: this needs to create a pointer to the function:
151
load_static_method(symbol: Symbols.Symbol) -> Value
152
=> Load.SYMBOL(null, symbol)
153
154
load_local_variable(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is
155
let function = _symbol_table.current_function
156
157
if function? /\ symbol.owner != function then
158
let raw = function.load_captured_value(location, symbol, self)
159
160
// Captured boxed locals: the frame field holds
161
// BOX[T]; the user-code-side read wants T, so
162
// unwrap via `.value`. Inter-frame transfers
163
// (load_outer_captured_value chaining for
164
// nested closures) call load_captured_value
165
// directly and don't hit this unwrap — they
166
// need the box reference to pass to the next
167
// frame's constructor.
168
if symbol.is_boxed then
169
let value_member = resolve_box_value_member(symbol)
170
171
if value_member? then
172
return value_member.load(location, raw, self)
173
fi
174
fi
175
176
return raw
177
fi
178
179
if symbol.is_boxed then
180
return _load_boxed_local(location, symbol)
181
fi
182
183
return Load.LOCAL_VARIABLE(symbol)
184
si
185
186
load_outer_local_variable(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is
187
let function = _symbol_table.current_function
188
189
if function? /\ symbol.owner != function then
190
let result = function.load_outer_captured_value(location, symbol, self)
191
192
assert result? else "load outer captured value did not return a value: {function} {symbol}"
193
194
return result
195
fi
196
197
let raw = Load.LOCAL_VARIABLE(symbol)
198
199
if symbol.is_boxed then
200
// The slot value IS the BOX[T] reference; tell the
201
// IR layer its type is BOX[T] rather than T (the
202
// load IR derives its type from `symbol.type`).
203
// Used by `closure.find_or_add_capture` -> frame
204
// construction at closure-build time.
205
return TYPE_WRAPPER(symbol.storage_type!, raw)
206
fi
207
208
return raw
209
si
210
211
store_local_variable(location: Source.LOCATION, symbol: Symbols.Variable, value: Value, is_initialize: bool) -> Value is
212
// A local written to before the declaration that types it
213
// has been compiled - an assignment inside the local's own
214
// initializer, which the walk reaches first - has no type to
215
// store through. The read that got there is already
216
// reported, so this is the consequence of a diagnostic
217
// rather than one of its own; report only where nothing
218
// else has, so that no store reaches code generation
219
// unexplained.
220
if !symbol.type? then
221
if IoC.CONTAINER.instance.logger.is_clean then
222
IoC.CONTAINER.instance.logger.error(location, "cannot assign to {symbol.name} here")
223
fi
224
225
return DUMMY(Types.ERROR(), location)
226
fi
227
228
let function = _symbol_table.current_function
229
230
if !is_initialize then
231
// Assignability is a static property of the declaration:
232
// a mut local is always assignable - captured mut locals
233
// are stored through a shared BOX[T] cell - and a non-mut
234
// local never is. Whether the box exists is a code
235
// generation concern, not a legality one.
236
if symbol.is_disposed then
237
IoC.CONTAINER.instance.logger.error(location, "scoped disposal value may not be assigned to")
238
elif !symbol.is_mutable_marked then
239
IoC.CONTAINER.instance.logger.error(location, "local value cannot be reassigned")
240
elif
241
!symbol.is_boxed /\
242
(symbol.is_captured \/ !function? \/ symbol.owner != function) /\
243
_wants_il
244
then
245
// mark-boxed-locals boxes every captured, reassigned
246
// mut local before compile-expressions on an IL-bound
247
// build, so reaching this store unboxed means the
248
// boxing analysis missed the assignment - erroring
249
// beats emitting a store the capture cannot observe.
250
IoC.CONTAINER.instance.logger.error(location, "captured value may not be assigned to")
251
fi
252
fi
253
254
// Boxed store from inside the closure body that
255
// captured this local: route through the closure's
256
// frame so we hit the shared BOX[T] cell, not the
257
// (invisible-from-here) outer slot.
258
if symbol.is_boxed /\ !is_initialize /\ function? /\ symbol.owner != function then
259
let result = function.store_captured_value(location, symbol, value, self)
260
261
return result
262
fi
263
264
if symbol.is_boxed then
265
return _store_boxed_local(location, symbol, value, is_initialize)
266
fi
267
268
return Store.LOCAL_VARIABLE(symbol, _value_boxer.box_if_needed(value, symbol.type!))
269
si
270
271
// Boxed-local read: load slot (gives BOX[T] reference)
272
// and field-load `.value`. The TYPE_WRAPPER re-types the
273
// slot load as BOX[T] for the benefit of the follow-on
274
// instance-field access; emitted IL is just ldloc.
275
// The `.value` access goes through `member.load`
276
// polymorphically — works whether the resolved member
277
// is a Field, INSTANCE_FIELD, or auto-generated property
278
// shape that wraps a public field.
279
_load_boxed_local(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is
280
let member = resolve_box_value_member(symbol)
281
282
if !member? then
283
return Load.LOCAL_VARIABLE(symbol)
284
fi
285
286
let raw_load = Load.LOCAL_VARIABLE(symbol)
287
let typed_box = TYPE_WRAPPER(symbol.storage_type!, raw_load)
288
289
return member.load(location, typed_box, self)
290
si
291
292
// Boxed-local write: on declaration init, construct a
293
// fresh `Ghul.BOX[T](value)` (or `Ghul.BOX[T]()` when no
294
// initializer is supplied) and store the reference; on
295
// reassignment, load the slot's BOX[T] and field-store to
296
// `.value`. Allocating the empty box at declaration time —
297
// not lazily on first write — is what makes by-reference
298
// capture work for a forward-declared mutable (`let f mut;
299
// let g = () => f(); f = ...`): the closure-frame ctor
300
// receives the same heap cell the later assignment writes
301
// to.
302
// Declaring a captured, reassigned local that has no initializer:
303
// there is no value to store, only the empty box to allocate.
304
store_empty_boxed_local(location: Source.LOCATION, symbol: Symbols.Variable) -> Value =>
305
_store_boxed_local(location, symbol, null, true)
306
307
_store_boxed_local(location: Source.LOCATION, symbol: Symbols.Variable, value: Value?, is_initialize: bool) -> Value is
308
if is_initialize then
309
// `box_type.symbol` is the specialised
310
// Symbols.GENERIC (BOX[T]), constructed by
311
// `Types.GENERIC.init`. find_member on it
312
// returns the specialised init constructor(s)
313
// with the concrete T substituted in — but
314
// BOX has two overloads (`init()` and
315
// `init(value: T)`), so we get a FUNCTION_GROUP
316
// back. Pick the overload matching what we have.
317
let box_type = symbol.storage_type!
318
let arity = if value? then 1 else 0 fi
319
let ctor = _resolve_box_constructor(box_type, arity)
320
321
assert ctor? else "no box constructor of arity {arity} for {box_type}"
322
323
let arguments =
324
if value? then
325
Collections.LIST[Value]([value])
326
else
327
Collections.LIST[Value]()
328
fi
329
let new_box = NEW(box_type, ctor, arguments)
330
331
return Store.LOCAL_VARIABLE(symbol, new_box)
332
fi
333
334
let member = resolve_box_value_member(symbol)
335
336
if !member? then
337
return Store.LOCAL_VARIABLE(symbol, _value_boxer.box_if_needed(value!, symbol.type!))
338
fi
339
340
let raw_load = Load.LOCAL_VARIABLE(symbol)
341
let typed_box = TYPE_WRAPPER(symbol.storage_type!, raw_load)
342
343
return member.store(location, typed_box, value!, self, false)
344
si
345
346
// Constructor of `Ghul.BOX[T]` taking `arity` arguments —
347
// 1 for the value-carrying overload, 0 for the empty one.
348
// `find_member("init")` on the specialised GENERIC
349
// returns a FUNCTION_GROUP (BOX has both); pick the
350
// matching overload. Returns null if box_type is unresolved
351
// or the constructor can't be located — callers null-check
352
// before use.
353
_resolve_box_constructor(box_type: Type?, arity: int) -> Symbols.Function? is
354
if !box_type? then
355
return null
356
fi
357
358
let member = box_type.symbol.find_member("init")
359
360
if !member? then
361
return null
362
fi
363
364
let direct = cast Symbols.Function?(member)
365
366
if direct? then
367
if direct.are_arguments_declared /\ direct.arguments.count == arity then
368
return direct
369
else
370
return null
371
fi
372
fi
373
374
let group = cast Symbols.FUNCTION_GROUP?(member)
375
376
if !group? then
377
return null
378
fi
379
380
for f in group.functions do
381
if f.are_arguments_declared /\ f.arguments.count == arity then
382
return f
383
fi
384
od
385
386
return null
387
si
388
389
// The `value` member of `Ghul.BOX[T]` — what reads and
390
// writes ultimately touch when `symbol.is_boxed`.
391
// Returned as a generic Symbol so polymorphic
392
// `.load(location, from, loader)` /
393
// `.store(location, from, value, loader, is_initialize)`
394
// dispatch handles Field vs INSTANCE_FIELD vs
395
// auto-generated property shapes uniformly.
396
resolve_box_value_member(symbol: Symbols.Variable) -> Symbols.Symbol? is
397
let box_type = symbol.storage_type
398
399
if !box_type? then
400
return null
401
fi
402
403
return box_type.symbol.find_direct("value")
404
si
405
406
load_local_argument(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is
407
let function = _symbol_table.current_function
408
409
if function? /\ symbol.owner != function then
410
let result = function.load_captured_value(location, symbol, self)
411
412
return result
413
fi
414
415
return Load.LOCAL_ARGUMENT(symbol)
416
si
417
418
load_outer_local_argument(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is
419
let function = _symbol_table.current_function
420
421
if function? /\ symbol.owner != function then
422
let result = function.load_outer_captured_value(location, symbol, self)
423
424
assert result? else "load outer captured value did not return a value: {function} {symbol}"
425
426
return result
427
fi
428
429
return Load.LOCAL_VARIABLE(symbol)
430
si
431
432
store_local_argument(location: Source.LOCATION, symbol: Symbols.Variable, value: Value, is_initialize: bool) -> Value is
433
let function = _symbol_table.current_function
434
435
if !is_initialize then
436
// FIXME: need proper dataflow analysis
437
if !symbol.is_mutable_marked then
438
IoC.CONTAINER.instance.logger.error(location, "local value cannot be reassigned")
439
elif symbol.is_captured \/ !function? \/ symbol.owner != function then
440
// Captured arguments are not yet routed through a
441
// shared BOX[T] cell the way captured mut locals
442
// are - the closure frame copies the argument value
443
// when it is constructed. Until they are boxed, an
444
// assignment on either side of the capture would be
445
// invisible to the other, so it is rejected even
446
// for a mut argument.
447
IoC.CONTAINER.instance.logger.error(location, "captured argument may not be assigned to")
448
fi
449
fi
450
451
return Store.LOCAL_ARGUMENT(symbol, _value_boxer.box_if_needed(value, symbol.type!))
452
si
453
454
// True when this build lowers the IR to IL - the only case where
455
// boxing decisions have observable consequences.
456
_wants_il: bool =>
457
IoC.CONTAINER.instance.build_flags.want_assembler \/
458
IoC.CONTAINER.instance.build_flags.want_executable
459
460
load_global_variable(symbol: Symbols.Variable) -> Value =>
461
Load.GLOBAL_FIELD(symbol)
462
463
// Whether the value wants boxing is a question about the
464
// variable's type, and a top-level `let` assigned above its own
465
// declaration has none - the statement that types it has not
466
// been walked. The assignment is reported at its own site, so
467
// the unboxed value stands in rather than forcing an absent
468
// type here.
469
store_global_variable(symbol: Symbols.Variable, value: Value) -> Value =>
470
Store.GLOBAL_FIELD(
471
symbol,
472
if let variable_type = symbol.type then
473
_value_boxer.box_if_needed(value, variable_type)
474
else
475
value
476
fi
477
)
478
479
load_instance_variable(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Variable) -> Value is
480
if !from? then
481
from = load_self(location)
482
fi
483
484
return Load.INSTANCE_FIELD(from, symbol)
485
si
486
487
store_instance_variable(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Variable, value: Value) -> Value is
488
if !from? then
489
from = load_self(location)
490
fi
491
492
return Store.INSTANCE_FIELD(from, symbol, _value_boxer.box_if_needed(value, symbol.type!))
493
si
494
495
load_struct_variable(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Variable) -> Value is
496
if !from? then
497
from = load_self(location)
498
elif from.has_address then
499
from = ADDRESS(from)
500
fi
501
502
return Load.INSTANCE_FIELD(from, symbol)
503
si
504
505
store_struct_variable(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Variable, value: Value) -> Value is
506
if !from? then
507
from = load_self(location)
508
elif from.has_address then
509
from = ADDRESS(from)
510
else
511
_logger.info(location, "member updated in discarded value")
512
fi
513
514
return Store.INSTANCE_FIELD(from, symbol, _value_boxer.box_if_needed(value, symbol.type!))
515
si
516
517
518
load_static_field(symbol: Symbols.Variable) -> Value =>
519
Load.STATIC_FIELD(symbol)
520
521
store_static_field(symbol: Symbols.Variable, value: Value) -> Value =>
522
Store.STATIC_FIELD(symbol, _value_boxer.box_if_needed(value, symbol.type!))
523
524
// A constant is carried in metadata as text, in the same form a
525
// reflected argument default is, so the same conversion reads it
526
// back at the type the constant was declared with - through the
527
// entry that takes the text as a value, because a constant string
528
// can hold any text and so cannot reserve one to mean null.
529
load_constant_field(symbol: Symbols.CONSTANT_FIELD) -> Value =>
530
if let text = symbol.constant_value then
531
Syntax.Process.DEFAULT_ARGUMENT_VALUES.build_value(
532
text,
533
symbol.type!,
534
_innate_symbol_lookup
535
)
536
else
537
IR.Values.DEFAULT(symbol.type!)
538
fi
539
540
// Reached when a constant is written to through a name brought
541
// into scope by `use`, which stores without asking whether the
542
// symbol accepts one. The member-access form is turned away
543
// earlier, as a field only its declaring type may assign to, so
544
// the two spellings report differently - but both have to
545
// report, because a constant has no field for a write to reach.
546
store_constant_field(location: Source.LOCATION, symbol: Symbols.CONSTANT_FIELD) -> Value is
547
_logger.error(location, "{symbol} is not assignable")
548
549
return IR.Values.DUMMY(Types.ERROR(), location)
550
si
551
552
// A property error recovery declared without building its read
553
// function has nothing to load, and the diagnostic for it is already
554
// reported, so the wrappers load an ERROR value there, as the
555
// `store_*_property` wrappers below do for a store.
556
load_instance_property(location: Source.LOCATION, from: Value?, symbol: Symbols.Property) -> Value is
557
let result = load_property(location, from, symbol, false)
558
return if result? then result else IR.Values.DUMMY(Types.ERROR(), location) fi
559
si
560
561
load_static_property(location: Source.LOCATION, symbol: Symbols.Property) -> Value is
562
let result = load_property(location, null, symbol, true)
563
return if result? then result else IR.Values.DUMMY(Types.ERROR(), location) fi
564
si
565
566
load_global_property(location: Source.LOCATION, symbol: Symbols.Property) -> Value is
567
let result = load_property(location, null, symbol, true)
568
return if result? then result else IR.Values.DUMMY(Types.ERROR(), location) fi
569
si
570
571
load_property(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Property, is_static: bool) -> Value? is
572
if !from? /\ !is_static then
573
from = load_self(location)
574
fi
575
576
// Error recovery can declare a property from a malformed
577
// declaration without a read function; the syntax error is the
578
// diagnosis, and reading the property is an ordinary error
579
// after it rather than the end of the compile.
580
if !symbol.read_function? then
581
_logger.error(location, "property {symbol.name} is not readable")
582
return null
583
fi
584
585
let read_function = symbol.read_function
586
587
find_symbol = _null_find_symbol
588
589
// Flow narrowing reconciles a narrowed property's
590
// `Property.type` the same way it does `Variable.type`;
591
// the load's value must carry that narrowed view rather
592
// than the getter's declared return type. Outside a
593
// narrow the two are the same type instance, so this is
594
// inert.
595
let narrowed_type =
596
if symbol.type? /\ symbol.type != read_function.return_type then
597
symbol.type
598
else
599
null
600
fi
601
602
return read_function.call(location, from, Collections.LIST[Value](0), narrowed_type, _function_caller)
603
si
604
605
store_instance_property(location: Source.LOCATION, from: Value?, symbol: Symbols.Property, value: Value) -> Value is
606
let result = store_property(location, from, symbol, value, false)
607
return if result? then result else IR.Values.DUMMY(Types.ERROR(), location) fi
608
si
609
store_static_property(location: Source.LOCATION, symbol: Symbols.Property, value: Value) -> Value is
610
let result = store_property(location, null, symbol, value, true)
611
return if result? then result else IR.Values.DUMMY(Types.ERROR(), location) fi
612
si
613
store_global_property(location: Source.LOCATION, symbol: Symbols.Property, value: Value) -> Value is
614
let result = store_property(location, null, symbol, value, true)
615
return if result? then result else IR.Values.DUMMY(Types.ERROR(), location) fi
616
si
617
618
store_property(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Property, value: Value, is_static: bool) -> Value? is
619
if !from? /\ !is_static then
620
from = load_self(location)
621
fi
622
623
if symbol.assign_function == null then
624
_logger.error(location, "property {symbol} is not assignable")
625
626
assert !symbol.is_assignable else "property {symbol} is assignable but does not have an assign accessor function"
627
628
return null
629
fi
630
631
find_symbol = _null_find_symbol
632
633
return TYPE_WRAPPER(
634
cast Types.Typed(symbol).type!,
635
symbol.assign_function!.call(location, from, Collections.LIST([value]), null, _function_caller)
636
)
637
si
638
si
639
si