I donβt think this is my kind of feature so I don't want to rain on anyone who wants it, but throws(A | B) seems like it needs more: is there a mechanism to ensure that A and B each conform to Error if that erases to Any? Does it still use the typed throw codegen, which is intended to not allocate?
Well, that's not it. You've added at least 3 new features to the type system:
- Narrowing Any
String | Int - Narrowed Type Bounds
T: NetworkError | DecodingErrorextension Array where Element == Int | String { β¦ }
- Narrowing typed throws
throws(NetworkError | DecodingError | AuthError)
What all three of these share is that they introduce this new kind of - admittedly bounded - disjunction into the type system. I am generally opposed to this as any form of disjunction forces the type checker to search*. For which, in particular, example 2.1 above requires.
I am also not convinced that the syntactic restrictions you've stated above are worth the restrictions in semantic power for a feature like this. If subtyping is completely off limits, why not make a set of macros that does simple injection at creation and checked casts for projection? As others have noted the type A | B is not just semantically - but even syntactically - sugar for
enum APlusB {
case a(A)
case b(B)
}
everywhere except typing bounds.
a compiler-generated enum has no place to hang the rule
Ah but it does! The Space Engine is quite clever about structurally uninhabited types.
enum Foo {
case empty(Never)
case full(String)
}
switch Foo.full("") {
case .full(let s):
print(s)
}
One question I have for you about this proposal is: is the type T | Nominal supported where T is an archetype? I notice none of the examples have a general type as part of the structure
func foo<T>() {
typealias Bar = T | String
}
It looks like you've implemented element-wise substitution so this ought to be supported.
Another is a note about the prototype: This
The constraint position is order-free:
where T: A | Bandwhere T: B | Aaccept the same set of substitutions.
Implies that you need to implement canonicalization for narrowed protocol types so that the signatures of
func foo<T: A | B>()
func foo<T: B | A>()
Are both properly rejected as duplicates and also mangle the same. I only skimmed your prototype but I didn't see where you were e.g. sorting the components. I'd point out too that if you're going to have this order-independence in just this one syntactic position why not extend it to all positions?
No OS upgrade, back-deploys to any Swift-supporting target
I want to be clear that it is really clever what you've done here, but I think it's important to call out that you have indeed added to the mangling which is going to require this back-deploy or, indeed, will require an OS upgrade to adopt the feature or the prior runtime will not be able to interact with these types.
*The most famous kind of semantic disjunct Swift has is in the form of overloading. I am also generally opposed to true negation (not the current ~ form, but true "Does not Conform to T") as this lets you encode !(A & B) which is equivalent to the search !A | !B.
I asked AI to help fix test failures, add more tests, and revise parts of the proposal.
This is a one-line-per-change index of what landed since post #20. Every entry links to the swift-fork commit (or swift-evolution doc commit), the proposal section it touches, and the test case where applicable β for design rationale on any specific item, jump to the proposal section linked.
1. Implementation completing earlier-announced design
- Per-leaf try-propagation (post #13) β wired end-to-end. Swift fork
93368136543; proposal Β§"Try-propagation is per-leaf"; test covers @ksluder'sdoBothThings. - Inhabited-subset rule for
Neverleaves (post #15) β wired end-to-end. Swift fork93368136543+c92aeec4495; proposal Β§"Uninhabited (; tests cover @Nobody1707'sErr<Never, Never>.
2. Genuinely new since post #20
- Disjoint cast
as?/as!β hard error in all three forms β unified across the proposal text (was previously a "warning" in three passages, "error" in the rule table). Swift forkff2910fac5b+4ee4d94f03d; proposal Β§"Cross-shape conversion". - Implicit cross-shape conversion β hard error + auto-fix-it β
let b: String | Int = a(wherea: Int | String) used to silently succeed; now hard error with a leaf-set-classifying fix-it. Swift fork4d0c3f9b4cc+6bc151db04a; test . - Per-element leaf injection at the extension boundary β auto-fix-it (biggest change in this window). The cast itself was already wired end-to-end; what landed is the auto-fix-it inserting
(receiver as [Int | String])at the same-type-requirement error site. The implicit form is deferred β[Int]and[Int | String]have different per-element layouts (8 vs 32-byte stride), so it needs Path A (CSApply coercion) or Path B (SILOptimizer specialisation). Swift forkae1bf61abce; proposal Β§"Containers and extensions" + Β§"Per-element leaf injection"; test . - Cross-spelling fix-it is a separate v1 limitation β
zs: [Int | String]reachingextension Array where Element == String | Intgoes through a different per-alternative path that doesn't carry narrowed-Anycontext, so the fix-it doesn't activate. The cast itself works. - Concrete cost numbers for the three reshape axes added to Β§"Containers and extensions": cross-spelling O(1) SIL relabel (runtime-free); leaf injection O(N) per-element wrap with ~4Γ transient memory peak (8 β 32-byte stride); narrowed β leaf via
as?is O(N) walk + dynamic check. The headlineO(N+M)typed-throws composition argument depends on cross-spelling being O(1). - Library-author guidance added: prefer
Sequence/Collectionextension overArray-specific so users can pay leaf-injection lazily viaxs.lazy.map { e -> Int | String in e }.method()for O(1) memory peak. - Tagged-union layout follow-up β explicit non-qualifier list in Β§"Tagged-union layout": non-POD leaves disqualify (
String,URL, classes); all-POD sets exceeding the size budget still need padding.
3. Internal audit corrections
- Scala-3 / "join" misattribution (post #4) fully resolved β visible-interface re-anchored as Swift's own LUB; comparison table no longer collapses Scala 3 / Ceylon / TypeScript under "Structural"; Maranget overclaim corrected. Swift-evolution
e53c308+0f8ee10. - Five misframings / contradictions swept (join example self-contradiction; "only implicit conversion is leaf-introduction"; Codable Never encode try-order; OneOf3/OneOf4 framing;
where T == A | Bannotation qualifier). Swift-evolutiond1e0308+534ff7d. - First-class type framing + extension priority model β Β§"It adds a new kind of type" reframed (first-class type-system identity + reused runtime existential machinery, dual nature like
any Ppost-SE-0335); two extension dispatch rules anchored: receiver static type owns method-dispatch priority and user extensions take priority over synthesis fallback. Swift-evolutionc9a5584. - Codable user-override
extension Int | String: Codable { ... }restored as the canonical Issue 5 motivator across Β§"Issue 5" / Β§"Conformance synthesis" / Β§"Codable user override" / Β§"Extending a narrowed-. Swift-evolution076e693. - Major correction: per-witness conformance synthesis is shipped in v1, not deferred. Stale text claimed
Hashable/Equatable/Comparable/CustomStringConvertible/Encodable/Decodablewere "Deferred from v1" β actually wired via 'sBuiltinConformance(NarrowedAnyDispatch)+ six SIL helpers in . Five proposal places fixed. Direct value-member access (v.description) is the only remaining piece, deferred to a focused follow-up. Swift-evolution2005dc8. - Codable debugger-interaction note β the synthesised
init(from:)invokes each leaf's decoder viatry_apply, so Xcode's "Swift Error Breakpoint" fires once per failing leaf attempt during multi-leaf decode. Β§ Issue 5 now documents the diagnosis + four compiler-side mitigations (proposal commits to #1 SIL[suppress_will_throw]as a focused follow-up). Same audit caught all-fail decode shape: prototype propagates the last leaf's error verbatim (notDecodingError.typeMismatchannotated with the full leaf set as the proposal previously claimed); v1 commits to the prototype shape. Swift-evolution54fb5f4. - Reverted a fabricated
#ext-rule-namespace(22583f0+1b8fb51) β the supposed "leaf-typed receivers can NOT call narrowed-Anyextension methods" was a fabricated problem; renamed to#ext-rule-leaf-reachwith reversed semantics (most-specific-wins handles the apparent overload conflict). - Tagged-union spare-bit table β Β§ Tagged-union layout gained a 6-row table showing which leaf sets qualify (
Bool | UInt8,Int | Doublevia NaN-boxing) vs. don't (Int | UInt32,Int | String). Swift-evolutionf3890c8. - Per-element cost numbers correction β 32-byte stride (24-byte buffer + 8-byte metadata), not 24-byte;
Int β Int | Stringstride growth is 4Γ. Swift-evolutionb84c914. - Path A vs Path B framing for the deferred implicit lift β Β§ Per-element leaf injection split into Path A (whole-receiver coercion, no runtime change) and Path B (per-element specialisation, zero overhead for pure-iterate workloads).
- Truncated-cell rendering bug fixed β Β§ Spelling is identity table had a Codable cell eaten by an unescaped
|. Swift-evolutionb47b802.
4. Test bed
Lit tests at . New since post #20:
- (verify-mode) β disjoint-cast errors, cross-spelling extension dispatch,
extension Int | String { }non-nominal note, implicit cross-shapeas/as?fix-its, per-element leaf-injection auto-fix-it. Added10372adb354+ Issue 9 / multi-clause-where in2582b126015. - (runtime) β cross-spelling extension reshape end-to-end. Added
21ce1541660; fixed an earlier Sema ICE inmatchDeepEqualityTypes(d0a5a54982e). - β per-leaf try-propagation, inhabited-subset
throws(A | Never),throws(Never | Never)non-throwing. phase3f_codable_struct_int_string.swift(runtime) β synthesisedCodableround-trip on a top-level struct withInt | Stringfield, 4 wire shapes Γ both spelling orders. Added9fede94cdc8; Β§5b (print(struct)field display) lock-in landed alongside the IRGen short-circuit inb5865b95a76.
5. Second-pass tightening (after the first draft of this update)
None of these change v1 design rules β they tighten v1 surface and shrink commitments.
- Cross-spelling container assignment β full diagnostic + auto-fix-it. Used to crash in
repairFailures; two-step fix (defensive fallback ingetExistentialLayout+ cross-spelling short-circuit inmatchDeepEqualityTypes). Swift forke1e2e9b55e2+7f56b39727b; test . - Cross-spelling fix-it β
as!βas(free relabel βas!over-states the cost). Swift forkc7e572f42f5; tests . - Cross-spelling at generic argument position β error + fix-it. Spelling-as-identity at constraint position; lifting to fully order-free leaf-set match is part of True set-membership. Swift fork
c7e572f42f5; proposal Β§"Generics" + Β§"Issue 6"; test . - Per-leaf propagation extends to return position.
func b() -> Int | String { return a() }(wherea() -> String | Int) is value-flow and type-checks without anas. Swift forkddf466c9da9; swift-evolution310485c; proposal Β§"Return-position is per-leaf"; tests + . - Direct member access on a narrowed-
Anyvalue β moved to Future directions. Conformance is reachable through generic dispatch / interpolation / any-cast; v1 doesn't block any expressible code. Wiring Sema's value-member-lookup plausibly touches every overload-ranking site that already special-casesany P. Swift-evolution55b643d; proposal Β§ Direct member access. - Constraint-position cross-spelling: corrected framing. v1 honours Spelling is identity everywhere a narrowed-
Anyis bound to a name, including constraint position. Real order-free leaf-set match parked in True set-membership. Swift-evolutionfd3670b. - Codable last-leaf-error β committed to the prototype shape (see Β§3 Codable debugger-interaction). Swift-evolution
55b643d. - Test-bed + commit-message hygiene. CJK scan on commit messages and source files cleaned 4 commit messages and 2 source-file comments where workspace-side memo wording (Chinese phrases) had leaked through. Branches re-pushed; SHA references resynced.
6. Colon-form constraint banned in v1
The colon form where T: A | B reads as set-membership but the prototype delivers same-type binding β readers familiar with T: SomeProtocol would mentally model it as set-membership and be surprised. v1 commits to where T == A | B and parser-rejects the colon form outright; the colon-form syntax slot is reserved for True set-membership (which gives true set-membership semantics). Swift fork 8ae7cd938f7; swift-evolution 57d1482; new diagnostics Β§16 locks the parser-reject; full lit migration to == form. Compound T == A|B, T: Error from old Β§12k dropped β Swift already forbids T == X + T: Proto mix-ins, and Error is synthesised by the leaves.
7. Strict-invariant rule's silent passes
Β§ Subtyping lattice commits containers to invariant in element type; Β§ Variance at the protocol-witness boundary keeps function types strict-invariant (relaxation deferred). I noticed the constraint solver was letting four conversions through silently. Now an explicit "Known v1 gaps" entry β see :
let _: [Int | String] = xsforxs: [Int](container leaf-injection at direct binding) β landed.let _: [Any] = zsforzs: [Int | String](container narrowing toAny) β landed.let _: Set<Int | String> = sfors: Set<Int>(Set element widening) β landed.let _: (Int) -> Void = fforf: (Int | String) -> Void(function-type parameter narrowing) β still silent, the remaining v1-blocking item.
Container axis fix is mechanical: in CSSimplify.cpp::simplifyRestrictedConstraintImpl's ArrayUpcast / DictionaryUpcast / SetUpcast cases, Type::findIf over either side's canonical tree forces inner element match to Bind; explicit as is exempted via the isExplicitCoerce idiom. Function-type axis fix lives in matchFunctionTypes. Swift fork 7f502492b87; swift-evolution 44f1a50 + 47e271e; new diagnostics Β§20; lit 13/13 PASS.
Spelling-is-identity is a landmine that I cannot personally accept. You're trying to co-opt punctuation / union-syntax which is commutative in other contexts.
String | Int == Int | String is a statement that must be true for me to accept a proposal. That issue alone makes it difficult for me to evaluate anything else in the proposal.
Further: Generics means that users may not have access to normalize the use-cases to any standard format, so you've made it impossible to benefit from this narrowing in tons of circumstances.
Please try again without that problem, or I'm opposed.