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-13812] Metatype subtype test behaves differently with swiftc and Playground/iOS app #56209

Closed
swift-ci opened this issue Nov 3, 2020 · 5 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself

Comments

@swift-ci
Copy link
Collaborator

swift-ci commented Nov 3, 2020

Previous ID SR-13812
Radar rdar://problem/70999129
Original Reporter fredrik.tb (JIRA User)
Type Bug
Status Resolved
Resolution Done

Attachment: Download

Environment

OS: macOS Catalina 10.15.7

Xcode 12.1

swift -version:
Apple Swift version 5.3 (swiftlang-1200.0.29.2 clang-1200.0.30.1)
Target: x86_64-apple-darwin19.6.0

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug
Assignee None
Priority Medium

md5: f0f62de5ffaf8f722d3f3bedff58b925

Issue Description:

I sought to build an API that had two generics part of a function signature, where one of them was a subtype of the other, but expressed only with metatypes, not instances. As this relationship cannot be expressed at compile time, I tried to instead describe it using runtime preconditions. Basically something like this:

func check<A, B>(a: A.Type, b: B.Type) {
 print(a is B.Type)
}
protocol Fruit {}
struct Apple: Fruit {} 
check(a: Apple.self, b: Fruit.self) // false is printed

This above actually didn't seem to work and printed "false". But spelling out the same thing without the generics works just fine:

print(Apple.self is Fruit.Type) // true is printed

What's funny is, this only seemed to behave differently in a Playground (or when running it straight in an app built with Xcode). When I tossed the exact same code into a .swift file and built it with swiftc, both of the things printed "true" as I expected.

After asking around about it on a big iOS Slack team, I was encouraged by @natecook1000 to file a bug report.

@swift-ci
Copy link
Collaborator Author

swift-ci commented Nov 3, 2020

Comment by Fredrik Tõnisson-Bystam (JIRA)

I just had an idea after posting this and tested it:

Creating a new "macOS command line tool" in Xcode 12.1 and running the code in there ALSO made it work "as expected". As in this printing true instead of false:
print(a is B.Type)

@typesanitizer
Copy link

@swift-ci create

@tbkka
Copy link
Contributor

tbkka commented Nov 10, 2020

There are a number of bugs in dynamic casting (which includes the `is`, `as!` and `as?` operators) in Swift 5.3. Many of these will be fixed in the next release as a result of PR #33561 (#33561 and related work.

If you could, I'd appreciate if you could test two things:

  1. Repeat your tests with both debug and optimized build settings, as many of the bugs that were fixed caused different behaviors for debug and optimized builds.
  2. Test with one of the recent toolchains from the "main" branch available on swift.org, which should fix most of those bugs

A more subtle issue here is the confusing behavior of `.Type`: Your report mentions `B.Type` and `Fruit.Type`. These are entirely different things: Because `Fruit` is the name of a protocol, `Fruit.Type` is the "existential metatype" for `Fruit`. In contrast, `B` is a generic type variable, and `B.Type` is the metatype for whatever is bound to that variable. In your example where `B` is bound to the protocol type `Fruit`, `B.Type` is the same as `Fruit.Protocol` and is entirely unrelated to `Fruit.Type`.

For a more detailed explanation of how casting in Swift is supposed to work (and should work in the next release), you can see the new Dynamic Casting Spec

@tbkka
Copy link
Contributor

tbkka commented Nov 12, 2020

In the current `main` branch, which includes the updated casting logic, the following test passes in both debug and release builds:

  func check<A, B>(a: A.Type, b: B.Type) -> Bool {
    return (a is B.Type)
  }
  protocol Fruit {}
  struct Apple: Fruit {}
  expectFalse(check(a: Apple.self, b: Fruit.self))
  expectFalse(Apple.self is Fruit.Protocol)
  expectTrue(Apple.self is Fruit.Type)

Based on the casting specification, I believe this is the correct behavior. In particular, note that the first two `expectFalse` here are testing the same thing; the last `is Fruit.Type` is a completely different test. Based on your description above, it sounds like you were seeing this behavior in some circumstances but not all.

Note that this behavior depends both on the compiler and on runtime libraries that are bundled with Apple operating systems. So you should see this behavior once both Xcode and the OS have these updates.

@swift-ci
Copy link
Collaborator Author

Comment by Fredrik Tõnisson-Bystam (JIRA)

Right! Thanks for the clarification.

A more subtle issue here is the confusing behavior of `.Type`: Your report mentions `B.Type` and `Fruit.Type`. These are entirely different things: Because `Fruit` is the name of a protocol, `Fruit.Type` is the "existential metatype" for `Fruit`

I initially thought that this was the case too - but then when the things behaved differently when I tried it by using `swiftc` I decided to post it here anyway.

Great to see it being resolved, and now I know what the expected behaviour is in the future. Thanks! �

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

No branches or pull requests

3 participants