Appearance
| 1 | namespace Semantic.Types is | |
| 2 | use IO.Std | |
| 3 | ||
| 4 | use System.Text.StringBuilder | |
| 5 | ||
| 6 | use Source | |
| 7 | ||
| 8 | trait SettableTyped: Typed is | |
| 9 | set_type(value: Type) | |
| 10 | si | |
| 11 | ||
| 12 | // Open so the unit-test assembly can stand in for it. Nothing | |
| 13 | // outside this compilation implements it otherwise. | |
| 14 | trait Typed is | |
| 15 | type: Type? | |
| 16 | si | |
| 17 | ||
| 18 | enum MATCH is | |
| 19 | SAME = 0, | |
| 20 | ASSIGNABLE = 1, | |
| 21 | CONVERTABLE = 2, | |
| 22 | PARTIAL = 3, | |
| 23 | WILD = 4, | |
| 24 | DIFFERENT = 100000 | |
| 25 | si | |
| 26 | ||
| 27 | class Type: Typed abstract is | |
| 28 | type: Type? => self | |
| 29 | ||
| 30 | name: string? => null | |
| 31 | depth: int => symbol.depth | |
| 32 | ||
| 33 | scope: Scope? => null | |
| 34 | ||
| 35 | symbol: Symbols.Symbol => | |
| 36 | if scope? /\ isa Symbols.Symbol(scope) then | |
| 37 | cast Symbols.Symbol?(scope)! | |
| 38 | else | |
| 39 | Symbols.NONE.instance | |
| 40 | fi | |
| 41 | ||
| 42 | ancestors: Collections.List[Type] => symbol.ancestors | |
| 43 | arguments: Collections.List[Type] => Collections.LIST[Type](0) | |
| 44 | ||
| 45 | // The declared upper bound of a type variable, or null for any | |
| 46 | // other type and for an unbounded variable. A value of a bounded | |
| 47 | // type variable can be used as its bound; narrowing, destructuring, | |
| 48 | // and operator resolution peel to this so a bounded `T` behaves as | |
| 49 | // its bound the way member access already does. | |
| 50 | bound_type: Type? => | |
| 51 | if is_type_variable /\ ancestors.count > 0 then | |
| 52 | ancestors[0] | |
| 53 | else | |
| 54 | null | |
| 55 | fi | |
| 56 | ||
| 57 | short_description: string => to_string() ?? "" | |
| 58 | ||
| 59 | // The type variable this type's runtime value is loaded as, or | |
| 60 | // null when there is none. A narrow composes a variable with | |
| 61 | // what it was tested against, and the load still puts a `!!N` | |
| 62 | // on the stack - so `box` and `constrained.` need the variable | |
| 63 | // rather than the composed view or the tested-against side. | |
| 64 | type_variable_side: Type? => | |
| 65 | if is_type_variable then self else null fi | |
| 66 | ||
| 67 | unspecialized_symbol: Symbols.Symbol? => | |
| 68 | let s = scope in | |
| 69 | if s? then | |
| 70 | s.unspecialized_symbol | |
| 71 | else | |
| 72 | null | |
| 73 | fi | |
| 74 | ||
| 75 | // FIXME: better than isa XXXX, but still should not need these: | |
| 76 | is_none: bool => false | |
| 77 | is_null: bool => false | |
| 78 | is_consumable: bool => !is_sentinel /\ !is_error /\ !is_wild | |
| 79 | is_error: bool => false | |
| 80 | is_wild: bool => false | |
| 81 | is_inferred: bool => false | |
| 82 | ||
| 83 | // ===== Inference-state predicates ===== | |
| 84 | // | |
| 85 | // Three states a type can be in during iterative | |
| 86 | // inference, captured by two predicates: | |
| 87 | // | |
| 88 | // is_sentinel is_settled | |
| 89 | // sentinel singleton true false e.g. INFERRED_VARIABLE_TYPE, ERROR | |
| 90 | // provisional composite false false e.g. Function[INFERRED_VARIABLE_TYPE, int] | |
| 91 | // settled false true e.g. Function[int, int] | |
| 92 | // | |
| 93 | // The canonical answers for "what state is this type | |
| 94 | // in?". Prefer these to ad-hoc combinations like | |
| 95 | // `!is_inferred /\ !is_error` or `is_error \/ | |
| 96 | // is_inferred` — and if you find yourself editing near | |
| 97 | // such a combination, migrate it to the named | |
| 98 | // predicate. The point is to have one spelling per | |
| 99 | // concept across the codebase; without that every new | |
| 100 | // call-site reinvents the question and gets the | |
| 101 | // top/deep distinction subtly wrong. | |
| 102 | ||
| 103 | // True if this type is one of the three singleton | |
| 104 | // inference markers: INFERRED_VARIABLE_TYPE, | |
| 105 | // INFERRED_RETURN_TYPE, or ERROR. Sentinels aren't types | |
| 106 | // a user could write; the first two stand in for slots | |
| 107 | // the inference machinery hasn't yet filled, the third | |
| 108 | // for a slot that failed to fill. Use !is_sentinel as | |
| 109 | // the "this is a real type I can work with" test — it's | |
| 110 | // true for every named/composite type, including | |
| 111 | // provisional composites like Function[placeholder,int] | |
| 112 | // whose outer shape is real even if inner slots are | |
| 113 | // unresolved. | |
| 114 | // | |
| 115 | // Prefer this over `!is_inferred /\ !is_error` — the | |
| 116 | // conjunction is just the unfactored spelling of this | |
| 117 | // predicate. | |
| 118 | is_sentinel: bool => false | |
| 119 | ||
| 120 | // True if self or any nested type argument is an | |
| 121 | // inference placeholder (INFERRED_VARIABLE_TYPE, | |
| 122 | // INFERRED_RETURN_TYPE). Use this in preference to | |
| 123 | // is_inferred whenever the type might be composite — | |
| 124 | // a Function[INFERRED_VARIABLE_TYPE, int] has | |
| 125 | // is_inferred=false at the top level but is *not* yet | |
| 126 | // resolved. Default looks only at self.is_inferred; | |
| 127 | // composite types (NAMED etc.) override to recurse. | |
| 128 | contains_inferred: bool => is_inferred | |
| 129 | ||
| 130 | // True if self or any nested type argument is a method-level | |
| 131 | // generic type parameter that has not yet been bound by the | |
| 132 | // in-flight overload resolution (FUNCTION_GENERIC_ARGUMENT) - | |
| 133 | // a bare `U` or a composite like `Tasks.TASK[U]` where `U` is | |
| 134 | // the callee's own unresolved type argument. Distinct from | |
| 135 | // is_wild, which also answers true for a class-level type | |
| 136 | // parameter that is already bound in its own context (e.g. a | |
| 137 | // generic class's own `T` referenced from inside a method) - | |
| 138 | // that case must not be treated as unresolved. Default looks | |
| 139 | // only at self.is_function_generic_argument; composite types | |
| 140 | // (NAMED etc.) override to recurse. | |
| 141 | contains_function_generic_argument: bool => is_function_generic_argument | |
| 142 | ||
| 143 | // True if this type's tree contains a method-level generic | |
| 144 | // type-parameter reference (see contains_function_generic_argument) | |
| 145 | // that does not belong to `owner` and is not lexically in | |
| 146 | // scope from it. Such a reference is legitimate only while it | |
| 147 | // names a generic parameter of the function currently being | |
| 148 | // compiled, or of an enclosing function a nested closure can | |
| 149 | // see — anywhere else its index has meaning only inside the | |
| 150 | // (possibly already-discarded) overload specialization that | |
| 151 | // produced it, and committing a type carrying it (e.g. as a | |
| 152 | // local variable's declared type, or as a call-site type | |
| 153 | // argument) leaks an unbound !!N into the emitted IL. | |
| 154 | has_function_generic_argument_foreign_to(owner: Scope?) -> bool is | |
| 155 | if !contains_function_generic_argument then | |
| 156 | return false | |
| 157 | fi | |
| 158 | ||
| 159 | for a in get_type_arguments() do | |
| 160 | if !a.is_function_generic_argument then | |
| 161 | continue | |
| 162 | fi | |
| 163 | ||
| 164 | if a.symbol.owner == owner then | |
| 165 | continue | |
| 166 | fi | |
| 167 | ||
| 168 | // A nested closure compiling its body may name the | |
| 169 | // enclosing function's type parameters; those are in | |
| 170 | // scope via find_enclosing and must not count as | |
| 171 | // foreign. | |
| 172 | if owner? then | |
| 173 | let found = owner.find_enclosing(a.symbol.name) | |
| 174 | ||
| 175 | if found? /\ found == a.symbol then | |
| 176 | continue | |
| 177 | fi | |
| 178 | fi | |
| 179 | ||
| 180 | return true | |
| 181 | od | |
| 182 | ||
| 183 | return false | |
| 184 | si | |
| 185 | ||
| 186 | // Fully resolved: no inference placeholder, no ERROR, | |
| 187 | // anywhere in the type tree. The canonical "ready to | |
| 188 | // commit / push as a constraint" test; describes the | |
| 189 | // universal end-state every inferred slot is expected | |
| 190 | // to converge to. !is_settled means a slot holds either | |
| 191 | // a sentinel or a provisional composite that the body- | |
| 192 | // retry loop may overwrite on the next iter. | |
| 193 | // | |
| 194 | // Stronger than !is_sentinel — provisional composites | |
| 195 | // pass !is_sentinel but fail is_settled. Pick the | |
| 196 | // weaker predicate (!is_sentinel) when you can work | |
| 197 | // with any real type; pick is_settled when you need | |
| 198 | // the inner slots filled too. | |
| 199 | // | |
| 200 | // Prefer this over `!is_error /\ !contains_inferred` | |
| 201 | // or any other recombination of the underlying flags; | |
| 202 | // if you spot one while editing, migrate it. | |
| 203 | is_settled: bool => !contains_inferred /\ !is_error | |
| 204 | is_named: bool => false // FIXME: what would it mean not to be named? | |
| 205 | is_object: bool => false | |
| 206 | is_root_value_type: bool => false | |
| 207 | is_void: bool => false | |
| 208 | is_type_variable: bool => false | |
| 209 | is_classy_generic_argument: bool => false | |
| 210 | is_function_generic_argument: bool => false | |
| 211 | is_value_type: bool => false | |
| 212 | ||
| 213 | // A pointer is a native integer, not an object, and the CLI has | |
| 214 | // no boxed form for one. | |
| 215 | is_pointer: bool => false | |
| 216 | ||
| 217 | is_inheritable: bool => false | |
| 218 | is_class: bool => false | |
| 219 | is_trait: bool => false | |
| 220 | is_action: bool => false | |
| 221 | is_function: bool => false | |
| 222 | ||
| 223 | // True for a function type marked `pure` — values are trusted | |
| 224 | // store-free. Not part of type identity or assignability; see | |
| 225 | // PURE_FUNCTION. | |
| 226 | is_pure_function: bool => false | |
| 227 | is_function_with_any_implicit_argument_types: bool => false | |
| 228 | is_ref: bool => false // specifically 'ref', not just a reference type | |
| 229 | is_value_tuple: bool => false | |
| 230 | is_unsafe_constraints: bool => symbol.is_unsafe_constraints | |
| 231 | ||
| 232 | // The element names of a value tuple, or null when this is not | |
| 233 | // a tuple or carries no names. The .NET ValueTuple type holds | |
| 234 | // no names — they ride on a TupleElementNamesAttribute at the | |
| 235 | // declaration site — so a reflected tuple starts nameless and | |
| 236 | // is rebuilt with `apply_tuple_element_names`. | |
| 237 | tuple_element_names: Collections.List[string?]? => null | |
| 238 | ||
| 239 | // Return an equivalent value-tuple type carrying `names` (one | |
| 240 | // per element, null for an unnamed element). A no-op for any | |
| 241 | // type that is not a value tuple. | |
| 242 | apply_tuple_element_names(names: Collections.List[string?]) -> Type => self | |
| 243 | ||
| 244 | // True for a reference type carrying an explicit `?` | |
| 245 | // nullability annotation, and for the value-type NULLABLE[T]. | |
| 246 | is_optional: bool => false | |
| 247 | ||
| 248 | // True when `null` is one of this type's own values rather than a | |
| 249 | // marker wrapped around them. Every optional type qualifies, and so | |
| 250 | // does a pointer, whose null is the zero address rather than a | |
| 251 | // wrapper around one, and which has no `?` spelling that would mean | |
| 252 | // the same thing. A `ref` does not: one is only ever formed by | |
| 253 | // taking the address of a variable, so there is no null to write. | |
| 254 | accepts_null: bool => is_optional \/ is_pointer | |
| 255 | ||
| 256 | // True for `Ghul.MAYBE[T]`, the runtime's unconstrained-T | |
| 257 | // optional carrier. `T?` slot boundaries accept it via an | |
| 258 | // implicit coercion. | |
| 259 | is_maybe: bool => false | |
| 260 | ||
| 261 | init() is | |
| 262 | si | |
| 263 | ||
| 264 | // The `T?` form of this type. A value type yields NULLABLE[T]; | |
| 265 | // a reference type yields itself flagged optional. Overridden | |
| 266 | // by NAMED; the base covers sentinels, which are left as-is. | |
| 267 | as_optional() -> Type => self | |
| 268 | ||
| 269 | // Reflected-import variant of `as_optional`. NAMED overrides | |
| 270 | // to skip the is_value_type / is_type_variable guards (those | |
| 271 | // would force premature materialization of a TYPE_WRAPPER's | |
| 272 | // symbol during bootstrap). Other types fall back to plain | |
| 273 | // `as_optional` — sentinels stay as-is, NULLABLE / MAYBE are | |
| 274 | // already optional. | |
| 275 | as_optional_unchecked() -> Type => as_optional() | |
| 276 | ||
| 277 | // The non-optional form of this type. For a reference type | |
| 278 | // carrying `?` this drops the flag; a no-op for everything | |
| 279 | // else — value-type optionality is the distinct NULLABLE[T], | |
| 280 | // and sentinels have no `?` form. Overridden by NAMED. Used | |
| 281 | // when flow-sensitive narrowing establishes a variable is | |
| 282 | // non-null at a use site. | |
| 283 | as_non_optional() -> Type => self | |
| 284 | ||
| 285 | // The `T` of a `T?` carrier, regardless of which lowering | |
| 286 | // produced it (reference-T flagged `NAMED`, value-T | |
| 287 | // `NULLABLE[T]`, or unconstrained-T `MAYBE[T]`). Null when | |
| 288 | // this type is not optional-shaped. The single accessor lets | |
| 289 | // compare/box sites ask "what's inside?" without having to | |
| 290 | // know which optional flavour they're looking at. | |
| 291 | optional_inner_type: Type? => null | |
| 292 | ||
| 293 | // Erased type identity: same type with the reference-`?` | |
| 294 | // annotation ignored, so `cat?` matches `cat`. This is the CLR's | |
| 295 | // view — the flag has no runtime existence — and it is what | |
| 296 | // override matching, reflected-signature comparison, and | |
| 297 | // synthesised-member wiring need. It is NOT safe for | |
| 298 | // assignability decisions; those go through `compare` / | |
| 299 | // `is_equivalent_to`, where the flag participates. | |
| 300 | // (`NULLABLE[T]` and `MAYBE[T]` are distinct CLR types, so | |
| 301 | // value-type and unconstrained optionals never erase.) | |
| 302 | // | |
| 303 | // Not an equivalence relation, and deliberately not spelled as | |
| 304 | // one: sentinel types match anything, so it is neither | |
| 305 | // symmetric (`NULL` matches `cat`, `cat` does not match `NULL`) | |
| 306 | // nor transitive (`cat` matches `ERROR` matches `dog`), and the | |
| 307 | // base returns false rather than true. Do not route it through | |
| 308 | // `equals` — .NET requires all three of those properties from | |
| 309 | // anything it uses as a dictionary key. | |
| 310 | matches(other: Type) -> bool => false | |
| 311 | ||
| 312 | // Full type equivalence: `matches` plus the optional flag, at | |
| 313 | // every nesting depth. Invariant generic-argument positions | |
| 314 | // compare with this — `box_like[cat?]` must not unify with | |
| 315 | // `box_like[cat]`, and a `cat?` local must not satisfy a | |
| 316 | // `cat ref` parameter. Sentinel tolerance follows `matches`. | |
| 317 | is_equivalent_to(other: Type) -> bool | |
| 318 | => self.matches(other) | |
| 319 | ||
| 320 | is_assignable_from(other: Type) -> bool | |
| 321 | => cast int(compare(other)) <= cast int (MATCH.ASSIGNABLE) | |
| 322 | ||
| 323 | compare(other: Type) -> MATCH | |
| 324 | => MATCH.DIFFERENT | |
| 325 | ||
| 326 | find_member(name: string) -> Symbols.Symbol? | |
| 327 | => null | |
| 328 | ||
| 329 | find_destructure_member(index: int) -> Symbols.Symbol? is | |
| 330 | // A bounded type variable destructures through its bound the | |
| 331 | // way member access already resolves through it. | |
| 332 | if let bound = bound_type then | |
| 333 | return bound.find_destructure_member(index) | |
| 334 | fi | |
| 335 | ||
| 336 | let name = get_destructure_member_name(index) | |
| 337 | ||
| 338 | if !name? then | |
| 339 | return null | |
| 340 | fi | |
| 341 | ||
| 342 | let result = find_member(name) | |
| 343 | ||
| 344 | if result? then | |
| 345 | return result | |
| 346 | fi | |
| 347 | ||
| 348 | if Symbols.Symbol.is_positional_member_name(name) then | |
| 349 | // Some assemblies name positional members with a leading | |
| 350 | // backtick (`0, `1, ...) rather than a bare index; retry | |
| 351 | // with that spelling before giving up. | |
| 352 | return find_member("`{name}") | |
| 353 | fi | |
| 354 | ||
| 355 | return null | |
| 356 | si | |
| 357 | ||
| 358 | get_destructure_member_name(index: int) -> string? | |
| 359 | => null | |
| 360 | ||
| 361 | find_ancestor(type: Type) -> Type? => null | |
| 362 | ||
| 363 | specialize(type_map: Collections.Map[Symbols.Symbol,Type]) -> Type => throw System.NotImplementedException("not implemented by {self.get_type()}") | |
| 364 | bind_type_variables(other: Type, results: GENERIC_ARGUMENT_BIND_RESULTS) -> bool => | |
| 365 | true | |
| 366 | ||
| 367 | get_type_arguments_into(results: Collections.LIST[GenericArgument]) is | |
| 368 | si | |
| 369 | ||
| 370 | get_type_arguments() -> Collections.LIST[GenericArgument] => ( | |
| 371 | let result = Collections.LIST[GenericArgument]() | |
| 372 | get_type_arguments_into(result) | |
| 373 | result | |
| 374 | ) | |
| 375 | ||
| 376 | freeze() -> Type? => null | |
| 377 | ||
| 378 | walk(action: (Type) -> void) => throw System.NotImplementedException("not implemented by {self.get_type()}") | |
| 379 | get_element_type() -> Type? => null | |
| 380 | ||
| 381 | format(result: StringBuilder) is | |
| 382 | result.append(self) | |
| 383 | si | |
| 384 | ||
| 385 | get_hash_code() -> int => symbol.get_hash_code() | |
| 386 | si | |
| 387 | si |