Buggy name lookup behavior in pattern matching

I've found that the Swift compiler name lookup behavior in pattern matching is rather odd.


// The full type declaration is below.
struct Bytes { ... }
enum ASCII: UInt8 {
    case lineFeed = 10
}

switch bytes {
  case ASCII.lineFeed: print("match") // error: enum case 'lineFeed' is not a member of type 'Bytes'
}

Related issues: [SR-7792] Odd Name Lookup in Switch Statements · Issue #50331 · apple/swift · GitHub

What's even more strange is that when I remove the type declaration, the code mysteriously executes successfully.

Note the output (from last run) on the right: "match\n".

Xcode Version 14.3.1 (14E300c)

So...
When I write .lineFeed, lookup type is ASCII;
When I write ASCII.lineFeed, lookup type is Bytes.

Full code is here.

struct Bytes {
    var content: [UInt8]

    init(_ content: [UInt8]) {
        self.content = content
    }

    static func ~=(lhs: ASCII, rhs: Bytes) -> Bool {
        rhs.content.count == 1 && lhs.rawValue == rhs.content.first
    }
}

enum ASCII: UInt8 {
    case lineFeed = 10
}

switch Bytes([10]) {
case .lineFeed:
    print("match")
default:
    print("not match")
}

switch Bytes([10]) {
case ASCII.lineFeed:
    print("match")
default:
    print("not match")
}
1 Like

Is struct Bytes missing something?

The following modified version of the code above compiles.

struct Bytes {
    enum ASCII: UInt8 {
        case lineFeed = 10
    }

    let content : ASCII
}

struct Test {
    static func run (value: Bytes.ASCII) {
        let bytes = Bytes (content: value)
        switch bytes.content {
        case Bytes.ASCII.lineFeed:
            print("match")
        default:
            print("not match")
        }
        
        switch bytes.content {
        case .lineFeed:
            print("match")
        default:
            print("not match")
        }
    }
}

Yeah, I think SR-7782, [SR-1121] Custom pattern matching with enum types in Swift 2.2 not working as expected · Issue #43734 · apple/swift · GitHub, and your issue are all part of the same underlying problem: if a name resolves to an enum case, the compiler really wants to match it like an enum, rather than an arbitrary expression. There’s a workaround in the bug I linked: add .self to the case to make it not look like an enum case pattern.

2 Likes

@jrose, after reading your post I have just realised that the code in the OP is expected to be legal. I did not know that the Swift can become cryptic. :slight_smile:

switch Bytes([10]) {
case .lineFeed:
    print("match")
default:
    print("not match")
}

I don't see the connection between Bytes ([10]) and case .lineFeed.

Yep, it took me a few moments to process. The simpler to comprehend version would be something like:

struct Bytes {
    var content: [UInt8]
}

let lineFeed: UInt8 = 10
let bytes = Bytes(content: [lineFeed])

switch bytes.content {
case [lineFeed]:
    print("match")
default:
    print("not match")
}

Just with the help of

enum ASCII: UInt8 { ... }
// and
static func ~=(lhs: ASCII, rhs: Bytes) -> Bool { ... }

the OP has a new type to enumerate standard ASCII constants and wants to simplify the use site to be able using "switch bytes" and pattern match bytes to ascii constants, with the further twist that the bytes array is not of type ASCII but of type UInt8.

It helps indeed :slight_smile:

case ASCII.lineFeed.self: // compiles fine
2 Likes

Add .self did solve the problem. After years of Swift programming, this is the first time I know there is such a syntax. Thanks.

It’s normally only used with types (Int.self) but it works on anything. In particular it can be useful when a key path is required. Here it’s mostly just a workaround.