Value type extensions with generic type constraint being ignored

Background

This question relates to a struct with a generic property that conforms to a protocol. I've included a simplified overview below.

struct Token<T>: CustomStringConvertible {
    let lexme: T
}

struct Literal { }
struct Keyword { }

The specific implementation of protocol conformance (CustomStringConvertible in this case) is dependent on the generic type.

extension Token {
    var description: String { "Regular token" }
}

extension Token where T == Literal {
    var description: String { "Token with literal" }
}

Observation

The above code compiles with no errors, but the dynamism specified is not observed at runtime.

print(Token(lexme: Keyword()))   /// Regular token
print(Token(lexme: Literal()))   /// Regular token

I'm able to resolve this by using a single declaration of description and using optional binding to check for specialised lexme.

extension Token {
    var description: String {
        if let _ = self as? Token<Literal> {
            return "Token with literal"
        } else { return  "Regular token" }
    }
}

print(Token(lexme: Keyword()))    /// Regular token
print(Token(lexme: Literal()))    /// Token with literal

Question

If description from the extension with generic type constraint is never used, is there a reason why the compiler does not emit Invalid redeclaration of 'description' error? I believe this could be insidious, given it nudges the programmer to look elsewhere while debugging.

This has come up before. IIRC it will use the specialization when the static type is Token<Literal>, but not in generic functions or when erased to any P or Any. In other words:

let foo: Token<Literal> = // ...
foo.description // "Token with literal"
let foo2: any CustomStringConvertible = foo
foo2.description // "Regular token"

func getDescription<T: CustomStringConvertible>(_ thing: T) -> String {
    return thing.description
}

getDescription(foo) // "Regular token"

You are allowed to shadow protocol requirements; as @bbrk24 says, this means that you can invoke the shadowing implementation on a value of concrete type.

However, this does not change that a type can conform to a protocol in exactly one way: where the conformance is unconditional, so too is the requirement that satisfies that conformance.

3 Likes

I don't know, if it's any help to you, but I would normally make Literal and Keyword conform to CustomStringConvertible or some other protocol and thus defer the decision to the generic type.

protocol LiteralOrNot
{
	var isLiteral: Bool { get }
}

extension LiteralOrNot
{
	var isLiteral: Bool { false }
}

struct Token<T: LiteralOrNot>
{
	let lexme: T
}

struct Literal: LiteralOrNot
{
	let isLiteral = true
}
struct Keyword: LiteralOrNot { }

extension Token: CustomStringConvertible
{
	var description: String { lexme.isLiteral ? "Token with literal" : "Regular token" }
}

print(Token(lexme: Keyword()))   /// Regular token
print(Token(lexme: Literal()))   /// Token with literal

Thanks- I can confirm this behaviour.

For future reference and those wondering why print(Token(lexme: Literal())) results in "Regular token", looking at the documentation for CustomStringConvertible provides an insight-

If the passed instance conforms to CustomStringConvertible , the String(describing:) initializer and the print(_:) function use the instance’s custom description property.

It would seem that print is performing type erasure and hence the non-specialised description is being used.

Is there an evolution post where this is discussed? I'm curious about the rationale for allowing this.

There are a number of past discussions about this: you can use the search feature on these forums.

It's allowed for the same reason method overloads in other contexts are allowed. The potential for confusion only exists when the overloaded method also has the same name as a protocol requirement of some protocol conformed to by the concrete type.