A confusing protocol extension behaviour/bug

Hi,

I hope everybody had a great New Year celebration.

I was tracking down a weird bug in my Swift code today. A property defined in a class in order to conform to a protocol was not being seen. A protocol extension provided a default value of `nil` for this property, so I knew where it was coming from. Turned out, in my class I had defined the property with the correct name but incorrect type - I declared it as `String` instead of `String?`.

I isolated this behaviour in a playground, shown below, and it is pretty weird behaviour.

The output is:

Something1 has id: nil
Something2 has id: nil
Something3 has id: Correct
-- Direct access--
Something1 - nil
Something2 - nil
Something2 with String(describing:) - Wrong but compiles, returns wrong value
Something3 - Correct

The playground code:

···

======================

protocol Trackable {
    static var analyticsID: String? { get }
}

extension Trackable {
    static var analyticsID: String? { return nil }
}

class Something1: Trackable {
}

class Something2: Trackable {
    static var analyticsID: String = "Wrong but compiles, returns wrong value"
}

class Something3: Trackable {
    static var analyticsID: String? = "Correct"
}

func getID<T: Trackable>(_ trackable: T.Type) {
    if let id = trackable.analyticsID {
        print("\(trackable) has id: \(id)")
    } else {
        print("\(trackable) has id: nil")
    }
}

getID(Something1.self)
getID(Something2.self)
getID(Something3.self)

