Skip to content

[Sema] Improve diagnostics for attempt to throw an error *code* #3940

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

Merged
merged 1 commit into from
Aug 2, 2016
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,10 @@ ERROR(cannot_convert_to_return_type_nil,none,

ERROR(cannot_convert_thrown_type,none,
"thrown expression type %0 does not conform to 'Error'", (Type))
ERROR(cannot_throw_error_code,none,
"thrown error code type %0 does not conform to 'Error'; construct an %1 "
"instance", (Type, Type))

ERROR(cannot_throw_nil,none,
"cannot infer concrete Error for thrown 'nil' value", ())

Expand Down
34 changes: 31 additions & 3 deletions lib/Sema/CSDiag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3677,7 +3677,7 @@ bool FailureDiagnosis::diagnoseContextualConversionError() {
diagIDProtocol = diag::cannot_convert_to_return_type_protocol;
nilDiag = diag::cannot_convert_to_return_type_nil;
break;
case CTP_ThrowStmt:
case CTP_ThrowStmt: {
if (isa<NilLiteralExpr>(expr->getValueProvidingExpr())) {
diagnose(expr->getLoc(), diag::cannot_throw_nil);
return true;
Expand All @@ -3686,14 +3686,42 @@ bool FailureDiagnosis::diagnoseContextualConversionError() {
if (isUnresolvedOrTypeVarType(exprType) ||
exprType->isEqual(contextualType))
return false;


// If we tried to throw the error code of an error type, suggest object
// construction.
auto &TC = CS->getTypeChecker();
if (auto errorCodeProtocol =
TC.Context.getProtocol(KnownProtocolKind::ErrorCodeProtocol)) {
ProtocolConformance *conformance = nullptr;
if (TC.conformsToProtocol(expr->getType(), errorCodeProtocol, CS->DC,
ConformanceCheckFlags::InExpression,
&conformance) &&
conformance) {
Type errorCodeType = expr->getType();
Type errorType =
ProtocolConformance::getTypeWitnessByName(errorCodeType, conformance,
TC.Context.Id_ErrorType,
&TC)->getCanonicalType();
if (errorType) {
auto diag = diagnose(expr->getLoc(), diag::cannot_throw_error_code,
errorCodeType, errorType);
if (auto unresolvedDot = dyn_cast<UnresolvedDotExpr>(expr)) {
diag.fixItInsert(unresolvedDot->getDotLoc(), "(");
diag.fixItInsertAfter(unresolvedDot->getEndLoc(), ")");
}
return true;
}
}
}

// The conversion destination of throw is always ErrorType (at the moment)
// if this ever expands, this should be a specific form like () is for
// return.
diagnose(expr->getLoc(), diag::cannot_convert_thrown_type, exprType)
.highlight(expr->getSourceRange());
return true;

}

case CTP_EnumCaseRawValue:
diagID = diag::cannot_convert_raw_initializer_value;
diagIDProtocol = diag::cannot_convert_raw_initializer_value;
Expand Down
5 changes: 5 additions & 0 deletions test/Constraints/ErrorBridging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,8 @@ extension Error {
return self // expected-error{{cannot convert return expression of type 'Self' to return type 'NSError'}}
}
}

// rdar://problem/27543121
func throwErrorCode() throws {
throw FictionalServerError.meltedDown // expected-error{{thrown error code type 'FictionalServerError.Code' does not conform to 'Error'; construct an 'FictionalServerError' instance}}{{29-29=(}}{{40-40=)}}
}
73 changes: 73 additions & 0 deletions test/Inputs/clang-importer-sdk/swift-modules/Foundation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,76 @@ func _convertNSErrorToError(_ string: NSError?) -> Error

@_silgen_name("swift_convertErrorToNSError")
func _convertErrorToNSError(_ string: Error) -> NSError

/// An internal protocol to represent Swift error enums that map to standard
/// Cocoa NSError domains.
public protocol _ObjectiveCBridgeableError : Error {
/// Produce a value of the error type corresponding to the given NSError,
/// or return nil if it cannot be bridged.
init?(_bridgedNSError: NSError)
}

/// Describes a bridged error that stores the underlying NSError, so
/// it can be queried.
public protocol _BridgedStoredNSError : _ObjectiveCBridgeableError {
/// The type of an error code.
associatedtype Code: _ErrorCodeProtocol

/// The error code for the given error.
var code: Code { get }

//// Retrieves the embedded NSError.
var _nsError: NSError { get }

/// Create a new instance of the error type with the given embedded
/// NSError.
///
/// The \c error must have the appropriate domain for this error
/// type.
init(_nsError error: NSError)
}

public protocol _ErrorCodeProtocol {
/// The corresponding error code.
associatedtype _ErrorType
}

public extension _BridgedStoredNSError {
public init?(_bridgedNSError error: NSError) {
self.init(_nsError: error)
}
}

/// Various helper implementations for _BridgedStoredNSError
public extension _BridgedStoredNSError
where Code: RawRepresentable, Code.RawValue: SignedInteger {
// FIXME: Generalize to Integer.
public var code: Code {
return Code(rawValue: numericCast(_nsError.code))!
}

/// Initialize an error within this domain with the given ``code``
/// and ``userInfo``.
public init(_ code: Code, userInfo: [String : Any] = [:]) {
self.init(_nsError: NSError(domain: "", code: 0, userInfo: [:]))
}

/// The user-info dictionary for an error that was bridged from
/// NSError.
var userInfo: [String : Any] { return [:] }
}

/// Various helper implementations for _BridgedStoredNSError
public extension _BridgedStoredNSError
where Code: RawRepresentable, Code.RawValue: UnsignedInteger {
// FIXME: Generalize to Integer.
public var code: Code {
return Code(rawValue: numericCast(_nsError.code))!
}

/// Initialize an error within this domain with the given ``code``
/// and ``userInfo``.
public init(_ code: Code, userInfo: [String : Any] = [:]) {
self.init(_nsError: NSError(domain: "", code: 0, userInfo: [:]))
}
}
7 changes: 7 additions & 0 deletions test/Inputs/clang-importer-sdk/usr/include/Foundation.h
Original file line number Diff line number Diff line change
Expand Up @@ -1065,3 +1065,10 @@ static const NSClothingStyle NSClothingStyleOfficeCasual __attribute__((availabi
void acceptError(NSError * _Nonnull error);
NSError * _Nonnull produceError(void);
NSError * _Nullable produceOptionalError(void);

extern NSString * const FictionalServerErrorDomain;

typedef enum __attribute__((ns_error_domain(FictionalServerErrorDomain))) FictionalServerErrorCode : NSInteger {
FictionalServerErrorMeltedDown = 1
} FictionalServerErrorCode;