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