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-2287] Forced downcast is inconsistent from Darwin to Linux from Any to AnyObject #44894

Open
phausler opened this issue Aug 5, 2016 · 17 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself

Comments

@phausler
Copy link
Member

phausler commented Aug 5, 2016

Previous ID SR-2287
Radar None
Original Reporter @phausler
Type Bug
Additional Detail from JIRA
Votes 2
Component/s Compiler
Labels Bug
Assignee None
Priority Medium

md5: ad6c5e8f031f6b621cbc1834d19758fe

Issue Description:

example:

func sample(_ input: [String : Any]) {
    var obj: AnyObject? = nil
    if let value = source["$class"] {
        obj = value as AnyObject // fails to compile on linux
        obj = value as! AnyObject // emits a warning on Darwin claiming to use `as?`
        obj = value as? AnyObject // fails to convert properly on Darwin
    }
}

This will cause some severe bugs and regressions in swift-corelibs-foundation and will probably make code less portable.

@belkadan
Copy link
Contributor

belkadan commented Aug 5, 2016

@jckarter?

@phausler
Copy link
Member Author

phausler commented Aug 5, 2016

This is causing unit test failures on Darwin builds of swift-corelibs-foundation (which is not built in CI currently)

@phausler
Copy link
Member Author

phausler commented Aug 5, 2016

a potentially more detailed failure example: (running on Darwin ToT swift)

class Foo {

}

class Bar : Foo {

}

let t: [String : Any] = [
    "bar" : Bar()
]

// the throws may be irrelevant but it is close to the actual code that is failing this way.
func thisReallyReturnsABar() throws -> AnyObject? {
    return t["bar"] as? AnyObject
}

guard let baz = try! thisReallyReturnsABar() as? Bar else {
    fatalError("Unexpected") // this is what currently hits
}
print("working correctly") // previous versions hit here

It is worth noting that the warning is at best a confabulation

test.swift:14:21: warning: conditional cast from 'Any?' to 'AnyObject' always succeeds
    return t["bar"] as? AnyObject

@jckarter
Copy link
Member

jckarter commented Aug 5, 2016

Why are you trying to as AnyObject on Linux?

@jckarter
Copy link
Member

jckarter commented Aug 5, 2016

And what do you mean that value as? AnyObject fails to convert properly on Darwin? It should always succeed now and have the same bridging effect as the unconditional as AnyObject.

@phausler
Copy link
Member Author

phausler commented Aug 5, 2016

The failure of `value as? AnyObject` is listed in the code example attached

It results in the fatal error on Darwin and hits the working correctly case on Linux (and offers no warning)

@jckarter
Copy link
Member

jckarter commented Aug 5, 2016

It may be that it's bridging the `Optional` to `AnyObject` by boxing rather than conditionally casting the inner value. Does:

t["bar"].flatMap { $0 as? AnyObject }

give the right behavior?

@phausler
Copy link
Member Author

phausler commented Aug 5, 2016

My concern is that this was existing code in swift-corelibs-foundation and the behavior is different from Linux to Darwin. One of the two is incorrect. I have a nasty #if os check to guard the behavior differential for now. And yes the flatMap works consistently between the two.

It does however still emit the warning with the flat map case

test.swift:15:42: warning: conditional cast from 'Any' to 'AnyObject' always succeeds

@jckarter
Copy link
Member

jckarter commented Aug 5, 2016

It's sort-of behaving "as designed", since id-as-Any means, well, anything can bridge to AnyObject including Optionals. I agree this is not the desired or expected behavior in this case. A couple things might mitigate it:

  • If we do a conditional cast on an optional, we should probably favor the interpretation of as? AnyObject as looking through the optional and conditionally casting its payload if it has one, over the universal boxing bridge.

  • Optional on Darwin might deserve to be _ObjectiveCBridgeable to its payload type, or NSNull if it happens to be nil. This would give you coherent behavior in this case (albeit with the warning on Darwin, which we could suppress), and would also allow collections of optionals to interoperate with ObjC as NS collections containing NSNull.

@DougGregor, would it be possible to tweak the type checker to favor T? as? AnyObject as conditionally unwrapping the optional?

@phausler
Copy link
Member Author

phausler commented Aug 5, 2016

I don't think we should bridge optional to NSNull. That is a pretty different concept than nullability. My concern is when code is written for Darwin platforms first, and then ported to other platforms developers will attempt to remove warnings; which in this case would make the code not portable.

@jckarter
Copy link
Member

jckarter commented Aug 5, 2016

What's your use case for checking whether something is AnyObject?

@phausler
Copy link
Member Author

phausler commented Aug 5, 2016

Because if the element is a structure like String or Array etc

@jckarter
Copy link
Member

jckarter commented Aug 5, 2016

I think your message got cut off…

@phausler
Copy link
Member Author

phausler commented Aug 5, 2016

The storage in the particular case that this was found in was storing a dictionary of strings to a heterogenous bag of items; Strings, Arrays, objects etc. Particularly the implementation for NSKeyedUnarchiver

@jckarter
Copy link
Member

jckarter commented Aug 5, 2016

What does it do with the information about whether something is an object? Can it be rephrased in terms of `Any` now?

@phausler
Copy link
Member Author

phausler commented Aug 5, 2016

The particular call site needs to decode an object of a class, so inherently the return value must be of the requested class (meaning it must be an AnyObject). But my worry is that if we have run across a quasi legitimate use case there is a definite potential that this will break pre-existing code in-the-wild and the resulting fixes suggested will result in un-portable code.

There are probably plenty more things we could do (given more time to work on them) to make this a non-issue in swift-corelibs-foundation but as it stands we will have a warning that is emitted that is misleading and "fixing" it will break the linux builds. I would need to take a while to look and see how much refactoring is needed to actually fix it to avoid the problem completely.

Here is the case where I am running across the issue (this fixes the unit test failures, but of course does not fix the underlying problem)
apple/swift-corelibs-foundation@a37cbdf

Per "id-as-Any means, well, anything can bridge", this is in the context of swift-corelibs-foundation which has no actual bridging, should this behavior be the case when Foundation is not imported?

@swift-ci
Copy link
Collaborator

swift-ci commented Oct 2, 2017

Comment by Helge Heß (JIRA)

Also came across this as a hack-around for https://bugs.swift.org/browse/SR-6039.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
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
Projects
None yet
Development

No branches or pull requests

4 participants