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