mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[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:
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
if (!shouldAttemptFixes())
|
||||||
return getTypeMatchFailure(locator);
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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() }
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user