Skip to content

Commit 31edf71

Browse files
committed
[Sema] Improve diagnostics for attempt to throw an error *code*
Attempting to throw an error code value, e.g., throw CocoaError.fileNoSuchFileError is now ill-formed, although it was well-formed prior to the introduction of NSError bridging (SE-0112). Provide a specialized diagnostic with a Fix-It to add the appropriate parentheses: throw CocoaError(.fileNoSuchFileError) Fixes rdar://problem/27543121.
1 parent d120638 commit 31edf71

File tree

5 files changed

+120
-3
lines changed

5 files changed

+120
-3
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,10 @@ ERROR(cannot_convert_to_return_type_nil,none,
283283

284284
ERROR(cannot_convert_thrown_type,none,
285285
"thrown expression type %0 does not conform to 'Error'", (Type))
286+
ERROR(cannot_throw_error_code,none,
287+
"thrown error code type %0 does not conform to 'Error'; construct an %1 "
288+
"instance", (Type, Type))
289+
286290
ERROR(cannot_throw_nil,none,
287291
"cannot infer concrete Error for thrown 'nil' value", ())
288292

lib/Sema/CSDiag.cpp

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3677,7 +3677,7 @@ bool FailureDiagnosis::diagnoseContextualConversionError() {
36773677
diagIDProtocol = diag::cannot_convert_to_return_type_protocol;
36783678
nilDiag = diag::cannot_convert_to_return_type_nil;
36793679
break;
3680-
case CTP_ThrowStmt:
3680+
case CTP_ThrowStmt: {
36813681
if (isa<NilLiteralExpr>(expr->getValueProvidingExpr())) {
36823682
diagnose(expr->getLoc(), diag::cannot_throw_nil);
36833683
return true;
@@ -3686,14 +3686,42 @@ bool FailureDiagnosis::diagnoseContextualConversionError() {
36863686
if (isUnresolvedOrTypeVarType(exprType) ||
36873687
exprType->isEqual(contextualType))
36883688
return false;
3689-
3689+
3690+
// If we tried to throw the error code of an error type, suggest object
3691+
// construction.
3692+
auto &TC = CS->getTypeChecker();
3693+
if (auto errorCodeProtocol =
3694+
TC.Context.getProtocol(KnownProtocolKind::ErrorCodeProtocol)) {
3695+
ProtocolConformance *conformance = nullptr;
3696+
if (TC.conformsToProtocol(expr->getType(), errorCodeProtocol, CS->DC,
3697+
ConformanceCheckFlags::InExpression,
3698+
&conformance) &&
3699+
conformance) {
3700+
Type errorCodeType = expr->getType();
3701+
Type errorType =
3702+
ProtocolConformance::getTypeWitnessByName(errorCodeType, conformance,
3703+
TC.Context.Id_ErrorType,
3704+
&TC)->getCanonicalType();
3705+
if (errorType) {
3706+
auto diag = diagnose(expr->getLoc(), diag::cannot_throw_error_code,
3707+
errorCodeType, errorType);
3708+
if (auto unresolvedDot = dyn_cast<UnresolvedDotExpr>(expr)) {
3709+
diag.fixItInsert(unresolvedDot->getDotLoc(), "(");
3710+
diag.fixItInsertAfter(unresolvedDot->getEndLoc(), ")");
3711+
}
3712+
return true;
3713+
}
3714+
}
3715+
}
3716+
36903717
// The conversion destination of throw is always ErrorType (at the moment)
36913718
// if this ever expands, this should be a specific form like () is for
36923719
// return.
36933720
diagnose(expr->getLoc(), diag::cannot_convert_thrown_type, exprType)
36943721
.highlight(expr->getSourceRange());
36953722
return true;
3696-
3723+
}
3724+
36973725
case CTP_EnumCaseRawValue:
36983726
diagID = diag::cannot_convert_raw_initializer_value;
36993727
diagIDProtocol = diag::cannot_convert_raw_initializer_value;

test/Constraints/ErrorBridging.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,8 @@ extension Error {
6868
return self // expected-error{{cannot convert return expression of type 'Self' to return type 'NSError'}}
6969
}
7070
}
71+
72+
// rdar://problem/27543121
73+
func throwErrorCode() throws {
74+
throw FictionalServerError.meltedDown // expected-error{{thrown error code type 'FictionalServerError.Code' does not conform to 'Error'; construct an 'FictionalServerError' instance}}{{29-29=(}}{{40-40=)}}
75+
}

test/Inputs/clang-importer-sdk/swift-modules/Foundation.swift

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,3 +236,76 @@ func _convertNSErrorToError(_ string: NSError?) -> Error
236236

237237
@_silgen_name("swift_convertErrorToNSError")
238238
func _convertErrorToNSError(_ string: Error) -> NSError
239+
240+
/// An internal protocol to represent Swift error enums that map to standard
241+
/// Cocoa NSError domains.
242+
public protocol _ObjectiveCBridgeableError : Error {
243+
/// Produce a value of the error type corresponding to the given NSError,
244+
/// or return nil if it cannot be bridged.
245+
init?(_bridgedNSError: NSError)
246+
}
247+
248+
/// Describes a bridged error that stores the underlying NSError, so
249+
/// it can be queried.
250+
public protocol _BridgedStoredNSError : _ObjectiveCBridgeableError {
251+
/// The type of an error code.
252+
associatedtype Code: _ErrorCodeProtocol
253+
254+
/// The error code for the given error.
255+
var code: Code { get }
256+
257+
//// Retrieves the embedded NSError.
258+
var _nsError: NSError { get }
259+
260+
/// Create a new instance of the error type with the given embedded
261+
/// NSError.
262+
///
263+
/// The \c error must have the appropriate domain for this error
264+
/// type.
265+
init(_nsError error: NSError)
266+
}
267+
268+
public protocol _ErrorCodeProtocol {
269+
/// The corresponding error code.
270+
associatedtype _ErrorType
271+
}
272+
273+
public extension _BridgedStoredNSError {
274+
public init?(_bridgedNSError error: NSError) {
275+
self.init(_nsError: error)
276+
}
277+
}
278+
279+
/// Various helper implementations for _BridgedStoredNSError
280+
public extension _BridgedStoredNSError
281+
where Code: RawRepresentable, Code.RawValue: SignedInteger {
282+
// FIXME: Generalize to Integer.
283+
public var code: Code {
284+
return Code(rawValue: numericCast(_nsError.code))!
285+
}
286+
287+
/// Initialize an error within this domain with the given ``code``
288+
/// and ``userInfo``.
289+
public init(_ code: Code, userInfo: [String : Any] = [:]) {
290+
self.init(_nsError: NSError(domain: "", code: 0, userInfo: [:]))
291+
}
292+
293+
/// The user-info dictionary for an error that was bridged from
294+
/// NSError.
295+
var userInfo: [String : Any] { return [:] }
296+
}
297+
298+
/// Various helper implementations for _BridgedStoredNSError
299+
public extension _BridgedStoredNSError
300+
where Code: RawRepresentable, Code.RawValue: UnsignedInteger {
301+
// FIXME: Generalize to Integer.
302+
public var code: Code {
303+
return Code(rawValue: numericCast(_nsError.code))!
304+
}
305+
306+
/// Initialize an error within this domain with the given ``code``
307+
/// and ``userInfo``.
308+
public init(_ code: Code, userInfo: [String : Any] = [:]) {
309+
self.init(_nsError: NSError(domain: "", code: 0, userInfo: [:]))
310+
}
311+
}

test/Inputs/clang-importer-sdk/usr/include/Foundation.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,3 +1065,10 @@ static const NSClothingStyle NSClothingStyleOfficeCasual __attribute__((availabi
10651065
void acceptError(NSError * _Nonnull error);
10661066
NSError * _Nonnull produceError(void);
10671067
NSError * _Nullable produceOptionalError(void);
1068+
1069+
extern NSString * const FictionalServerErrorDomain;
1070+
1071+
typedef enum __attribute__((ns_error_domain(FictionalServerErrorDomain))) FictionalServerErrorCode : NSInteger {
1072+
FictionalServerErrorMeltedDown = 1
1073+
} FictionalServerErrorCode;
1074+

0 commit comments

Comments
 (0)