Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Sema] @objc functions shall not have typed throw #81054

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

DataCorrupted
Copy link
Contributor

@DataCorrupted DataCorrupted commented Apr 23, 2025

@nkcsgexi nkcsgexi requested a review from beccadax April 23, 2025 23:32
@drodriguez
Copy link
Contributor

@swift-ci please test

@drodriguez
Copy link
Contributor

@swift-ci Please Test Source Compatibility Release

Copy link
Contributor

@beccadax beccadax left a comment

Choose a reason for hiding this comment

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

Hi, @DataCorrupted. You've got the spirit right, but let's refine some of the details.

Comment on lines 2575 to 2576
if (AFD->getAttrs().hasAttribute<ObjCAttr>()) {
Context.Diags.diagnose(loc, diag::typed_thrown_in_objc_forbidden);
Copy link
Contributor

@beccadax beccadax Apr 24, 2025

Choose a reason for hiding this comment

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

You probably don't want to check this in InterfaceTypeRequest::evaluate()@objc can be inferred in many situations, and that inference won't take your new rule into account if you add it here.

To make sure this works with @objc inference, modify swift::isRepresentableInLanguage() (in TypeCheckDeclObjC.cpp) or one of its callees to make it say that a typed-throws method is not representable as an @objc declaration. This function should return false (and, depending on the ObjCReason it's passed, likely diagnose an error) if the function it's passed isn't allowed to be @objc.

For this specific task, look at the places where that function checks hasThrows() to find the places where you'll need to add logic. There will be two places you need to modify—one for just throws, another for async throws—so make sure you add this logic for both of them, and make sure you have test cases for both of them. (There will also be a couple of places where you don't need to change anything, though, so read the code around each call to figure out what's going on there.)

Also, take a look at how other diagnostics in this function use limitBehavior() and softenIfAccessNote() and try to do the same for your diagnostic. It's a little funky, so if you can't figure it out, feel free to ask for help!

Comment on lines 225 to 226
@objc class ObjCClass: NSObject {
@objc func objcTypedThrow() throws(ObjCError) -> () {}
Copy link
Contributor

@beccadax beccadax Apr 24, 2025

Choose a reason for hiding this comment

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

Some of Swift's platforms don't support @objc, so if you're going to test something with @objc, you'll need to do it in a test file that only runs if ObjC interop is supported.

For this change, I'll recommend adding your tests to test/attr/attr_objc.swift. There are already some tests of ordinary throws (search for ClassThrows1), so you should be able to add some typed throws tests somewhere around there.

(As a nice bonus, this file already has infrastructure in place to check that you've used softenIfAccessNote() correctly.)

Like I mentioned before, you should also test typed throws with async; test/attr/attr_objc_async.swift looks like a good place to do that.

Signed-off-by: Peter Rong <[email protected]>
Signed-off-by: Peter Rong <[email protected]>
@alx32
Copy link
Contributor

alx32 commented Apr 25, 2025

@swift-ci please test

1 similar comment
@drodriguez
Copy link
Contributor

@swift-ci please test

Copy link
Contributor

@beccadax beccadax left a comment

Choose a reason for hiding this comment

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

Great! The tests are perfect—just a few more little refinements to the implementation...

Comment on lines 814 to 815
if (!AFD->getThrownTypeRepr())
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

There's a subtle pitfall here: A TypeRepr is an exact representation of how a type is written in source code, so it's only available when the Swift compiler has directly parsed source code to create the declaration. If a declaration uses typed throws but it was deserialized from a .swiftmodule file or synthesized by the compiler, then getThrownTypeRepr() will return nullptr even though there is actually a typed throws.

The various methods that return the thrown type as a Type, rather than a TypeRepr *, don't have this problem, so you can just remove the getThrownTypeRepr() check—checking getThrownInterfaceType() alone will tell you everything you need to know.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am fairly new to swift, can you elaborate more on "If a declaration uses typed throws but it was deserialized from a .swiftmodule file or synthesized by the compiler", how to create either case, and how are they used in practice?

// Throwing `any MyError` that confronts `Error` is not implemented yet.
// Shall we allow `any MyError` in the future, we should check against
// `isExistentialType` instead.
if (thrownType->isErrorExistentialType())
Copy link
Contributor

Choose a reason for hiding this comment

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

Digging into the implementation underlying getThrownInterfaceType(), it turns out that if a function is non-throwing, it will return the type for Swift.Never. So if you wrote this condition:

Suggested change
if (thrownType->isErrorExistentialType())
if (thrownType->isNever() || thrownType->isErrorExistentialType())

You would not need to explicitly check hasThrows() before calling your isTypedThrow() function.

(If you don't do the isNever() check, you might want to remove the call to getCanonicalType() on line 817—TypeBase::isErrorExistentialType() implicitly canonicalizes the type, so you don't have to.)

Copy link
Contributor Author

@DataCorrupted DataCorrupted Apr 26, 2025

Choose a reason for hiding this comment

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

thrownType could be a null pointer if not getCanonicalType, even if AFD hasThrow. I'm not sure why.

I'd prefer keep the hasThrow check to make the intention more clear, it also made more sense to erase the closure that this point.

Reason.describe(AFD);
return true;
};
if (AFD->hasThrows() && isTypedThrow())
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, checking this before the sync and async code paths diverge is clever!

@@ -5461,6 +5461,9 @@ WARNING(no_throw_in_do_with_catch,none,
ERROR(thrown_type_not_error,none,
"thrown type %0 does not conform to the 'Error' protocol", (Type))

ERROR(typed_thrown_in_objc_forbidden,none,
"@objc functions cannot have typed throw", ())
Copy link
Contributor

Choose a reason for hiding this comment

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

I would probably word this more similarly to the other diagnostics emitted by isRepresentableInLanguage(), such as not_objc_function_async:

Suggested change
"@objc functions cannot have typed throw", ())
"typed 'throws' %kindonly0 cannot be represented in Objective-C",
(const AbstractFunctionDecl *))

Note that this will insert a string like "instance method" where %kindonly0 is written—you'll need to pass the decl into the diagnose() call, though.

(There are other ways you might word this—if you'd like, take a look around at a few other diagnostics in this method and see if there's something you like better.)

Signed-off-by: Peter Rong <[email protected]>
@NuriAmari
Copy link
Contributor

@swift-ci Please test

@DataCorrupted
Copy link
Contributor Author

@beccadax Hi Becca, do you think this PR is ready to land? Let me know if you wish me to change anything else.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[SILGenFunction] emitBridgeErrorForForeignError hits assertion error when an @objc function throws(ObjCError)
5 participants