Skip to content

Commit a87369b

Browse files
authored
Merge pull request #30763 from jckarter/protocol-infer-objc-error-convention
Sema: Infer error convention from @objc protocol requirements.
2 parents 9878212 + 90062f1 commit a87369b

File tree

7 files changed

+120
-4
lines changed

7 files changed

+120
-4
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4342,6 +4342,13 @@ NOTE(objc_ambiguous_inference_candidate,none,
43424342
"%0 (in protocol %1) provides Objective-C name %2",
43434343
(DeclName, DeclName, ObjCSelector))
43444344

4345+
ERROR(objc_ambiguous_error_convention,none,
4346+
"%0 overrides or implements protocol requirements for Objective-C "
4347+
"declarations with incompatible error argument conventions",
4348+
(DeclName))
4349+
NOTE(objc_ambiguous_error_convention_candidate,none,
4350+
"%0 provides an error argument here", (DeclName))
4351+
43454352
ERROR(nonlocal_bridged_to_objc,none,
43464353
"conformance of %0 to %1 can only be written in module %2",
43474354
(Identifier, Identifier, Identifier))

include/swift/AST/ForeignErrorConvention.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,16 @@ class ForeignErrorConvention {
182182
getKind() == NonZeroResult);
183183
return ResultType;
184184
}
185+
186+
bool operator==(ForeignErrorConvention other) const {
187+
return info.TheKind == other.info.TheKind
188+
&& info.ErrorIsOwned == other.info.ErrorIsOwned
189+
&& info.ErrorParameterIsReplaced == other.info.ErrorParameterIsReplaced
190+
&& info.ErrorParameterIndex == other.info.ErrorParameterIndex;
191+
}
192+
bool operator!=(ForeignErrorConvention other) const {
193+
return !(*this == other);
194+
}
185195
};
186196

187197
}

