Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-3871] Protocol passing via objective-c / Any can't be cast back to protocol type #46456

Closed
swift-ci opened this issue Feb 6, 2017 · 31 comments
Assignees
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself runtime The Swift Runtime

Comments

@swift-ci
Copy link
Collaborator

swift-ci commented Feb 6, 2017

Previous ID SR-3871
Radar rdar://problem/28281488
Original Reporter deanWombourne (JIRA User)
Type Bug
Status Resolved
Resolution Done

Attachment: Download

Environment

Apple Swift version 3.0.2 (swiftlang-800.0.63 clang-800.0.42.1)
Target: x86_64-apple-macosx10.9

Additional Detail from JIRA
Votes 20
Component/s Compiler
Labels Bug, Runtime
Assignee @tbkka
Priority Medium

md5: 0252e1a81c162f1d23e390d03e7e44bc

is duplicated by:

  • SR-4516 Something goes badly wrong with enum to protocol casting
  • SR-5489 Casting Objective-C id to Swift protocol type fails
  • SR-5523 After UIKit uses, the object is not conforming to any protocol
  • SR-5575 Swift -> Obj-C boxing results in loss of type info
  • SR-5590 Failed to cast to protocol
  • SR-6309 Could not cast value of type '_SwiftValue' to a protocol
  • SR-8651 Protocol test on _SwiftValue returned from Obj-C doesn't work

Issue Description:

I have a public protocol

public protocol MyProtocol { }

And I implement this in a module, with a public method which return an instance.

internal struct MyStruct: MyProtocol { }
public func make() -> MyProtocol {
  return MyStruct() 
}

Then, in my view controller, I trigger a segue with that object as the sender

let myStruct = make()
self.performSegue(withIdentifier: "Bob", sender: myStruct)

All good so far. The problem is in my prepare(for:sender:) method.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "Bob" {
        if let instance = sender as? MyProtocol {
            print("Yay")
        }
    }
}

However, the cast of instance to MyProtocol always returns nil.

Using po sender! in the console will output the correct type.

@belkadan
Copy link
Contributor

belkadan commented Feb 7, 2017

@jckarter, does this sound familiar?

deanWombourne (JIRA User), if you could attach a test project that would be helpful. Your instructions are probably clear enough for us to reproduce it but attaching a sample project is the best way to ensure that we're looking at the same thing. If you don't have time that's okay, though.

@jckarter
Copy link
Member

jckarter commented Feb 7, 2017

Yes, this is an instance of the general "runtime dynamic casting doesn't bridge if necessary to bridge to a protocol" bug. Since sender is seen as _SwiftValue object type, and we're trying to get to a protocol it doesn't conform to, we give up without also trying the bridged type.

@swift-ci
Copy link
Collaborator Author

swift-ci commented Feb 7, 2017

Comment by Sam Dean (JIRA)

@belkadan Hi - I've attached an example project - just tap the button in the ui and an assert will fail where my issue is. You can almost certainly reduce it down into a single test case, but I'm not confident enough to do that 🙂 If you need anything else, let me know.

@hamishknight
Copy link
Collaborator

Here's a minimal example that reproduces the same problem:

protocol P {}
struct S : P {}

let val : Any = S() as AnyObject // bridge to Obj-C as a _SwiftValue

print(val as? P) // nil

Interestingly, it does work if you first cast to AnyObject:

print(val as AnyObject as? P) // S()

I guess the bridged type is checked when it's statically typed as AnyObject?

@jckarter
Copy link
Member

jckarter commented Feb 7, 2017

