[ConstraintSystem] Repair and diagnose failures relared to throws mismatch

If the only difference between two functions is `throws` and it
is not a subtype relationship, let's repair the problem by dropping
`throws` attribute and letting solver continue to search for
a solution, which would later be diagnosed.
This commit is contained in:
Pavel Yaskevich
2019-08-12 09:37:03 -07:00
parent 290ec3c1cc
commit 15ae692da0
9 changed files with 89 additions and 31 deletions

View File

@@ -1858,19 +1858,6 @@ bool ContextualFailure::diagnoseAsError() {
if (diagnoseConversionToBool()) if (diagnoseConversionToBool())
return true; return true;
auto *fnType1 = getFromType()->getAs<FunctionType>();
auto *fnType2 = getToType()->getAs<FunctionType>();
if (fnType1 && fnType2) {
if (fnType1->throws() != fnType2->throws()) {
auto throwingTy = fnType1->throws() ? fnType1 : fnType2;
emitDiagnostic(anchor->getLoc(), diag::throws_functiontype_mismatch,
throwingTy, throwingTy == fnType1 ? fnType2 : fnType1)
.highlight(anchor->getSourceRange());
return true;
}
}
if (auto msg = getDiagnosticFor(getContextualTypePurpose(), if (auto msg = getDiagnosticFor(getContextualTypePurpose(),
/*forProtocol=*/false)) { /*forProtocol=*/false)) {
diagnostic = *msg; diagnostic = *msg;
@@ -4084,3 +4071,10 @@ bool InvalidTupleSplatWithSingleParameterFailure::diagnoseAsError() {
.fixItInsert(argExpr->getEndLoc(), ")"); .fixItInsert(argExpr->getEndLoc(), ")");
return true; return true;
} }
bool ThrowingFunctionConversionFailure::diagnoseAsError() {
auto *anchor = getAnchor();
emitDiagnostic(anchor->getLoc(), diag::throws_functiontype_mismatch,
getFromType(), getToType());
return true;
}

View File

@@ -764,6 +764,28 @@ protected:
getDiagnosticFor(ContextualTypePurpose context, bool forProtocol); getDiagnosticFor(ContextualTypePurpose context, bool forProtocol);
}; };
/// Diagnose failures related to conversion between throwing function type
/// and non-throwing one e.g.
///
/// ```swift
/// func foo<T>(_ t: T) throws -> Void {}
/// let _: (Int) -> Void = foo // `foo` can't be implictly converted to
/// // non-throwing type `(Int) -> Void`
/// ```
class ThrowingFunctionConversionFailure final : public ContextualFailure {
public:
ThrowingFunctionConversionFailure(Expr *root, ConstraintSystem &cs,
Type fromType, Type toType,
ConstraintLocator *locator)
: ContextualFailure(root, cs, fromType, toType, locator) {
auto fnType1 = fromType->castTo<FunctionType>();
auto fnType2 = toType->castTo<FunctionType>();
assert(fnType1->throws() != fnType2->throws());
}
bool diagnoseAsError() override;
};
/// Diagnose failures related attempt to implicitly convert types which /// Diagnose failures related attempt to implicitly convert types which
/// do not support such implicit converstion. /// do not support such implicit converstion.
/// "as" or "as!" has to be specified explicitly in cases like that. /// "as" or "as!" has to be specified explicitly in cases like that.

View File

