Skip to content
← Back

src/semantic/match_propagator.ghul

1
namespace Semantic is
2
use Logging;
3
use Types.Type;
4
5
// Pushes constraints onto INFERRED_VARIABLE_TYPE placeholders by
6
// walking paired (formal, actual) types and recording the
7
// opposite side's concrete type as a constraint on the
8
// placeholder's `origin`. When a placeholder learns a new
9
// constraint, the underlying logger's `mark_consumed_any` is
10
// called so the iterative body-retry loop knows to walk again.
11
//
12
// Three shapes are recognised:
13
//
14
// - actual is placeholder, formal concrete -> formal as
15
// UPPER bound on actual.origin (the placeholder is being
16
// passed AS the formal — formal places an upper-bound
17
// "must be assignable to" constraint, never widens the
18
// placeholder's lower-bound LUB).
19
//
20
// - formal is placeholder, actual concrete -> actual as
21
// LOWER bound on formal.origin (lambda-arg match propagation:
22
// drives #1210 inference where a concrete call-site
23
// actual tells the lambda's parameter what value type it
24
// must accept; constructor-type-arg match propagation similarly).
25
//
26
// - same generic head on both sides -> recurse on
27
// paired type-args. Picks up call-arg-as-constraint cases
28
// where actual is `Box[placeholder]` and formal is `Box[int]`.
29
//
30
// - outer heads differ but actual *extends* formal's head ->
31
// walk actual's specialised ancestors looking for a match
32
// and recurse on that. Picks up union variants (`NIL[T]` ->
33
// `List[int]`) and trait-implementation arguments.
34
class MATCH_PROPAGATOR(_logger: Logger) is
35
_delegate_shape: DELEGATE_SHAPE;
36
37
super();
38
39
init(..) is
40
_delegate_shape = DELEGATE_SHAPE();
41
si
42
43
// Top-level entry. Safe to call with null on either side
44
// (no-op). When a constraint is recorded, `mark_consumed_any`
45
// is invoked on the logger so the retry loop continues.
46
propagate_match(formal: Type?, actual: Type?) is
47
if !formal? \/ !actual? then
48
return;
49
fi
50
51
// A named delegate slot constrains a function actual by its
52
// call shape: the heads never match otherwise, so a literal
53
// still inferring its parameter types would learn nothing
54
// from the slot it is being passed to.
55
if actual.is_function /\ _delegate_shape.is_named_delegate(formal) then
56
let shape =
57
_delegate_shape.try_get_function_type(
58
formal,
59
IoC.CONTAINER.instance.innate_symbol_lookup);
60
61
if shape? then
62
propagate_match(shape, actual);
63
fi
64
65
return;
66
fi
67
68
if
69
isa Types.INFERRED_VARIABLE_TYPE(actual) /\
70
!formal.is_sentinel /\ !formal.is_type_variable
71
then
72
let placeholder = actual;
73
// Upper-bound, not lower: formal here is the slot the
74
// placeholder must fit INTO. Treating it as a peer
75
// LUB candidate widens spuriously when the slot is
76
// a supertype of the placeholder's true value type
77
// (eg. `g(v: object)` with `v = false` -> v widened
78
// to object, breaking subsequent `if v`).
79
_logger.mark_consumed_any_if(placeholder.origin.add_upper_bound(formal));
80
return;
81
fi
82
83
if
84
isa Types.INFERRED_VARIABLE_TYPE(formal) /\
85
!actual.is_sentinel /\ !actual.is_type_variable
86
then
87
let placeholder = formal;
88
_logger.mark_consumed_any_if(placeholder.origin.add_lower_bound(actual));
89
return;
90
fi
91
92
// Recurse on structurally-matching generics (same outer
93
// symbol, same arity): paired type-args may carry
94
// placeholders even when the outer types compared cleanly.
95
let formal_generic = cast Types.GENERIC?(formal);
96
let actual_generic = cast Types.GENERIC?(actual);
97
98
if formal_generic? /\ actual_generic? then
99
let formal_symbol = cast Symbols.GENERIC?(formal_generic.symbol);
100
let actual_symbol = cast Symbols.GENERIC?(actual_generic.symbol);
101
102
if
103
formal_symbol? /\ actual_symbol? /\
104
formal_symbol.symbol =~ actual_symbol.symbol /\
105
formal_generic.arguments.count == actual_generic.arguments.count
106
then
107
for j in 0..formal_generic.arguments.count do
108
propagate_match(formal_generic.arguments[j], actual_generic.arguments[j]);
109
od
110
elif
111
formal_symbol? /\ actual_symbol? /\
112
actual_symbol.ancestors.count > 0
113
then
114
// Outer heads differ but actual may extend formal's
115
// head — e.g. union variant NIL<placeholder> being
116
// passed to a List<int> slot, or SACK<placeholder>
117
// to a Sack<int> slot. Walk actual's specialised
118
// ancestors looking for one whose head matches the
119
// formal's. When found, recurse on the formal's
120
// args paired against the specialised ancestor's,
121
// which carries the placeholders in the right
122
// positions for match propagation to take effect.
123
for i in 0..actual_symbol.ancestors.count do
124
let ancestor = actual_symbol.get_ancestor(i);
125
126
if isa Types.GENERIC(ancestor) then
127
let ancestor_generic = ancestor;
128
let ancestor_symbol = cast Symbols.GENERIC?(ancestor_generic.symbol);
129
130
if
131
ancestor_symbol? /\
132
ancestor_symbol.symbol =~ formal_symbol.symbol
133
then
134
propagate_match(formal, ancestor);
135
return;
136
fi
137
fi
138
od
139
fi
140
fi
141
si
142
143
// Convenience overload for the common case of walking a
144
// paired formal/actual list. Truncates to the shorter of the
145
// two so a call site with mismatched argument counts (e.g.
146
// a partial function-group resolution) still makes progress
147
// on the prefix.
148
propagate_matches(
149
formals: Collections.List[Type]?,
150
actuals: Collections.List[Type]?
151
) is
152
if !formals? \/ !actuals? then
153
return;
154
fi
155
156
let count mut = formals.count;
157
158
if count > actuals.count then
159
count = actuals.count;
160
fi
161
162
for i in 0..count do
163
propagate_match(formals[i], actuals[i]);
164
od
165
si
166
si
167
si