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 (`new()`) constraint read from .NET
11
// metadata.
12
//
13
// Type variables, sentinels and unresolved or error types are not
14
// checkable: the conservative answer is that the constraint holds,
15
// so no false positive is reported.
16
class GENERIC_CONSTRAINT_CHECKER is
17
init() is si
18
19
is_checkable(actual: Type?) -> bool =>
20
actual? /\
21
actual.is_settled /\
22
!actual.is_type_variable /\
23
!actual.is_sentinel;
24
25
is_satisfied(kind: TypeParameterConstraintKind, actual: Type) -> bool is
26
if
27
kind == TypeParameterConstraintKind.NONE \/
28
!is_checkable(actual)
29
then
30
return true;
31
fi
32
33
if kind == TypeParameterConstraintKind.REFERENCE then
34
return !actual.is_value_type;
35
elif kind == TypeParameterConstraintKind.OPTIONAL then
36
return actual.is_optional;
37
fi
38
39
// The `struct` constraint is non-nullable-value-type, matching
40
// the CLR: `Nullable[T]` is itself a value type but does not
41
// satisfy it. This also keeps the generic `struct`-constrained
42
// order comparison from binding its type parameter to a value
43
// optional, so `a < b` on optionals is a clean overload-not-
44
// found diagnostic instead of a silent absent-value comparison.
45
return actual.is_value_type /\ !actual.is_optional;
46
si
47
48
describe(kind: TypeParameterConstraintKind) -> string =>
49
if kind == TypeParameterConstraintKind.REFERENCE then
50
"a reference type";
51
elif kind == TypeParameterConstraintKind.VALUE then
52
"a value type";
53
elif kind == TypeParameterConstraintKind.OPTIONAL then
54
"an optional type";
55
else
56
"?";
57
fi;
58
59
// Builds a name → actual map so a parameter's bound that
60
// references another parameter (`[TDerived: TBase]` for a
61
// generic with `TBase, TDerived`) can be substituted against
62
// the actuals before the assignability check.
63
build_type_map(
64
argument_names: Collections.List[string],
65
actual_type_arguments: Collections.List[Type]
66
) -> Collections.Map[string,Type] is
67
let result = Collections.MAP[string,Type]();
68
69
for i in 0 .. argument_names.count do
70
if i < actual_type_arguments.count then
71
result[argument_names[i]] = actual_type_arguments[i];
72
fi
73
od
74
75
return result;
76
si
77
78
// True when `actual` has its own accessible parameterless
79
// constructor — what a `new()` constraint requires. A value type
80
// always has one; a class or struct carries the answer on its
81
// symbol (`Classy.has_parameterless_constructor`), recorded when
82
// the type is declared or imported — a ghūl constructor's
83
// signature is not resolved early enough to inspect here. Any
84
// other type shape is not checkable, so the answer is true.
85
has_accessible_parameterless_constructor(actual: Type) -> bool is
86
if actual.is_value_type then
87
return true;
88
fi
89
90
let symbol = actual.symbol;
91
92
if isa Classy(symbol) then
93
return symbol.has_parameterless_constructor;
94
fi
95
96
return true;
97
si
98
99
// Reports a diagnostic when the actual type argument at `index`
100
// violates its parameter's declared type bound (`[T: SomeBase]`).
101
// `type_map` maps every parameter name to its actual so a bound
102
// that references a sibling parameter (`[TDerived: TBase]`) is
103
// substituted before the assignability check. No bound, or an
104
// unresolved / error / type-variable actual, reports nothing.
105
report_bound_violation(
106
location: LOCATION,
107
logger: Logger,
108
owner: Symbol,
109
index: int,
110
name: string,
111
actual: Type,
112
type_map: Collections.Map[string,Type]
113
) is
114
let bound = owner.get_argument_type_bound(index);
115
116
if !bound? then
117
return;
118
fi
119
120
let specialized_bound = bound.specialize(type_map);
121
122
if !specialized_bound.is_assignable_from(actual) then
123
logger.error(
124
location,
125
"type argument {actual} for {name} must be {specialized_bound} or a subtype"
126
);
127
fi
128
si
129
130
// Checks only the type bounds of each actual type argument.
131
// Used at type-expression positions (`f: CC[X]`, `-> CC[X]`,
132
// `let x: CC[X]`), where the kind and `new()` constraints are
133
// already checked as the type expression resolves but the bound
134
// is not yet attached to the parameter symbol at that point.
135
check_argument_type_bounds(
136
location: LOCATION,
137
logger: Logger,
138
owner: Symbol,
139
argument_names: Collections.List[string],
140
actual_type_arguments: Collections.List[Type]
141
) is
142
let type_map: Collections.Map[string,Type]? mut = null;
143
144
for i in 0 .. actual_type_arguments.count do
145
let actual = actual_type_arguments[i];
146
147
if is_checkable(actual) then
148
if !type_map? then
149
type_map = build_type_map(argument_names, actual_type_arguments);
150
fi
151
152
report_bound_violation(location, logger, owner, i, argument_names[i], actual, type_map);
153
fi
154
od
155
si
156
157
// Checks each actual type argument of a generic class or
158
// function against its declared constraints — a kind constraint
159
// (`class` / `struct` / `optional`), a type bound
160
// (`[T: SomeBase]`), and the `new()` constructor constraint.
161
// `owner` is the generic whose scope holds the type-parameter
162
// symbols.
163
check_arguments(
164
location: LOCATION,
165
logger: Logger,
166
owner: Symbol,
167
argument_names: Collections.List[string],
168
actual_type_arguments: Collections.List[Type]
169
) is
170
check_argument_kinds(location, logger, owner, argument_names, actual_type_arguments);
171
check_argument_type_bounds(location, logger, owner, argument_names, actual_type_arguments);
172
si
173
174
// Checks only the kind (`class` / `struct` / `optional`) and
175
// `new()` constructor constraints — the checks that do not depend
176
// on the type bound being attached. Used at type-expression
177
// positions as the type expression resolves; the bound is checked
178
// afterwards by check_argument_type_bounds once it is attached.
179
check_argument_kinds(
180
location: LOCATION,
181
logger: Logger,
182
owner: Symbol,
183
argument_names: Collections.List[string],
184
actual_type_arguments: Collections.List[Type]
185
) is
186
for i in 0 .. actual_type_arguments.count do
187
let name = argument_names[i];
188
let actual = actual_type_arguments[i];
189
190
let kind = owner.get_argument_constraint_kind(i);
191
192
if !is_satisfied(kind, actual) then
193
logger.error(
194
location,
195
"type argument {actual} for {name} must be {describe(kind)}"
196
);
197
fi
198
199
if
200
owner.get_argument_has_constructor_constraint(i) /\
201
is_checkable(actual) /\
202
!has_accessible_parameterless_constructor(actual)
203
then
204
logger.error(
205
location,
206
"type argument {actual} for {name} must have an accessible parameterless constructor"
207
);
208
fi
209
od
210
si
211
si
212
si