Appearance
| 1 | namespace Semantic.Types is | |
| 2 | use IO.Std; | |
| 3 | ||
| 4 | use Source.LOCATION; | |
| 5 | ||
| 6 | use Logging; | |
| 7 | ||
| 8 | // Internal narrowing-only sum type. Represents "value of closed | |
| 9 | // root R restricted to a non-empty proper subset of R's | |
| 10 | // alternatives" — built only by the narrowing path in | |
| 11 | // compile_expressions when an if/elif chain rules out one or | |
| 12 | // more alternatives, never produced by user-written type | |
| 13 | // expressions and never reaches IL gen (reads inside a narrowed | |
| 14 | // scope compile against the underlying root; release_scope | |
| 15 | // restores the declared type). Two closed-root shapes feed | |
| 16 | // this: a union (alternatives are its variants) and a closed | |
| 17 | // class (alternatives are its in-assembly direct subclasses). | |
| 18 | // | |
| 19 | // Subclasses NAMED with the root's own symbol so find_member, | |
| 20 | // ancestors, scope, gen_* and qualified_name all delegate to | |
| 21 | // the root — the issue's "members common to every subtype" | |
| 22 | // requirement is met by exposing the root's own members as a | |
| 23 | // strict lower bound. Construction is by Types.ONE_OF.create | |
| 24 | // which degenerates |subtypes| = 1 to a plain subtype and | |
| 25 | // rejects |subtypes| = 0 (returns null — the caller's signal | |
| 26 | // that the chain is exhaustive). | |
| 27 | // | |
| 28 | // Identity is by underlying type + subtype set: the `matches` override | |
| 29 | // refuses equality against the plain underlying root so that | |
| 30 | // NARROWING.try_push sees the type as changed. Comparison is | |
| 31 | // handled symmetrically: NAMED.compare(ONE_OF) already produces | |
| 32 | // SAME because the symbols match (assignability-to-root holds | |
| 33 | // by construction), GENERIC.compare(ONE_OF) delegates to the | |
| 34 | // underlying type via a small guard so the same identity falls | |
| 35 | // out for generic roots. | |
| 36 | class ONE_OF: NAMED is | |
| 37 | _underlying_type: NAMED; | |
| 38 | _subtypes: Collections.LIST[Symbols.Classy]; | |
| 39 | ||
| 40 | underlying_type: NAMED => _underlying_type; | |
| 41 | subtypes: Collections.Iterable[Symbols.Classy] => _subtypes; | |
| 42 | subtypes_count: int => _subtypes.count; | |
| 43 | ||
| 44 | init(underlying_type: NAMED, subtypes: Collections.LIST[Symbols.Classy]) is | |
| 45 | super.init(underlying_type.symbol); | |
| 46 | ||
| 47 | _underlying_type = underlying_type; | |
| 48 | _subtypes = subtypes; | |
| 49 | si | |
| 50 | ||
| 51 | // Build the narrowed type for `subtypes` over the given | |
| 52 | // underlying root type. Returns null when `subtypes` is | |
| 53 | // empty (the caller's exhaustiveness signal). Returns a | |
| 54 | // plain subtype for the singleton case so the size-1 | |
| 55 | // ONE_OF edge case is never representable. Returns a | |
| 56 | // ONE_OF for |subtypes| >= 2. | |
| 57 | create( | |
| 58 | underlying_type: NAMED?, | |
| 59 | subtypes: Collections.LIST[Symbols.Classy]? | |
| 60 | ) -> NAMED? static is | |
| 61 | if !underlying_type? \/ !subtypes? \/ subtypes.count == 0 then | |
| 62 | return null; | |
| 63 | fi | |
| 64 | ||
| 65 | if subtypes.count == 1 then | |
| 66 | return build_singleton_subtype(underlying_type, subtypes[0]); | |
| 67 | fi | |
| 68 | ||
| 69 | return ONE_OF(underlying_type, subtypes); | |
| 70 | si | |
| 71 | ||
| 72 | // Build the subtype carrying the underlying receiver's generic | |
| 73 | // args. For a variant subtype of a generic union, args are | |
| 74 | // shared with the union slot-for-slot and the singleton | |
| 75 | // collapses to a GENERIC over the variant. For a closed-class | |
| 76 | // subclass the receiver's generic args don't lift uniformly, | |
| 77 | // so we emit the bare subtype NAMED — the narrowing | |
| 78 | // complement path already rejects generic-class receivers. | |
| 79 | build_singleton_subtype( | |
| 80 | underlying_type: NAMED, | |
| 81 | subtype: Symbols.Classy | |
| 82 | ) -> NAMED? static => | |
| 83 | if isa GENERIC(underlying_type) /\ subtype.is_variant then | |
| 84 | let generic = underlying_type in | |
| 85 | GENERIC(subtype.location, subtype, generic.arguments) | |
| 86 | else | |
| 87 | NAMED(subtype) | |
| 88 | fi; | |
| 89 | ||
| 90 | contains_subtype(v: Symbols.Classy?) -> bool is | |
| 91 | if !v? then | |
| 92 | return false; | |
| 93 | fi | |
| 94 | ||
| 95 | for u in _subtypes do | |
| 96 | if u == v then | |
| 97 | return true; | |
| 98 | fi | |
| 99 | od | |
| 100 | ||
| 101 | return false; | |
| 102 | si | |
| 103 | ||
| 104 | // Join two flow-narrow types over the same closed root by | |
| 105 | // subtype-set union — the exact join at a control-flow merge | |
| 106 | // where each edge narrowed the same variable to a subset of | |
| 107 | // the root's alternatives. Applicable when at least one side | |
| 108 | // is a ONE_OF and the other is a ONE_OF over the same root or | |
| 109 | // a single alternative of it. Returns the underlying root | |
| 110 | // type when the union covers every alternative (the merge | |
| 111 | // learns nothing beyond the declared type), a ONE_OF or | |
| 112 | // single subtype otherwise, and null when the shapes don't | |
| 113 | // line up — the caller falls back to the general LUB. | |
| 114 | // | |
| 115 | // Optionality is unioned too: an edge that can be null keeps | |
| 116 | // the null in the merged type. | |
| 117 | try_join(a: Type?, b: Type?) -> Type? static is | |
| 118 | if !a? \/ !b? then | |
| 119 | return null; | |
| 120 | fi | |
| 121 | ||
| 122 | if !isa ONE_OF(a) /\ !isa ONE_OF(b) then | |
| 123 | return null; | |
| 124 | fi | |
| 125 | ||
| 126 | let result_is_optional = a.is_optional \/ b.is_optional; | |
| 127 | ||
| 128 | let one_of mut = cast ONE_OF?(a.as_non_optional()); | |
| 129 | let other mut = b.as_non_optional(); | |
| 130 | ||
| 131 | if !one_of? then | |
| 132 | one_of = cast ONE_OF?(b.as_non_optional()); | |
| 133 | other = a.as_non_optional(); | |
| 134 | fi | |
| 135 | ||
| 136 | if !one_of? then | |
| 137 | return null; | |
| 138 | fi | |
| 139 | ||
| 140 | let underlying = cast NAMED?(one_of.underlying_type.as_non_optional()); | |
| 141 | let root_classy = _try_drill_to_classy(underlying); | |
| 142 | ||
| 143 | if !underlying? \/ !root_classy? then | |
| 144 | return null; | |
| 145 | fi | |
| 146 | ||
| 147 | let members = Collections.LIST[Symbols.Classy](); | |
| 148 | ||
| 149 | for s in one_of.subtypes do | |
| 150 | members.add(s); | |
| 151 | od | |
| 152 | ||
| 153 | let other_one_of = cast ONE_OF?(other); | |
| 154 | ||
| 155 | if other_one_of? then | |
| 156 | if !underlying.matches(other_one_of.underlying_type) then | |
| 157 | return null; | |
| 158 | fi | |
| 159 | ||
| 160 | for s in other_one_of.subtypes do | |
| 161 | if !one_of.contains_subtype(s) then | |
| 162 | members.add(s); | |
| 163 | fi | |
| 164 | od | |
| 165 | else | |
| 166 | let classy = _try_drill_to_classy(other); | |
| 167 | ||
| 168 | if !classy? then | |
| 169 | return null; | |
| 170 | fi | |
| 171 | ||
| 172 | if classy == root_classy then | |
| 173 | // The other edge holds the whole root — the merge | |
| 174 | // is the root itself. | |
| 175 | return _flag_optional(underlying, result_is_optional); | |
| 176 | fi | |
| 177 | ||
| 178 | let is_alternative mut = false; | |
| 179 | ||
| 180 | for s in root_classy.closed_alternatives do | |
| 181 | if s == classy then | |
| 182 | is_alternative = true; | |
| 183 | fi | |
| 184 | od | |
| 185 | ||
| 186 | if !is_alternative then | |
| 187 | return null; | |
| 188 | fi | |
| 189 | ||
| 190 | if !one_of.contains_subtype(classy) then | |
| 191 | members.add(classy); | |
| 192 | fi | |
| 193 | fi | |
| 194 | ||
| 195 | if _covers_every_alternative(root_classy, members) then | |
| 196 | return _flag_optional(underlying, result_is_optional); | |
| 197 | fi | |
| 198 | ||
| 199 | return _flag_optional(create(underlying, members), result_is_optional); | |
| 200 | si | |
| 201 | ||
| 202 | // True when `members` includes every alternative of the | |
| 203 | // closed root — the same universe rule the complement | |
| 204 | // builder uses: the root's closed alternatives, plus the | |
| 205 | // root itself when it is a concrete class. | |
| 206 | _covers_every_alternative( | |
| 207 | root_classy: Symbols.Classy, | |
| 208 | members: Collections.LIST[Symbols.Classy] | |
| 209 | ) -> bool static is | |
| 210 | if !root_classy.is_closed_root then | |
| 211 | return false; | |
| 212 | fi | |
| 213 | ||
| 214 | for s in root_classy.closed_alternatives do | |
| 215 | if !members.contains(s) then | |
| 216 | return false; | |
| 217 | fi | |
| 218 | od | |
| 219 | ||
| 220 | if | |
| 221 | root_classy.is_class /\ | |
| 222 | !root_classy.is_abstract /\ | |
| 223 | !members.contains(root_classy) | |
| 224 | then | |
| 225 | return false; | |
| 226 | fi | |
| 227 | ||
| 228 | return true; | |
| 229 | si | |
| 230 | ||
| 231 | _flag_optional(type: Type?, is_optional: bool) -> Type? static => | |
| 232 | if type? /\ is_optional then | |
| 233 | type.as_optional(); | |
| 234 | else | |
| 235 | type; | |
| 236 | fi; | |
| 237 | ||
| 238 | // The Classy behind `type`, peeling Symbols.GENERIC wrapping | |
| 239 | // for specialized generics. Null when `type` isn't a NAMED | |
| 240 | // of a Classy. | |
| 241 | _try_drill_to_classy(type: Type?) -> Symbols.Classy? static is | |
| 242 | if !type? then | |
| 243 | return null; | |
| 244 | fi | |
| 245 | ||
| 246 | if !isa NAMED(type) then | |
| 247 | return null; | |
| 248 | fi | |
| 249 | ||
| 250 | let symbol mut = type.symbol; | |
| 251 | ||
| 252 | if isa Symbols.GENERIC(symbol) then | |
| 253 | symbol = symbol.symbol; | |
| 254 | fi | |
| 255 | ||
| 256 | return cast Symbols.Classy?(symbol); | |
| 257 | si | |
| 258 | ||
| 259 | matches(other: Type) -> bool is | |
| 260 | if !isa ONE_OF(other) then | |
| 261 | return false; | |
| 262 | fi | |
| 263 | ||
| 264 | let o = other; | |
| 265 | ||
| 266 | if !_underlying_type.matches(o._underlying_type) then | |
| 267 | return false; | |
| 268 | fi | |
| 269 | ||
| 270 | if _subtypes.count != o._subtypes.count then | |
| 271 | return false; | |
| 272 | fi | |
| 273 | ||
| 274 | for v in _subtypes do | |
| 275 | if !o.contains_subtype(v) then | |
| 276 | return false; | |
| 277 | fi | |
| 278 | od | |
| 279 | ||
| 280 | return true; | |
| 281 | si | |
| 282 | ||
| 283 | short_description: string is | |
| 284 | let buffer = System.Text.StringBuilder(); | |
| 285 | ||
| 286 | buffer.append(_underlying_type.short_description); | |
| 287 | buffer.append('{'); | |
| 288 | ||
| 289 | let seen_any mut = false; | |
| 290 | ||
| 291 | for v in _subtypes do | |
| 292 | if seen_any then | |
| 293 | buffer.append('|'); | |
| 294 | fi | |
| 295 | ||
| 296 | buffer.append(v.name); | |
| 297 | ||
| 298 | seen_any = true; | |
| 299 | od | |
| 300 | ||
| 301 | buffer.append('}'); | |
| 302 | ||
| 303 | if is_optional then | |
| 304 | buffer.append('?'); | |
| 305 | fi | |
| 306 | ||
| 307 | return buffer.to_string(); | |
| 308 | si | |
| 309 | ||
| 310 | to_string() -> string => short_description; | |
| 311 | ||
| 312 | walk(action: (Type) -> void) is | |
| 313 | _underlying_type.walk(action); | |
| 314 | ||
| 315 | action(self); | |
| 316 | si | |
| 317 | si | |
| 318 | si |