Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -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.)


//------------------------------------------------------------------------------
// MARK: Concurrency
//------------------------------------------------------------------------------
Expand Down
12 changes: 6 additions & 6 deletions lib/Sema/TypeCheckDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2569,12 +2569,12 @@ InterfaceTypeRequest::evaluate(Evaluator &eval, ValueDecl *D) const {
ProtocolDecl *errorProto = Context.getErrorDecl();
if (thrownTy && !thrownTy->hasError() && errorProto) {
Type thrownTyInContext = AFD->mapTypeIntoContext(thrownTy);
if (!checkConformance(thrownTyInContext, errorProto)) {
SourceLoc loc;
if (auto thrownTypeRepr = AFD->getThrownTypeRepr())
loc = thrownTypeRepr->getLoc();
else
loc = AFD->getLoc();
auto thrownTypeRepr = AFD->getThrownTypeRepr();
SourceLoc loc =
(thrownTypeRepr) ? thrownTypeRepr->getLoc() : AFD->getLoc();
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!

} else if (!checkConformance(thrownTyInContext, errorProto)) {
Context.Diags.diagnose(loc, diag::thrown_type_not_error, thrownTy);
}
}
Expand Down
10 changes: 10 additions & 0 deletions test/decl/func/typed_throws.swift
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,13 @@ struct NotAnError<T> {}

func badThrowingFunctionType<T>(_: () throws(NotAnError<T>) -> ()) {}
// expected-error@-1 {{thrown type 'NotAnError<T>' does not conform to the 'Error' protocol}}

enum ObjCError: Int, Error {
case Others
}

import Foundation
@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.

// expected-error@-1 {{@objc functions cannot have typed throw}}
}