From 083333f17fde5530728be284dc1ca046afc365a3 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 16 Oct 2023 15:46:11 -0700 Subject: [PATCH] [Typed throws] Add upcoming feature FullTypedThrows Introduce the upcoming feature `FullTypedThrows`. When enabled, infer the error type of a `throw` statement based on its original type, instead of always being `any Error`. This is technically a source-breaking change, hence the upcoming feature flag. --- include/swift/Basic/Features.def | 1 + lib/AST/ASTPrinter.cpp | 4 +++ lib/Sema/TypeCheckEffects.cpp | 46 +++++++++++++++++++++++++++++--- test/stmt/typed_throws.swift | 17 ++++++++---- 4 files changed, 60 insertions(+), 8 deletions(-) diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 5f8f437ec02c4..f98a441e58681 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -118,6 +118,7 @@ UPCOMING_FEATURE(ExistentialAny, 335, 6) UPCOMING_FEATURE(ImportObjcForwardDeclarations, 384, 6) UPCOMING_FEATURE(DisableOutwardActorInference, 401, 6) UPCOMING_FEATURE(InternalImportsByDefault, 409, 6) +UPCOMING_FEATURE(FullTypedThrows, 410, 6) EXPERIMENTAL_FEATURE(StaticAssert, false) EXPERIMENTAL_FEATURE(NamedOpaqueTypes, false) diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index c9e5760d6b44e..36ae285e10700 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -3575,6 +3575,10 @@ static bool usesFeatureNewCxxMethodSafetyHeuristics(Decl *decl) { return decl->hasClangNode(); } +static bool usesFeatureFullTypedThrows(Decl *decl) { + return false; +} + static bool usesFeatureTypedThrows(Decl *decl) { if (auto func = dyn_cast(decl)) return func->getThrownTypeRepr() != nullptr; diff --git a/lib/Sema/TypeCheckEffects.cpp b/lib/Sema/TypeCheckEffects.cpp index abb61117e4867..c21f7c300fe14 100644 --- a/lib/Sema/TypeCheckEffects.cpp +++ b/lib/Sema/TypeCheckEffects.cpp @@ -616,6 +616,27 @@ static void simple_display(llvm::raw_ostream &out, ConditionalEffectKind kind) { llvm_unreachable("Bad conditional effect kind"); } +/// Remove the type erasure to an existential error, to extract the +/// underlying error. +static Expr *removeErasureToExistentialError(Expr *expr) { + Type type = expr->getType(); + if (!type) + return expr; + + ASTContext &ctx = type->getASTContext(); + if (!ctx.LangOpts.hasFeature(Feature::FullTypedThrows) || + !ctx.LangOpts.hasFeature(Feature::TypedThrows)) + return expr; + + // Look for an outer erasure expression. + if (auto erasure = dyn_cast(expr)) { + if (type->isEqual(ctx.getErrorExistentialType())) + return erasure->getSubExpr(); + } + + return expr; +} + /// A type expressing the result of classifying whether a call or function /// throws or is async. class Classification { @@ -989,6 +1010,10 @@ class ApplyClassifier { if (!thrownValue) return Classification::forInvalidCode(); + // If we are doing full typed throws, look through an existential + // conversion to find the underlying type. + thrownValue = removeErasureToExistentialError(thrownValue); + Type thrownType = thrownValue->getType(); if (!thrownType) return Classification::forInvalidCode(); @@ -2893,6 +2918,18 @@ class CheckEffectsCoverage : public EffectsHandlingWalker if (!CurContext.handlesThrows(ConditionalEffectKind::Always)) CurContext.diagnoseUnhandledThrowStmt(Ctx.Diags, S); + else { + SourceLoc loc = S->getThrowLoc(); + Expr *thrownValue = S->getSubExpr(); + Type thrownErrorType = thrownValue->getType(); + Type caughtErrorType = getCaughtErrorTypeAt(loc); + if (!caughtErrorType->isEqual(thrownErrorType)) { + thrownValue = removeErasureToExistentialError(thrownValue); + Type thrownErrorType = thrownValue->getType(); + if (!checkThrownErrorType(loc, thrownErrorType)) + S->setSubExpr(thrownValue); + } + } } return ShouldRecurse; @@ -2978,16 +3015,19 @@ class CheckEffectsCoverage : public EffectsHandlingWalker /// Check the thrown error type against the type that can be caught or /// rethrown by the context. - void checkThrownErrorType(SourceLoc loc, Type thrownErrorType) { + /// + /// Returns \c true if an error occurred, false otherwise. + bool checkThrownErrorType(SourceLoc loc, Type thrownErrorType) { Type caughtErrorType = getCaughtErrorTypeAt(loc); if (caughtErrorType->isEqual(thrownErrorType)) - return; + return false ; OpaqueValueExpr *opaque = new (Ctx) OpaqueValueExpr(loc, thrownErrorType); Expr *rethrowExpr = opaque; - TypeChecker::typeCheckExpression( + Type resultType = TypeChecker::typeCheckExpression( rethrowExpr, CurContext.getDeclContext(), {caughtErrorType, /*FIXME:*/CTP_ThrowStmt}); + return resultType.isNull(); } ShouldRecurse_t checkAwait(AwaitExpr *E) { diff --git a/test/stmt/typed_throws.swift b/test/stmt/typed_throws.swift index cf9c48f2fa8dc..0318802f889d5 100644 --- a/test/stmt/typed_throws.swift +++ b/test/stmt/typed_throws.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -enable-experimental-feature TypedThrows +// RUN: %target-typecheck-verify-swift -enable-experimental-feature TypedThrows -enable-upcoming-feature FullTypedThrows enum MyError: Error { case failed @@ -15,16 +15,23 @@ func processMyError(_: MyError) { } func doSomething() throws(MyError) { } func doHomework() throws(HomeworkError) { } -func testDoCatchErrorTyped() { - #if false - // FIXME: Deal with throws directly in the do...catch blocks. +func testDoCatchErrorTyped(cond: Bool) { do { throw MyError.failed } catch { assert(error == .failed) processMyError(error) } - #endif + + do { + if cond { + throw MyError.failed + } else { + throw HomeworkError.dogAteIt + } + } catch { + processMyError(error) // expected-error{{cannot convert value of type 'any Error' to expected argument type 'MyError'}} + } // Throwing a typed error in a do...catch catches the error with that type. do {