diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 44c576153442b..b023104a13a4b 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -615,6 +615,9 @@ ERROR(throws_functiontype_mismatch,none, "invalid conversion from throwing function of type %0 to " "non-throwing function type %1", (Type, Type)) +ERROR(thrown_error_type_mismatch,none, + "invalid conversion of thrown error type %0 to %1", (Type, Type)) + ERROR(async_functiontype_mismatch,none, "invalid conversion from 'async' function of type %0 to " "synchronous function type %1", (Type, Type)) diff --git a/include/swift/Sema/CSFix.h b/include/swift/Sema/CSFix.h index 0bdd3235e7d19..fe0fe0f746e4f 100644 --- a/include/swift/Sema/CSFix.h +++ b/include/swift/Sema/CSFix.h @@ -375,6 +375,9 @@ enum class FixKind : uint8_t { /// `throws` attribute from the source function. DropThrowsAttribute, + /// Ignore a mismatch in the thrown error type. + IgnoreThrownErrorMismatch, + /// Fix conversion from async to sync function by removing explicit /// `async` attribute from the source function. DropAsyncAttribute, @@ -1005,7 +1008,6 @@ class DropThrowsAttribute final : public ContextualMismatch { FunctionType *toType, ConstraintLocator *locator) : ContextualMismatch(cs, FixKind::DropThrowsAttribute, fromType, toType, locator) { - assert(fromType->isThrowing() != toType->isThrowing()); } public: @@ -1023,6 +1025,30 @@ class DropThrowsAttribute final : public ContextualMismatch { } }; +/// This is a contextual mismatch between the thrown error types of two +/// function types, which could be repaired by fixing one of the types. +class IgnoreThrownErrorMismatch final : public ContextualMismatch { + IgnoreThrownErrorMismatch(ConstraintSystem &cs, Type fromErrorType, + Type toErrorType, ConstraintLocator *locator) + : ContextualMismatch(cs, FixKind::IgnoreThrownErrorMismatch, + fromErrorType, toErrorType, locator) { + assert(!fromErrorType->isEqual(toErrorType)); + } + +public: + std::string getName() const override { return "drop 'throws' attribute"; } + + bool diagnose(const Solution &solution, bool asNote = false) const override; + + static IgnoreThrownErrorMismatch *create(ConstraintSystem &cs, + Type fromErrorType, + Type toErrorType, + ConstraintLocator *locator); + + static bool classof(const ConstraintFix *fix) { + return fix->getKind() == FixKind::IgnoreThrownErrorMismatch; + } +}; /// This is a contextual mismatch between async and non-async /// function types, repair it by dropping `async` attribute. class DropAsyncAttribute final : public ContextualMismatch { diff --git a/include/swift/Sema/ConstraintLocatorPathElts.def b/include/swift/Sema/ConstraintLocatorPathElts.def index feee1045599b6..3b20a03b315e1 100644 --- a/include/swift/Sema/ConstraintLocatorPathElts.def +++ b/include/swift/Sema/ConstraintLocatorPathElts.def @@ -275,6 +275,9 @@ ABSTRACT_LOCATOR_PATH_ELT(PatternDecl) /// A function type global actor. SIMPLE_LOCATOR_PATH_ELT(GlobalActorType) +/// The thrown error of a function type. +SIMPLE_LOCATOR_PATH_ELT(ThrownErrorType) + /// A type coercion operand. SIMPLE_LOCATOR_PATH_ELT(CoercionOperand) diff --git a/lib/AST/TypeWalker.cpp b/lib/AST/TypeWalker.cpp index f38dd0847756e..db7925febafa3 100644 --- a/lib/AST/TypeWalker.cpp +++ b/lib/AST/TypeWalker.cpp @@ -115,6 +115,11 @@ class Traversal : public TypeVisitor return true; } + if (Type thrownError = ty->getThrownError()) { + if (doIt(thrownError)) + return true; + } + return doIt(ty->getResult()); } diff --git a/lib/Sema/CSDiagnostics.cpp b/lib/Sema/CSDiagnostics.cpp index 7688c3a4e320b..e9292a745939e 100644 --- a/lib/Sema/CSDiagnostics.cpp +++ b/lib/Sema/CSDiagnostics.cpp @@ -7096,6 +7096,12 @@ bool ThrowingFunctionConversionFailure::diagnoseAsError() { return true; } +bool ThrownErrorTypeConversionFailure::diagnoseAsError() { + emitDiagnostic(diag::thrown_error_type_mismatch, getFromType(), + getToType()); + return true; +} + bool AsyncFunctionConversionFailure::diagnoseAsError() { auto *locator = getLocator(); diff --git a/lib/Sema/CSDiagnostics.h b/lib/Sema/CSDiagnostics.h index b18d8da22be4c..a8f35c548f39c 100644 --- a/lib/Sema/CSDiagnostics.h +++ b/lib/Sema/CSDiagnostics.h @@ -933,6 +933,24 @@ class ThrowingFunctionConversionFailure final : public ContextualFailure { bool diagnoseAsError() override; }; +/// Diagnose failures related to conversion between the thrown error type +/// of two function types, e.g., +/// +/// ```swift +/// func foo(_ t: T) throws(MyError) -> Void {} +/// let _: (Int) throws (OtherError)-> Void = foo +/// // `MyError` can't be implicitly converted to `OtherError` +/// ``` +class ThrownErrorTypeConversionFailure final : public ContextualFailure { +public: + ThrownErrorTypeConversionFailure(const Solution &solution, Type fromType, + Type toType, ConstraintLocator *locator) + : ContextualFailure(solution, fromType, toType, locator) { + } + + bool diagnoseAsError() override; +}; + /// Diagnose failures related to conversion between 'async' function type /// and a synchronous one e.g. /// diff --git a/lib/Sema/CSFix.cpp b/lib/Sema/CSFix.cpp index 8b8dbf87d2e93..349e12008f929 100644 --- a/lib/Sema/CSFix.cpp +++ b/lib/Sema/CSFix.cpp @@ -1627,6 +1627,21 @@ DropThrowsAttribute *DropThrowsAttribute::create(ConstraintSystem &cs, DropThrowsAttribute(cs, fromType, toType, locator); } +bool IgnoreThrownErrorMismatch::diagnose(const Solution &solution, + bool asNote) const { + ThrownErrorTypeConversionFailure failure(solution, getFromType(), + getToType(), getLocator()); + return failure.diagnose(asNote); +} + +IgnoreThrownErrorMismatch *IgnoreThrownErrorMismatch::create(ConstraintSystem &cs, + Type fromErrorType, + Type toErrorType, + ConstraintLocator *locator) { + return new (cs.getAllocator()) + IgnoreThrownErrorMismatch(cs, fromErrorType, toErrorType, locator); +} + bool DropAsyncAttribute::diagnose(const Solution &solution, bool asNote) const { AsyncFunctionConversionFailure failure(solution, getFromType(), diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp index 2aee184de84f1..b3b0aba0b623f 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -2453,7 +2453,6 @@ namespace { auto resultLocator = CS.getConstraintLocator(closure, ConstraintLocator::ClosureResult); - // FIXME: Need a better locator. auto thrownErrorLocator = CS.getConstraintLocator(closure, ConstraintLocator::ClosureThrownError); diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index b43db997d1e36..42228f60a6f46 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -2945,23 +2945,139 @@ bool ConstraintSystem::hasPreconcurrencyCallee( return calleeOverload->choice.getDecl()->preconcurrency(); } +namespace { + /// Classifies a thrown error kind as Never, a specific type, or 'any Error'. + enum class ThrownErrorKind { + Never, + Specific, + AnyError, + }; + + ThrownErrorKind getThrownErrorKind(Type type) { + if (type->isNever()) + return ThrownErrorKind::Never; + + if (type->isExistentialType()) { + Type anyError = type->getASTContext().getErrorExistentialType(); + if (anyError->isEqual(type)) + return ThrownErrorKind::AnyError; + } + + return ThrownErrorKind::Specific; + } +} + +/// Match the throwing specifier of the two function types. +static ConstraintSystem::TypeMatchResult +matchFunctionThrowing(ConstraintSystem &cs, + FunctionType *func1, FunctionType *func2, + ConstraintKind kind, + ConstraintSystem::TypeMatchOptions flags, + ConstraintLocatorBuilder locator) { + // A function type that throws the error type E1 is a subtype of a function + // that throws error type E2 when E1 is a subtype of E2. For the purpose + // of this comparison, a non-throwing function has thrown error type 'Never', + // and an untyped throwing function has thrown error type 'any Error'. + Type neverType = cs.getASTContext().getNeverType(); + Type thrownError1 = func1->getEffectiveThrownInterfaceType().value_or(neverType); + Type thrownError2 = func2->getEffectiveThrownInterfaceType().value_or(neverType); + if (!thrownError1 || !thrownError2 || thrownError1->isEqual(thrownError2)) + return cs.getTypeMatchSuccess(); + + auto thrownErrorKind1 = getThrownErrorKind(thrownError1); + auto thrownErrorKind2 = getThrownErrorKind(thrownError2); + + bool mustUnify = false; + bool dropThrows = false; + + switch (thrownErrorKind1) { + case ThrownErrorKind::Specific: + // If the specific thrown error contains no type variables and we're + // going to try to convert it to \c Never, treat this as dropping throws. + if (thrownErrorKind2 == ThrownErrorKind::Never && + !thrownError1->hasTypeVariable()) { + dropThrows = true; + } else { + // We need to unify the thrown error types. + mustUnify = true; + } + break; + + case ThrownErrorKind::Never: + switch (thrownErrorKind2) { + case ThrownErrorKind::Specific: + // We need to unify the thrown error types. + mustUnify = true; + break; + + case ThrownErrorKind::Never: + llvm_unreachable("The thrown error types should have been equal"); + break; + + case ThrownErrorKind::AnyError: + // We have a subtype. If we're not allowed to do the subtype, + // then we need to drop "throws". + if (kind < ConstraintKind::Subtype) + dropThrows = true; + break; + } + break; + + case ThrownErrorKind::AnyError: + switch (thrownErrorKind2) { + case ThrownErrorKind::Specific: + // We need to unify the thrown error types. + mustUnify = true; + break; + + case ThrownErrorKind::Never: + // We're going to have to drop the "throws" entirely. + dropThrows = true; + break; + + case ThrownErrorKind::AnyError: + llvm_unreachable("The thrown error types should have been equal"); + } + break; + } + + // If we know we need to drop 'throws', try it now. + if (dropThrows) { + if (!cs.shouldAttemptFixes()) + return cs.getTypeMatchFailure(locator); + + auto *fix = DropThrowsAttribute::create(cs, func1, func2, + cs.getConstraintLocator(locator)); + if (cs.recordFix(fix)) + return cs.getTypeMatchFailure(locator); + } + + // If we need to unify the thrown error types, do so now. + if (mustUnify) { + ConstraintKind subKind = (kind < ConstraintKind::Subtype) + ? ConstraintKind::Equal + : ConstraintKind::Subtype; + const auto subflags = getDefaultDecompositionOptions(flags); + auto result = cs.matchTypes( + thrownError1, thrownError2, + subKind, subflags, + locator.withPathElement(LocatorPathElt::ThrownErrorType())); + if (result == ConstraintSystem::SolutionKind::Error) + return cs.getTypeMatchFailure(locator); + } + + return cs.getTypeMatchSuccess(); +} + ConstraintSystem::TypeMatchResult ConstraintSystem::matchFunctionTypes(FunctionType *func1, FunctionType *func2, ConstraintKind kind, TypeMatchOptions flags, ConstraintLocatorBuilder locator) { - // A non-throwing function can be a subtype of a throwing function. - if (func1->isThrowing() != func2->isThrowing()) { - // Cannot drop 'throws'. - if (func1->isThrowing() || kind < ConstraintKind::Subtype) { - if (!shouldAttemptFixes()) - return getTypeMatchFailure(locator); - - auto *fix = DropThrowsAttribute::create(*this, func1, func2, - getConstraintLocator(locator)); - if (recordFix(fix)) - return getTypeMatchFailure(locator); - } - } + // Match the 'throws' effect. + TypeMatchResult throwsResult = + matchFunctionThrowing(*this, func1, func2, kind, flags, locator); + if (throwsResult.isFailure()) + return throwsResult; // A synchronous function can be a subtype of an 'async' function. if (func1->isAsync() != func2->isAsync()) { @@ -5152,6 +5268,13 @@ bool ConstraintSystem::repairFailures( getConstraintLocator(locator))) return true; + if (locator.endsWith()) { + conversionsOrFixes.push_back( + IgnoreThrownErrorMismatch::create(*this, lhs, rhs, + getConstraintLocator(locator))); + return true; + } + if (path.empty()) { if (!anchor) return false; @@ -15002,6 +15125,9 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint( case FixKind::IgnoreGenericSpecializationArityMismatch: { return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved; } + case FixKind::IgnoreThrownErrorMismatch: { + return recordFix(fix, 2) ? SolutionKind::Error : SolutionKind::Solved; + } case FixKind::IgnoreInvalidASTNode: { return recordFix(fix, 10) ? SolutionKind::Error : SolutionKind::Solved; } diff --git a/lib/Sema/ConstraintLocator.cpp b/lib/Sema/ConstraintLocator.cpp index 6af5e64792553..9bfccb1f4c097 100644 --- a/lib/Sema/ConstraintLocator.cpp +++ b/lib/Sema/ConstraintLocator.cpp @@ -109,6 +109,7 @@ unsigned LocatorPathElt::getNewSummaryFlags() const { case ConstraintLocator::GlobalActorType: case ConstraintLocator::CoercionOperand: case ConstraintLocator::PackExpansionType: + case ConstraintLocator::ThrownErrorType: return 0; case ConstraintLocator::FunctionArgument: @@ -519,6 +520,10 @@ void LocatorPathElt::dump(raw_ostream &out) const { << expansionElt.getOpenedType()->getString(PO) << ")"; break; } + case ConstraintLocator::ThrownErrorType: { + out << "thrown error type"; + break; + } } } diff --git a/lib/Sema/ConstraintSystem.cpp b/lib/Sema/ConstraintSystem.cpp index f8ec5c52b6edd..6a29d960b078b 100644 --- a/lib/Sema/ConstraintSystem.cpp +++ b/lib/Sema/ConstraintSystem.cpp @@ -5971,6 +5971,9 @@ void constraints::simplifyLocator(ASTNode &anchor, case ConstraintLocator::GenericParameter: break; + case ConstraintLocator::ThrownErrorType: + break; + case ConstraintLocator::OpenedGeneric: case ConstraintLocator::OpenedOpaqueArchetype: break; diff --git a/lib/Sema/TypeCheckEffects.cpp b/lib/Sema/TypeCheckEffects.cpp index 39bb3495988a1..9d4fa023073ce 100644 --- a/lib/Sema/TypeCheckEffects.cpp +++ b/lib/Sema/TypeCheckEffects.cpp @@ -1118,9 +1118,6 @@ class ApplyClassifier { classifyFunctionBody(fnRef, PotentialEffectReason::forApply(), kind)); - assert(result.getConditionalKind(kind) - != ConditionalEffectKind::None && - "body classification decided function had no effect?"); } }; diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index b2a74c0c4196f..7782735541f0d 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -3633,6 +3633,7 @@ NeverNullType TypeResolver::resolveASTFunctionType( if (thrownTy->hasError()) { thrownTy = Type(); } else if (!options.contains(TypeResolutionFlags::SilenceErrors) && + !thrownTy->hasTypeParameter() && !TypeChecker::conformsToProtocol( thrownTy, ctx.getErrorDecl(), resolution.getDeclContext()->getParentModule())) { diff --git a/test/attr/typed_throws_availability_osx.swift b/test/attr/typed_throws_availability_osx.swift index 7edc67eecd02b..00a9e94847b79 100644 --- a/test/attr/typed_throws_availability_osx.swift +++ b/test/attr/typed_throws_availability_osx.swift @@ -1,15 +1,12 @@ -// RUN: %swift -typecheck -verify -target x86_64-apple-macosx10.10 %s -enable-experimental-feature TypedThrows +// RUN: %swift -typecheck -verify -target %target-cpu-apple-macosx11 %s -enable-experimental-feature TypedThrows // REQUIRES: OS=macosx -@available(macOS 12, *) +@available(macOS 13, *) enum MyError: Error { case fail } -@available(macOS 11, *) +@available(macOS 12, *) func throwMyErrorBadly() throws(MyError) { } -// expected-error@-1{{'MyError' is only available in macOS 12 or newer}} - - - +// expected-error@-1{{'MyError' is only available in macOS 13 or newer}} diff --git a/test/decl/func/typed_throws.swift b/test/decl/func/typed_throws.swift index 8682f3579b918..545a43ec3bcad 100644 --- a/test/decl/func/typed_throws.swift +++ b/test/decl/func/typed_throws.swift @@ -41,13 +41,15 @@ func throwsUnusedInSignature() throws(T) { } func testSubstitutedType() { let _: (_: MyError.Type) -> Void = throwsGeneric - // expected-error@-1{{invalid conversion from throwing function of type '(MyError.Type) throws(MyError) -> ()'}} + // expected-error@-1{{invalid conversion of thrown error type 'MyError' to 'Never'}} let _: (_: (any Error).Type) -> Void = throwsGeneric - // expected-error@-1{{invalid conversion from throwing function of type '((any Error).Type) throws((any Error)) -> ()' to non-throwing function type}} + // expected-error@-1{{invalid conversion of thrown error type 'any Error' to 'Never'}} + + let _: (_: MyOtherError.Type) throws(MyError) -> Void = throwsGeneric + // expected-error@-1{{invalid conversion of thrown error type 'MyOtherError' to 'MyError'}} let _: (_: Never.Type) -> Void = throwsGeneric - // FIXME wrong: expected-error@-1{{invalid conversion from throwing function of type '(Never.Type) throws(Never) -> ()'}} } @@ -64,3 +66,40 @@ func testThrowingInFunction(cond: Bool, cond2: Bool) throws(MyError) { public func testThrowingInternal() throws(MyError) { } // expected-error@-1{{function cannot be declared public because its thrown error uses an internal type}} + +// A form of "map" for arrays that carries the error type from the closure +// through to the function itself, as a more specific form of rethrows. +func mapArray(_ array: [T], body: (T) throws(E) -> U) throws(E) -> [U] { + var resultArray: [U] = .init() + for value in array { + resultArray.append(try body(value)) + } + return resultArray +} + +func addOrThrowUntyped(_ i: Int, _ j: Int) throws -> Int { i + j } +func addOrThrowMyError(_ i: Int, _ j: Int) throws(MyError) -> Int { i + j } + +func testMapArray(numbers: [Int]) { + // Note: try is not required, because this throws Never + _ = mapArray(numbers) { $0 + 1 } + + do { + _ = try mapArray(numbers) { try addOrThrowUntyped($0, 1) } + } catch { + let _: Int = error // expected-error{{cannot convert value of type 'any Error'}} + } + + do { + _ = try mapArray(numbers) { try addOrThrowMyError($0, 1) } + } catch { + let _: Int = error // expected-error{{cannot convert value of type 'any Error' to specified type 'Int'}} + // TODO: with better inference, we infer MyError + } + + do { + _ = try mapArray(numbers) { (x) throws(MyError) in try addOrThrowMyError(x, 1) } + } catch { + let _: Int = error // expected-error{{cannot convert value of type 'MyError' to specified type 'Int'}} + } +}