Skip to content

Commit a8ed80d

Browse files
committed
[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.
1 parent 0bb3130 commit a8ed80d

File tree

5 files changed

+231
-89
lines changed

5 files changed

+231
-89
lines changed

lib/Sema/CSSimplify.cpp

Lines changed: 45 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include "CSDiagnostics.h"
1919
#include "TypeCheckConcurrency.h"
20+
#include "TypeCheckEffects.h"
2021
#include "swift/AST/ASTPrinter.h"
2122
#include "swift/AST/Decl.h"
2223
#include "swift/AST/ExistentialLayout.h"
@@ -2945,28 +2946,6 @@ bool ConstraintSystem::hasPreconcurrencyCallee(
29452946
return calleeOverload->choice.getDecl()->preconcurrency();
29462947
}
29472948

2948-
namespace {
2949-
/// Classifies a thrown error kind as Never, a specific type, or 'any Error'.
2950-
enum class ThrownErrorKind {
2951-
Never,
2952-
Specific,
2953-
AnyError,
2954-
};
2955-
2956-
ThrownErrorKind getThrownErrorKind(Type type) {
2957-
if (type->isNever())
2958-
return ThrownErrorKind::Never;
2959-
2960-
if (type->isExistentialType()) {
2961-
Type anyError = type->getASTContext().getErrorExistentialType();
2962-
if (anyError->isEqual(type))
2963-
return ThrownErrorKind::AnyError;
2964-
}
2965-
2966-
return ThrownErrorKind::Specific;
2967-
}
2968-
}
2969-
29702949
/// Match the throwing specifier of the two function types.
29712950
static ConstraintSystem::TypeMatchResult
29722951
matchFunctionThrowing(ConstraintSystem &cs,
@@ -2978,82 +2957,45 @@ matchFunctionThrowing(ConstraintSystem &cs,
29782957
// that throws error type E2 when E1 is a subtype of E2. For the purpose
29792958
// of this comparison, a non-throwing function has thrown error type 'Never',
29802959
// and an untyped throwing function has thrown error type 'any Error'.
2981-
Type neverType = cs.getASTContext().getNeverType();
2982-
Type thrownError1 = func1->getEffectiveThrownInterfaceType().value_or(neverType);
2983-
Type thrownError2 = func2->getEffectiveThrownInterfaceType().value_or(neverType);
2984-
if (!thrownError1 || !thrownError2 || thrownError1->isEqual(thrownError2))
2960+
Type thrownError1 = getEffectiveThrownErrorTypeOrNever(func1);
2961+
Type thrownError2 = getEffectiveThrownErrorTypeOrNever(func2);
2962+
if (!thrownError1 || !thrownError2)
29852963
return cs.getTypeMatchSuccess();
29862964

2987-
auto thrownErrorKind1 = getThrownErrorKind(thrownError1);
2988-
auto thrownErrorKind2 = getThrownErrorKind(thrownError2);
2989-
2990-
bool mustUnify = false;
2991-
bool dropThrows = false;
2992-
2993-
switch (thrownErrorKind1) {
2994-
case ThrownErrorKind::Specific:
2995-
// If the specific thrown error contains no type variables and we're
2996-
// going to try to convert it to \c Never, treat this as dropping throws.
2997-
if (thrownErrorKind2 == ThrownErrorKind::Never &&
2998-
!thrownError1->hasTypeVariable()) {
2999-
dropThrows = true;
3000-
} else {
3001-
// We need to unify the thrown error types.
3002-
mustUnify = true;
3003-
}
3004-
break;
3005-
3006-
case ThrownErrorKind::Never:
3007-
switch (thrownErrorKind2) {
3008-
case ThrownErrorKind::Specific:
3009-
// We need to unify the thrown error types.
3010-
mustUnify = true;
3011-
break;
3012-
3013-
case ThrownErrorKind::Never:
3014-
llvm_unreachable("The thrown error types should have been equal");
3015-
break;
3016-
3017-
case ThrownErrorKind::AnyError:
3018-
// We have a subtype. If we're not allowed to do the subtype,
3019-
// then we need to drop "throws".
3020-
if (kind < ConstraintKind::Subtype)
3021-
dropThrows = true;
3022-
break;
3023-
}
3024-
break;
3025-
3026-
case ThrownErrorKind::AnyError:
3027-
switch (thrownErrorKind2) {
3028-
case ThrownErrorKind::Specific:
3029-
// We need to unify the thrown error types.
3030-
mustUnify = true;
3031-
break;
3032-
3033-
case ThrownErrorKind::Never:
3034-
// We're going to have to drop the "throws" entirely.
3035-
dropThrows = true;
3036-
break;
3037-
3038-
case ThrownErrorKind::AnyError:
3039-
llvm_unreachable("The thrown error types should have been equal");
3040-
}
3041-
break;
3042-
}
3043-
3044-
// If we know we need to drop 'throws', try it now.
3045-
if (dropThrows) {
2965+
switch (compareThrownErrorsForSubtyping(thrownError1, thrownError2, cs.DC)) {
2966+
case ThrownErrorSubtyping::DropsThrows: {
2967+
// We need to drop 'throws' to make this work.
30462968
if (!cs.shouldAttemptFixes())
30472969
return cs.getTypeMatchFailure(locator);
30482970

30492971
auto *fix = DropThrowsAttribute::create(cs, func1, func2,
30502972
cs.getConstraintLocator(locator));
30512973
if (cs.recordFix(fix))
30522974
return cs.getTypeMatchFailure(locator);
2975+
2976+
return cs.getTypeMatchSuccess();
30532977
}
30542978

3055-
// If we need to unify the thrown error types, do so now.
3056-
if (mustUnify) {
2979+
case ThrownErrorSubtyping::ExactMatch:
2980+
return cs.getTypeMatchSuccess();
2981+
2982+
case ThrownErrorSubtyping::Subtype:
2983+
// We know this is going to work, but we might still need to generate a
2984+
// constraint if one of the error types involves type variables.
2985+
if (thrownError1->hasTypeVariable() || thrownError2->hasTypeVariable()) {
2986+
// Fall through to the dependent case.
2987+
} else if (kind < ConstraintKind::Subtype) {
2988+
// We aren't allowed to have a subtype, so fail here.
2989+
return cs.getTypeMatchFailure(locator);
2990+
} else {
2991+
// We have a subtype. All set!
2992+
return cs.getTypeMatchSuccess();
2993+
}
2994+
LLVM_FALLTHROUGH;
2995+
2996+
case ThrownErrorSubtyping::Dependent: {
2997+
// The presence of type variables in the thrown error types require that
2998+
// we generate a constraint to unify the thrown error types, so do so now.
30572999
ConstraintKind subKind = (kind < ConstraintKind::Subtype)
30583000
? ConstraintKind::Equal
30593001
: ConstraintKind::Subtype;
@@ -3064,9 +3006,24 @@ matchFunctionThrowing(ConstraintSystem &cs,
30643006
locator.withPathElement(LocatorPathElt::ThrownErrorType()));
30653007
if (result == ConstraintSystem::SolutionKind::Error)
30663008
return cs.getTypeMatchFailure(locator);
3009+
3010+
return cs.getTypeMatchSuccess();
30673011
}
30683012

3069-
return cs.getTypeMatchSuccess();
3013+
case ThrownErrorSubtyping::Mismatch: {
3014+
auto thrownErrorLocator = cs.getConstraintLocator(
3015+
locator.withPathElement(LocatorPathElt::ThrownErrorType()));
3016+
if (!cs.shouldAttemptFixes())
3017+
return cs.getTypeMatchFailure(thrownErrorLocator);
3018+
3019+
auto *fix = IgnoreThrownErrorMismatch::create(
3020+
cs, thrownError1, thrownError2, thrownErrorLocator);
3021+
if (cs.recordFix(fix))
3022+
return cs.getTypeMatchFailure(thrownErrorLocator);
3023+
3024+
return cs.getTypeMatchSuccess();
3025+
}
3026+
}
30703027
}
30713028

30723029
ConstraintSystem::TypeMatchResult

lib/Sema/TypeCheckEffects.cpp

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include "TypeChecker.h"
1919
#include "TypeCheckConcurrency.h"
20+
#include "TypeCheckEffects.h"
2021
#include "swift/AST/ASTWalker.h"
2122
#include "swift/AST/DiagnosticsSema.h"
2223
#include "swift/AST/Effects.h"
@@ -3303,3 +3304,129 @@ Type TypeChecker::errorUnion(Type type1, Type type2) {
33033304
// actual union type here.
33043305
return type1->getASTContext().getErrorExistentialType();
33053306
}
3307+
3308+
Type swift::getEffectiveThrownErrorTypeOrNever(AnyFunctionType *func) {
3309+
if (auto thrownError = func->getEffectiveThrownInterfaceType())
3310+
return *thrownError;
3311+
3312+
return func->getASTContext().getNeverType();
3313+
}
3314+
3315+
namespace {
3316+
3317+
/// Classifies a thrown error kind as Never, a specific type, or 'any Error'.
3318+
enum class ThrownErrorClassification {
3319+
/// The `Never` type, which represents a non-throwing function.
3320+
Never,
3321+
3322+
/// A specific error type that is neither `Never` nor `any Error`.
3323+
Specific,
3324+
3325+
/// A specific error type that depends on a type variable or type parameter,
3326+
/// and therefore we cannot determine whether it is a subtype of another
3327+
/// type or not.
3328+
Dependent,
3329+
3330+
/// The type `any Error`, used for untyped throws.
3331+
AnyError,
3332+
};
3333+
3334+
}
3335+
3336+
/// Classify the given thrown error type.
3337+
static ThrownErrorClassification classifyThrownErrorType(Type type) {
3338+
if (type->isNever())
3339+
return ThrownErrorClassification::Never;
3340+
3341+
if (type->isExistentialType()) {
3342+
Type anyError = type->getASTContext().getErrorExistentialType();
3343+
if (anyError->isEqual(type))
3344+
return ThrownErrorClassification::AnyError;
3345+
}
3346+
3347+
if (type->hasTypeVariable() || type->hasTypeParameter())
3348+
return ThrownErrorClassification::Dependent;
3349+
3350+
return ThrownErrorClassification::Specific;
3351+
}
3352+
3353+
ThrownErrorSubtyping
3354+
swift::compareThrownErrorsForSubtyping(
3355+
Type subThrownError, Type superThrownError, DeclContext *dc
3356+
) {
3357+
// Easy case: exact match.
3358+
if (superThrownError->isEqual(subThrownError))
3359+
return ThrownErrorSubtyping::ExactMatch;
3360+
3361+
auto superThrownErrorKind = classifyThrownErrorType(superThrownError);
3362+
auto subThrownErrorKind = classifyThrownErrorType(subThrownError);
3363+
3364+
switch (subThrownErrorKind) {
3365+
case ThrownErrorClassification::Dependent:
3366+
switch (superThrownErrorKind) {
3367+
case ThrownErrorClassification::AnyError:
3368+
// This is a clear subtype relationship, because the supertype throws
3369+
// anything.
3370+
return ThrownErrorSubtyping::Subtype;
3371+
3372+
case ThrownErrorClassification::Never:
3373+
case ThrownErrorClassification::Dependent:
3374+
case ThrownErrorClassification::Specific:
3375+
// We have to compare the types. Do so below.
3376+
break;
3377+
}
3378+
break;
3379+
3380+
case ThrownErrorClassification::Specific:
3381+
switch (superThrownErrorKind) {
3382+
case ThrownErrorClassification::AnyError:
3383+
// This is a clear subtype relationship, because the supertype throws
3384+
// anything.
3385+
return ThrownErrorSubtyping::Subtype;
3386+
3387+
case ThrownErrorClassification::Never:
3388+
// The supertype doesn't throw, so this has to drop 'throws' to work.
3389+
return ThrownErrorSubtyping::DropsThrows;
3390+
3391+
case ThrownErrorClassification::Dependent:
3392+
case ThrownErrorClassification::Specific:
3393+
// We have to compare the types. Do so below.
3394+
break;
3395+
}
3396+
break;
3397+
3398+
case ThrownErrorClassification::Never:
3399+
// A function type throwing 'Never' is a subtype of all function types.
3400+
return ThrownErrorSubtyping::Subtype;
3401+
3402+
case ThrownErrorClassification::AnyError:
3403+
switch (superThrownErrorKind) {
3404+
case ThrownErrorClassification::Dependent:
3405+
case ThrownErrorClassification::Specific:
3406+
// We have to compare the types. Do so below.
3407+
break;
3408+
3409+
case ThrownErrorClassification::Never:
3410+
// We're going to have to drop the "throws" entirely.
3411+
return ThrownErrorSubtyping::DropsThrows;
3412+
3413+
case ThrownErrorClassification::AnyError:
3414+
llvm_unreachable("The thrown error types should have been equal");
3415+
}
3416+
break;
3417+
}
3418+
3419+
// If either of the types was dependent on a type variable or type parameter,
3420+
// we can't do the comparison at all.
3421+
if (superThrownErrorKind == ThrownErrorClassification::Dependent ||
3422+
subThrownErrorKind == ThrownErrorClassification::Dependent)
3423+
return ThrownErrorSubtyping::Dependent;
3424+
3425+
// Check whether the subtype's thrown error type is convertible to the
3426+
// supertype's thrown error type.
3427+
if (TypeChecker::isConvertibleTo(subThrownError, superThrownError, dc))
3428+
return ThrownErrorSubtyping::Subtype;
3429+
3430+
// We know it doesn't work.
3431+
return ThrownErrorSubtyping::Mismatch;
3432+
}

lib/Sema/TypeCheckEffects.h

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//===--- TypeCheckEffects.h - Effects checking ------------------*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// This file provides type checking support for Swift's effect checking, which
14+
// includes error handling (throws) and asynchronous (async) effects.
15+
//
16+
//===----------------------------------------------------------------------===//
17+
18+
#ifndef SWIFT_SEMA_TYPECHECKEFFECTS_H
19+
#define SWIFT_SEMA_TYPECHECKEFFECTS_H
20+
21+
#include "swift/AST/Type.h"
22+
23+
namespace swift {
24+
25+
/// Retrieve the effective thrown error type for the given function type, which
26+
/// is the thrown error type (is specified), `any Error` if untyped throwing, or
27+
/// `Never` if non-throwing.
28+
Type getEffectiveThrownErrorTypeOrNever(AnyFunctionType *func);
29+
30+
/// Classifies the result of a subtyping comparison between two thrown error
31+
/// types.
32+
enum class ThrownErrorSubtyping {
33+
/// There is no subtyping relationship, and we're trying to convert from a
34+
/// throwning type to a non-throwing type.
35+
DropsThrows,
36+
/// There is no subtyping relationship because the types mismatch.
37+
Mismatch,
38+
/// The thrown error types exactly match; there is a subtype relationship.
39+
ExactMatch,
40+
/// The thrown error types are different, but there is an obvious subtype
41+
/// relationship.
42+
Subtype,
43+
/// The thrown error types are different, and the presence of type variables
44+
/// or type parameters prevents us from determining now whether there is a
45+
/// subtype relationship.
46+
Dependent,
47+
};
48+
49+
/// Compare the thrown error types for the purposes of subtyping.
50+
ThrownErrorSubtyping compareThrownErrorsForSubtyping(
51+
Type subThrownError, Type superThrownError, DeclContext *dc);
52+
53+
}
54+
55+
#endif // SWIFT_SEMA_TYPECHECKEFFECTS_H

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,8 @@ RequirementMatch swift::matchWitness(
832832
}
833833

834834
// If the witness is 'throws', the requirement must be.
835+
// FIXME: We need the same matching we do in the constraint solver,
836+
// with the same fast paths for obvious throws/nonthrows cases.
835837
if (witnessFnType->getExtInfo().isThrowing() &&
836838
!reqFnType->getExtInfo().isThrowing()) {
837839
return RequirementMatch(witness, MatchKind::ThrowsConflict);

test/decl/func/throwing_functions.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,14 @@ func fooT(_ callback: () -> Bool) {}
7070

7171
// Throwing and non-throwing types are not equivalent.
7272
struct X<T> { }
73+
// expected-note@-1{{arguments to generic parameter 'T' ('(String) -> Int' and '(String) throws -> Int') are expected to be equal}}
7374

7475
func specializedOnFuncType1(_ x: X<(String) throws -> Int>) { }
7576
func specializedOnFuncType2(_ x: X<(String) -> Int>) { }
7677
func testSpecializedOnFuncType(_ xThrows: X<(String) throws -> Int>,
7778
xNonThrows: X<(String) -> Int>) {
7879
specializedOnFuncType1(xThrows) // ok
79-
specializedOnFuncType1(xNonThrows) // expected-error{{invalid conversion from throwing function of type '(String) -> Int' to non-throwing function type '(String) throws -> Int'}}
80+
specializedOnFuncType1(xNonThrows) // expected-error{{cannot convert value of type 'X<(String) -> Int>' to expected argument type 'X<(String) throws -> Int>'}}
8081
specializedOnFuncType2(xThrows) // expected-error{{invalid conversion from throwing function of type '(String) throws -> Int' to non-throwing function type '(String) -> Int'}}
8182
specializedOnFuncType2(xNonThrows) // ok
8283
}

0 commit comments

Comments
 (0)