@hamishknight Yeah, that sounds like a real possibility. The runtime has had a number of bugs where it only attempts certain conversions to one level of depth, but not after performing other conversions (so AnyObject -> bridge -> Protocol might work, but Any -> AnyObject -> bridge -> Protocol doesn't). It ought to work, at any rate.

@iby
Copy link

iby commented Apr 22, 2017

This is becoming the most painful issues of all times. I'm working with AXUIElement and there are points where it simply impossible to cast as conformed protocol. Intermediary cast to AnyObject works sometimes, but not always. I'm placing hacks like below and I hate myself…

@tapi
Copy link
Mannequin

tapi mannequin commented Jun 12, 2017

Found the same (or similar) issue when converting to Swift 3.1

Example here
http://swift.sandbox.bluemix.net/#/repl/593ec8e6063832217318f380

@jckarter
Copy link
Member

@tapi That's a different bug. The `ImplicitlyUnwrappedOptional` should get implicitly changed to normal `Optional` before ending up in an `Any`. Mind filing a separate bug for that?

@swift-ci
Copy link
Collaborator Author

swift-ci commented Sep 5, 2017

Comment by Vyacheslav Dubovitsky (JIRA)

Not sure this is the same problem or not, but you actually can't cast a class (no matter @objc or not) to @objc protocol. Even through the AnyObject hack:

@objc protocol Foo {}
class A: NSObject {}

let a = A()
print(a as? Foo) // nil
print(a as! Foo) // Could not cast value of type ...

@hamishknight
Copy link
Collaborator

exevil (JIRA User) A doesn't conform to Foo, so that's entirely expected behaviour. You need to say class A : NSObject, Foo {}

@swift-ci
Copy link
Collaborator Author

swift-ci commented Sep 6, 2017

Comment by Vyacheslav Dubovitsky (JIRA)

@hamishknight : Yeah, I see. Thanks!

@amomchilov
Copy link
Contributor

Self contained example:

import Foundation

protocol P {}
struct S: P {}

class Wrapper {
    @objc dynamic let any: Any = S() //dynamic ensures the struct will be boxed into _SwiftValue
    init() {}
}

print(Wrapper().any is P) // false :(
print(Wrapper().any as AnyObject is P) // workaround: true :)

@swift-ci
Copy link
Collaborator Author

Comment by JP Wright (JIRA)

I just experienced this bug in a tests for a pure swift library with no NSObject subclasses present anywhere. Sure enough, casting to {{AnyObject}} first does solve the problem but I find it quite strange. Is this really an issue with bridging to Objective-C?

@belkadan
Copy link
Contributor

It's an issue with a struct value being converted to AnyObject and then to Any. It's not strictly about Objective-C.

@swift-ci
Copy link
Collaborator Author

Comment by Jason R Tibbetts (JIRA)

I can confirm that this is still an issue in Xcode 10.2 playgrounds.

@swift-ci
Copy link
Collaborator Author

Comment by Diogo Autilio (JIRA)

Still an issue in Xcode playgrounds version 11.2.1 (11B500).

@tbkka
Copy link
Contributor

tbkka commented Dec 17, 2019

Should be fixed by this PR: #28835 (currently in review)

Note that I'm not at all sure the PR will fix all of the duplicates here. All of them involve casting to a protocol and from a nested set of containers (Any/AnyObject/Optional/Obj-C boxing), but there are a lot of paths through the dynamic casting code that handle subtle variations in different ways.

@swift-ci
Copy link
Collaborator Author

Comment by Jason Bobier (JIRA)

I can verify that this is still a problem in 11.4.1.

@tbkka
Copy link
Contributor

tbkka commented Apr 16, 2020

If you are still seeing this, please indicate the OS version as well and if possible, a short piece of code that shows the problem. The fix mentioned above is part of the standard library code distributed with Apple operating systems, so merely using a new compiler may not demonstrate any difference.

@swift-ci
Copy link
Collaborator Author

Comment by Jason Bobier (JIRA)

OS is macOS 10.15.4 (19E287)

It might be related to crossing a framework boundary. I moved the function directly into a playground and it worked, but if I built it into a framework and then called it from the playground it didn't work.

It's as simple as this (this isn't the exact code, but approximately):

protocol P { }

struct S<T> {
    let t: T
}

extension S: P { }

func string(for obj: Any?) -> String? {
    guard let obj = obj as? P else {
        return nil
    }
    return "test"
}

let s = string(for: S(t: 0))

@iby
Copy link

iby commented Apr 17, 2020

jasonbobier (JIRA User) Funny you mention the framework boundary. I was head-aching over a different (what seems like) regression SR-12610 with auto-linked static framework. Wondering if there's a deeper issue with linking in general?

@swift-ci
Copy link
Collaborator Author

Comment by Jason Bobier (JIRA)

Interesting. It is possible that this is a regression too because I was porting some old code to Swift 5.2 when this issue appeared in my unit tests. I went back and looked at the old code and it didn't need the intermediary cast to AnyObject for the unit test to succeed.

@swift-ci
Copy link
Collaborator Author

Comment by Jason Bobier (JIRA)

@tbkka

Looks like it is related to inheritance from obj-c. Here is a sample project showing the failure.

CastFromAnyBug.zip

@hujunfeng
Copy link

I can still reproduce the problem with the example posted previously by @hamishknight:

protocol P {}
struct S : P {}

let val : Any = S() as AnyObject // bridge to Obj-C as a _SwiftValue

print(val as? P) // nil

Xcode: 11.4.1 (11E503a)
OS: 10.15.4 (19E287)

@bobergj
Copy link

bobergj commented Aug 18, 2020

I just hit this and looked into which Swift runtime release it's supposedly fixed.

The PR #28835 mentioned above is not included until the swift 5.3 release (as can be seen with git branch -r --contains b8c090d).

This means the fix - being a runtime fix - is only in the iOS 14 and macOS 11 betas

@swift-ci
Copy link
Collaborator Author

Comment by Jason Bobier (JIRA)

I have verified that the test case that I attached above now works on macOS 11 and Xcode 12.0 beta 4.

@swift-ci
Copy link
Collaborator Author

Comment by Jason R Tibbetts (JIRA)

Thanks for confirming this. It’s too late for us to go back and un-refactor all of our code that exhibited the bug, but it’s good to know that it won’t be an issue going forward.

@tbkka
Copy link
Contributor

tbkka commented Aug 18, 2020

Thanks for confirming! I'll mark this as "Resolved". If people are still seeing variations of this problem (in macOS 11), please file a new bug report and we'll look into it.

@tbkka
Copy link
Contributor

tbkka commented Aug 18, 2020

As mentioned in the comments, I'm not entirely certain all of the other issues that were duplicated here are fixed (there are a lot of paths through the code in question).

For anyone encountering a similar issue, here are a few critical things to watch for:

  • Version of Swift

  • Version of Xcode (if you're on macOS)

  • OS version

  • Please test in both debug and release builds

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
@tmaly1980
Copy link

I still seem to be experiencing this issue on iOS 14/XCode 14.2/Swift 5/MacOS 12.5.

Is there a setting in my Xcode project I need to enable to bypass this issue?

@tbkka
Copy link
Contributor

tbkka commented Jun 1, 2023

@tmaly1980 Can you please provide a specific example of what you're trying to do and how it's failing?

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself runtime The Swift Runtime
Projects
None yet
Development

No branches or pull requests

10 participants