print("-- Direct access--")
print("Something1 - \(Something1.self.analyticsID ?? "nil")")
print("Something2 A - \(Something2.self.analyticsID ?? "nil")")
print("Something2 with String(describing:) - \(String(describing: Something2.self.analyticsID))")
print("Something3 - \(Something3.self.analyticsID ?? "nil")”)

Thanks in advance for any information about my misinterpretations or recommendations of what parts are actually undesirable so that I can raise the JIRAs.

Cheers


Marc Palmer
Montana Floss Co. Ltd.

Soundproof – Music Player for Practice
http://getsoundproof.com

Hi Marc,

There are several subtleties here, but I think the compiler is actually doing the right thing.

The second class defines a static property that looks like it is 'overriding' the static property from
the protocol extension, but since the types don't match (String vs. String?), it sort of 'overloads'
the property (similar to function overloading). Nevertheless, the class still fulfills the requirements
of the Trackable protocol, by inheriting the static property from the protocol extension.

When you access analyticsID like a regular static property, the Swift compiler will choose the String property,
because it shadows the String? property:

let x = Something2.analyticsID
print(x) // Wrong but compilers, returns wrong value
print(type(of: x)) // String

However, when the context of the expression Something2.analyticsID expects a String?, the Swift compiler will
choose the String? property:

let a: String? = Something2.analyticsID // explicit type annotation demands a String?
print(a) // nil
print(type(of: a)) // Optional<String>

let b = Something2.analyticsID as String? // type cast demands a String?
print(b) // nil
print(type(of: b)) // Optional<String>

A similar thing happens, when you write Something2.analyticsID ?? "nil". The nil coalescing operator ?? demands that the first parameter
is an optional. Therefore, the Swift compiler will choose the String? property instead of the String property.

I hope this helps!

Best regards,
Toni

···

Am 01.01.2018 um 18:29 schrieb Marc Palmer via swift-users <swift-users@swift.org>:

Hi,

I hope everybody had a great New Year celebration.

I was tracking down a weird bug in my Swift code today. A property defined in a class in order to conform to a protocol was not being seen. A protocol extension provided a default value of `nil` for this property, so I knew where it was coming from. Turned out, in my class I had defined the property with the correct name but incorrect type - I declared it as `String` instead of `String?`.

I isolated this behaviour in a playground, shown below, and it is pretty weird behaviour.

The output is:

Something1 has id: nil
Something2 has id: nil
Something3 has id: Correct
-- Direct access--
Something1 - nil
Something2 - nil
Something2 with String(describing:) - Wrong but compiles, returns wrong value
Something3 - Correct

The playground code:

======================

protocol Trackable {
   static var analyticsID: String? { get }
}

extension Trackable {
   static var analyticsID: String? { return nil }
}

class Something1: Trackable {
}

class Something2: Trackable {
   static var analyticsID: String = "Wrong but compiles, returns wrong value"
}

class Something3: Trackable {
   static var analyticsID: String? = "Correct"
}

func getID<T: Trackable>(_ trackable: T.Type) {
   if let id = trackable.analyticsID {
       print("\(trackable) has id: \(id)")
   } else {
       print("\(trackable) has id: nil")
   }
}

getID(Something1.self)
getID(Something2.self)
getID(Something3.self)

print("-- Direct access--")
print("Something1 - \(Something1.self.analyticsID ?? "nil")")
print("Something2 A - \(Something2.self.analyticsID ?? "nil")")
print("Something2 with String(describing:) - \(String(describing: Something2.self.analyticsID))")
print("Something3 - \(Something3.self.analyticsID ?? "nil")”)

Thanks in advance for any information about my misinterpretations or recommendations of what parts are actually undesirable so that I can raise the JIRAs.

Cheers


Marc Palmer
Montana Floss Co. Ltd.

Soundproof – Music Player for Practice
http://getsoundproof.com

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

Unfortunately the error messages you get with protocols are limited :). The compiler is doing the right thing, no bug. The problem is that it is really easy to mak

-- Howard.

···

On 1 Jan 2018, at 3:40 pm, Toni Suter via swift-users <swift-users@swift.org> wrote:

Hi Marc,

There are several subtleties here, but I think the compiler is actually doing the right thing.

The second class defines a static property that looks like it is 'overriding' the static property from
the protocol extension, but since the types don't match (String vs. String?), it sort of 'overloads'
the property (similar to function overloading). Nevertheless, the class still fulfills the requirements
of the Trackable protocol, by inheriting the static property from the protocol extension.

When you access analyticsID like a regular static property, the Swift compiler will choose the String property,
because it shadows the String? property:

let x = Something2.analyticsID
print(x) // Wrong but compilers, returns wrong value
print(type(of: x)) // String

However, when the context of the expression Something2.analyticsID expects a String?, the Swift compiler will
choose the String? property:

let a: String? = Something2.analyticsID // explicit type annotation demands a String?
print(a) // nil
print(type(of: a)) // Optional<String>

let b = Something2.analyticsID as String? // type cast demands a String?
print(b) // nil
print(type(of: b)) // Optional<String>

A similar thing happens, when you write Something2.analyticsID ?? "nil". The nil coalescing operator ?? demands that the first parameter
is an optional. Therefore, the Swift compiler will choose the String? property instead of the String property.

I hope this helps!

Best regards,
Toni

Am 01.01.2018 um 18:29 schrieb Marc Palmer via swift-users <swift-users@swift.org>:

Hi,

I hope everybody had a great New Year celebration.

I was tracking down a weird bug in my Swift code today. A property defined in a class in order to conform to a protocol was not being seen. A protocol extension provided a default value of `nil` for this property, so I knew where it was coming from. Turned out, in my class I had defined the property with the correct name but incorrect type - I declared it as `String` instead of `String?`.

I isolated this behaviour in a playground, shown below, and it is pretty weird behaviour.

The output is:

Something1 has id: nil
Something2 has id: nil
Something3 has id: Correct
-- Direct access--
Something1 - nil
Something2 - nil
Something2 with String(describing:) - Wrong but compiles, returns wrong value
Something3 - Correct

The playground code:

======================

protocol Trackable {
   static var analyticsID: String? { get }
}

extension Trackable {
   static var analyticsID: String? { return nil }
}

class Something1: Trackable {
}

class Something2: Trackable {
   static var analyticsID: String = "Wrong but compiles, returns wrong value"
}

class Something3: Trackable {
   static var analyticsID: String? = "Correct"
}

func getID<T: Trackable>(_ trackable: T.Type) {
   if let id = trackable.analyticsID {
       print("\(trackable) has id: \(id)")
   } else {
       print("\(trackable) has id: nil")
   }
}

getID(Something1.self)
getID(Something2.self)
getID(Something3.self)

print("-- Direct access--")
print("Something1 - \(Something1.self.analyticsID ?? "nil")")
print("Something2 A - \(Something2.self.analyticsID ?? "nil")")
print("Something2 with String(describing:) - \(String(describing: Something2.self.analyticsID))")
print("Something3 - \(Something3.self.analyticsID ?? "nil")”)

Thanks in advance for any information about my misinterpretations or recommendations of what parts are actually undesirable so that I can raise the JIRAs.

Cheers


Marc Palmer
Montana Floss Co. Ltd.

Soundproof – Music Player for Practice
http://getsoundproof.com

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

Hi Marc,

There are several subtleties here, but I think the compiler is actually doing the right thing.

Hi Toni,

Thanks for your detailed response.

[..]

The second class defines a static property that looks like it is 'overriding' the static property from
the protocol extension, but since the types don't match (String vs. String?), it sort of 'overloads'
the property (similar to function overloading). Nevertheless, the class still fulfills the requirements
of the Trackable protocol, by inheriting the static property from the protocol extension.

Isn’t this a compiler bug?

I didn’t realise that overloading properties was a thing? I’m pretty sure it isn’t, in which case the compiler should choke because there are two properties of the same name but divergent types available on the type.

This behaviour seems to create major hard-to-detect hazards when using protocol extensions, which seems against the spirit of Swift.

Marc

···

On 1 Jan 2018, at 20:40, Toni Suter <tonisuter@me.com> wrote:

Rest of message - hit send by mistake.

The problem is that it is really easy to make the mistake you made. There have been a number of suggestions on Swift Evolution to add key words so that diagnostics can be improved. For example:

    protocol P {
        func m()
    }
    extension P {
        override func m() { ... }
    }

Which obviously breaks old code but does let the compiler diagnose problems. However none of these suggestions have gained enough support to date.

You could put in an RFE asking for better diagnostics.

-- Howard.

···

On 1 Jan 2018, at 11:21 pm, Howard Lovatt via swift-users <swift-users@swift.org> wrote:

Unfortunately the error messages you get with protocols are limited :). The compiler is doing the right thing, no bug. The problem is that it is really easy to mak

-- Howard.

On 1 Jan 2018, at 3:40 pm, Toni Suter via swift-users <swift-users@swift.org> wrote:

Hi Marc,

There are several subtleties here, but I think the compiler is actually doing the right thing.

The second class defines a static property that looks like it is 'overriding' the static property from
the protocol extension, but since the types don't match (String vs. String?), it sort of 'overloads'
the property (similar to function overloading). Nevertheless, the class still fulfills the requirements
of the Trackable protocol, by inheriting the static property from the protocol extension.

When you access analyticsID like a regular static property, the Swift compiler will choose the String property,
because it shadows the String? property:

let x = Something2.analyticsID
print(x) // Wrong but compilers, returns wrong value
print(type(of: x)) // String

However, when the context of the expression Something2.analyticsID expects a String?, the Swift compiler will
choose the String? property:

let a: String? = Something2.analyticsID // explicit type annotation demands a String?
print(a) // nil
print(type(of: a)) // Optional<String>

let b = Something2.analyticsID as String? // type cast demands a String?
print(b) // nil
print(type(of: b)) // Optional<String>

A similar thing happens, when you write Something2.analyticsID ?? "nil". The nil coalescing operator ?? demands that the first parameter
is an optional. Therefore, the Swift compiler will choose the String? property instead of the String property.

I hope this helps!

Best regards,
Toni

Am 01.01.2018 um 18:29 schrieb Marc Palmer via swift-users <swift-users@swift.org>:

Hi,

I hope everybody had a great New Year celebration.

I was tracking down a weird bug in my Swift code today. A property defined in a class in order to conform to a protocol was not being seen. A protocol extension provided a default value of `nil` for this property, so I knew where it was coming from. Turned out, in my class I had defined the property with the correct name but incorrect type - I declared it as `String` instead of `String?`.

I isolated this behaviour in a playground, shown below, and it is pretty weird behaviour.

The output is:

Something1 has id: nil
Something2 has id: nil
Something3 has id: Correct
-- Direct access--
Something1 - nil
Something2 - nil
Something2 with String(describing:) - Wrong but compiles, returns wrong value
Something3 - Correct

The playground code:

======================

protocol Trackable {
   static var analyticsID: String? { get }
}

extension Trackable {
   static var analyticsID: String? { return nil }
}

class Something1: Trackable {
}

class Something2: Trackable {
   static var analyticsID: String = "Wrong but compiles, returns wrong value"
}

class Something3: Trackable {
   static var analyticsID: String? = "Correct"
}

func getID<T: Trackable>(_ trackable: T.Type) {
   if let id = trackable.analyticsID {
       print("\(trackable) has id: \(id)")
   } else {
       print("\(trackable) has id: nil")
   }
}

getID(Something1.self)
getID(Something2.self)
getID(Something3.self)

print("-- Direct access--")
print("Something1 - \(Something1.self.analyticsID ?? "nil")")
print("Something2 A - \(Something2.self.analyticsID ?? "nil")")
print("Something2 with String(describing:) - \(String(describing: Something2.self.analyticsID))")
print("Something3 - \(Something3.self.analyticsID ?? "nil")”)

Thanks in advance for any information about my misinterpretations or recommendations of what parts are actually undesirable so that I can raise the JIRAs.

Cheers


Marc Palmer
Montana Floss Co. Ltd.

Soundproof – Music Player for Practice
http://getsoundproof.com

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

Hi Marc,

The second class defines a static property that looks like it is 'overriding' the static property from
the protocol extension, but since the types don't match (String vs. String?), it sort of 'overloads'
the property (similar to function overloading). Nevertheless, the class still fulfills the requirements
of the Trackable protocol, by inheriting the static property from the protocol extension.

Isn’t this a compiler bug?

I didn’t realise that overloading properties was a thing? I’m pretty sure it isn’t, in which case the compiler should choke because there are two properties of the same name but divergent types available on the type.

This behaviour seems to create major hard-to-detect hazards when using protocol extensions, which seems against the spirit of Swift.

Yeah, I guess overloading might not be the right term. You're right, properties can't be overloaded in general. For example:

struct S {
    static var x: Int { return 2 }
    static var x = "" // error: invalid redeclaration of 'x'
}

You can only 'overload' properties that are inherited from a protocol extension.

protocol P {}
extension P {
    static var x: Int { return 2 }
}
struct S: P {
    static var x = ""
}

I am not exactly sure why this is allowed, but maybe someone from the Swift team can help.

Best regards,
Toni

FYI there’s a little more nuance to this. Even if `x` is defined as a requirement in the protocol, the compiler still doesn’t complain, and IMO this is the most heinous problem.

So it looks like:

1. If the property is declared in the protocol and something is marked as conforming to it, there is no excuse for permitting a property with the same name but wrong type

2. If the property is not declared in the protocol but only in an extension, not types should be allowed to define a property with the same name but different type as the property defined in the extension.

Marc

···

On 2 Jan 2018, at 09:12, Toni Suter <tonisuter@me.com> wrote:

You can only 'overload' properties that are inherited from a protocol extension.

protocol P {}
extension P {
    static var x: Int { return 2 }
}
struct S: P {
    static var x = ""
}

I am not exactly sure why this is allowed, but maybe someone from the Swift team can help.

Yes, I think I agree with you. Maybe we are missing something, but I don't
see a reason why declaring a property with the same name and a different type
should be allowed in the 1. scenario.

So maybe you should file a bug in JIRA, after all.

Best regards,
Toni

···

Am 02.01.2018 um 13:56 schrieb Marc Palmer <marc@anyware.co.uk>:

On 2 Jan 2018, at 09:12, Toni Suter <tonisuter@me.com> wrote:

You can only 'overload' properties that are inherited from a protocol extension.

protocol P {}
extension P {
   static var x: Int { return 2 }
}
struct S: P {
   static var x = ""
}

I am not exactly sure why this is allowed, but maybe someone from the Swift team can help.

FYI there’s a little more nuance to this. Even if `x` is defined as a requirement in the protocol, the compiler still doesn’t complain, and IMO this is the most heinous problem.

So it looks like:

1. If the property is declared in the protocol and something is marked as conforming to it, there is no excuse for permitting a property with the same name but wrong type

2. If the property is not declared in the protocol but only in an extension, not types should be allowed to define a property with the same name but different type as the property defined in the extension.

Marc

Done: https://bugs.swift.org/browse/SR-6689

Cheers

···

On 2 Jan 2018, at 13:06, Toni Suter <tonisuter@me.com> wrote:

Yes, I think I agree with you. Maybe we are missing something, but I don't
see a reason why declaring a property with the same name and a different type
should be allowed in the 1. scenario.

So maybe you should file a bug in JIRA, after all.