Appearance
| 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 |