Title:
Property-Based Type Narrowing and Union Types
Author:
John McGlone
Introduction:
This proposal introduces two interrelated enhancements to the Swift type system:
- Value-based union types (
A | B
) - Flow-sensitive type narrowing based on property checks
These features enable safer and more expressive control flow when working with values of multiple possible types, similar to what languages like TypeScript and Kotlin support.
Motivation:
Currently, Swift relies heavily on manual type casting (as?
, as!
) and enums with associated values to handle values that may conform to multiple types. This creates boilerplate and weakens readability in many situations, especially when working with protocols or loosely structured models.
In contrast, TypeScript allows developers to write more expressive code by narrowing union types based on:
- Property presence (
'meow' in animal
) - Literal discriminants (
type == "cat"
) - Type guards (
instanceof
,typeof
)
We propose a similar system for Swift, allowing smarter narrowing in if
, switch
, and similar control flow blocks.
Proposed Solution
1. Introduce Union Types
Enable declaring a parameter that can be one of multiple value types:
func handle(_ input: Cat | Dog) { ... }
2. Enable Flow-Sensitive Type Narrowing by Property Presence
Allow Swift to infer the type based on property existence, so this code becomes valid:
func speak(_ animal: Cat | Dog) {
if animal.meow != nil {
// animal is inferred as Cat
animal.meow()
} else {
// animal is inferred as Dog
animal.bark()
}
}
3. Narrowing with Literal Discriminants
When using a shared type
field or enum-like value:
struct Cat {
let type = "cat"
let meow: () -> Void
}
struct Dog {
let type = "dog"
let bark: () -> Void
}
func speak(_ animal: Cat | Dog) {
if animal.type == "cat" {
animal.meow() // inferred as Cat
} else {
animal.bark() // inferred as Dog
}
}
Detailed Design
This would require:
- Introducing structural union types
- Enhancing the compiler's type inference engine to track property accesses across branches
- Possibly introducing syntactic sugar like
'property' in object
orhas(object.property)
Source Compatibility
This would be an additive change to the language and would not break existing Swift code.
Alternatives Considered
- Continue relying on
as?
, enums, and protocols - Use enums with associated values (heavier but already supported)
- Type erasure patterns (boilerplate-heavy)
Conclusion
By adopting property-based type narrowing and union types, Swift becomes more expressive, reducing boilerplate and improving safety when dealing with heterogeneous types — especially in modern app development and data-driven UIs.