Skip to content

Conversation

@SharonXSharon
Copy link

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.

Diagnose on ObjC protocols which have the non_runtime_protocol attribute, when their metadata is actually required.
@drodriguez
Copy link
Contributor

@swift-ci please test

@drodriguez
Copy link
Contributor

@swift-ci please test macOS platform

Diagnose on ObjC protocols which have the non_runtime_protocol attribute, when their metadata is actually required.
@drodriguez
Copy link
Contributor

@swift-ci please test

@SharonXSharon
Copy link
Author

@rjmccall could you please take a look? Thanks!

auto *proto = dyn_cast<clang::ObjCProtocolDecl>(ClangD);
if (!proto || !proto->isNonRuntimeProtocol())
return;
auto *clangImporter = static_cast<ClangImporter *>(D->getASTContext().getClangModuleLoader());
Copy link
Contributor

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,
Copy link
Contributor

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?

Copy link
Author

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?

Copy link
Contributor

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.

Comment on lines 6761 to 6762
"this Objective-C protocol '%0' has the non_runtime_protocol attribute"
" and its metadata is not available in Swift.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Diagnostic style consistency suggestion:

Suggested change
"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.}}
Copy link
Contributor

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?

Copy link
Contributor

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
Copy link
Contributor

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?

Comment on lines 9 to 17
class AppDelegate {
@inline(never) func FOOBARwrapper() {
// CustomObject is defined in ObjC.
let instanceOfCustomObject = CustomObject()
instanceOfCustomObject.someProperty = "Hello World"
print(instanceOfCustomObject.someProperty)
instanceOfCustomObject.someMethod()
}
}
Copy link
Contributor

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"
Copy link
Contributor

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?

Diags.diagnose(Loc, diag::non_runtime_objc_protocol_metadata_not_available,
D->getNameStr());
Diags
.diagnose(clangImporter->importSourceLocation(proto->getLocation()),
Copy link
Contributor

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.

if (Where.mustOnlyReferenceExportedDecls())
TypeChecker::diagnoseDeclRefExportability(Loc, decl, Where);

diagnoseNonRuntimeProtocol(Loc, decl);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rjmccall

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.

Copy link
Contributor

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.

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?

Copy link
Contributor

@rjmccall rjmccall Dec 20, 2024

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.

if (Where.mustOnlyReferenceExportedDecls())
TypeChecker::diagnoseDeclRefExportability(Loc, decl, Where);

diagnoseNonRuntimeProtocol(Loc, decl);
Copy link
Contributor

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))
Copy link
Author

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

Copy link
Author

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

6 participants