Skip to content
← Back

src/semantic/least_upper_bound_map.ghul

1
namespace Semantic is
2
use Collections;
3
use Semantic.Symbols.Symbol;
4
use Semantic.Types.Type;
5
6
use Ghul.Pipes;
7
8
use Logging;
9
10
enum LubMode is
11
ONLY_ELEMENT_TYPES,
12
ANY_CONCRETE,
13
ANY_TRAIT
14
si
15
16
class LEAST_UPPER_BOUND_MAP is
17
_any_unsafe_constraints: bool;
18
_mode: LubMode;
19
20
types: LIST[Type];
21
element_names: LIST[string?]?;
22
23
result: MAP[Symbol,LIST[Type]]?;
24
current: MAP[Symbol,LIST[Type]]?;
25
26
init() is
27
types = LIST[Type]();
28
_mode = LubMode.ONLY_ELEMENT_TYPES;
29
si
30
31
add(type: Type) is
32
// Per-position structural merge: if an already-collected
33
// constraint shares a head with `type` and they're each
34
// concrete in slots the other isn't, collapse them into a
35
// single refined entry instead of leaving both in the LUB
36
// pool where one would out-rank the other and lose the
37
// complementary information. Required for iterative
38
// inference cases where two match propagation channels each pin a
39
// different position of the same Function shape.
40
//
41
// Only useful when at least one side is a structurally-
42
// recursable GENERIC carrying placeholder slots — for the
43
// common (and very hot) flat-type case (int + int, bool +
44
// bool, ...) the existing LUB picker does the right thing
45
// and the merge would be pure overhead.
46
if isa Types.GENERIC(type) /\ type.contains_inferred then
47
for i in 0..types.count do
48
let merged = _try_merge_per_position(types[i], type);
49
50
if merged? then
51
types[i] = merged;
52
53
if type.is_unsafe_constraints then
54
_any_unsafe_constraints = true;
55
fi
56
57
_update_element_names_from(merged);
58
return;
59
fi
60
od
61
else
62
for i in 0..types.count do
63
let existing = types[i];
64
65
if isa Types.GENERIC(existing) /\ existing.contains_inferred then
66
let merged = _try_merge_per_position(existing, type);
67
68
if merged? then
69
types[i] = merged;
70
71
if type.is_unsafe_constraints then
72
_any_unsafe_constraints = true;
73
fi
74
75
_update_element_names_from(merged);
76
return;
77
fi
78
fi
79
od
80
fi
81
82
types.add(type);
83
84
if type.is_unsafe_constraints then
85
_any_unsafe_constraints = true;
86
fi
87
88
_update_element_names_from(type);
89
si
90
91
// When the per-position merge defers a placeholder slot to a
92
// concrete one, push the concrete back to the placeholder's
93
// origin so iterative-inference resolves the phantom to the
94
// same concrete type. Without this the LUB result picks the
95
// right concrete at the top level but the AST nodes that
96
// produced the placeholder side keep their placeholder-bearing
97
// value type — surfaces in analyse-mode HOVER as `***` and in
98
// ancestor LUB merges as the placeholder collapsing to object.
99
//
100
// Only fires for INFERRED_VARIABLE_TYPE (the placeholder kind
101
// that carries a back-pointer origin). INFERRED_RETURN_TYPE is
102
// a singleton sentinel with no origin to push into; ERROR
103
// similarly. Concrete must itself not be a sentinel / inferred
104
// — pushing one placeholder into another's LUB would just
105
// propagate uncertainty.
106
//
107
// Identical in spirit to `Semantic.MATCH_PROPAGATOR.pair`'s
108
// placeholder-formal branch, but reached from inside the LUB
109
// merge — the LUB has no Logger field and isn't constructed
110
// with one (callers create instances ad-hoc), so the retry
111
// signal is sent via the IoC-resolved logger rather than a
112
// dependency-injected reference. Calling through MATCH_PROPAGATOR
113
// would have required threading a Logger through every LUB
114
// construction site.
115
_propagate_to_placeholder(placeholder: Type, concrete: Type?) is
116
if !isa Types.INFERRED_VARIABLE_TYPE(placeholder) then
117
return;
118
fi
119
120
if !concrete? \/ concrete.is_sentinel \/ concrete.is_inferred \/ concrete.is_error then
121
return;
122
fi
123
124
let p = cast Types.INFERRED_VARIABLE_TYPE(placeholder);
125
126
IoC.CONTAINER.instance.logger.mark_consumed_any_if(p.origin.add_lower_bound(concrete));
127
si
128
129
// Returns a per-position structural refinement of a and b, or
130
// null if the two aren't shape-compatible. At each position the
131
// non-placeholder side wins; recurses through generic args to
132
// handle nested cases like Function[Function[?, int], bool].
133
_try_merge_per_position(a: Type?, b: Type?) -> Type? is
134
if !a? then return b; fi
135
if !b? then return a; fi
136
137
// Inference placeholder OR ERROR at the top — defer to the
138
// other. ERROR is the "this slot failed" sentinel; an
139
// inference placeholder is "still working on it". Either
140
// way the other side, however it's resolved, is strictly
141
// more informative than this one. Without the is_error leg
142
// the tuple LUB from `let v18; v18 = (delayed_lambda([…]),
143
// 5);` froze at `(ERROR, int)` on iter 1 (call to the
144
// not-yet-resolved lambda returned DUMMY(ERROR)) and out-
145
// ranked iter 2's refined `(placeholder, int)` because the
146
// top of the latter was treated as is_inferred and got
147
// discarded in favour of ERROR.
148
if a.is_inferred \/ a.is_error then
149
_propagate_to_placeholder(a, b);
150
return b;
151
fi
152
if b.is_inferred \/ b.is_error then
153
_propagate_to_placeholder(b, a);
154
return a;
155
fi
156
157
// Otherwise both must be GENERIC with the same head and arity.
158
if !isa Types.GENERIC(a) \/ !isa Types.GENERIC(b) then
159
if a.matches(b) then return a; fi
160
return null;
161
fi
162
163
let ga = a;
164
let gb = b;
165
166
let sa = cast Symbols.GENERIC?(ga.symbol);
167
let sb = cast Symbols.GENERIC?(gb.symbol);
168
169
if !sa? \/ !sb? then return null; fi
170
171
// Heads differ — try promoting both to a shared generic
172
// ancestor (variant -> union being the motivating case:
173
// `DONE[<phantom>]` and `YIELD[int]` share `STEP[…]`).
174
// The non-merge fallback in get_result walks ancestors too
175
// but intersects via `matches` on the specialised ancestor; a
176
// phantom slot vs concrete won't equate, so that path
177
// drops to `object`. Merging through the ancestor here
178
// lets the per-position rule (placeholder slot defers to
179
// concrete) fire on the slots that need it.
180
if sa.symbol != sb.symbol then
181
return _try_merge_through_shared_ancestor(ga, gb, sa, sb);
182
fi
183
184
if ga.arguments.count != gb.arguments.count then return null; fi
185
186
let merged_args = LIST[Types.Type](ga.arguments.count);
187
188
for j in 0..ga.arguments.count do
189
let merged_arg = _try_merge_per_position(ga.arguments[j], gb.arguments[j]);
190
191
if !merged_arg? then return null; fi
192
193
merged_args.add(merged_arg);
194
od
195
196
// Tuples carry element names alongside element types.
197
// Reconciliation rules: both unnamed → unnamed; one named one
198
// unnamed → take the name; both named the same → that name;
199
// both named differently → take the name from the side whose
200
// arg is concrete, or fail the merge if both are concrete.
201
if a.is_value_tuple /\ b.is_value_tuple then
202
let merged_names = LIST[string](ga.arguments.count);
203
let any_name mut = false;
204
205
for j in 0..ga.arguments.count do
206
let name_a = ga.symbol.get_element_name(j);
207
let name_b = gb.symbol.get_element_name(j);
208
209
let name = _try_merge_element_name(name_a, name_b, ga.arguments[j], gb.arguments[j]);
210
211
if name_a? /\ name_b? /\ name_a !~ name_b /\ !name? then
212
return null;
213
fi
214
215
if name? then any_name = true; fi
216
217
// Positional placeholder for an unnamed slot, per the
218
// tuple-construction convention.
219
merged_names.add(if name? then name else "{j}" fi);
220
od
221
222
let names: Collections.List[string] = if any_name then merged_names else LIST[string]() fi;
223
224
return IoC.CONTAINER.instance.innate_symbol_lookup.get_tuple_type(merged_args, names);
225
fi
226
227
return ga.create(ga.symbol.location, sa.symbol, merged_args);
228
si
229
230
// For two GENERICs with different head symbols, look for a
231
// shared generic ancestor with matching head and arity, and
232
// attempt the per-position merge on the specialised ancestor
233
// pair. Returns the merged ancestor type, or null if no
234
// ancestor pair lined up.
235
//
236
// Specialised ancestors come from `Symbols.GENERIC.get_ancestor`,
237
// which substitutes the GENERIC's type_map — so for `DONE[?p_T]`
238
// the STEP ancestor is `STEP[?p_T]`, and the recursive merge
239
// can defer the placeholder slot to the concrete slot of
240
// `STEP[int]` coming from a sibling `YIELD[int]`.
241
//
242
// Walks `sa`'s ancestor list outermost-first (push_ancestor
243
// puts direct parents at index 0, so STEP appears before
244
// object for a variant), so the closest shared ancestor wins.
245
_try_merge_through_shared_ancestor(
246
ga: Types.GENERIC, gb: Types.GENERIC,
247
sa: Symbols.GENERIC, sb: Symbols.GENERIC
248
) -> Types.Type? is
249
for i in 0..sa.ancestors.count do
250
let a_anc = sa.get_ancestor(i);
251
252
if !isa Types.GENERIC(a_anc) then
253
continue;
254
fi
255
256
let a_anc_gen = a_anc;
257
let a_anc_sym = cast Symbols.GENERIC?(a_anc_gen.symbol);
258
259
if !a_anc_sym? then
260
continue;
261
fi
262
263
for j in 0..sb.ancestors.count do
264
let b_anc = sb.get_ancestor(j);
265
266
if !isa Types.GENERIC(b_anc) then
267
continue;
268
fi
269
270
let b_anc_gen = b_anc;
271
let b_anc_sym = cast Symbols.GENERIC?(b_anc_gen.symbol);
272
273
if !b_anc_sym? then
274
continue;
275
fi
276
277
if
278
a_anc_sym.symbol =~ b_anc_sym.symbol /\
279
a_anc_gen.arguments.count == b_anc_gen.arguments.count
280
then
281
let merged = _try_merge_per_position(a_anc, b_anc);
282
283
if merged? then
284
return merged;
285
fi
286
fi
287
od
288
od
289
290
return null;
291
si
292
293
_try_merge_element_name(name_a: string? mut, name_b: string? mut, arg_a: Types.Type, arg_b: Types.Type) -> string? is
294
// Auto-generated positional names like 0 / 1 don't carry
295
// user intent; treat them as absent.
296
if name_a? /\ Symbols.Symbol.is_positional_member_name(name_a) then name_a = null; fi
297
if name_b? /\ Symbols.Symbol.is_positional_member_name(name_b) then name_b = null; fi
298
299
if !name_a? /\ !name_b? then return null; fi
300
if !name_a? then return name_b; fi
301
if !name_b? then return name_a; fi
302
if name_a =~ name_b then return name_a; fi
303
304
// Names differ. If one side's element type is still a
305
// placeholder we trust the named side's intent more than
306
// the placeholder's; otherwise the conflict is real.
307
if !arg_a.contains_inferred /\ arg_b.contains_inferred then return name_a; fi
308
if arg_a.contains_inferred /\ !arg_b.contains_inferred then return name_b; fi
309
310
return null;
311
si
312
313
_add(type: Type) is
314
if !result? then
315
result = MAP[Symbol,LIST[Type]]();
316
_add_all_first(type);
317
else
318
current = MAP[Symbol,LIST[Type]]();
319
320
_add_all_subsequent(type);
321
322
_apply_intersection();
323
fi
324
si
325
326
get_result() -> Type? is
327
if types.count == 0 then
328
return null;
329
fi
330
331
// this was the original common element type
332
// inference heuristic
333
let best_assignable = _try_get_best_assignable();
334
if best_assignable? then
335
return best_assignable;
336
fi
337
338
// this is LUB applied to only element types, but
339
// it's actually worse that the original heuristic
340
// in some cases because it doesn't handle type variance
341
// TODO consider removing
342
let best_element_type = _try_get_best_element_type();
343
if best_element_type? then
344
return best_element_type;
345
fi
346
347
// this is LUB applied to all elements and all concrete
348
// element ancestor types. If all the types are concrete
349
// then this will always return a result. In the worse
350
// case it will return object, which may still be better
351
// than a fairly random choice of interface type
352
let best_concrete_type = _try_get_best_concrete();
353
354
// this is LUB applied to all elements and all traits
355
// they implement. It has a tendency to select unhelpful
356
// traits, and needs tuning somehow to prioritize
357
// traits that are relevant based on context
358
let best_trait_type = _try_get_best_trait();
359
360
// this still might not be enough - we might need to record the
361
// average depth difference (='specificity') per type, but that
362
// could be slow
363
if best_concrete_type? /\ best_trait_type? then
364
let total_concrete_depth_difference =
365
types |> reduce(0, (d, t) => d + (t.depth - best_concrete_type.depth));
366
367
let total_trait_depth_difference =
368
types |> reduce(0, (d, t) => d + (t.depth - best_trait_type.depth));
369
370
if total_trait_depth_difference < total_concrete_depth_difference then
371
return best_trait_type
372
else
373
return best_concrete_type
374
fi
375
elif best_concrete_type? then
376
return best_concrete_type
377
else
378
return best_trait_type
379
fi
380
si
381
382
_try_get_best_assignable() -> Type? is
383
let best: Type? mut = null;
384
385
for type in types do
386
if !best? then
387
best = type;
388
elif type.is_assignable_from(best) then
389
best = type;
390
elif !best.is_assignable_from(type) then
391
return null;
392
fi
393
od
394
395
return _with_element_names(best);
396
si
397
398
_try_get_best_element_type() -> Type? is
399
_mode = LubMode.ONLY_ELEMENT_TYPES;
400
401
for type in types do
402
_add(type);
403
od
404
405
return
406
let best = _get_best() in
407
if best? /\ !best.is_object then
408
best
409
else
410
null
411
fi
412
si
413
414
_try_get_best_concrete() -> Type? is
415
result = null;
416
current = null;
417
_mode = LubMode.ANY_CONCRETE;
418
419
for type in types do
420
_add(type);
421
od
422
423
return _get_best();
424
si
425
426
_try_get_best_trait() -> Type? is
427
result = null;
428
current = null;
429
_mode = LubMode.ANY_TRAIT;
430
431
for type in types do
432
_add(type);
433
od
434
435
return _get_best();
436
si
437
438
_get_best() -> Type? is
439
let result = self.result!;
440
441
let best: Type? mut = null;
442
let is_ambiguous mut = false;
443
444
for list in result.values do
445
for type in list do
446
if !_any_unsafe_constraints /\ type.is_unsafe_constraints then
447
// only allow unsafe constraints if some of the element
448
// types have unsafe constraints:
449
continue;
450
fi
451
452
if !best? then
453
best = type;
454
is_ambiguous = false;
455
elif type.depth > best.depth then
456
best = type;
457
is_ambiguous = false;
458
elif type.depth == best.depth then
459
is_ambiguous = true;
460
fi
461
od
462
od
463
464
if !is_ambiguous then
465
return _with_element_names(best);
466
fi
467
return null;
468
si
469
470
_apply_intersection() is
471
let result = self.result!;
472
let current = self.current!;
473
474
let new_result = MAP[Symbol,LIST[Type]]();
475
476
for kv in result do
477
let rsf = kv.key;
478
let result_list = kv.value;
479
480
let current_list: LIST[Type] mut;
481
482
if !current.try_get_value(rsf, current_list ref) then
483
continue;
484
fi
485
486
// TODO this could be slow, although in practice for non-generic types
487
// there will only ever be a single type in the list
488
let new_result_list = result_list |> filter(t => current_list |> any(u => t.matches(u))) |> collect_list();
489
490
if new_result_list.count > 0 then
491
new_result.add(rsf, new_result_list);
492
fi
493
od
494
495
self.result = new_result;
496
si
497
498
// Yield ancestors of `type` for the LUB walk, specialised via
499
// the type's type-map when the symbol is generic. Without
500
// specialisation, the ancestors come back with each class's
501
// OWN type-variable symbols intact — so two siblings sharing
502
// an `Iterable[T]` ancestor look distinct to the intersection
503
// step because their T's are different symbols. Specialising
504
// collapses `array_class[int].Iterable[T]` and
505
// `list_class[int].Iterable[T]` to a common `Iterable[int]`,
506
// which is the only way the trait-walk can find them
507
// intersecting.
508
_specialised_ancestor(type: Type, i: int) -> Type is
509
if let symbol: Symbols.GENERIC = type.symbol then
510
return symbol.get_ancestor(i);
511
fi
512
513
return type.ancestors[i];
514
si
515
516
// populate the result set from type and all
517
// its ancestor types
518
_add_all_first(type: Type) is
519
if
520
_mode == LubMode.ONLY_ELEMENT_TYPES \/
521
(_mode == LubMode.ANY_CONCRETE /\ !type.is_trait) \/
522
(_mode == LubMode.ANY_TRAIT /\ type.is_trait)
523
then
524
_add_single_first(type);
525
fi
526
527
if _mode == LubMode.ANY_CONCRETE /\ type.ancestors.count > 0 then
528
_add_all_first(_specialised_ancestor(type, 0));
529
elif _mode == LubMode.ANY_TRAIT then
530
for i in 0..type.ancestors.count do
531
_add_all_first(_specialised_ancestor(type, i));
532
od
533
fi
534
si
535
536
_add_all_subsequent(type: Type) is
537
if
538
_mode == LubMode.ONLY_ELEMENT_TYPES \/
539
(_mode == LubMode.ANY_CONCRETE /\ !type.is_trait) \/
540
(_mode == LubMode.ANY_TRAIT /\ type.is_trait)
541
then
542
_add_single_subsequent(type);
543
fi
544
545
if _mode == LubMode.ANY_CONCRETE /\ type.ancestors.count > 0 then
546
_add_all_subsequent(_specialised_ancestor(type, 0));
547
elif _mode == LubMode.ANY_TRAIT then
548
for i in 0..type.ancestors.count do
549
_add_all_subsequent(_specialised_ancestor(type, i));
550
od
551
fi
552
si
553
554
// add a single type to the result set
555
_add_single_first(type: Type) is
556
if type.is_root_value_type then
557
// System.ValueType is not a real type for
558
// our purposes here, so exclude it:
559
return;
560
fi
561
562
// what unspecialized type was this type ultimately
563
// specialized from. If the type is not generic
564
// then this is the classy type that represents it
565
let rsf = type.symbol.root_unspecialized_symbol;
566
567
// we can't put types in a set directly, because types
568
// are not interned. However we can reduce the amount
569
// of linear searching we need to do by partitioning
570
// by root-specialized-from symbols, which are unique
571
// per unspecialized generic type. we then only need
572
// to search under the matching root specialized from symbol
573
574
let result = self.result!;
575
576
let list: LIST[Type] mut;
577
578
if !result.try_get_value(rsf, list ref) then
579
list = LIST[Type]();
580
result.add(rsf, list);
581
list.add(type);
582
elif list |> all(t => !t.matches(type)) then
583
list.add(type);
584
fi
585
586
assert result.contains_key(rsf) else "somehow haven't added {rsf} to {result.keys |}";
587
si
588
589
// add a single type to the current set
590
_add_single_subsequent(type: Type) is
591
let rsf = type.symbol.root_unspecialized_symbol;
592
593
let result = self.result!;
594
let current = self.current!;
595
596
if !result.contains_key(rsf) then
597
// cannot intersect
598
return;
599
fi
600
601
let list: LIST[Type] mut;
602
603
if !current.try_get_value(rsf, list ref) then
604
list = LIST[Type]();
605
current.add(rsf, list);
606
list.add(type);
607
elif list |> all(t => !t.matches(type)) then
608
list.add(type);
609
fi
610
si
611
612
_update_element_names_from(type: Type) is
613
if type.is_value_tuple then
614
if !element_names? then
615
element_names = LIST[string?]();
616
fi
617
618
let names = element_names!;
619
620
for i in 0..type.arguments.count do
621
if i >= names.count then
622
names.add(null);
623
fi
624
625
let element_name = type.symbol.get_element_name(i);
626
627
if element_name? /\ ! Symbols.Symbol.is_positional_member_name(element_name) then
628
names[i] = element_name;
629
fi
630
od
631
fi
632
si
633
634
_with_element_names(type: Type?) -> Type? =>
635
if !type? then
636
null
637
elif type.is_value_tuple /\ element_names? /\ element_names.count == type.arguments.count then
638
IoC.CONTAINER.instance.innate_symbol_lookup.get_tuple_type(type.arguments, element_names);
639
else
640
type
641
fi;
642
si
643
si