@@ -744,6 +744,21 @@ bool AllowTupleSplatForSingleParameter::attempt(
return cs.recordFix(fix); return cs.recordFix(fix);
} }
bool DropThrowsAttribute::diagnose(Expr *root, bool asNote) const {
auto &cs = getConstraintSystem();
ThrowingFunctionConversionFailure failure(root, cs, getFromType(),
getToType(), getLocator());
return failure.diagnose(asNote);
}
DropThrowsAttribute *DropThrowsAttribute::create(ConstraintSystem &cs,
FunctionType *fromType,
FunctionType *toType,
ConstraintLocator *locator) {
return new (cs.getAllocator())
DropThrowsAttribute(cs, fromType, toType, locator);
}
bool IgnoreContextualType::diagnose(Expr *root, bool asNote) const { bool IgnoreContextualType::diagnose(Expr *root, bool asNote) const {
auto &cs = getConstraintSystem(); auto &cs = getConstraintSystem();
ContextualFailure failure(root, cs, getFromType(), getToType(), getLocator()); ContextualFailure failure(root, cs, getFromType(), getToType(), getLocator());

View File

@@ -494,6 +494,26 @@ public:
ConstraintLocator *locator); ConstraintLocator *locator);
}; };
/// This is a contextual mismatch between throwing and non-throwing
/// function types, repair it by dropping `throws` attribute.
class DropThrowsAttribute final : public ContextualMismatch {
DropThrowsAttribute(ConstraintSystem &cs, FunctionType *fromType,
FunctionType *toType, ConstraintLocator *locator)
: ContextualMismatch(cs, fromType, toType, locator) {
assert(fromType->throws() != toType->throws());
}
public:
std::string getName() const override { return "drop 'throws' attribute"; }
bool diagnose(Expr *root, bool asNote = false) const override;
static DropThrowsAttribute *create(ConstraintSystem &cs,
FunctionType *fromType,
FunctionType *toType,
ConstraintLocator *locator);
};
/// Append 'as! T' to force a downcast to the specified type. /// Append 'as! T' to force a downcast to the specified type.
class ForceDowncast final : public ContextualMismatch { class ForceDowncast final : public ContextualMismatch {
ForceDowncast(ConstraintSystem &cs, Type fromType, Type toType, ForceDowncast(ConstraintSystem &cs, Type fromType, Type toType,

View File

@@ -1323,8 +1323,15 @@ ConstraintSystem::matchFunctionTypes(FunctionType *func1, FunctionType *func2,
// A non-throwing function can be a subtype of a throwing function. // A non-throwing function can be a subtype of a throwing function.
if (func1->throws() != func2->throws()) { if (func1->throws() != func2->throws()) {
// Cannot drop 'throws'. // Cannot drop 'throws'.
if (func1->throws() || kind < ConstraintKind::Subtype) if (func1->throws() || kind < ConstraintKind::Subtype) {
return getTypeMatchFailure(locator); if (!shouldAttemptFixes())
return getTypeMatchFailure(locator);
auto *fix = DropThrowsAttribute::create(*this, func1, func2,
getConstraintLocator(locator));
if (recordFix(fix))
return getTypeMatchFailure(locator);
}
} }
// A non-@noescape function type can be a subtype of a @noescape function // A non-@noescape function type can be a subtype of a @noescape function
@@ -7154,8 +7161,8 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
// been diagnosed as "missing explicit call", let's // been diagnosed as "missing explicit call", let's
// increase the score to make sure that we don't impede that. // increase the score to make sure that we don't impede that.
if (auto *fnType = type1->getAs<FunctionType>()) { if (auto *fnType = type1->getAs<FunctionType>()) {
auto result = auto result = matchTypes(fnType->getResult(), type2, matchKind,
matchTypes(fnType->getResult(), type2, matchKind, subflags, locator); TMF_ApplyingFix, locator);
if (result == SolutionKind::Solved) if (result == SolutionKind::Solved)
increaseScore(SK_Fix); increaseScore(SK_Fix);
} }

View File

@@ -53,7 +53,7 @@ func process(_ line: UInt = #line) -> Int { return 0 }
func dangerous() throws {} func dangerous() throws {}
func test() { func test() {
process { // expected-error {{invalid conversion from throwing function of type '() throws -> ()' to non-throwing function type '() -> Void'}} process { // expected-error {{invalid conversion from throwing function of type '() throws -> Void' to non-throwing function type '() -> Void'}}
try dangerous() try dangerous()
test() test()
} }

View File

@@ -1411,7 +1411,7 @@ func processArrayOfFunctions(f1: [((Bool, Bool)) -> ()],
} }
f2.forEach { (block: ((Bool, Bool)) -> ()) in f2.forEach { (block: ((Bool, Bool)) -> ()) in
// expected-error@-1 {{cannot convert value of type '(((Bool, Bool)) -> ()) -> ()' to expected argument type '(@escaping (Bool, Bool) -> ()) -> Void}} // expected-error@-1 {{cannot convert value of type '(((Bool, Bool)) -> ()) -> ()' to expected argument type '(@escaping (Bool, Bool) -> ()) throws -> Void'}}
block(p) block(p)
block((c, c)) block((c, c))
block(c, c) block(c, c)

View File

@@ -51,7 +51,7 @@ func partialApply2<T: Parallelogram>(_ t: T) {
func barG<T>(_ t : T) throws -> T { return t } func barG<T>(_ t : T) throws -> T { return t }
func fooG<T>(_ t : T) -> T { return t } func fooG<T>(_ t : T) -> T { return t }
var bGE: (_ i: Int) -> Int = barG // expected-error{{invalid conversion from throwing function of type '(_) throws -> _' to non-throwing function type '(Int) -> Int'}} var bGE: (_ i: Int) -> Int = barG // expected-error{{invalid conversion from throwing function of type '(Int) throws -> Int' to non-throwing function type '(Int) -> Int'}}
var bg: (_ i: Int) throws -> Int = barG var bg: (_ i: Int) throws -> Int = barG
var fG: (_ i: Int) throws -> Int = fooG var fG: (_ i: Int) throws -> Int = fooG
@@ -69,15 +69,15 @@ func fooT(_ callback: () throws -> Bool) {} //OK
func fooT(_ callback: () -> Bool) {} func fooT(_ callback: () -> Bool) {}
// Throwing and non-throwing types are not equivalent. // Throwing and non-throwing types are not equivalent.
struct X<T> { } // expected-note {{arguments to generic parameter 'T' ('(String) -> Int' and '(String) throws -> Int') are expected to be equal}} struct X<T> { }
// expected-note@-1 {{arguments to generic parameter 'T' ('(String) throws -> Int' and '(String) -> Int') are expected to be equal}}
func specializedOnFuncType1(_ x: X<(String) throws -> Int>) { } func specializedOnFuncType1(_ x: X<(String) throws -> Int>) { }
func specializedOnFuncType2(_ x: X<(String) -> Int>) { } func specializedOnFuncType2(_ x: X<(String) -> Int>) { }
func testSpecializedOnFuncType(_ xThrows: X<(String) throws -> Int>, func testSpecializedOnFuncType(_ xThrows: X<(String) throws -> Int>,
xNonThrows: X<(String) -> Int>) { xNonThrows: X<(String) -> Int>) {
specializedOnFuncType1(xThrows) // ok specializedOnFuncType1(xThrows) // ok
specializedOnFuncType1(xNonThrows) // expected-error{{cannot convert value of type 'X<(String) -> Int>' to expected argument type 'X<(String) throws -> Int>'}} specializedOnFuncType1(xNonThrows) // expected-error{{invalid conversion from throwing function of type '(String) -> Int' to non-throwing function type '(String) throws -> Int'}}
specializedOnFuncType2(xThrows) // expected-error{{cannot convert value of type 'X<(String) throws -> Int>' to expected argument type 'X<(String) -> 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 specializedOnFuncType2(xNonThrows) // ok
} }
@@ -85,7 +85,7 @@ func testSpecializedOnFuncType(_ xThrows: X<(String) throws -> Int>,
func subtypeResult1(_ x: (String) -> ((Int) -> String)) { } func subtypeResult1(_ x: (String) -> ((Int) -> String)) { }
func testSubtypeResult1(_ x1: (String) -> ((Int) throws -> String), func testSubtypeResult1(_ x1: (String) -> ((Int) throws -> String),
x2: (String) -> ((Int) -> String)) { x2: (String) -> ((Int) -> String)) {
subtypeResult1(x1) // expected-error{{cannot convert value of type '(String) -> ((Int) throws -> String)' to expected argument type '(String) -> ((Int) -> String)'}} subtypeResult1(x1) // expected-error{{invalid conversion from throwing function of type '(Int) throws -> String' to non-throwing function type '(Int) -> String'}}
subtypeResult1(x2) subtypeResult1(x2)
} }
@@ -106,7 +106,7 @@ func testSubtypeArgument1(_ x1: (_ fn: ((String) -> Int)) -> Int,
func subtypeArgument2(_ x: (_ fn: ((String) throws -> Int)) -> Int) { } func subtypeArgument2(_ x: (_ fn: ((String) throws -> Int)) -> Int) { }
func testSubtypeArgument2(_ x1: (_ fn: ((String) -> Int)) -> Int, func testSubtypeArgument2(_ x1: (_ fn: ((String) -> Int)) -> Int,
x2: (_ fn: ((String) throws -> Int)) -> Int) { x2: (_ fn: ((String) throws -> Int)) -> Int) {
subtypeArgument2(x1) // expected-error{{cannot convert value of type '(((String) -> Int)) -> Int' to expected argument type '(((String) throws -> Int)) -> Int'}} subtypeArgument2(x1) // expected-error{{invalid conversion from throwing function of type '(String) throws -> Int' to non-throwing function type '(String) -> Int'}}
subtypeArgument2(x2) subtypeArgument2(x2)
} }
@@ -117,10 +117,10 @@ var c3 : () -> Int = c1 // expected-error{{invalid conversion from throwing func
var c4 : () -> Int = {() throws -> Int in 0} // expected-error{{invalid conversion from throwing function of type '() throws -> Int' to non-throwing function type '() -> Int'}} var c4 : () -> Int = {() throws -> Int in 0} // expected-error{{invalid conversion from throwing function of type '() throws -> Int' to non-throwing function type '() -> Int'}}
var c5 : () -> Int = { try c2() } // expected-error{{invalid conversion from throwing function of type '() throws -> Int' to non-throwing function type '() -> Int'}} var c5 : () -> Int = { try c2() } // expected-error{{invalid conversion from throwing function of type '() throws -> Int' to non-throwing function type '() -> Int'}}
var c6 : () throws -> Int = { do { _ = try c2() } ; return 0 } var c6 : () throws -> Int = { do { _ = try c2() } ; return 0 }
var c7 : () -> Int = { do { try c2() } ; return 0 } // expected-error{{invalid conversion from throwing function of type '() throws -> _' to non-throwing function type '() -> Int'}} var c7 : () -> Int = { do { try c2() } ; return 0 } // expected-error{{invalid conversion from throwing function of type '() throws -> Int' to non-throwing function type '() -> Int'}}
var c8 : () -> Int = { do { _ = try c2() } catch _ { var x = 0 } ; return 0 } // expected-warning {{initialization of variable 'x' was never used; consider replacing with assignment to '_' or removing it}} var c8 : () -> Int = { do { _ = try c2() } catch _ { var x = 0 } ; return 0 } // expected-warning {{initialization of variable 'x' was never used; consider replacing with assignment to '_' or removing it}}
var c9 : () -> Int = { do { try c2() } catch Exception.A { var x = 0 } ; return 0 }// expected-error{{invalid conversion from throwing function of type '() throws -> _' to non-throwing function type '() -> Int'}} var c9 : () -> Int = { do { try c2() } catch Exception.A { var x = 0 } ; return 0 }// expected-error{{invalid conversion from throwing function of type '() throws -> Int' to non-throwing function type '() -> Int'}}
var c10 : () -> Int = { throw Exception.A; return 0 } // expected-error{{invalid conversion from throwing function of type '() throws -> _' to non-throwing function type '() -> Int'}} var c10 : () -> Int = { throw Exception.A; return 0 } // expected-error{{invalid conversion from throwing function of type '() throws -> Int' to non-throwing function type '() -> Int'}}
var c11 : () -> Int = { try! c2() } var c11 : () -> Int = { try! c2() }
var c12 : () -> Int? = { try? c2() } var c12 : () -> Int? = { try? c2() }

View File

@@ -145,7 +145,7 @@ func eleven_two() {
enum Twelve { case Payload(Int) } enum Twelve { case Payload(Int) }
func twelve_helper(_ fn: (Int, Int) -> ()) {} func twelve_helper(_ fn: (Int, Int) -> ()) {}
func twelve() { func twelve() {
twelve_helper { (a, b) in // expected-error {{invalid conversion from throwing function of type '(_, _) throws -> ()' to non-throwing function type '(Int, Int) -> ()'}} twelve_helper { (a, b) in // expected-error {{invalid conversion from throwing function of type '(Int, Int) throws -> ()' to non-throwing function type '(Int, Int) -> ()'}}
do { do {
try thrower() try thrower()
} catch Twelve.Payload(a...b) { } catch Twelve.Payload(a...b) {
@@ -158,7 +158,7 @@ func ==(a: Thirteen, b: Thirteen) -> Bool { return true }
func thirteen_helper(_ fn: (Thirteen) -> ()) {} func thirteen_helper(_ fn: (Thirteen) -> ()) {}
func thirteen() { func thirteen() {
thirteen_helper { (a) in // expected-error {{invalid conversion from throwing function of type '(_) throws -> ()' to non-throwing function type '(Thirteen) -> ()'}} thirteen_helper { (a) in // expected-error {{invalid conversion from throwing function of type '(Thirteen) throws -> ()' to non-throwing function type '(Thirteen) -> ()'}}
do { do {
try thrower() try thrower()
} catch a { } catch a {