`some protocol` gives out different result for the same code

If you run the above Xcode SwiftUI project, output should be:

hello
100.0
100.0
hello
100.0
hello

They are both the same code but one produce 100.0 and the other produce hello. Why does this happen?

Forgot to add system info:
macOS: 15.0.1 (24A348)
Xcode: Version 16.0 (16A242d)

Cool! I think you've found a bug. I can reduce it to the following:

protocol P {
  var x: Double { get }
}

extension P {
  var x: Double { 42 }
}

struct S: P {
  var x: String = "concrete non-witness"
}

func foo(_ x: Int) {
  let someP: some P = S()
  print(someP.x)
}

let x: Int? = 42
x.map(foo)
// prints 42

x.map { _ in
  let someP: some P = S()
  print(someP.x)
}
// prints "concrete non-witness"
3 Likes

Filed an issue:

3 Likes

OMG! That feels awesome! Not for the compiler team though... Hope this is a quick fix :+1:

Hello, @xwu

Please ignore - I got it now. :slight_smile:

Expected behavior

The following—

x.map { _ in
  let someP: some P = S()
  print(someP.x)
}

—should print "42", just as it does when you write the exact same code in a plain old function.

Sorry, but I feel confused after reading the bug description.

My understanding is that what is printed by the closure is correct, but what is printed by func foo is wrong, simply because of some P = S() (the compiler has more information about the concrete type.)

If I replace some P with any P, 42 is printed in both cases, as I expect.

Could you help me understand the discrepancy? I feel as though my mental model of any and some is totally wrong.

The key point in the code is that x.map(foo) and x.map { ... } are essentially the same. Notice the function body of foo is the same as the closure that past in to x.map.

// The following two are the same
x.map { _ in                  | x.map(foo)
  let someP: some P = S()     |
  print(someP.x)              |
}                             |

// Reminder of what foo is
func foo(_ x: Int) {
  let someP: some P = S()
  print(someP.x)
}

But they print out different results. The is the bug/discrepancy.
Your mention of the behavior of any (boxed type) is correct and expected.
The thing is when a protocol has a default implementation provided with an extension. It should work similar to a box type for that specific default implementation. AND THIS IS STILL THE CASE even if the default implementation would result a conflict in variable name with the original type.

protocol P {
  var x: Double { get }
}

extension P {                  // Default implementation
  var x: Double { 42 }         // <- force x to be a Double
}

struct S: P {
  var x: String = "concrete"   // <- x is String!!!
}

How would swift resolve this case?
Well... Swift allows S to have TWO variables having THE SAME NAME!

// After forced default implementation
// NOT actual swift code; just for illustration purpose
struct S: P {
   var x_string: String = "concrete"  // This is the original
   var x_double: Double { 42 }  // This is forced by default implementation

Weird, I know but it's useful...

Now back to the expected behavior of the bug:
Due to the forced default implementation, S now has TWO identity:

  • First: S is S type itself (Duh)
  • Second: S is P (the protocol it conforms to)
let expectP: some P = S() // This SHOULD bring out the second (P) identity

However, here is the bug, in a closure, the second identity got ignored and went straight for the true underlaying identity.

So that's the bug. Hopefully I did a good job explaining! :smile:

1 Like

Hello, @Luminaron

Thank you for the detailed explanation - Much appreciated.

This is what I am finding hard to wrap my mind around.

func bar () {
    let someP: some P = S()
    print (#function, "with some P", someP.x)
      
    let anyP: any P = S()
    print (#function, "with any P", anyP.x)
}

// bar() with some P 42.0
// bar() with any P 42.0

// --> They both print 42!

Here is my reasoning.

Given

let someP: some P = S()

The compiler has more information about the concrete type and therefore someP.x should resolve to the concrete implementation, not to the default one provided by the protocol.

Obviously, my reasoning is wrong because there is no difference between this:

let someP: some P = S()
let u = someP.X

And this:

let anyP: any P = S()
let u = anyP.X

Would you be able to provide a use case for this?

It's actually from my previous question. Summarize the link below:

  • Animatable tries to force a default implementation of itself to a certain type even in the case of conflict.
  • using where clause where self.AnimatableData == EmptyAnimatableData to purge default implementation where conformation is successful.

You end up with a nice behavior where if a type does NOT actually conform to Animatable, SwiftUI wouldn't scream because it sees EmptyAnimatableData and thus ignore silently. To the user, when they use that type the user see the type itself which is expected.

So, animatableData as a variable is double-faced and that keeps every one satisfied; that is unless you dig just a little bit deeper.

I still find this weird tho... especially the where clause is applied AFTER the default implementation.

1 Like

The default implementation is the concrete implementation for the protocol requirement.

The x that returns a String just has the same name (but not type) as the protocol requirement and doesn't actually witness it.

1 Like

@Jnosh, @Luminaron

Thank you for prodding my memory.

I now understand where I went wrong in my reasoning. I completely missed the fact that The x that returns a String does not actually satisfy the protocol requirement.