This is weird. From what I can tell the only thing being disrupted is how the description for case seven is generated. Also seems unique to ScenePhase as you mentioned. But otherwise pattern matching works as expected:
import SwiftUI
enum Foo {
case one
case two(ScenePhase)
case three
case four(Bool)
case five
case six
case seven
}
switch Foo.seven {
case .one: print("is one")
case .seven: print("is seven") // this is what is executed
default: print("is something else")
}
Hmmmm, might be related to how Swift lays out and pads enums with associated values in memory. Could you give is the different MemoryLayout properties of ScenePhase and your enum Foo?
This is pretty remarkable! Please file a bug over at bugs.swift.org. I can even add another case and have this produce additional bogus results (Xcode 13.2):
import SwiftUI
enum Foo {
case one
case two(ScenePhase)
case three
case four(Bool)
case five
case six
case seven
case eight
}
print(Foo.eight, Foo.seven, Foo.six) // "three one six"
Type Foo has a size of 2 bytes (stride of 2, alignment of 1) and ScenePhase has a size of 1 byte, (stride of 1, alignment of 1).
I tried to reproduce with a custom type instead of ScenePhase that has the shape (i.e. ScenePhase appears to be an enum with 3 cases, conforming to Comparable and Hashable). However, it only triggers with ScenePhase.
In the code base where this is triggered, pattern matching is impacted. The value of .seven is used at the call-site but the value .one instead is propagated from the call site through a deep chain of function calls to a function that then matches against one producing unexpected results. I can see that the first function on the call stack immediately receives one instead of seven.
At a glance, it looks like this occurs for non-frozen resilient enums.
Text.TruncationMode is another example:
enum Foo {
case one
case two(Text.TruncationMode) // <---
case three
case four(Bool)
case five
case six
case seven
}
print(Foo.seven) // "one"
But if you make your own, in-module enum, everything is fine:
enum MyScenePhase : Comparable {
case background
case inactive
case active
}
enum Foo {
case one
case two(MyScenePhase)
case three
case four(Bool)
case five
case six
case seven
}
print(Foo.seven) // "seven"
I opened an issue in feedback assistant, and for now am working around by dropping one of the cases. Thanks for the sanity check, it appears that I was just really unlucky in my specific use case.
Thanks for the insight. This leads to a better work around where I can just map ScenePhase to my own version, and preserve the original semantics I was aiming for.