Skip to content

Commit 90062f1

Browse files
committed
Sema: Infer error convention from @objc protocol requirements.
When a Swift declaration witnesses an ObjC protocol requirement, its error convention needs to match the requirement. Furthermore, if there are different protocol requirements that the Swift method can witness, with different error conventions, we need to bail out because we can't simultaneously match all of them. Fixes rdar://problem/59496036 | SR-12201.
1 parent 56695a1 commit 90062f1

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
@@ -4326,6 +4326,13 @@ NOTE(objc_ambiguous_inference_candidate,none,
43264326
"%0 (in protocol %1) provides Objective-C name %2",
43274327
(DeclName, DeclName, ObjCSelector))
43284328

4329+
ERROR(objc_ambiguous_error_convention,none,
4330+
"%0 overrides or implements protocol requirements for Objective-C "
4331+
"declarations with incompatible error argument conventions",
4332+
(DeclName))
4333+
NOTE(objc_ambiguous_error_convention_candidate,none,
4334+
"%0 provides an error argument here", (DeclName))
4335+
43294336
ERROR(nonlocal_bridged_to_objc,none,
43304337
"conformance of %0 to %1 can only be written in module %2",
43314338
(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)