lib/Sema/TypeCheckDeclObjC.cpp

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1562,6 +1562,8 @@ void markAsObjC(ValueDecl *D, ObjCReason reason,
15621562

15631563
if (auto method = dyn_cast<AbstractFunctionDecl>(D)) {
15641564
// Determine the foreign error convention.
1565+
Optional<ForeignErrorConvention> inheritedConvention;
1566+
AbstractFunctionDecl *declProvidingInheritedConvention = nullptr;
15651567
if (auto baseMethod = method->getOverriddenDecl()) {
15661568
// If the overridden method has a foreign error convention,
15671569
// adopt it. Set the foreign error convention for a throwing
@@ -1570,14 +1572,44 @@ void markAsObjC(ValueDecl *D, ObjCReason reason,
15701572
if (method->hasThrows()) {
15711573
if (auto baseErrorConvention
15721574
= baseMethod->getForeignErrorConvention()) {
1573-
errorConvention = baseErrorConvention;
1575+
inheritedConvention = baseErrorConvention;
1576+
declProvidingInheritedConvention = baseMethod;
15741577
}
1578+
}
1579+
}
1580+
1581+
for (auto req : findWitnessedObjCRequirements(method)) {
1582+
auto reqMethod = dyn_cast<AbstractFunctionDecl>(req);
1583+
if (!reqMethod) continue;
1584+
1585+
// If the method witnesses an ObjC requirement that throws, adopt its
1586+
// error convention.
1587+
if (reqMethod->hasThrows()) {
1588+
if (auto reqErrorConvention = reqMethod->getForeignErrorConvention()) {
1589+
// Check for a conflict among protocol conformances or inherited
1590+
// methods.
1591+
if (declProvidingInheritedConvention
1592+
&& inheritedConvention != reqErrorConvention) {
1593+
method->diagnose(diag::objc_ambiguous_error_convention,
1594+
method->getFullName());
1595+
declProvidingInheritedConvention->diagnose(
1596+
diag::objc_ambiguous_error_convention_candidate,
1597+
declProvidingInheritedConvention->getFullName());
1598+
reqMethod->diagnose(diag::objc_ambiguous_error_convention_candidate,
1599+
reqMethod->getFullName());
1600+
break;
1601+
}
15751602

1576-
assert(errorConvention && "Missing error convention");
1577-
method->setForeignErrorConvention(*errorConvention);
1603+
inheritedConvention = reqErrorConvention;
1604+
declProvidingInheritedConvention = reqMethod;
1605+
}
15781606
}
1607+
}
1608+
1609+
// Attach the foreign error convention.
1610+
if (inheritedConvention) {
1611+
method->setForeignErrorConvention(*inheritedConvention);
15791612
} else if (method->hasThrows()) {
1580-
// Attach the foreign error convention.
15811613
assert(errorConvention && "Missing error convention");
15821614
method->setForeignErrorConvention(*errorConvention);
15831615
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
@import Foundation;
2+
3+
@protocol Caller1
4+
@required
5+
- (BOOL)call:(void (^_Nonnull)(void))callback error:(NSError**)error;
6+
@end;
7+
8+
@protocol CallerA
9+
@required
10+
- (BOOL)use:(NSInteger)x thenCall:(void (^_Nonnull)(void))callback error:(NSError**)error;
11+
@end;
12+
13+
@protocol CallerB
14+
@required
15+
- (BOOL)use:(NSInteger)x error:(NSError**)error thenCall:(void (^_Nonnull)(void))callback;
16+
@end;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-silgen %s -import-objc-header %S/Inputs/objc_error_convention_from_protocol.h | %FileCheck %s
2+
// REQUIRES: objc_interop
3+
4+
import Foundation
5+
6+
class Caller1Impl: Caller1 {
7+
// declared as - (BOOL)call:(void (^_Nonnull)(void))callback error:(NSError**)error;
8+
// CHECK-LABEL: sil {{.*}} @$s{{.*}}11Caller1Impl{{.*}}call{{.*}}To :
9+
// CHECK-SAME: $@convention(objc_method) (@convention(block) () -> (), Optional<AutoreleasingUnsafeMutablePointer<Optional<NSError>>>, Caller1Impl) -> ObjCBool
10+
func call(_ callback: @escaping () -> ()) throws { }
11+
}
12+
13+
class CallerAImpl: CallerA {
14+
// declared as - (BOOL)use:(NSInteger)x thenCall:(void (^_Nonnull)(void))callback error:(NSError**)error;
15+
// CHECK-LABEL: sil {{.*}} @$s{{.*}}11CallerAImpl{{.*}}use{{.*}}thenCall{{.*}}To :
16+
// CHECK-SAME: $@convention(objc_method) (Int, @convention(block) () -> (), Optional<AutoreleasingUnsafeMutablePointer<Optional<NSError>>>, CallerAImpl) -> ObjCBool
17+
func use(_ x: Int, thenCall callback: @escaping () -> ()) throws { }
18+
}
19+
20+
class CallerBImpl: CallerB {
21+
// declared as - (BOOL)use:(NSInteger)x error:(NSError**)error thenCall:(void (^_Nonnull)(void))callback;
22+
// CHECK-LABEL: sil {{.*}} @$s{{.*}}11CallerBImpl{{.*}}use{{.*}}thenCall{{.*}}To :
23+
// CHECK-SAME: $@convention(objc_method) (Int, Optional<AutoreleasingUnsafeMutablePointer<Optional<NSError>>>, @convention(block) () -> (), CallerBImpl) -> ObjCBool
24+
func use(_ x: Int, thenCall callback: @escaping () -> ()) throws { }
25+
}
26+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@import Foundation;
2+
3+
@protocol CallerX
4+
@required
5+
- (BOOL)use:(NSInteger)x error:(void (^_Nonnull)(void))callback error:(NSError*_Nullable*_Nullable)error;
6+
@end;
7+
@protocol CallerY
8+
@required
9+
- (BOOL)use:(NSInteger)x error:(NSError*_Nullable*_Nullable)error error:(void (^_Nonnull)(void))callback;
10+
@end;
11+
12+
@interface CallerBaseY
13+
- (BOOL)use:(NSInteger)x error:(NSError*_Nullable*_Nullable)error error:(void (^_Nonnull)(void))callback;
14+
@end
15+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -verify %s -import-objc-header %S/Inputs/objc_error_convention_conflict.h
2+
// REQUIRES: objc_interop
3+
4+
class CallerXYImpl: CallerX, CallerY {
5+
func use(_ x: Int, error callback: @escaping () -> ()) throws { } // expected-error {{incompatible error argument conventions}}
6+
}
7+
8+
class CallerXDerivedY: CallerBaseY, CallerX {
9+
override func use(_ x: Int, error callback: @escaping () -> ()) throws { } // expected-error {{incompatible error argument conventions}}
10+
}

0 commit comments

Comments
 (0)