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
_enclosing_type_variable: ENCLOSING_TYPE_VARIABLE
37
38
super()
39
40
init(..) is
41
_delegate_shape = DELEGATE_SHAPE()
42
_enclosing_type_variable = ENCLOSING_TYPE_VARIABLE()
43
si
44
45
// Top-level entry. Safe to call with null on either side
46
// (no-op). When a constraint is recorded, `mark_consumed_any`
47
// is invoked on the logger so the retry loop continues.
48
propagate_match(formal: Type?, actual: Type?) is
49
if !formal? \/ !actual? then
50
return
51
fi
52
53
// A named delegate slot constrains a function actual by its
54
// call shape: the heads never match otherwise, so a literal
55
// still inferring its parameter types would learn nothing
56
// from the slot it is being passed to.
57
if actual.is_function /\ _delegate_shape.is_named_delegate(formal, IoC.CONTAINER.instance.innate_symbol_lookup) then
58
let shape =
59
_delegate_shape.try_get_function_type(
60
formal,
61
IoC.CONTAINER.instance.innate_symbol_lookup)
62
63
if shape? then
64
propagate_match(shape, actual)
65
fi
66
67
return
68
fi
69
70
// A type variable of a declaration this code is written
71
// inside already stands for a definite type here, so it
72
// settles a slot as readily as a concrete type would. The
73
// bound is a lower one, matching how a sibling
74
// type-argument binding pushes one back, and it is taken
75
// from whichever side carries the variable. A variable
76
// belonging to the declaration being resolved is left
77
// alone: it is itself what the resolution is working out.
78
if
79
isa Types.INFERRED_VARIABLE_TYPE(actual) /\
80
formal.is_type_variable /\
81
_enclosing_type_variable.is_fixed_in(
82
formal,
83
IoC.CONTAINER.instance.symbol_table.current_function)
84
then
85
let placeholder = actual
86
_logger.mark_consumed_any_if(Semantic.INFERENCE_TRACE.add_lower_bound("match.formal_type_variable", placeholder.origin, formal))
87
return
88
fi
89
90
if
91
isa Types.INFERRED_VARIABLE_TYPE(formal) /\
92
actual.is_type_variable /\
93
_enclosing_type_variable.is_fixed_in(
94
actual,
95
IoC.CONTAINER.instance.symbol_table.current_function)
96
then
97
let placeholder = formal
98
_logger.mark_consumed_any_if(Semantic.INFERENCE_TRACE.add_lower_bound("match.actual_type_variable", placeholder.origin, actual))
99
return
100
fi
101
102
if
103
isa Types.INFERRED_VARIABLE_TYPE(actual) /\
104
!formal.is_sentinel /\ !formal.is_type_variable
105
then
106
let placeholder = actual
107
// Upper-bound, not lower: formal here is the slot the
108
// placeholder must fit INTO. Treating it as a peer
109
// LUB candidate widens spuriously when the slot is
110
// a supertype of the placeholder's true value type
111
// (eg. `g(v: object)` with `v = false` -> v widened
112
// to object, breaking subsequent `if v`).
113
_logger.mark_consumed_any_if(Semantic.INFERENCE_TRACE.add_upper_bound("match.placeholder_actual", placeholder.origin, formal))
114
return
115
fi
116
117
if
118
isa Types.INFERRED_VARIABLE_TYPE(formal) /\
119
!actual.is_sentinel /\ !actual.is_type_variable
120
then
121
let placeholder = formal
122
_logger.mark_consumed_any_if(Semantic.INFERENCE_TRACE.add_lower_bound("match.placeholder_formal", placeholder.origin, actual))
123
return
124
fi
125
126
// Recurse on structurally-matching generics (same outer
127
// symbol, same arity): paired type-args may carry
128
// placeholders even when the outer types compared cleanly.
129
let formal_generic = cast Types.GENERIC?(formal)
130
let actual_generic = cast Types.GENERIC?(actual)
131
132
if formal_generic? /\ actual_generic? then
133
let formal_symbol = cast Symbols.GENERIC?(formal_generic.symbol)
134
let actual_symbol = cast Symbols.GENERIC?(actual_generic.symbol)
135
136
if
137
formal_symbol? /\ actual_symbol? /\
138
formal_symbol.symbol =~ actual_symbol.symbol /\
139
formal_generic.arguments.count == actual_generic.arguments.count
140
then
141
for j in 0..formal_generic.arguments.count do
142
propagate_match(formal_generic.arguments[j], actual_generic.arguments[j])
143
od
144
elif
145
formal_symbol? /\ actual_symbol? /\
146
actual_symbol.ancestors.count > 0
147
then
148
// Outer heads differ but actual may extend formal's
149
// head — e.g. union variant NIL<placeholder> being
150
// passed to a List<int> slot, or SACK<placeholder>
151
// to a Sack<int> slot. Walk actual's specialised
152
// ancestors looking for one whose head matches the
153
// formal's. When found, recurse on the formal's
154
// args paired against the specialised ancestor's,
155
// which carries the placeholders in the right
156
// positions for match propagation to take effect.
157
for i in 0..actual_symbol.ancestors.count do
158
let ancestor = actual_symbol.get_ancestor(i)
159
160
if isa Types.GENERIC(ancestor) then
161
let ancestor_generic = ancestor
162
let ancestor_symbol = cast Symbols.GENERIC?(ancestor_generic.symbol)
163
164
if
165
ancestor_symbol? /\
166
ancestor_symbol.symbol =~ formal_symbol.symbol
167
then
168
propagate_match(formal, ancestor)
169
return
170
fi
171
fi
172
od
173
fi
174
fi
175
si
176
177
// Convenience overload for the common case of walking a
178
// paired formal/actual list. Truncates to the shorter of the
179
// two so a call site with mismatched argument counts (e.g.
180
// a partial function-group resolution) still makes progress
181
// on the prefix.
182
propagate_matches(
183
formals: Collections.List[Type]?,
184
actuals: Collections.List[Type]?
185
) is
186
if !formals? \/ !actuals? then
187
return
188
fi
189
190
let count mut = formals.count
191
192
if count > actuals.count then
193
count = actuals.count
194
fi
195
196
for i in 0..count do
197
propagate_match(formals[i], actuals[i])
198
od
199
si
200
si
201
si