Skip to content

Instantly share code, notes, and snippets.

@YOCKOW
Last active February 27, 2026 03:57
Show Gist options
  • Select an option

  • Save YOCKOW/1e14076994e4090890d01e7fc0337cf3 to your computer and use it in GitHub Desktop.

Select an option

Save YOCKOW/1e14076994e4090890d01e7fc0337cf3 to your computer and use it in GitHub Desktop.
How deeply will the compiler infer the types behind parameterized protocols?
// Thread: https://forums.swift.org/t/how-deeply-will-the-compiler-infer-the-types-behind-parameterized-protocols/85038
/*
**Base Protocols**
Here are two protocols: `P` and `Q`.
`Q` inherits from `P` and has an additional `associatedtype`.
*/
protocol P {}
protocol Q: P {
associatedtype QA
var qa: QA { get }
}
/*
**Concrete Types that conform to `P` or `Q`**
Let's implement concrete types:
- `ConcreteP` conforms to `P`.
- `ConcreteQ` conforms to `Q`.
*/
struct ConcreteP: P {}
struct ConcreteQ: Q {
typealias QA = Int
var qa: QA { 0 }
}
/*
**Container Protocols**
Here are two other protocols:
- `PContainer` has its `content` which conforms to `P`.
- `QContainer` inherits from `PContainer` and has its `content` which conforms to `Q`.
*/
protocol PContainer<Content> where Content: P {
associatedtype Content
var content: Content { get }
}
protocol QContainer<Content>: PContainer where Content: Q {}
/*
**Concrete Type `Container`**
- It basically conforms to `PContainer`
- It conditionally conforms to `QContainer` when `Content` conforms to `Q`
*/
struct Container<Content>: PContainer where Content: P {
var content: Content
init(_ content: Content) { self.content = content }
}
extension Container: QContainer where Content: Q {}
// Prepare two containers:
let pContainer = Container<ConcreteP>(ConcreteP())
let qContainer = Container<ConcreteQ>(ConcreteQ())
/*
**Test Case #1**
We can detect whether or not `Container` conforms to `QContainer` at runtime.
*/
extension Container {
func test1() {
if self is any QContainer<Content> {
print("I'm also `QContainer`.")
} else {
print("I'm just `PContainer`.")
}
}
}
pContainer.test1() // Prints "I'm just `PContainer`."
qContainer.test1() // Prints "I'm also `QContainer`."
/*
**Test Case #2**
Of course, `Content` of `any QContainer<Content>` is always `any Q`.
*/
extension Container {
func test2() {
if case let qSelf as any QContainer<Content> = self {
print("Content is `Q`: \(qSelf.content is any Q)")
}
}
}
qContainer.test2() // Prints "Content is `Q`: true"
/*
**Test Case #3**
In theory, the compiler *can* *statically* know `qSelf.content` is `any Q` in the following code.
However, this code fails to be compiled (with `-DCOMPILER_CAN_ASSERT_CONTENT_IS_Q` option).
(That seems to be because `Content` constraints never change in `if case ... { }` scope.)
*/
extension Container {
func test3() {
if case let qSelf as any QContainer<Content> = self {
#if COMPILER_CAN_ASSERT_CONTENT_IS_Q
print("Value of `qa` is: \(qSelf.content.qa)")
// ⛔️ error: value of type 'Content' has no member 'qa'
#endif
}
}
}
qContainer.test3() // ⛔️
/*
**Workaround #1**
One of workarounds for Test Case #3 is to remove `<Content>` from `as any QContainer`.
- Pros: Very simple.
- Cons: The fact that `qSelf.content is Content` is lost in static analysis.
*/
extension Container {
func workaround1() {
if case let qSelf as any QContainer = self {
print("Value of `qa` is: \(qSelf.content.qa)")
}
}
}
qContainer.workaround1() // Prints "Value of `qa` is: 0"
/*
**Workaround #2**
Another workaround is explicit coercion for `qSelf.content`.
- Pros: Compiler can still assert `qSelf.content is Content`.
- Cons: Redundunt for humans.
*/
extension Container {
func workaround2() {
if case let qSelf as any QContainer<Content> = self {
print("Value of `qa` is: \((qSelf.content as! any Q).qa)")
}
}
}
qContainer.workaround2() // Prints "Value of `qa` is: 0"
/*
**Workaround #3**
Third workaround is to extend `QContainer` protocol.
- Pros: We can open the existential explicitly(?).
- Cons: It may be a hassle to write in some cases.
*/
extension QContainer {
func printQA() {
print("Value of `qa` is: \(self.content.qa)")
}
}
extension Container {
func workaround3() {
if case let qSelf as any QContainer<Content> = self {
qSelf.printQA()
}
}
}
qContainer.workaround3() // Prints "Value of `qa` is: 0"
/*
**Question:**
Do you think Test Case #3(`func test3`) should be compiled successfully in the future?
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment