Appearance
| 1 | namespace Semantic.Types is | |
| 2 | use System.Text.StringBuilder | |
| 3 | use Ghul.Disposable | |
| 4 | ||
| 5 | // Compiler-internal "value is A AND B (AND ...)" representation, | |
| 6 | // produced only by the narrowing pass when `isa T(x: D)` fires | |
| 7 | // and neither type strict-subtypes the other. Tracks both the | |
| 8 | // declared-side type and each isa-target so member lookup can | |
| 9 | // see members from any side. | |
| 10 | // | |
| 11 | // Never user-spellable, never appears in slot types (fields, | |
| 12 | // arguments, return types, generic-argument positions), never | |
| 13 | // reaches IL emission or reflection metadata. Reads of a narrowed | |
| 14 | // variable in IL load against the variable's declared type — the | |
| 15 | // intersection is purely a compile-time member-resolution and | |
| 16 | // assignability fact. | |
| 17 | // | |
| 18 | // Invariants (enforced by `INTERSECTION.create`): | |
| 19 | // - At most one class / struct / union member (the runtime | |
| 20 | // value has exactly one concrete identity). | |
| 21 | // - Zero or more trait members. | |
| 22 | // - No duplicate members (`matches` identity). | |
| 23 | // - No redundant supertype members — if A and B are both in | |
| 24 | // the list and B subtypes A, A is dropped (B implies A). | |
| 25 | // - If after de-dup / supertype-drop only one member remains, | |
| 26 | // `create` returns that plain type, never a singleton | |
| 27 | // INTERSECTION. | |
| 28 | // - Canonical order: class/struct/union first (if any), then | |
| 29 | // traits sorted by `to_string`. Lets `matches` be element-wise. | |
| 30 | // | |
| 31 | // Design note: `docs/claude/intersection-types.md`. | |
| 32 | class INTERSECTION: NAMED is | |
| 33 | _members: Collections.LIST[Type] | |
| 34 | ||
| 35 | members: Collections.Iterable[Type] => _members | |
| 36 | members_count: int => _members.count | |
| 37 | ||
| 38 | // The class / struct / union member, if any. The intersection's | |
| 39 | // "concrete identity" for queries that need a single CLR-type | |
| 40 | // answer. Null when every member is a trait. | |
| 41 | class_side: Type? is | |
| 42 | for m in _members do | |
| 43 | if _is_concrete_kind(m) then | |
| 44 | return m | |
| 45 | fi | |
| 46 | od | |
| 47 | return null | |
| 48 | si | |
| 49 | ||
| 50 | // Default delegation target for Type-level queries that don't | |
| 51 | // have a defined intersection semantics: class side if any, | |
| 52 | // first trait otherwise. Members list is non-empty by | |
| 53 | // construction so this never reads `_members[0]` on an empty | |
| 54 | // list. | |
| 55 | representative: Type => | |
| 56 | let cs = class_side in | |
| 57 | if cs? then cs else _members[0] fi | |
| 58 | ||
| 59 | // `_build` orders members class-side first (if any), then | |
| 60 | // traits — so members[0] is the natural CLR-level identity | |
| 61 | // for any NAMED-shaped query (gen_type, etc). | |
| 62 | init(members: Collections.LIST[Type]) is | |
| 63 | super.init(members[0].symbol) | |
| 64 | _members = members | |
| 65 | si | |
| 66 | ||
| 67 | // Build the intersection of `a` and `b`. Applies de-dup, | |
| 68 | // supertype-drop, singleton collapse, and canonical ordering. | |
| 69 | // Returns a plain Type (one of the inputs, or the surviving | |
| 70 | // member after collapse) when no real intersection is needed. | |
| 71 | create(a: Type, b: Type) -> Type static is | |
| 72 | let result = try_create(a, b) | |
| 73 | ||
| 74 | assert result? | |
| 75 | else "intersection contains more than one class/struct/union member" | |
| 76 | ||
| 77 | return result | |
| 78 | si | |
| 79 | ||
| 80 | // Like `create`, but returns null when `a` and `b` carry | |
| 81 | // unrelated concrete identities (two classes with no subtype | |
| 82 | // relation, a struct and a class, ...) — no runtime value can | |
| 83 | // be both, so no intersection exists. Callers that can reach | |
| 84 | // that combination (narrow stacking on a statically-impossible | |
| 85 | // test edge) use this and decide what the empty intersection | |
| 86 | // means for them. | |
| 87 | try_create(a: Type, b: Type) -> Type? static is | |
| 88 | let collected = Collections.LIST[Type]() | |
| 89 | ||
| 90 | _collect(a, collected) | |
| 91 | _collect(b, collected) | |
| 92 | ||
| 93 | return _try_build(collected) | |
| 94 | si | |
| 95 | ||
| 96 | // True for types that can be the "concrete identity" of a | |
| 97 | // runtime value: classes, structs, value types, unions, | |
| 98 | // variants. At most one of these may appear in an intersection. | |
| 99 | _is_concrete_kind(t: Type) -> bool static => | |
| 100 | t.is_class \/ | |
| 101 | t.is_value_type \/ | |
| 102 | t.symbol.is_union | |
| 103 | ||
| 104 | // Flatten `t` into `into`, applying _add_member's | |
| 105 | // de-dup / supertype-drop rules. Skips ERROR / inferred / | |
| 106 | // null inputs. | |
| 107 | _collect(t: Type?, into: Collections.LIST[Type]) static is | |
| 108 | if !t? \/ t.is_error \/ t.is_inferred then | |
| 109 | return | |
| 110 | fi | |
| 111 | ||
| 112 | if let inner: INTERSECTION = t then | |
| 113 | for m in inner.members do | |
| 114 | _add_member(m, into) | |
| 115 | od | |
| 116 | else | |
| 117 | _add_member(t, into) | |
| 118 | fi | |
| 119 | si | |
| 120 | ||
| 121 | // Add `m` to `into` with redundancy elimination. | |
| 122 | // - If an existing member subtypes `m` (existing implies | |
| 123 | // `m`), `m` is redundant — skip. | |
| 124 | // - Otherwise, drop every existing member that `m` subtypes | |
| 125 | // (more-specific `m` supersedes them). | |
| 126 | // - Then add `m`. | |
| 127 | // | |
| 128 | // Subtype tests strip optional layers from both sides. | |
| 129 | // Intersection members represent a runtime value's identity, | |
| 130 | // and the at-most-one-concrete invariant is about that runtime | |
| 131 | // identity, not its declared optionality. With the strict | |
| 132 | // non-nullable-by-default rule, comparing `A` to `B?` directly | |
| 133 | // would return false on the subtype check and miss real | |
| 134 | // redundancies — the dedup needs to see the bare relationship. | |
| 135 | // | |
| 136 | // Precision-vs-soundness tradeoff: when A is non-optional | |
| 137 | // and B? subtypes A bare, this drops A and keeps B? — the | |
| 138 | // narrower result would be `B` (non-null), but the wider B? | |
| 139 | // is sound (rejects more code than necessary, never accepts | |
| 140 | // unsound code). Could be tightened by preserving the | |
| 141 | // optional flag at the more-specific position, but the | |
| 142 | // current code paths don't seem to exercise the case. | |
| 143 | _add_member(m: Type, into: Collections.LIST[Type]) static is | |
| 144 | let m_bare = _strip_optional(m) | |
| 145 | ||
| 146 | for existing in into do | |
| 147 | if existing.matches(m) then | |
| 148 | return | |
| 149 | fi | |
| 150 | ||
| 151 | if m_bare.is_assignable_from(_strip_optional(existing)) then | |
| 152 | return | |
| 153 | fi | |
| 154 | od | |
| 155 | ||
| 156 | let kept = Collections.LIST[Type]() | |
| 157 | ||
| 158 | for existing in into do | |
| 159 | if !_strip_optional(existing).is_assignable_from(m_bare) then | |
| 160 | kept.add(existing) | |
| 161 | fi | |
| 162 | od | |
| 163 | ||
| 164 | into.clear() | |
| 165 | ||
| 166 | for k in kept do | |
| 167 | into.add(k) | |
| 168 | od | |
| 169 | ||
| 170 | into.add(m) | |
| 171 | si | |
| 172 | ||
| 173 | _strip_optional(t: Type) -> Type static => | |
| 174 | if t.is_optional then | |
| 175 | t.as_non_optional() | |
| 176 | else | |
| 177 | t | |
| 178 | fi | |
| 179 | ||
| 180 | // Final assembly: validate the at-most-one-concrete invariant | |
| 181 | // (null when it fails — no value can carry two unrelated | |
| 182 | // concrete identities), sort canonically, collapse singleton | |
| 183 | // to plain type. | |
| 184 | _try_build(members: Collections.LIST[Type]) -> Type? static is | |
| 185 | if members.count == 0 then | |
| 186 | return ERROR() | |
| 187 | fi | |
| 188 | ||
| 189 | if members.count == 1 then | |
| 190 | return members[0] | |
| 191 | fi | |
| 192 | ||
| 193 | let concrete_count mut = 0 | |
| 194 | ||
| 195 | for m in members do | |
| 196 | if _is_concrete_kind(m) then | |
| 197 | concrete_count = concrete_count + 1 | |
| 198 | fi | |
| 199 | od | |
| 200 | ||
| 201 | if concrete_count > 1 then | |
| 202 | return null | |
| 203 | fi | |
| 204 | ||
| 205 | let ordered = Collections.LIST[Type]() | |
| 206 | let traits = Collections.LIST[Type]() | |
| 207 | ||
| 208 | for m in members do | |
| 209 | if _is_concrete_kind(m) then | |
| 210 | ordered.add(m) | |
| 211 | else | |
| 212 | traits.add(m) | |
| 213 | fi | |
| 214 | od | |
| 215 | ||
| 216 | // Order the traits by their fully-qualified names so the | |
| 217 | // canonical ordering (which makes `matches` element-wise) does not | |
| 218 | // depend on the scope names happen to be rendered relative to. | |
| 219 | let use render_scope = IoC.CONTAINER.instance.name_display.with_scope(null) | |
| 220 | ||
| 221 | for i in 0..traits.count do | |
| 222 | for j in (i + 1)..traits.count do | |
| 223 | if traits[i].to_string()!.compare_to(traits[j].to_string()) > 0 then | |
| 224 | let temp = traits[i] | |
| 225 | traits[i] = traits[j] | |
| 226 | traits[j] = temp | |
| 227 | fi | |
| 228 | od | |
| 229 | od | |
| 230 | ||
| 231 | for t in traits do | |
| 232 | ordered.add(t) | |
| 233 | od | |
| 234 | ||
| 235 | return INTERSECTION(ordered) | |
| 236 | si | |
| 237 | ||
| 238 | // ===== Type-property delegation ===== | |
| 239 | // | |
| 240 | // Most queries that ask "what kind of type is this?" delegate | |
| 241 | // to the representative (class side, or first trait). The | |
| 242 | // intersection's runtime value behaves like the class side | |
| 243 | // for CLR-level queries (gen_type, gen_class_name); for | |
| 244 | // type-system queries that have a defined intersection | |
| 245 | // semantics (is_assignable_from, find_member) we override. | |
| 246 | ||
| 247 | symbol: Symbols.Symbol => representative.symbol | |
| 248 | ||
| 249 | is_value_type: bool => false | |
| 250 | is_class: bool => class_side? | |
| 251 | is_trait: bool => !class_side? | |
| 252 | is_named: bool => true | |
| 253 | ||
| 254 | is_inheritable: bool => representative.is_inheritable | |
| 255 | is_object: bool => false | |
| 256 | is_root_value_type: bool => false | |
| 257 | is_void: bool => false | |
| 258 | is_action: bool => false | |
| 259 | is_function: bool => false | |
| 260 | is_ref: bool => false | |
| 261 | is_type_variable: bool => false | |
| 262 | is_value_tuple: bool => false | |
| 263 | is_optional: bool => false | |
| 264 | ||
| 265 | // A composed narrow of a type variable keeps the variable as a | |
| 266 | // member, and that is what the value is loaded as. | |
| 267 | type_variable_side: Type? is | |
| 268 | for m in _members do | |
| 269 | if let side = m.type_variable_side then | |
| 270 | return side | |
| 271 | fi | |
| 272 | od | |
| 273 | ||
| 274 | return null | |
| 275 | si | |
| 276 | ||
| 277 | as_optional() -> Type => self | |
| 278 | as_non_optional() -> Type => self | |
| 279 | ||
| 280 | // ===== Equality and hashing ===== | |
| 281 | // | |
| 282 | // Element-wise compare in canonical order. Two intersections | |
| 283 | // with the same membership match regardless of construction | |
| 284 | // path because the factory enforces canonical ordering. | |
| 285 | ||
| 286 | matches(other: Type) -> bool is | |
| 287 | if !isa INTERSECTION(other) then | |
| 288 | return false | |
| 289 | fi | |
| 290 | ||
| 291 | let o = other | |
| 292 | ||
| 293 | if o._members.count != _members.count then | |
| 294 | return false | |
| 295 | fi | |
| 296 | ||
| 297 | for i in 0.._members.count do | |
| 298 | if !_members[i].matches(o._members[i]) then | |
| 299 | return false | |
| 300 | fi | |
| 301 | od | |
| 302 | ||
| 303 | return true | |
| 304 | si | |
| 305 | ||
| 306 | get_hash_code() -> int is | |
| 307 | let h mut = 0 | |
| 308 | ||
| 309 | for m in _members do | |
| 310 | h = h * 31 + m.get_hash_code() | |
| 311 | od | |
| 312 | ||
| 313 | return h | |
| 314 | si | |
| 315 | ||
| 316 | // ===== Assignability ===== | |
| 317 | // | |
| 318 | // A value of type `D & T1 & T2 ...` IS each of D, T1, T2 ... | |
| 319 | // (D & T1 & ...).is_assignable_from(X) | |
| 320 | // iff X is assignable to every member. | |
| 321 | // X.is_assignable_from(D & T1 & ...) | |
| 322 | // iff X is assignable to any member | |
| 323 | // (handled by the existing dispatch — each member's | |
| 324 | // compare(X) finds X among its supertypes when | |
| 325 | // applicable). | |
| 326 | ||
| 327 | is_assignable_from(other: Type) -> bool is | |
| 328 | for m in _members do | |
| 329 | if !m.is_assignable_from(other) then | |
| 330 | return false | |
| 331 | fi | |
| 332 | od | |
| 333 | ||
| 334 | return true | |
| 335 | si | |
| 336 | ||
| 337 | compare(other: Type) -> MATCH is | |
| 338 | let worst mut = MATCH.SAME | |
| 339 | ||
| 340 | for m in _members do | |
| 341 | let c = m.compare(other) | |
| 342 | ||
| 343 | if c == MATCH.DIFFERENT then | |
| 344 | return MATCH.DIFFERENT | |
| 345 | fi | |
| 346 | ||
| 347 | if cast int(c) > cast int(worst) then | |
| 348 | worst = c | |
| 349 | fi | |
| 350 | od | |
| 351 | ||
| 352 | return worst | |
| 353 | si | |
| 354 | ||
| 355 | // ===== Member lookup ===== | |
| 356 | // | |
| 357 | // Walk each member, collect the non-null lookups. Resolution: | |
| 358 | // - 0 results → null | |
| 359 | // - 1 result → that result | |
| 360 | // - all FUNCTION_GROUPs → merge (same shape as | |
| 361 | // classy.find_enclosing, dedup by | |
| 362 | // override_class) | |
| 363 | // - mixed kinds → class-side wins for non-function | |
| 364 | // members (per design's tie-break); | |
| 365 | // else first trait wins (pragmatic | |
| 366 | // fallback for multi-trait clash — | |
| 367 | // documented limitation, matches | |
| 368 | // existing classy.find_enclosing | |
| 369 | // non-function handling) | |
| 370 | ||
| 371 | find_member(name: string) -> Symbols.Symbol? is | |
| 372 | let class_member: Symbols.Symbol? mut = null | |
| 373 | let trait_members = Collections.LIST[Symbols.Symbol]() | |
| 374 | let cs = class_side | |
| 375 | ||
| 376 | for m in _members do | |
| 377 | let r = m.find_member(name) | |
| 378 | ||
| 379 | if r? then | |
| 380 | if cs? /\ m == cs then | |
| 381 | class_member = r | |
| 382 | else | |
| 383 | trait_members.add(r) | |
| 384 | fi | |
| 385 | fi | |
| 386 | od | |
| 387 | ||
| 388 | if !class_member? /\ trait_members.count == 0 then | |
| 389 | return null | |
| 390 | fi | |
| 391 | ||
| 392 | if class_member? /\ trait_members.count == 0 then | |
| 393 | return class_member | |
| 394 | fi | |
| 395 | ||
| 396 | if !class_member? /\ trait_members.count == 1 then | |
| 397 | return trait_members[0] | |
| 398 | fi | |
| 399 | ||
| 400 | let all_groups mut = true | |
| 401 | ||
| 402 | if class_member? /\ !isa Symbols.FUNCTION_GROUP(class_member) then | |
| 403 | all_groups = false | |
| 404 | fi | |
| 405 | ||
| 406 | for t in trait_members do | |
| 407 | if !isa Symbols.FUNCTION_GROUP(t) then | |
| 408 | all_groups = false | |
| 409 | fi | |
| 410 | od | |
| 411 | ||
| 412 | if all_groups then | |
| 413 | return _merge_function_groups(class_member, trait_members, name) | |
| 414 | fi | |
| 415 | ||
| 416 | if class_member? then | |
| 417 | return class_member | |
| 418 | fi | |
| 419 | ||
| 420 | return trait_members[0] | |
| 421 | si | |
| 422 | ||
| 423 | // Build a combined FUNCTION_GROUP from `class_member` | |
| 424 | // (optional) and `trait_members`. Dedupe by `override_class` | |
| 425 | // so the same method declared by the same trait doesn't | |
| 426 | // appear twice when reached via multiple paths. Functions | |
| 427 | // without an override_class — typically synthesized — are | |
| 428 | // added unconditionally; collisions there are not expected | |
| 429 | // in practice. | |
| 430 | _merge_function_groups( | |
| 431 | class_member: Symbols.Symbol?, | |
| 432 | trait_members: Collections.LIST[Symbols.Symbol], | |
| 433 | name: string | |
| 434 | ) -> Symbols.Symbol is | |
| 435 | let sample = | |
| 436 | if class_member? then | |
| 437 | class_member | |
| 438 | else | |
| 439 | trait_members[0] | |
| 440 | fi | |
| 441 | ||
| 442 | let combined = Symbols.FUNCTION_GROUP(sample.location, sample.owner!, name) | |
| 443 | let seen = Collections.SET[METHOD_OVERRIDE_CLASS]() | |
| 444 | ||
| 445 | _absorb_function_group(class_member, combined, seen) | |
| 446 | ||
| 447 | for t in trait_members do | |
| 448 | _absorb_function_group(t, combined, seen) | |
| 449 | od | |
| 450 | ||
| 451 | return combined | |
| 452 | si | |
| 453 | ||
| 454 | _absorb_function_group( | |
| 455 | source: Symbols.Symbol?, | |
| 456 | into: Symbols.FUNCTION_GROUP, | |
| 457 | seen: Collections.SET[METHOD_OVERRIDE_CLASS] | |
| 458 | ) static is | |
| 459 | if !source? then | |
| 460 | return | |
| 461 | fi | |
| 462 | ||
| 463 | if let group: Symbols.FUNCTION_GROUP = source then | |
| 464 | for f in group.functions do | |
| 465 | if !seen.contains(f.override_class) then | |
| 466 | seen.add(f.override_class) | |
| 467 | into.add(f) | |
| 468 | fi | |
| 469 | od | |
| 470 | fi | |
| 471 | si | |
| 472 | ||
| 473 | // ===== Display ===== | |
| 474 | // | |
| 475 | // Plain text join with ` & `. Single canonical form used in | |
| 476 | // error messages, hovers, snapshots, debug dumps. | |
| 477 | ||
| 478 | to_string() -> string is | |
| 479 | let buffer = StringBuilder() | |
| 480 | let first mut = true | |
| 481 | ||
| 482 | for m in _members do | |
| 483 | if !first then | |
| 484 | buffer.append(" & ") | |
| 485 | fi | |
| 486 | ||
| 487 | buffer.append(m.to_string()) | |
| 488 | first = false | |
| 489 | od | |
| 490 | ||
| 491 | return buffer.to_string() | |
| 492 | si | |
| 493 | ||
| 494 | short_description: string => to_string() | |
| 495 | ||
| 496 | // ===== IL emission boundary ===== | |
| 497 | // | |
| 498 | // Intersections never appear in slot types — narrowed-variable | |
| 499 | // reads load against the variable's declared type, not the | |
| 500 | // narrowed view. These shouldn't normally execute; if they | |
| 501 | // do, emit the representative's IL (the concrete CLR-level | |
| 502 | // identity). | |
| 503 | ||
| 504 | walk(action: (Type) -> void) is | |
| 505 | for m in _members do | |
| 506 | m.walk(action) | |
| 507 | od | |
| 508 | ||
| 509 | action(self) | |
| 510 | si | |
| 511 | ||
| 512 | // ===== Specialization ===== | |
| 513 | // | |
| 514 | // Intersections shouldn't appear in generic-argument | |
| 515 | // positions, so this path is defensive. If it fires, | |
| 516 | // specialize each member and rebuild through the factory | |
| 517 | // to preserve invariants. | |
| 518 | ||
| 519 | specialize(type_map: Collections.Map[Symbols.Symbol,Type]) -> Type is | |
| 520 | if _members.count < 2 then | |
| 521 | return self | |
| 522 | fi | |
| 523 | ||
| 524 | let result mut = _members[0].specialize(type_map) | |
| 525 | ||
| 526 | for i in 1.._members.count do | |
| 527 | result = INTERSECTION.create(result, _members[i].specialize(type_map)) | |
| 528 | od | |
| 529 | ||
| 530 | return result | |
| 531 | si | |
| 532 | si | |
| 533 | si |