Skip to content
← Back

src/semantic/symbols/generic_constraint_checker.ghul

1
namespace Semantic.Symbols is
2
use Source
3
use Logging
4
5
use Types.Type
6
7
// Decides whether a type argument satisfies a type parameter's
8
// declared constraints — a kind constraint (`class` / `struct` /
9
// `optional`), a type bound (`[T: SomeBase]`), and the
10
// parameterless-constructor (`init`) constraint.
11
//
12
// Sentinels and unresolved or error types are not checkable: the
13
// conservative answer is that the constraint holds, so no false
14
// positive is reported.
15
//
16
// A type variable is checkable. It is a declared type parameter
17
// carrying its own constraints, so whether it satisfies another
18
// parameter's is decided by whether what it guarantees entails what
19
// is required — an unbounded one guarantees nothing. Answering
20
// "satisfied" instead would let a call through that the CLR rejects
21
// when it is reached, as a VerificationException with no source
22
// location.
23
class GENERIC_CONSTRAINT_CHECKER is
24
init() is si
25
26
is_checkable(actual: Type?) -> bool =>
27
actual? /\
28
actual.is_settled /\
29
!actual.is_type_variable /\
30
!actual.is_sentinel
31
32
// As is_checkable, but admitting a type variable — a declared type
33
// parameter whose own constraints decide the answer.
34
is_declared_type_checkable(actual: Type?) -> bool =>
35
actual? /\
36
actual.is_settled /\
37
!actual.is_sentinel
38
39
// Whether a type variable's own declared constraints guarantee
40
// `kind`. A type variable is not an unknown type — it is a declared
41
// type parameter, and what it guarantees is exactly what it was
42
// declared to guarantee. Its `is_value_type` is unconditionally
43
// true (values of one are boxed), so the answer has to come from
44
// the symbol rather than from the type.
45
satisfies_kind_as_type_variable(kind: TypeParameterConstraintKind, actual: Type) -> bool =>
46
_satisfies_kind_as_type_variable(kind, actual, Collections.SET[Symbol]())
47
48
_satisfies_kind_as_type_variable(
49
kind: TypeParameterConstraintKind,
50
actual: Type,
51
seen: Collections.SET[Symbol]
52
) -> bool is
53
if actual.symbol.constraint_kind == kind then
54
return true
55
fi
56
57
if kind != TypeParameterConstraintKind.REFERENCE then
58
return false
59
fi
60
61
// A class bound guarantees the reference kind, the way a CLR
62
// `where T : SomeClass` satisfies a `where U : class`. A trait
63
// bound does not — a struct can implement one — and neither does
64
// the `object` ancestor an unbounded parameter carries, since a
65
// value type is an object too. Getting either wrong reintroduces
66
// the same VerificationException this check exists to prevent.
67
let bound = actual.bound_type
68
69
if !bound? \/ bound.is_object \/ bound.is_trait then
70
return false
71
fi
72
73
// A bound that is itself a type parameter (`[TDerived: TBase]`)
74
// guarantees whatever its own effective bound guarantees, so the
75
// chain is walked to its end. The test has to precede the value-
76
// type one, whose answer for a type variable is unconditionally
77
// true. `seen` stops a cyclic declaration looping.
78
if bound.is_type_variable then
79
if !seen.add(actual.symbol) then
80
return false
81
fi
82
83
return _satisfies_kind_as_type_variable(kind, bound, seen)
84
fi
85
86
return !bound.is_value_type
87
si
88
89
// A type-variable actual answers satisfied here whatever it
90
// declares, since is_checkable excludes one: the real decision is
91
// deferred to check_argument_type_bounds, which runs once its bound
92
// is attached. Deciding it here as well would report the same
93
// violation twice at a call site, where both checks run.
94
is_satisfied(kind: TypeParameterConstraintKind, actual: Type) -> bool is
95
if
96
kind == TypeParameterConstraintKind.NONE \/
97
!is_checkable(actual)
98
then
99
return true
100
fi
101
102
if kind == TypeParameterConstraintKind.REFERENCE then
103
return !actual.is_value_type
104
elif kind == TypeParameterConstraintKind.OPTIONAL then
105
return actual.is_optional
106
fi
107
108
// The `struct` constraint is non-nullable-value-type, matching
109
// the CLR: `Nullable[T]` is itself a value type but does not
110
// satisfy it. This also keeps the generic `struct`-constrained
111
// order comparison from binding its type parameter to a value
112
// optional, so `a < b` on optionals is a clean overload-not-
113
// found diagnostic instead of a silent absent-value comparison.
114
return actual.is_value_type /\ !actual.is_optional
115
si
116
117
describe(kind: TypeParameterConstraintKind) -> string =>
118
if kind == TypeParameterConstraintKind.REFERENCE then
119
"a reference type"
120
elif kind == TypeParameterConstraintKind.VALUE then
121
"a value type"
122
elif kind == TypeParameterConstraintKind.OPTIONAL then
123
"an optional type"
124
else
125
"?"
126
fi
127
128
// The declared parameter symbol at `index` of the generic the
129
// bounds being checked were written on. The bound's own type
130
// references carry these symbols, so a substitution map has to
131
// be keyed on them for the lookup to match.
132
_parameter_at(owner: Symbol, index: int) -> Symbol? is
133
if let classy = cast Classy?(owner) then
134
return cast GenericArgument?(classy.type_parameter_at(index))
135
fi
136
137
// The parameter is read off the function the bounds were
138
// declared on. A specialized copy has had its
139
// `generic_arguments` replaced by the actual type arguments,
140
// so reading one off that answers with an actual rather than
141
// with the parameter a bound mentions, and the bound is then
142
// never substituted.
143
let function = cast Function?(cast Symbol?(owner)!.root_specialized_from)
144
145
if !function? \/ index >= function.generic_argument_names.count then
146
return null
147
fi
148
149
if let declared = function.find_direct(function.generic_argument_names[index]) then
150
return cast GenericArgument?(declared)
151
fi
152
153
// A reflected generic method declares no parameters into its
154
// own scope and carries them positionally instead, where the
155
// mapped type's symbol is the one its bounds mention.
156
if index < function.generic_arguments.count then
157
return cast GenericArgument?(function.generic_arguments[index].symbol)
158
fi
159
160
return null
161
si
162
163
// How many arguments both lists hold, so a mismatched arity
164
// cannot index past the shorter.
165
_checkable_count(
166
argument_names: Collections.List[string],
167
actual_type_arguments: Collections.List[Type]
168
) -> int static =>
169
if argument_names.count < actual_type_arguments.count then
170
argument_names.count
171
else
172
actual_type_arguments.count
173
fi
174
175
// Builds a parameter → actual map so a bound that references
176
// another parameter (`[TDerived: TBase]` for a generic with
177
// `TBase, TDerived`) can be substituted against the actuals
178
// before the assignability check.
179
build_type_map(
180
owner: Symbol,
181
argument_names: Collections.List[string],
182
actual_type_arguments: Collections.List[Type]
183
) -> Collections.Map[Symbol,Type] is
184
let result = Collections.MAP[Symbol,Type]()
185
186
for i in 0 .. argument_names.count do
187
if i < actual_type_arguments.count then
188
let parameter = _parameter_at(owner, i)
189
190
if parameter? then
191
result[parameter] = actual_type_arguments[i]
192
fi
193
fi
194
od
195
196
return result
197
si
198
199
// True when `actual` has its own accessible parameterless
200
// constructor — what an `init` constraint requires. A value type
201
// always has one; a class or struct carries the answer on its
202
// symbol (`Classy.has_parameterless_constructor`), recorded when
203
// the type is declared or imported — a ghūl constructor's
204
// signature is not resolved early enough to inspect here. Any
205
// other type shape is not checkable, so the answer is true.
206
has_accessible_parameterless_constructor(actual: Type) -> bool is
207
// A type variable guarantees a constructor only if it was
208
// declared to — either directly, or by being a non-nullable
209
// value type, which always has one.
210
if actual.is_type_variable then
211
return
212
actual.symbol.has_constructor_constraint \/
213
actual.symbol.constraint_kind == TypeParameterConstraintKind.VALUE
214
fi
215
216
if actual.is_value_type then
217
return true
218
fi
219
220
let symbol = actual.symbol
221
222
if isa Classy(symbol) then
223
return symbol.has_parameterless_constructor
224
fi
225
226
return true
227
si
228
229
// Reports a diagnostic when the actual type argument at `index`
230
// violates one of its parameter's declared bounds (`[T: A /\ B]`).
231
// `type_map` maps every parameter name to its actual so a bound
232
// that references a sibling parameter (`[TDerived: TBase]`) is
233
// substituted before the assignability check. No bounds, or an
234
// unresolved or error actual, reports nothing; each violated bound
235
// reports on its own.
236
//
237
// A type-variable actual is reported on: assignability reads its
238
// declared bounds, so an unbounded one satisfies nothing and a
239
// bounded one satisfies whichever of its bounds the formal's
240
// bound is assignable to.
241
report_bound_violation(
242
location: LOCATION,
243
logger: Logger,
244
owner: Symbol,
245
index: int,
246
name: string,
247
actual: Type,
248
type_map: Collections.Map[Symbol,Type]
249
) is
250
for bound in owner.get_argument_type_bounds(index) do
251
let specialized_bound = bound.specialize(type_map)
252
253
if !specialized_bound.is_assignable_from(actual) then
254
logger.error(
255
location,
256
"type argument {actual} for {name} must be {specialized_bound} or a subtype"
257
)
258
fi
259
od
260
si
261
262
// Reports the kind and constructor constraints for a type-variable
263
// actual. They live here rather than in check_argument_kinds
264
// because their answer reads the actual's own declared bound, which
265
// is only attached during resolve-explicit-types — after every type
266
// expression has resolved, and so after the kinds are checked at a
267
// type-expression position. Running them there instead reports a
268
// bounded parameter as satisfying nothing, since its bound is not
269
// attached yet.
270
report_type_variable_kind_violations(
271
location: LOCATION,
272
logger: Logger,
273
owner: Symbol,
274
index: int,
275
name: string,
276
actual: Type
277
) is
278
let kind = owner.get_argument_constraint_kind(index)
279
280
if kind != TypeParameterConstraintKind.NONE /\ !satisfies_kind_as_type_variable(kind, actual) then
281
logger.error(
282
location,
283
"type argument {actual} for {name} must be {describe(kind)}"
284
)
285
fi
286
287
if
288
owner.get_argument_has_constructor_constraint(index) /\
289
!has_accessible_parameterless_constructor(actual)
290
then
291
logger.error(
292
location,
293
"type argument {actual} for {name} must have an accessible parameterless constructor"
294
)
295
fi
296
si
297
298
// Checks the type bounds of each actual type argument, and every
299
// constraint of a type-variable actual. Used at type-expression
300
// positions (`f: CC[X]`, `-> CC[X]`, `let x: CC[X]`), where the
301
// kind and `init` constraints of an ordinary actual are already
302
// checked as the type expression resolves but the bound is not yet
303
// attached to the parameter symbol at that point.
304
check_argument_type_bounds(
305
location: LOCATION,
306
logger: Logger,
307
owner: Symbol,
308
argument_names: Collections.List[string],
309
actual_type_arguments: Collections.List[Type]
310
) is
311
let type_map: Collections.Map[Symbol,Type]? mut = null
312
313
// A call may supply more type arguments than the generic
314
// declares, which is reported where the arity is checked;
315
// the names run out first, so the check stops with them.
316
for i in 0 .. _checkable_count(argument_names, actual_type_arguments) do
317
let actual = actual_type_arguments[i]
318
319
if is_declared_type_checkable(actual) then
320
if !type_map? then
321
type_map = build_type_map(owner, argument_names, actual_type_arguments)
322
fi
323
324
if actual.is_type_variable then
325
report_type_variable_kind_violations(
326
location, logger, owner, i, argument_names[i], actual
327
)
328
fi
329
330
report_bound_violation(location, logger, owner, i, argument_names[i], actual, type_map)
331
fi
332
od
333
si
334
335
// Checks each actual type argument of a generic class or
336
// function against its declared constraints — a kind constraint
337
// (`class` / `struct` / `optional`), a type bound
338
// (`[T: SomeBase]`), and the `init` constructor constraint.
339
// `owner` is the generic whose scope holds the type-parameter
340
// symbols.
341
check_arguments(
342
location: LOCATION,
343
logger: Logger,
344
owner: Symbol,
345
argument_names: Collections.List[string],
346
actual_type_arguments: Collections.List[Type]
347
) is
348
check_argument_kinds(location, logger, owner, argument_names, actual_type_arguments)
349
check_argument_type_bounds(location, logger, owner, argument_names, actual_type_arguments)
350
si
351
352
// Checks only the kind (`class` / `struct` / `optional`) and
353
// `init` constructor constraints — the checks that do not depend
354
// on the type bound being attached. Used at type-expression
355
// positions as the type expression resolves; the bound is checked
356
// afterwards by check_argument_type_bounds once it is attached.
357
check_argument_kinds(
358
location: LOCATION,
359
logger: Logger,
360
owner: Symbol,
361
argument_names: Collections.List[string],
362
actual_type_arguments: Collections.List[Type]
363
) is
364
for i in 0 .. _checkable_count(argument_names, actual_type_arguments) do
365
let name = argument_names[i]
366
let actual = actual_type_arguments[i]
367
368
let kind = owner.get_argument_constraint_kind(i)
369
370
if !is_satisfied(kind, actual) then
371
logger.error(
372
location,
373
"type argument {actual} for {name} must be {describe(kind)}"
374
)
375
fi
376
377
if
378
owner.get_argument_has_constructor_constraint(i) /\
379
is_checkable(actual) /\
380
!has_accessible_parameterless_constructor(actual)
381
then
382
logger.error(
383
location,
384
"type argument {actual} for {name} must have an accessible parameterless constructor"
385
)
386
fi
387
od
388
si
389
si
390
si