- Notifications
You must be signed in to change notification settings - Fork 10.6k
Diagnose non_runtime_protocol #77339
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
base: main
Are you sure you want to change the base?
Conversation
Diagnose on ObjC protocols which have the non_runtime_protocol attribute, when their metadata is actually required.
| @swift-ci please test |
| @swift-ci please test macOS platform |
Diagnose on ObjC protocols which have the non_runtime_protocol attribute, when their metadata is actually required.
| @swift-ci please test |
| @rjmccall could you please take a look? Thanks! |
lib/Sema/TypeCheckAvailability.cpp Outdated
| auto *proto = dyn_cast<clang::ObjCProtocolDecl>(ClangD); | ||
| if (!proto || !proto->isNonRuntimeProtocol()) | ||
| return; | ||
| auto *clangImporter = static_cast<ClangImporter *>(D->getASTContext().getClangModuleLoader()); |
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.
There are a few formatting issues in this function, please run clang-format
| " and its metadata is not available in Swift.", | ||
| (StringRef)) | ||
| | ||
| NOTE(non_runtime_objc_protocol_metadata_not_available_note,none, |
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.
Can this new diagnostic string be avoided by reusing decl_declared_here instead?
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.
Just checked , the decl_declared_here family are all using swift types, e.g. ValueDecl DeclName, https://github.com/swiftlang/swift/blob/main/include/swift/AST/DiagnosticsSema.def#L30
not clang decl types, like const clang::NamedDecl*, do you suggest I add a new decl_declared_here with the clang type, or just keep this non_runtime_objc_protocol_metadata_not_available_note?
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.
Yes, I think it would be worth introducing a generic diagnostic string that's compatible with this use case, rather than creating a diagnostic with a very specific name but very generic text.
| "this Objective-C protocol '%0' has the non_runtime_protocol attribute" | ||
| " and its metadata is not available in Swift.", |
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.
Diagnostic style consistency suggestion:
| "this Objective-C protocol '%0' has the non_runtime_protocol attribute" | |
| " and its metadata is not available in Swift.", | |
| "Objective-C protocol '%0' has the non_runtime_protocol attribute" | |
| " and its metadata is not available in Swift", |
| } | ||
| | ||
| @objc class Wrapper : NSObject { | ||
| @objc public func runMutations(_ mutations: [ComposerMutationBridging]) { // expected-error {{this Objective-C protocol 'ComposerMutationBridging' has the non_runtime_protocol attribute and its metadata is not available in Swift.}} |
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.
Could you add more test cases covering additional positions that the protocol type could appear in?
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 test cases that we don't diagnose in places where we wouldn't need runtime support for it, like just as the type of a local variable.
| // REQUIRES: objc_interop | ||
| // REQUIRES: foundation | ||
| | ||
| // RUN: %target-swift-frontend -enable-objc-interop -typecheck -verify -import-objc-header %S/Inputs/availability_non_runtime_protocol_objc_protocol.h %s -emit-objc-header -emit-objc-header-path %t/SwiftCallObjC-Swift.h |
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.
Why does this test use -emit-objc-header?
| class AppDelegate { | ||
| @inline(never) func FOOBARwrapper() { | ||
| // CustomObject is defined in ObjC. | ||
| let instanceOfCustomObject = CustomObject() | ||
| instanceOfCustomObject.someProperty = "Hello World" | ||
| print(instanceOfCustomObject.someProperty) | ||
| instanceOfCustomObject.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.
This code looks like it might be superfluous to what is being tested in the Swift compiler here
| @@ -0,0 +1,23 @@ | |||
| #import "availability_non_runtime_protocol_objc_protocol.h" | |||
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.
Why is this .m file included in the test inputs?
lib/Sema/TypeCheckAvailability.cpp Outdated
| Diags.diagnose(Loc, diag::non_runtime_objc_protocol_metadata_not_available, | ||
| D->getNameStr()); | ||
| Diags | ||
| .diagnose(clangImporter->importSourceLocation(proto->getLocation()), |
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.
Do we need to go to the ClangDecl from D? I wonder if we actually construct the Swift declaration with the correct ObjC header locations. If that is not the case, I wonder if we should fix that instead of going through the ClangDecl here.
lib/Sema/TypeCheckAvailability.cpp Outdated
| if (Where.mustOnlyReferenceExportedDecls()) | ||
| TypeChecker::diagnoseDeclRefExportability(Loc, decl, Where); | ||
| | ||
| diagnoseNonRuntimeProtocol(Loc, decl); |
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 test cases that we don't diagnose in places where we wouldn't need runtime support for it, like just as the type of a local variable.
I tried to add some test case as follows
public func foo(){ let _ : ObjCNonRuntimeProtocolUsedinSwift } and found out it will also throw the newly added diagnostics for the test case, is my test case not correct, or is this not actually the right place to add the diagnostic?
When debugging, I found the above code would call visitNominalType which will call this visitTypeDecl and trigger the 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.
Hmm, no, you want to trigger this from checkTypeMetadataAvailabilityInternal, I think. This place is much broader and will catch every use of the type.
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.
@rjmccall IIUC any use of a non-runtime protocol internal to a module should be okay (if/because) the compiler can flatten the substitution map into the concrete type no? Would we be able to lower other use of NRP to AnyObject after type-checking to use the objc runtime instead of Swift's protocol witnesses?
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.
ObjC protocols (NRP or not) always use the ObjC runtime, so yes, any uses as a value should be fine. That's not "any use ... internal to a module", though; it would not be correct to erase NRP from type uses of the protocol, and Swift does not reliably specialize generic code in ways that would eliminate all uses of the metadata. If you need an Array<any NRP>, you can (and must) wrap the any NRP in a struct so that the metadata of the NRP itself is not required.
lib/Sema/TypeCheckAvailability.cpp Outdated
| if (Where.mustOnlyReferenceExportedDecls()) | ||
| TypeChecker::diagnoseDeclRefExportability(Loc, decl, Where); | ||
| | ||
| diagnoseNonRuntimeProtocol(Loc, decl); |
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.
Hmm, no, you want to trigger this from checkTypeMetadataAvailabilityInternal, I think. This place is much broader and will catch every use of the type.
| | ||
| public func iterate(collection mutations: some Collection<any ObjCNonRuntimeProtocolUsedinSwift>) { | ||
| for m in mutations { | ||
| print(typeOf(anyNRP: m)) |
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.
@rjmccall I moved the check to checkTypeMetadataAvailabilityInternal, but I am still a bit confused with what scenarios we should throw the diagnostics, e.g. is it expected we don't need metadata for this call at line 26, while we need metadata for the next call in line 27? Or are we missing this case in checkTypeMetadataAvailabilityInternal
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.
Hi @rjmccall just checking in that do you have any suggestions how should I proceed with this patch?
Diagnose on usage of ObjC protocols when their metadata are required, but the protocol actually have the non_runtime_protocol attribute, which removes the metadata on the ObjC side.
The patch is based on the discussion with John McCall and Manman Ren.