-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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
Allow protocols to be nested in non-generic contexts #66247
Conversation
lib/AST/ASTPrinter.cpp
Outdated
@@ -3425,6 +3425,14 @@ static bool usesFeatureParameterPacks(Decl *decl) { | |||
return false; | |||
} | |||
|
|||
static bool usesFeatureNestedProtocols(Decl *decl) { | |||
// TODO: How do I test this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can test this through swfitinterface printing. Check out test/ModuleInterface/pack_expansion_type.swift
as an example, which exercises usesFeatureParameterPacks
and checks for #if
guards around declarations that use parameter packs.
Cool! It looks like you're still actively working on this, so let me know if/when you'd like to kick off CI, e.g. to build a toolchain |
So, one thing that is interesting is the module interface printing - the logic in the PR is a bit too simplistic; if the goal is to print an interface which can be successfully parsed by an older compiler (using feature guards), then it's not enough to only guard the protocol declarations themselves -- it also needs to guard any use of a nested protocol (e.g. in a function's parameters or return type, or within a generic signature). And I think that also extends to typealiases which are bound to nested protocols, across translation units, etc. So it's kind of complex. What is the best way to approach that? Can it even be automated, or will users have to guard those uses manually using |
Now imagine a declaration that transitively depends on a declaration that uses a nested protocol; it would also need to be feature-guarded, if you were going for an airtight implementation.
I would simply punt on the issue. Feature guards are only intended to solve a limited set of problems with staging in toolchain/stdlib/SDK changes and we can't really hope to always generate an interface file that remains parseable by older compilers. |
@@ -0,0 +1,261 @@ | |||
// RUN: %target-typecheck-verify-swift -parse-as-library -enable-experimental-feature NestedProtocols |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also add some IRGen tests to cover emission of protocol descriptors, and execution tests. In particular, I'm curious if a dynamic cast to a conditional conformance involving one of these things will work immediately, or require runtime changes:
enum E {
protocol P {}
}
struct S<T> {}
extension S: E.P where T: E.P {}
extension Int: E.P {}
let x: Any = S<Int>()
print(x as? any E.P)
OK. I thought about maybe hiding just the protocol type, but the type declaration will almost always live alongside some uses of the protocol so it probably wouldn't be very useful in real projects, and in the worst case an older compiler could end up with an incorrect interface: protocol Foo { ... }
enum MyEnum {
#if compiler(>=5.3) && $NestedProtocols
protocol Foo { ... }
#endif
func doSomething<T: Foo>(_: T)
// ^ Argument may be top-level Foo or nested Foo depending on compiler version!
} Probably (hopefully) that would end in a linker failure, but it's still not good. Maybe type declarations should not be guarded in general because of this. So yeah, in summary - I'll remove the feature guard in the module interface and add a test to ensure it isn't printed.
Your example works for me using this patch. I will add an executable test which includes it (as well as the IRGen descriptor tests). |
A couple more things I thought of:
|
Why? I mean, there is an issue that we don't namespace generated Obj-C names, but that is equally a problem for nested classes. For example, see the test @objc @objcMembers class Nested {
@objc @objcMembers class Inner {
@objc @objcMembers class DeeperIn {}
}
} And generates the following Obj-C header. Note that the SWIFT_CLASS("_TtC7classes6Nested")
@interface Nested
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
SWIFT_CLASS("_TtCC7classes6Nested5Inner")
@interface Inner
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
SWIFT_CLASS("_TtCCC7classes6Nested5Inner8DeeperIn")
@interface DeeperIn
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end So clearly that's a problem, but it is not uniquely a problem for protocols and deserves a comprehensive solution. That solution would be source-breaking, and it's not going to be worse for protocols than any other nested type. Evidently, some people seem to be able to work with the current limitations, and it's not clear to me that they should be prohibited from exposing nested protocols. They may wish to use a custom Obj-C name as a workaround, while using nesting for the Swift version of the interface. |
Good point. Let's just add some test cases then. |
@slavapestov OK I've added:
Still todo:
And I think that's it, right? Could you run CI tests on what I have so far, just to confirm it works like it does for me locally? |
@swift-ci Please test |
@swift-ci Please test source compatibility |
@swift-ci please build toolchain |
@karwa I resolved conflicts via merge because I didn't want to force-push to your branch, but please feel free to redo it as a rebase if you'd like. |
9f8e6de
to
9cc80a2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just have a couple more minor suggestions.
9cc80a2
to
f3a4558
Compare
@slavapestov Implemented the change you suggested. Could you trigger CI please? |
// expected-error@-1 {{protocol 'P' cannot be nested inside another declaration}} | ||
struct NestedInGenericMethod<T> { | ||
func someMethod() { | ||
protocol AnotherTest {} // expected-error{{type 'AnotherTest' cannot be nested in generic function 'someMethod()'}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this use the "protocol %0 cannot be nested in a generic context" diagnostic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another change (that I rebased in) guarded these checks inside if (auto *parentDecl = DC->getSelfNominalTypeDecl())
. In the function, getSelfNominalTypeDecl
returns null, so this goes along the regular nominal-in-generic-context error path. That path gives nicer diagnostics, so I left it that way.
@swift-ci Please smoke test |
4f81f3d
to
853d3c8
Compare
Test failure is due to actor availability. Doesn't happen locally when I run the tests on Sonoma. I guess it happens because CI is using an ancient version of macOS?
Fixed by changing that actor be an enum. Added an additional test for nesting a protocol in an actor with an availability annotation. |
I believe it's about the default deployment target in the SDK, but I forget the details.
It is also fine to pass -disable-availability-checking in tests like this. |
@swift-ci Please smoke test |
Looks like this test fails on a case-sensitive file system. |
@slavapestov Oops. But yeah, the macOS filesystem seems to be case-insensitive by default, so that didn't happen locally. At least that should be the last thing, since the macOS test was okay. |
@swift-ci Please smoke test |
4b98f6c
to
f71d7b1
Compare
Hmm it seems I need to use the substitution For some reason that also wasn't necessary on macOS. |
@swift-ci Please smoke test |
@karwa Great job with the PR. I merged it since I don't think there is anything else outstanding here. Also, the proposal was accepted, so I think you can remove the experimental feature flag now. |
Also do you want to cherry-pick this to the release/5.10 branch? |
Thanks @slavapestov! And thanks for your guidance along the way! Yeah I'll cherry-pick this to the 5.10 branch :) |
Allows protocols to be nested in non-generic types/functions.
Gated behind flag
-enable-experimental-feature NestedProtocols
. SE proposal coming soon.TODO: