From ccf8619453d6fdc581703fab1813d516469b7445 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sat, 7 Oct 2023 11:06:24 -0700 Subject: [PATCH 1/3] [Typed throws] Refactor thrown error subtyping check for reuse. Lift the subtyping check for thrown error types out of the constraint solver, so we can re-use it elsewhere. There is a minor diagnostic change, from one that is actively misleading (it shows a legitimate conversion that's wrong) to one that is correct, which comes from us not treating "dropping throws" as a legitimate way to handle equality of function types. --- lib/Sema/CSSimplify.cpp | 133 ++++++++---------------- lib/Sema/TypeCheckEffects.cpp | 127 ++++++++++++++++++++++ lib/Sema/TypeCheckEffects.h | 55 ++++++++++ lib/Sema/TypeCheckProtocol.cpp | 2 + test/decl/func/throwing_functions.swift | 3 +- 5 files changed, 231 insertions(+), 89 deletions(-) create mode 100644 lib/Sema/TypeCheckEffects.h diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 42228f60a6f46..282ce5a9327f4 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -17,6 +17,7 @@ #include "CSDiagnostics.h" #include "TypeCheckConcurrency.h" +#include "TypeCheckEffects.h" #include "swift/AST/ASTPrinter.h" #include "swift/AST/Decl.h" #include "swift/AST/ExistentialLayout.h" @@ -2945,28 +2946,6 @@ 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, @@ -2978,71 +2957,14 @@ matchFunctionThrowing(ConstraintSystem &cs, // 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)) + Type thrownError1 = getEffectiveThrownErrorTypeOrNever(func1); + Type thrownError2 = getEffectiveThrownErrorTypeOrNever(func2); + if (!thrownError1 || !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) { + switch (compareThrownErrorsForSubtyping(thrownError1, thrownError2, cs.DC)) { + case ThrownErrorSubtyping::DropsThrows: { + // We need to drop 'throws' to make this work. if (!cs.shouldAttemptFixes()) return cs.getTypeMatchFailure(locator); @@ -3050,10 +2972,30 @@ matchFunctionThrowing(ConstraintSystem &cs, cs.getConstraintLocator(locator)); if (cs.recordFix(fix)) return cs.getTypeMatchFailure(locator); + + return cs.getTypeMatchSuccess(); } - // If we need to unify the thrown error types, do so now. - if (mustUnify) { + case ThrownErrorSubtyping::ExactMatch: + return cs.getTypeMatchSuccess(); + + case ThrownErrorSubtyping::Subtype: + // We know this is going to work, but we might still need to generate a + // constraint if one of the error types involves type variables. + if (thrownError1->hasTypeVariable() || thrownError2->hasTypeVariable()) { + // Fall through to the dependent case. + } else if (kind < ConstraintKind::Subtype) { + // We aren't allowed to have a subtype, so fail here. + return cs.getTypeMatchFailure(locator); + } else { + // We have a subtype. All set! + return cs.getTypeMatchSuccess(); + } + LLVM_FALLTHROUGH; + + case ThrownErrorSubtyping::Dependent: { + // The presence of type variables in the thrown error types require that + // we generate a constraint to unify the thrown error types, so do so now. ConstraintKind subKind = (kind < ConstraintKind::Subtype) ? ConstraintKind::Equal : ConstraintKind::Subtype; @@ -3064,9 +3006,24 @@ matchFunctionThrowing(ConstraintSystem &cs, locator.withPathElement(LocatorPathElt::ThrownErrorType())); if (result == ConstraintSystem::SolutionKind::Error) return cs.getTypeMatchFailure(locator); + + return cs.getTypeMatchSuccess(); } - return cs.getTypeMatchSuccess(); + case ThrownErrorSubtyping::Mismatch: { + auto thrownErrorLocator = cs.getConstraintLocator( + locator.withPathElement(LocatorPathElt::ThrownErrorType())); + if (!cs.shouldAttemptFixes()) + return cs.getTypeMatchFailure(thrownErrorLocator); + + auto *fix = IgnoreThrownErrorMismatch::create( + cs, thrownError1, thrownError2, thrownErrorLocator); + if (cs.recordFix(fix)) + return cs.getTypeMatchFailure(thrownErrorLocator); + + return cs.getTypeMatchSuccess(); + } + } } ConstraintSystem::TypeMatchResult diff --git a/lib/Sema/TypeCheckEffects.cpp b/lib/Sema/TypeCheckEffects.cpp index 9d4fa023073ce..69ccf3a4fff25 100644 --- a/lib/Sema/TypeCheckEffects.cpp +++ b/lib/Sema/TypeCheckEffects.cpp @@ -17,6 +17,7 @@ #include "TypeChecker.h" #include "TypeCheckConcurrency.h" +#include "TypeCheckEffects.h" #include "swift/AST/ASTWalker.h" #include "swift/AST/DiagnosticsSema.h" #include "swift/AST/Effects.h" @@ -3303,3 +3304,129 @@ Type TypeChecker::errorUnion(Type type1, Type type2) { // actual union type here. return type1->getASTContext().getErrorExistentialType(); } + +Type swift::getEffectiveThrownErrorTypeOrNever(AnyFunctionType *func) { + if (auto thrownError = func->getEffectiveThrownInterfaceType()) + return *thrownError; + + return func->getASTContext().getNeverType(); +} + +namespace { + +/// Classifies a thrown error kind as Never, a specific type, or 'any Error'. +enum class ThrownErrorClassification { + /// The `Never` type, which represents a non-throwing function. + Never, + + /// A specific error type that is neither `Never` nor `any Error`. + Specific, + + /// A specific error type that depends on a type variable or type parameter, + /// and therefore we cannot determine whether it is a subtype of another + /// type or not. + Dependent, + + /// The type `any Error`, used for untyped throws. + AnyError, +}; + +} + +/// Classify the given thrown error type. +static ThrownErrorClassification classifyThrownErrorType(Type type) { + if (type->isNever()) + return ThrownErrorClassification::Never; + + if (type->isExistentialType()) { + Type anyError = type->getASTContext().getErrorExistentialType(); + if (anyError->isEqual(type)) + return ThrownErrorClassification::AnyError; + } + + if (type->hasTypeVariable() || type->hasTypeParameter()) + return ThrownErrorClassification::Dependent; + + return ThrownErrorClassification::Specific; +} + +ThrownErrorSubtyping +swift::compareThrownErrorsForSubtyping( + Type subThrownError, Type superThrownError, DeclContext *dc +) { + // Easy case: exact match. + if (superThrownError->isEqual(subThrownError)) + return ThrownErrorSubtyping::ExactMatch; + + auto superThrownErrorKind = classifyThrownErrorType(superThrownError); + auto subThrownErrorKind = classifyThrownErrorType(subThrownError); + + switch (subThrownErrorKind) { + case ThrownErrorClassification::Dependent: + switch (superThrownErrorKind) { + case ThrownErrorClassification::AnyError: + // This is a clear subtype relationship, because the supertype throws + // anything. + return ThrownErrorSubtyping::Subtype; + + case ThrownErrorClassification::Never: + case ThrownErrorClassification::Dependent: + case ThrownErrorClassification::Specific: + // We have to compare the types. Do so below. + break; + } + break; + + case ThrownErrorClassification::Specific: + switch (superThrownErrorKind) { + case ThrownErrorClassification::AnyError: + // This is a clear subtype relationship, because the supertype throws + // anything. + return ThrownErrorSubtyping::Subtype; + + case ThrownErrorClassification::Never: + // The supertype doesn't throw, so this has to drop 'throws' to work. + return ThrownErrorSubtyping::DropsThrows; + + case ThrownErrorClassification::Dependent: + case ThrownErrorClassification::Specific: + // We have to compare the types. Do so below. + break; + } + break; + + case ThrownErrorClassification::Never: + // A function type throwing 'Never' is a subtype of all function types. + return ThrownErrorSubtyping::Subtype; + + case ThrownErrorClassification::AnyError: + switch (superThrownErrorKind) { + case ThrownErrorClassification::Dependent: + case ThrownErrorClassification::Specific: + // We have to compare the types. Do so below. + break; + + case ThrownErrorClassification::Never: + // We're going to have to drop the "throws" entirely. + return ThrownErrorSubtyping::DropsThrows; + + case ThrownErrorClassification::AnyError: + llvm_unreachable("The thrown error types should have been equal"); + } + break; + } + + // If either of the types was dependent on a type variable or type parameter, + // we can't do the comparison at all. + if (superThrownErrorKind == ThrownErrorClassification::Dependent || + subThrownErrorKind == ThrownErrorClassification::Dependent) + return ThrownErrorSubtyping::Dependent; + + // Check whether the subtype's thrown error type is convertible to the + // supertype's thrown error type. + if (TypeChecker::isConvertibleTo(subThrownError, superThrownError, dc)) + return ThrownErrorSubtyping::Subtype; + + // We know it doesn't work. + return ThrownErrorSubtyping::Mismatch; +} diff --git a/lib/Sema/TypeCheckEffects.h b/lib/Sema/TypeCheckEffects.h new file mode 100644 index 0000000000000..985b4155caf79 --- /dev/null +++ b/lib/Sema/TypeCheckEffects.h @@ -0,0 +1,55 @@ +//===--- TypeCheckEffects.h - Effects checking ------------------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This file provides type checking support for Swift's effect checking, which +// includes error handling (throws) and asynchronous (async) effects. +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_SEMA_TYPECHECKEFFECTS_H +#define SWIFT_SEMA_TYPECHECKEFFECTS_H + +#include "swift/AST/Type.h" + +namespace swift { + +/// Retrieve the effective thrown error type for the given function type, which +/// is the thrown error type (is specified), `any Error` if untyped throwing, or +/// `Never` if non-throwing. +Type getEffectiveThrownErrorTypeOrNever(AnyFunctionType *func); + +/// Classifies the result of a subtyping comparison between two thrown error +/// types. +enum class ThrownErrorSubtyping { + /// There is no subtyping relationship, and we're trying to convert from a + /// throwning type to a non-throwing type. + DropsThrows, + /// There is no subtyping relationship because the types mismatch. + Mismatch, + /// The thrown error types exactly match; there is a subtype relationship. + ExactMatch, + /// The thrown error types are different, but there is an obvious subtype + /// relationship. + Subtype, + /// The thrown error types are different, and the presence of type variables + /// or type parameters prevents us from determining now whether there is a + /// subtype relationship. + Dependent, +}; + +/// Compare the thrown error types for the purposes of subtyping. +ThrownErrorSubtyping compareThrownErrorsForSubtyping( + Type subThrownError, Type superThrownError, DeclContext *dc); + +} + +#endif // SWIFT_SEMA_TYPECHECKEFFECTS_H diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index ed12bce89f81c..609880ca29f9c 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -832,6 +832,8 @@ RequirementMatch swift::matchWitness( } // If the witness is 'throws', the requirement must be. + // FIXME: We need the same matching we do in the constraint solver, + // with the same fast paths for obvious throws/nonthrows cases. if (witnessFnType->getExtInfo().isThrowing() && !reqFnType->getExtInfo().isThrowing()) { return RequirementMatch(witness, MatchKind::ThrowsConflict); diff --git a/test/decl/func/throwing_functions.swift b/test/decl/func/throwing_functions.swift index f898e6a9df840..12e55aa3710d8 100644 --- a/test/decl/func/throwing_functions.swift +++ b/test/decl/func/throwing_functions.swift @@ -70,13 +70,14 @@ func fooT(_ callback: () -> Bool) {} // Throwing and non-throwing types are not equivalent. struct X { } +// expected-note@-1{{arguments to generic parameter 'T' ('(String) -> Int' and '(String) throws -> Int') are expected to be equal}} func specializedOnFuncType1(_ x: X<(String) throws -> Int>) { } func specializedOnFuncType2(_ x: X<(String) -> Int>) { } func testSpecializedOnFuncType(_ xThrows: X<(String) throws -> Int>, xNonThrows: X<(String) -> Int>) { specializedOnFuncType1(xThrows) // ok - specializedOnFuncType1(xNonThrows) // expected-error{{invalid conversion from throwing function of type '(String) -> Int' to non-throwing function type '(String) throws -> Int'}} + specializedOnFuncType1(xNonThrows) // expected-error{{cannot convert value of type 'X<(String) -> Int>' to expected argument type 'X<(String) throws -> Int>'}} specializedOnFuncType2(xThrows) // expected-error{{invalid conversion from throwing function of type '(String) throws -> Int' to non-throwing function type '(String) -> Int'}} specializedOnFuncType2(xNonThrows) // ok } From fc78ad3263490a119add12a91d8da9ea428f8ef9 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 9 Oct 2023 16:16:09 -0700 Subject: [PATCH 2/3] [Typed throws] Teach witness matching to check typed throws. --- lib/Sema/TypeCheckProtocol.cpp | 67 +++++++++++++++++-- .../decl/protocol/conforms/typed_throws.swift | 40 +++++++++++ 2 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 test/decl/protocol/conforms/typed_throws.swift diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index 609880ca29f9c..619197e2e09c5 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -22,6 +22,7 @@ #include "TypeCheckAvailability.h" #include "TypeCheckConcurrency.h" #include "TypeCheckDistributed.h" +#include "TypeCheckEffects.h" #include "TypeCheckObjC.h" #include "swift/AST/ASTContext.h" #include "swift/AST/ASTMangler.h" @@ -589,6 +590,8 @@ RequirementMatch swift::matchWitness( // Perform basic matching of the requirement and witness. bool decomposeFunctionType = false; bool ignoreReturnType = false; + Type reqThrownError; + Type witnessThrownError; if (isa(req) && isa(witness)) { auto funcReq = cast(req); auto funcWitness = cast(witness); @@ -681,12 +684,36 @@ RequirementMatch swift::matchWitness( MatchKind::MutatingConflict); } + // Check for async mismatches. + if (!witnessASD->isLessEffectfulThan(reqASD, EffectKind::Async)) { + return RequirementMatch( + getStandinForAccessor(witnessASD, AccessorKind::Get), + MatchKind::AsyncConflict); + } + // Check that the witness has no more effects than the requirement. if (auto problem = checkEffects(witnessASD, reqASD)) return problem.value(); // Decompose the parameters for subscript declarations. decomposeFunctionType = isa(req); + + // Dig out the thrown error types from the getter so we can compare them + // later. + auto getThrownErrorType = [](AbstractStorageDecl *asd) -> Type { + if (auto getter = asd->getEffectfulGetAccessor()) { + if (Type thrownErrorType = getter->getThrownInterfaceType()) { + return thrownErrorType; + } else if (getter->hasThrows()) { + return asd->getASTContext().getAnyExistentialType(); + } + } + + return asd->getASTContext().getNeverType(); + }; + + reqThrownError = getThrownErrorType(reqASD); + witnessThrownError = getThrownErrorType(witnessASD); } else if (isa(witness)) { decomposeFunctionType = true; ignoreReturnType = true; @@ -831,12 +858,11 @@ RequirementMatch swift::matchWitness( return RequirementMatch(witness, MatchKind::AsyncConflict); } - // If the witness is 'throws', the requirement must be. - // FIXME: We need the same matching we do in the constraint solver, - // with the same fast paths for obvious throws/nonthrows cases. - if (witnessFnType->getExtInfo().isThrowing() && - !reqFnType->getExtInfo().isThrowing()) { - return RequirementMatch(witness, MatchKind::ThrowsConflict); + if (!reqThrownError) { + // Save the thrown error types of the requirement and witness so we + // can check them later. + reqThrownError = getEffectiveThrownErrorTypeOrNever(reqFnType); + witnessThrownError = getEffectiveThrownErrorTypeOrNever(witnessFnType); } } } else { @@ -859,6 +885,35 @@ RequirementMatch swift::matchWitness( } } + // Check the thrown error types. This includes 'any Error' and 'Never' for + // untyped throws and non-throwing cases as well. + if (reqThrownError && witnessThrownError) { + auto thrownErrorTypes = getTypesToCompare( + req, reqThrownError, false, witnessThrownError, false, + VarianceKind::None); + + Type reqThrownError = std::get<0>(thrownErrorTypes); + Type witnessThrownError = std::get<1>(thrownErrorTypes); + switch (compareThrownErrorsForSubtyping(witnessThrownError, reqThrownError, + dc)) { + case ThrownErrorSubtyping::DropsThrows: + case ThrownErrorSubtyping::Mismatch: + return RequirementMatch(witness, MatchKind::ThrowsConflict); + + case ThrownErrorSubtyping::ExactMatch: + case ThrownErrorSubtyping::Subtype: + // All is well. + break; + + case ThrownErrorSubtyping::Dependent: + // We need to perform type matching + if (auto result = matchTypes(witnessThrownError, reqThrownError)) { + return std::move(result.value()); + } + break; + } + } + // Now finalize the match. auto result = finalize(anyRenaming, optionalAdjustments); // Check if the requirement's `@differentiable` attributes are satisfied by diff --git a/test/decl/protocol/conforms/typed_throws.swift b/test/decl/protocol/conforms/typed_throws.swift new file mode 100644 index 0000000000000..ae4220ef3e162 --- /dev/null +++ b/test/decl/protocol/conforms/typed_throws.swift @@ -0,0 +1,40 @@ +// RUN: %target-typecheck-verify-swift -parse-as-library -enable-experimental-feature TypedThrows + +enum MyError: Error { +case failed +} + +enum HomeworkError: Error { +case dogAteIt +} + +class SuperError: Error { } +class SubError: SuperError { } + +protocol VeryThrowing { + func f() throws + func g() throws(MyError) + func h() throws(HomeworkError) // expected-note{{protocol requires function 'h()' with type '() throws(HomeworkError) -> ()'}} + func i() throws(SuperError) + + var prop1: Int { get throws } + var prop2: Int { get throws(MyError) } + var prop3: Int { get throws(HomeworkError) } // expected-note{{protocol requires property 'prop3' with type 'Int'; add a stub for conformance}} + // FIXME: poor diagnostic above + var prop4: Int { get throws(SuperError) } +} + +// expected-error@+1{{type 'ConformingToVeryThrowing' does not conform to protocol 'VeryThrowing'}} +struct ConformingToVeryThrowing: VeryThrowing { + func f() throws(MyError) { } // okay to make type more specific + func g() { } // okay to be non-throwing + func h() throws(MyError) { } // expected-note{{candidate throws, but protocol does not allow it}} + // FIXME: Diagnostic above should be better + func i() throws(SubError) { } // okay to have a subtype + + var prop1: Int { get throws(MyError) { 0 } } + var prop2: Int { 0 } // okay to be non-throwing + var prop3: Int { get throws(MyError) { 0 } } // expected-note{{candidate throws, but protocol does not allow it}} + // FIXME: Diagnostic above should be better + var prop4: Int { get throws(SubError) { 0 } } +} From 7d7c726efe9a8afbecd918e87092ba94f821f337 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 9 Oct 2023 18:40:55 -0700 Subject: [PATCH 3/3] [Typed throws] Teach associated type inference to infer from thrown errors When comparing a requirement that uses typed throws and uses an associated type for the thrown error type against a potential witness, infer the associated type from the thrown error of the witness---whether explicitly specified, untyped throws (`any Error`), or non-throwing (`Never`). --- include/swift/AST/Decl.h | 2 +- include/swift/AST/TypeMatcher.h | 13 ++++++++ include/swift/AST/Types.h | 11 +++++-- lib/AST/Decl.cpp | 4 +-- lib/AST/Expr.cpp | 2 +- lib/AST/Type.cpp | 9 +++++- lib/SILGen/SILGenBackDeploy.cpp | 2 +- lib/SILGen/SILGenConstructor.cpp | 6 ++-- lib/SILGen/SILGenFunction.cpp | 2 +- lib/Sema/CSSimplify.cpp | 4 +-- lib/Sema/TypeCheckEffects.cpp | 11 ++----- lib/Sema/TypeCheckEffects.h | 5 --- lib/Sema/TypeCheckProtocol.cpp | 14 ++++++--- lib/Sema/TypeCheckProtocolInference.cpp | 4 +++ .../decl/protocol/conforms/typed_throws.swift | 31 +++++++++++++++++++ 15 files changed, 88 insertions(+), 32 deletions(-) diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 5f5545edbe204..247029af3b7d0 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -7123,7 +7123,7 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl { /// /// Functions with untyped throws will produce "any Error", functions that /// cannot throw or are specified to throw "Never" will return llvm::None. - llvm::Optional getEffectiveThrownInterfaceType() const; + llvm::Optional getEffectiveThrownErrorType() const; /// Returns if the function throws or is async. bool hasEffect(EffectKind kind) const; diff --git a/include/swift/AST/TypeMatcher.h b/include/swift/AST/TypeMatcher.h index 217e5250621df..e1fb3420155ea 100644 --- a/include/swift/AST/TypeMatcher.h +++ b/include/swift/AST/TypeMatcher.h @@ -397,6 +397,15 @@ class TypeMatcher { return false; } + // If requested, compare the thrown error types. + Type thrownError1 = firstFunc->getEffectiveThrownErrorTypeOrNever(); + Type thrownError2 = secondFunc->getEffectiveThrownErrorTypeOrNever(); + if (Matcher.asDerived().considerThrownErrorTypes(thrownError1, + thrownError2) && + !this->visit(thrownError1->getCanonicalType(), + thrownError2, thrownError1)) + return false; + return this->visit(firstFunc.getResult(), secondFunc->getResult(), sugaredFirstFunc->getResult()); } @@ -558,6 +567,10 @@ class TypeMatcher { return MatchVisitor(*this).visit(first->getCanonicalType(), second, first); } + + bool considerThrownErrorTypes(Type errorType1, Type errorType2) const { + return false; + } }; } // end namespace swift diff --git a/include/swift/AST/Types.h b/include/swift/AST/Types.h index 80f3811f9d911..d2eb351ca3eca 100644 --- a/include/swift/AST/Types.h +++ b/include/swift/AST/Types.h @@ -3391,8 +3391,15 @@ class AnyFunctionType : public TypeBase { /// /// Functions with untyped throws will produce "any Error", functions that /// cannot throw or are specified to throw "Never" will return llvm::None. - llvm::Optional getEffectiveThrownInterfaceType() const; - + llvm::Optional getEffectiveThrownErrorType() const; + + /// Retrieve the "effective" thrown interface type, or `Never` if + /// this function cannot throw. + /// + /// Functions with untyped throws will produce `any Error`, functions that + /// cannot throw or are specified to throw `Never` will return `Never`. + Type getEffectiveThrownErrorTypeOrNever() const; + /// Returns true if the function type stores a Clang type that cannot /// be derived from its Swift type. Returns false otherwise, including if /// the function type is not @convention(c) or @convention(block). diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 4a89214c7172c..b32ca8f37beb1 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -951,7 +951,7 @@ Type AbstractFunctionDecl::getThrownInterfaceType() const { } llvm::Optional -AbstractFunctionDecl::getEffectiveThrownInterfaceType() const { +AbstractFunctionDecl::getEffectiveThrownErrorType() const { Type interfaceType = getInterfaceType(); if (hasImplicitSelfDecl()) { if (auto fnType = interfaceType->getAs()) @@ -959,7 +959,7 @@ AbstractFunctionDecl::getEffectiveThrownInterfaceType() const { } return interfaceType->castTo() - ->getEffectiveThrownInterfaceType(); + ->getEffectiveThrownErrorType(); } Expr *AbstractFunctionDecl::getSingleExpressionBody() const { diff --git a/lib/AST/Expr.cpp b/lib/AST/Expr.cpp index d854759dcb7bc..67daec6d4b3af 100644 --- a/lib/AST/Expr.cpp +++ b/lib/AST/Expr.cpp @@ -1937,7 +1937,7 @@ Type AbstractClosureExpr::getResultType( llvm::Optional AbstractClosureExpr::getEffectiveThrownType() const { return getType()->castTo() - ->getEffectiveThrownInterfaceType(); + ->getEffectiveThrownErrorType(); } bool AbstractClosureExpr::isBodyThrowing() const { diff --git a/lib/AST/Type.cpp b/lib/AST/Type.cpp index 58a4057d35daa..bf706adb6335d 100644 --- a/lib/AST/Type.cpp +++ b/lib/AST/Type.cpp @@ -5419,7 +5419,7 @@ AnyFunctionType *AnyFunctionType::getWithoutThrowing() const { return withExtInfo(info); } -llvm::Optional AnyFunctionType::getEffectiveThrownInterfaceType() const { +llvm::Optional AnyFunctionType::getEffectiveThrownErrorType() const { // A non-throwing function... has no thrown interface type. if (!isThrowing()) return llvm::None; @@ -5437,6 +5437,13 @@ llvm::Optional AnyFunctionType::getEffectiveThrownInterfaceType() const { return thrownError; } +Type AnyFunctionType::getEffectiveThrownErrorTypeOrNever() const { + if (auto thrown = getEffectiveThrownErrorType()) + return *thrown; + + return getASTContext().getNeverType(); +} + llvm::Optional TypeBase::getAutoDiffTangentSpace(LookupConformanceFn lookupConformance) { assert(lookupConformance); diff --git a/lib/SILGen/SILGenBackDeploy.cpp b/lib/SILGen/SILGenBackDeploy.cpp index b2e02d479ea96..5fd29d9553cb7 100644 --- a/lib/SILGen/SILGenBackDeploy.cpp +++ b/lib/SILGen/SILGenBackDeploy.cpp @@ -238,7 +238,7 @@ void SILGenFunction::emitBackDeploymentThunk(SILDeclRef thunk) { } prepareEpilog(getResultInterfaceType(AFD), - AFD->getEffectiveThrownInterfaceType(), + AFD->getEffectiveThrownErrorType(), CleanupLocation(AFD)); SILBasicBlock *availableBB = createBasicBlock("availableBB"); diff --git a/lib/SILGen/SILGenConstructor.cpp b/lib/SILGen/SILGenConstructor.cpp index 031b0a5428d8c..d65a6ff295ec1 100644 --- a/lib/SILGen/SILGenConstructor.cpp +++ b/lib/SILGen/SILGenConstructor.cpp @@ -680,7 +680,7 @@ void SILGenFunction::emitValueConstructor(ConstructorDecl *ctor) { // Create a basic block to jump to for the implicit 'self' return. // We won't emit this until after we've emitted the body. // The epilog takes a void return because the return of 'self' is implicit. - prepareEpilog(llvm::None, ctor->getEffectiveThrownInterfaceType(), + prepareEpilog(llvm::None, ctor->getEffectiveThrownErrorType(), CleanupLocation(ctor)); // If the constructor can fail, set up an alternative epilog for constructor @@ -1185,7 +1185,7 @@ void SILGenFunction::emitClassConstructorInitializer(ConstructorDecl *ctor) { // Create a basic block to jump to for the implicit 'self' return. // We won't emit the block until after we've emitted the body. - prepareEpilog(llvm::None, ctor->getEffectiveThrownInterfaceType(), + prepareEpilog(llvm::None, ctor->getEffectiveThrownErrorType(), CleanupLocation(endOfInitLoc)); auto resultType = ctor->mapTypeIntoContext(ctor->getResultInterfaceType()); @@ -1751,7 +1751,7 @@ void SILGenFunction::emitInitAccessor(AccessorDecl *accessor) { } prepareEpilog(accessor->getResultInterfaceType(), - accessor->getEffectiveThrownInterfaceType(), + accessor->getEffectiveThrownErrorType(), CleanupLocation(accessor)); emitProfilerIncrement(accessor->getTypecheckedBody()); diff --git a/lib/SILGen/SILGenFunction.cpp b/lib/SILGen/SILGenFunction.cpp index b34d9644522b5..0c8965d8307ee 100644 --- a/lib/SILGen/SILGenFunction.cpp +++ b/lib/SILGen/SILGenFunction.cpp @@ -1051,7 +1051,7 @@ void SILGenFunction::emitFunction(FuncDecl *fd) { emitDistributedActorFactory(fd); } else { prepareEpilog(fd->getResultInterfaceType(), - fd->getEffectiveThrownInterfaceType(), CleanupLocation(fd)); + fd->getEffectiveThrownErrorType(), CleanupLocation(fd)); if (fd->requiresUnavailableDeclABICompatibilityStubs()) emitApplyOfUnavailableCodeReached(); diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 282ce5a9327f4..2d6d8cc1168af 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -2957,8 +2957,8 @@ matchFunctionThrowing(ConstraintSystem &cs, // 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 thrownError1 = getEffectiveThrownErrorTypeOrNever(func1); - Type thrownError2 = getEffectiveThrownErrorTypeOrNever(func2); + Type thrownError1 = func1->getEffectiveThrownErrorTypeOrNever(); + Type thrownError2 = func2->getEffectiveThrownErrorTypeOrNever(); if (!thrownError1 || !thrownError2) return cs.getTypeMatchSuccess(); diff --git a/lib/Sema/TypeCheckEffects.cpp b/lib/Sema/TypeCheckEffects.cpp index 69ccf3a4fff25..2d6487152a65c 100644 --- a/lib/Sema/TypeCheckEffects.cpp +++ b/lib/Sema/TypeCheckEffects.cpp @@ -762,7 +762,7 @@ class Classification { if (considerThrows) { if (auto thrownInterfaceType = - func->getEffectiveThrownInterfaceType()) { + func->getEffectiveThrownErrorType()) { Type thrownType = thrownInterfaceType->subst(declRef.getSubstitutions()); result.merge(Classification::forThrows(thrownType, @@ -1660,7 +1660,7 @@ class ApplyClassifier { return Classification(); case EffectKind::Throws: - if (auto thrownError = fnType->getEffectiveThrownInterfaceType()) + if (auto thrownError = fnType->getEffectiveThrownErrorType()) return Classification::forThrows(*thrownError, conditional, reason); return Classification(); @@ -3305,13 +3305,6 @@ Type TypeChecker::errorUnion(Type type1, Type type2) { return type1->getASTContext().getErrorExistentialType(); } -Type swift::getEffectiveThrownErrorTypeOrNever(AnyFunctionType *func) { - if (auto thrownError = func->getEffectiveThrownInterfaceType()) - return *thrownError; - - return func->getASTContext().getNeverType(); -} - namespace { /// Classifies a thrown error kind as Never, a specific type, or 'any Error'. diff --git a/lib/Sema/TypeCheckEffects.h b/lib/Sema/TypeCheckEffects.h index 985b4155caf79..7399d2bef1b6d 100644 --- a/lib/Sema/TypeCheckEffects.h +++ b/lib/Sema/TypeCheckEffects.h @@ -22,11 +22,6 @@ namespace swift { -/// Retrieve the effective thrown error type for the given function type, which -/// is the thrown error type (is specified), `any Error` if untyped throwing, or -/// `Never` if non-throwing. -Type getEffectiveThrownErrorTypeOrNever(AnyFunctionType *func); - /// Classifies the result of a subtyping comparison between two thrown error /// types. enum class ThrownErrorSubtyping { diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index 619197e2e09c5..ef6ee0032e766 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -861,8 +861,8 @@ RequirementMatch swift::matchWitness( if (!reqThrownError) { // Save the thrown error types of the requirement and witness so we // can check them later. - reqThrownError = getEffectiveThrownErrorTypeOrNever(reqFnType); - witnessThrownError = getEffectiveThrownErrorTypeOrNever(witnessFnType); + reqThrownError = reqFnType->getEffectiveThrownErrorTypeOrNever(); + witnessThrownError = witnessFnType->getEffectiveThrownErrorTypeOrNever(); } } } else { @@ -901,13 +901,19 @@ RequirementMatch swift::matchWitness( return RequirementMatch(witness, MatchKind::ThrowsConflict); case ThrownErrorSubtyping::ExactMatch: - case ThrownErrorSubtyping::Subtype: // All is well. break; + case ThrownErrorSubtyping::Subtype: + // If there were no type parameters, we're done. + if (!reqThrownError->hasTypeParameter()) + break; + + LLVM_FALLTHROUGH; + case ThrownErrorSubtyping::Dependent: // We need to perform type matching - if (auto result = matchTypes(witnessThrownError, reqThrownError)) { + if (auto result = matchTypes(reqThrownError, witnessThrownError)) { return std::move(result.value()); } break; diff --git a/lib/Sema/TypeCheckProtocolInference.cpp b/lib/Sema/TypeCheckProtocolInference.cpp index d2169b5451f8e..c6658ff5b839a 100644 --- a/lib/Sema/TypeCheckProtocolInference.cpp +++ b/lib/Sema/TypeCheckProtocolInference.cpp @@ -751,6 +751,10 @@ AssociatedTypeInference::inferTypeWitnessesViaValueWitness(ValueDecl *req, TypeBase *secondType, Type sugaredFirstType) { return true; } + + bool considerThrownErrorTypes(Type errorType1, Type errorType2) const { + return errorType1->hasTypeParameter(); + } }; // Match a requirement and witness type. diff --git a/test/decl/protocol/conforms/typed_throws.swift b/test/decl/protocol/conforms/typed_throws.swift index ae4220ef3e162..9be3e3b751e1f 100644 --- a/test/decl/protocol/conforms/typed_throws.swift +++ b/test/decl/protocol/conforms/typed_throws.swift @@ -38,3 +38,34 @@ struct ConformingToVeryThrowing: VeryThrowing { // FIXME: Diagnostic above should be better var prop4: Int { get throws(SubError) { 0 } } } + +// Associated type inference. +protocol FailureAssociatedType { + associatedtype Failure: Error + + func f() throws(Failure) +} + +struct S1: FailureAssociatedType { + func f() throws(MyError) { } +} + +struct S2: FailureAssociatedType { + func f() throws { } +} + +struct S3: FailureAssociatedType { + func f() { } +} + +func testAssociatedTypes() { + let _ = S1.Failure() // expected-error{{'S1.Failure' (aka 'MyError') cannot be constructed because it has no accessible initializers}} + let _ = S2.Failure() // expected-error{{'S2.Failure' (aka 'any Error') cannot be constructed because it has no accessible initializers}} + let _: Int = S3.Failure() // expected-error{{cannot convert value of type 'S3.Failure' (aka 'Never') to specified type 'Int'}} + // expected-error@-1{{missing argument for parameter 'from' in call}} +} + +// Make sure we can throw the generic failure type. +func assocFailureType(_ value: T, _ error: T.Failure) throws(T.Failure) { + throw error +}