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
init(
24
logger: Logger,
25
symbol_table: SYMBOL_TABLE,
26
function_caller: FUNCTION_CALLER,
27
value_boxer: VALUE_BOXER,
28
innate_symbol_lookup: Lookups.InnateSymbolLookup
29
) is
30
super.init();
31
32
// calls to null anon functions don't always produce a sane stack trace
33
if _null_find_symbol == null then
34
_null_find_symbol =
35
(name: string) -> Symbols.Symbol? is
36
throw NotImplementedException("find_symbol is not set");
37
si;
38
fi
39
40
_logger = logger;
41
_symbol_table = symbol_table;
42
_function_caller = function_caller;
43
_value_boxer = value_boxer;
44
_innate_symbol_lookup = innate_symbol_lookup;
45
46
find_symbol = _null_find_symbol;
47
si
48
49
load_self(location: Source.LOCATION) -> Value is
50
let context = _symbol_table.current_function;
51
52
if context? then
53
let result = context.load_self(location, self);
54
55
return result;
56
fi
57
58
IoC.CONTAINER.instance.logger.error(location, "cannot access instance member from non-instance context");
59
60
return IR.Values.DUMMY(Types.ERROR(), Source.LOCATION.internal);
61
si
62
63
load_outer_self(location: Source.LOCATION) -> Value is
64
let context = _symbol_table.current_function;
65
66
if context? then
67
let result = context.load_outer_self(location, self);
68
69
assert result? else "load outer self did not return a value: {context}";
70
71
return result;
72
fi
73
74
IoC.CONTAINER.instance.logger.error(location, "cannot access instance member from non-instance context");
75
76
return IR.Values.DUMMY(Types.ERROR(), Source.LOCATION.internal);
77
si
78
79
load_namespace(symbol: Symbols.NAMESPACE) -> Value =>
80
Load.SYMBOL(null, symbol);
81
82
load_class(symbol: Symbols.CLASS) -> Value =>
83
TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal);
84
85
load_trait(symbol: Symbols.TRAIT) -> Value =>
86
TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal);
87
88
load_struct(symbol: Symbols.STRUCT) -> Value =>
89
TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal);
90
91
load_union(symbol: Symbols.UNION) -> Value =>
92
TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal);
93
94
load_variant(symbol: Symbols.VARIANT) -> Value =>
95
TYPE_EXPRESSION(symbol.type!, Source.LOCATION.internal);
96
97
load_enum_struct_member(symbol: Symbols.ENUM_STRUCT_MEMBER) -> Value =>
98
Literal.NUMBER("{symbol.value}", symbol.type, "i4");
99
100
load_instance_anonymous_function(symbol: Symbols.Symbol, func_type: Type) -> Value =>
101
Load.INSTANCE_ANONYMOUS_FUNCTION(symbol, func_type);
102
103
load_static_anonymous_function(symbol: Symbols.Symbol, func_type: Type) -> Value =>
104
Load.STATIC_ANONYMOUS_FUNCTION(symbol, func_type);
105
106
load_global_anonymous_function(symbol: Symbols.Symbol, func_type: Type) -> Value =>
107
Load.GLOBAL_ANONYMOUS_FUNCTION(symbol, func_type);
108
109
load_function_group(from: Value?, symbol: Symbols.Symbol) -> Value =>
110
Load.SYMBOL(from, symbol);
111
112
load_global_function(symbol: Symbols.Symbol) -> Value =>
113
Load.SYMBOL(null, symbol);
114
115
load_instance_method(location: Source.LOCATION, from: Value?, symbol: Symbols.Symbol) -> Value
116
=> Load.SYMBOL(from, symbol);
117
118
load_struct_method(location: Source.LOCATION, from: Value?, symbol: Symbols.Symbol) -> Value
119
=> Load.SYMBOL(from, symbol);
120
121
// FIXME: this needs to create a pointer to the function:
122
load_static_method(symbol: Symbols.Symbol) -> Value
123
=> Load.SYMBOL(null, symbol);
124
125
load_local_variable(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is
126
let function = _symbol_table.current_function;
127
128
if function? /\ symbol.owner != function then
129
let raw = function.load_captured_value(location, symbol, self);
130
131
// Captured boxed locals: the frame field holds
132
// BOX[T]; the user-code-side read wants T, so
133
// unwrap via `.value`. Inter-frame transfers
134
// (load_outer_captured_value chaining for
135
// nested closures) call load_captured_value
136
// directly and don't hit this unwrap — they
137
// need the box reference to pass to the next
138
// frame's constructor.
139
if symbol.is_boxed then
140
let value_member = resolve_box_value_member(symbol);
141
142
if value_member? then
143
return value_member.load(location, raw, self);
144
fi
145
fi
146
147
return raw;
148
fi
149
150
if symbol.is_boxed then
151
return _load_boxed_local(location, symbol);
152
fi
153
154
return Load.LOCAL_VARIABLE(symbol);
155
si
156
157
load_outer_local_variable(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is
158
let function = _symbol_table.current_function;
159
160
if function? /\ symbol.owner != function then
161
let result = function.load_outer_captured_value(location, symbol, self);
162
163
assert result? else "load outer captured value did not return a value: {function} {symbol}";
164
165
return result;
166
fi
167
168
let raw = Load.LOCAL_VARIABLE(symbol);
169
170
if symbol.is_boxed then
171
// The slot value IS the BOX[T] reference; tell the
172
// IR layer its type is BOX[T] rather than T (the
173
// load IR derives its type from `symbol.type`).
174
// Used by `closure.find_or_add_capture` -> frame
175
// construction at closure-build time.
176
return TYPE_WRAPPER(symbol.storage_type!, raw);
177
fi
178
179
return raw;
180
si
181
182
store_local_variable(location: Source.LOCATION, symbol: Symbols.Variable, value: Value, is_initialize: bool) -> Value is
183
let function = _symbol_table.current_function;
184
185
if !is_initialize then
186
// Assignability is a static property of the declaration:
187
// a mut local is always assignable - captured mut locals
188
// are stored through a shared BOX[T] cell - and a non-mut
189
// local never is. Whether the box exists is a code
190
// generation concern, not a legality one.
191
if symbol.is_disposed then
192
IoC.CONTAINER.instance.logger.error(location, "scoped disposal value may not be assigned to");
193
elif !symbol.is_mutable_marked then
194
IoC.CONTAINER.instance.logger.error(location, "local value cannot be reassigned");
195
elif
196
!symbol.is_boxed /\
197
(symbol.is_captured \/ !function? \/ symbol.owner != function) /\
198
_wants_il
199
then
200
// mark-boxed-locals boxes every captured, reassigned
201
// mut local before compile-expressions on an IL-bound
202
// build, so reaching this store unboxed means the
203
// boxing analysis missed the assignment - erroring
204
// beats emitting a store the capture cannot observe.
205
IoC.CONTAINER.instance.logger.error(location, "captured value may not be assigned to");
206
fi
207
fi
208
209
// Boxed store from inside the closure body that
210
// captured this local: route through the closure's
211
// frame so we hit the shared BOX[T] cell, not the
212
// (invisible-from-here) outer slot.
213
if symbol.is_boxed /\ !is_initialize /\ function? /\ symbol.owner != function then
214
let result = function.store_captured_value(location, symbol, value, self);
215
216
return result;
217
fi
218
219
if symbol.is_boxed then
220
return _store_boxed_local(location, symbol, value, is_initialize);
221
fi
222
223
return Store.LOCAL_VARIABLE(symbol, _value_boxer.box_if_needed(value, symbol.type!));
224
si
225
226
// Boxed-local read: load slot (gives BOX[T] reference)
227
// and field-load `.value`. The TYPE_WRAPPER re-types the
228
// slot load as BOX[T] for the benefit of the follow-on
229
// instance-field access; emitted IL is just ldloc.
230
// The `.value` access goes through `member.load`
231
// polymorphically — works whether the resolved member
232
// is a Field, INSTANCE_FIELD, or auto-generated property
233
// shape that wraps a public field.
234
_load_boxed_local(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is
235
let member = resolve_box_value_member(symbol);
236
237
if !member? then
238
return Load.LOCAL_VARIABLE(symbol);
239
fi
240
241
let raw_load = Load.LOCAL_VARIABLE(symbol);
242
let typed_box = TYPE_WRAPPER(symbol.storage_type!, raw_load);
243
244
return member.load(location, typed_box, self);
245
si
246
247
// Boxed-local write: on declaration init, construct a
248
// fresh `Ghul.BOX[T](value)` (or `Ghul.BOX[T]()` when no
249
// initializer is supplied) and store the reference; on
250
// reassignment, load the slot's BOX[T] and field-store to
251
// `.value`. Allocating the empty box at declaration time —
252
// not lazily on first write — is what makes by-reference
253
// capture work for a forward-declared mutable (`let f mut;
254
// let g = () => f(); f = ...`): the closure-frame ctor
255
// receives the same heap cell the later assignment writes
256
// to.
257
_store_boxed_local(location: Source.LOCATION, symbol: Symbols.Variable, value: Value?, is_initialize: bool) -> Value is
258
if is_initialize then
259
// `box_type.symbol` is the specialised
260
// Symbols.GENERIC (BOX[T]), constructed by
261
// `Types.GENERIC.init`. find_member on it
262
// returns the specialised init constructor(s)
263
// with the concrete T substituted in — but
264
// BOX has two overloads (`init()` and
265
// `init(value: T)`), so we get a FUNCTION_GROUP
266
// back. Pick the overload matching what we have.
267
let box_type = symbol.storage_type!;
268
let arity = if value? then 1 else 0 fi;
269
let ctor = _resolve_box_constructor(box_type, arity);
270
271
assert ctor? else "no box constructor of arity {arity} for {box_type}";
272
273
let arguments =
274
if value? then
275
Collections.LIST[Value]([value])
276
else
277
Collections.LIST[Value]()
278
fi;
279
let new_box = NEW(box_type, ctor, arguments);
280
281
return Store.LOCAL_VARIABLE(symbol, new_box);
282
fi
283
284
let member = resolve_box_value_member(symbol);
285
286
if !member? then
287
return Store.LOCAL_VARIABLE(symbol, _value_boxer.box_if_needed(value!, symbol.type!));
288
fi
289
290
let raw_load = Load.LOCAL_VARIABLE(symbol);
291
let typed_box = TYPE_WRAPPER(symbol.storage_type!, raw_load);
292
293
return member.store(location, typed_box, value!, self, false);
294
si
295
296
// Constructor of `Ghul.BOX[T]` taking `arity` arguments —
297
// 1 for the value-carrying overload, 0 for the empty one.
298
// `find_member("init")` on the specialised GENERIC
299
// returns a FUNCTION_GROUP (BOX has both); pick the
300
// matching overload. Returns null if box_type is unresolved
301
// or the constructor can't be located — callers null-check
302
// before use.
303
_resolve_box_constructor(box_type: Type?, arity: int) -> Symbols.Function? is
304
if !box_type? then
305
return null;
306
fi
307
308
let member = box_type.symbol.find_member("init");
309
310
if !member? then
311
return null;
312
fi
313
314
let direct = cast Symbols.Function?(member);
315
316
if direct? then
317
if direct.are_arguments_declared /\ direct.arguments.count == arity then
318
return direct;
319
else
320
return null;
321
fi
322
fi
323
324
let group = cast Symbols.FUNCTION_GROUP?(member);
325
326
if !group? then
327
return null;
328
fi
329
330
for f in group.functions do
331
if f.are_arguments_declared /\ f.arguments.count == arity then
332
return f;
333
fi
334
od
335
336
return null;
337
si
338
339
// The `value` member of `Ghul.BOX[T]` — what reads and
340
// writes ultimately touch when `symbol.is_boxed`.
341
// Returned as a generic Symbol so polymorphic
342
// `.load(location, from, loader)` /
343
// `.store(location, from, value, loader, is_initialize)`
344
// dispatch handles Field vs INSTANCE_FIELD vs
345
// auto-generated property shapes uniformly.
346
resolve_box_value_member(symbol: Symbols.Variable) -> Symbols.Symbol? is
347
let box_type = symbol.storage_type;
348
349
if !box_type? then
350
return null;
351
fi
352
353
return box_type.symbol.find_direct("value");
354
si
355
356
load_local_argument(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is
357
let function = _symbol_table.current_function;
358
359
if function? /\ symbol.owner != function then
360
let result = function.load_captured_value(location, symbol, self);
361
362
return result;
363
fi
364
365
return Load.LOCAL_ARGUMENT(symbol);
366
si
367
368
load_outer_local_argument(location: Source.LOCATION, symbol: Symbols.Variable) -> Value is
369
let function = _symbol_table.current_function;
370
371
if function? /\ symbol.owner != function then
372
let result = function.load_outer_captured_value(location, symbol, self);
373
374
assert result? else "load outer captured value did not return a value: {function} {symbol}";
375
376
return result;
377
fi
378
379
return Load.LOCAL_VARIABLE(symbol);
380
si
381
382
store_local_argument(location: Source.LOCATION, symbol: Symbols.Variable, value: Value, is_initialize: bool) -> Value is
383
let function = _symbol_table.current_function;
384
385
if !is_initialize then
386
// FIXME: need proper dataflow analysis
387
if !symbol.is_mutable_marked then
388
IoC.CONTAINER.instance.logger.error(location, "local value cannot be reassigned");
389
elif symbol.is_captured \/ !function? \/ symbol.owner != function then
390
// Captured arguments are not yet routed through a
391
// shared BOX[T] cell the way captured mut locals
392
// are - the closure frame copies the argument value
393
// when it is constructed. Until they are boxed, an
394
// assignment on either side of the capture would be
395
// invisible to the other, so it is rejected even
396
// for a mut argument.
397
IoC.CONTAINER.instance.logger.error(location, "captured argument may not be assigned to");
398
fi
399
fi
400
401
return Store.LOCAL_ARGUMENT(symbol, _value_boxer.box_if_needed(value, symbol.type!));
402
si
403
404
// True when this build lowers the IR to IL - the only case where
405
// boxing decisions have observable consequences.
406
_wants_il: bool =>
407
IoC.CONTAINER.instance.build_flags.want_assembler \/
408
IoC.CONTAINER.instance.build_flags.want_executable;
409
410
load_global_variable(symbol: Symbols.Variable) -> Value =>
411
Load.GLOBAL_FIELD(symbol);
412
413
store_global_variable(symbol: Symbols.Variable, value: Value) -> Value =>
414
Store.GLOBAL_FIELD(symbol, _value_boxer.box_if_needed(value, symbol.type!));
415
416
load_instance_variable(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Variable) -> Value is
417
if !from? then
418
from = load_self(location);
419
fi
420
421
return Load.INSTANCE_FIELD(from, symbol);
422
si
423
424
store_instance_variable(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Variable, value: Value) -> Value is
425
if !from? then
426
from = load_self(location);
427
fi
428
429
return Store.INSTANCE_FIELD(from, symbol, _value_boxer.box_if_needed(value, symbol.type!));
430
si
431
432
load_struct_variable(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Variable) -> Value is
433
if !from? then
434
from = load_self(location);
435
elif from.has_address then
436
from = ADDRESS(from);
437
fi
438
439
return Load.INSTANCE_FIELD(from, symbol);
440
si
441
442
store_struct_variable(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Variable, value: Value) -> Value is
443
if !from? then
444
from = load_self(location);
445
elif from.has_address then
446
from = ADDRESS(from);
447
else
448
_logger.info(location, "member updated in discarded value");
449
fi
450
451
return Store.INSTANCE_FIELD(from, symbol, _value_boxer.box_if_needed(value, symbol.type!));
452
si
453
454
455
load_static_field(symbol: Symbols.Variable) -> Value =>
456
Load.STATIC_FIELD(symbol);
457
458
store_static_field(symbol: Symbols.Variable, value: Value) -> Value =>
459
Store.STATIC_FIELD(symbol, _value_boxer.box_if_needed(value, symbol.type!));
460
461
// The `load_*_property` wrappers always return non-null when called
462
// with a real Property — the only null paths are when symbol or
463
// read_function is null, which the wrappers' callers (Property.load)
464
// never hit because they pass `self`. Asserting non-null here lets
465
// those callers consume a `Value` while load_property stays honestly
466
// `Value?` for internal use.
467
load_instance_property(location: Source.LOCATION, from: Value?, symbol: Symbols.Property) -> Value is
468
let result = load_property(location, from, symbol, false);
469
assert result? else "load_instance_property returned null for {symbol}";
470
return result;
471
si
472
473
load_static_property(location: Source.LOCATION, symbol: Symbols.Property) -> Value is
474
let result = load_property(location, null, symbol, true);
475
assert result? else "load_static_property returned null for {symbol}";
476
return result;
477
si
478
479
load_global_property(location: Source.LOCATION, symbol: Symbols.Property) -> Value is
480
let result = load_property(location, null, symbol, true);
481
assert result? else "load_global_property returned null for {symbol}";
482
return result;
483
si
484
485
load_property(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Property, is_static: bool) -> Value? is
486
if !from? /\ !is_static then
487
from = load_self(location);
488
fi
489
490
if !symbol.read_function? then
491
_logger.poison(location, "property {symbol.qualified_name} does not have a read function");
492
return null;
493
fi
494
495
let read_function = symbol.read_function;
496
497
find_symbol = _null_find_symbol;
498
499
// Flow narrowing reconciles a narrowed property's
500
// `Property.type` the same way it does `Variable.type`;
501
// the load's value must carry that narrowed view rather
502
// than the getter's declared return type. Outside a
503
// narrow the two are the same type instance, so this is
504
// inert.
505
let narrowed_type =
506
if symbol.type? /\ symbol.type != read_function.return_type then
507
symbol.type
508
else
509
null
510
fi;
511
512
return read_function.call(location, from, Collections.LIST[Value](0), narrowed_type, _function_caller);
513
si
514
515
store_instance_property(location: Source.LOCATION, from: Value?, symbol: Symbols.Property, value: Value) -> Value is
516
let result = store_property(location, from, symbol, value, false);
517
return if result? then result else IR.Values.DUMMY(Types.ERROR(), location) fi;
518
si
519
store_static_property(location: Source.LOCATION, symbol: Symbols.Property, value: Value) -> Value is
520
let result = store_property(location, null, symbol, value, true);
521
return if result? then result else IR.Values.DUMMY(Types.ERROR(), location) fi;
522
si
523
store_global_property(location: Source.LOCATION, symbol: Symbols.Property, value: Value) -> Value is
524
let result = store_property(location, null, symbol, value, true);
525
return if result? then result else IR.Values.DUMMY(Types.ERROR(), location) fi;
526
si
527
528
store_property(location: Source.LOCATION, from: Value? mut, symbol: Symbols.Property, value: Value, is_static: bool) -> Value? is
529
if symbol == null then
530
return null;
531
fi
532
533
if !from? /\ !is_static then
534
from = load_self(location);
535
fi
536
537
if symbol.assign_function == null then
538
_logger.error(location, "property {symbol} is not assignable");
539
540
assert !symbol.is_assignable else "property {symbol} is assignable but does not have an assign accessor function";
541
542
return null;
543
fi
544
545
find_symbol = _null_find_symbol;
546
547
return TYPE_WRAPPER(
548
cast Types.Typed(symbol).type!,
549
symbol.assign_function!.call(location, from, Collections.LIST([value]), null, _function_caller)
550
);
551
si
552
si
553
si