diff --git a/lib/Sema/CSDiag.cpp b/lib/Sema/CSDiag.cpp index 5c6caee3cb643..7ca8b1eb689f4 100644 --- a/lib/Sema/CSDiag.cpp +++ b/lib/Sema/CSDiag.cpp @@ -1147,10 +1147,9 @@ bool FailureDiagnosis::diagnoseGeneralConversionFailure(Constraint *constraint){ if (auto fromTT = fromType->getAs()) if (auto toTT = toType->getAs()) { if (fromTT->getNumElements() != toTT->getNumElements()) { - diagnose(anchor->getLoc(), diag::tuple_types_not_convertible_nelts, - fromTT, toTT) - .highlight(anchor->getSourceRange()); - return true; + auto failure = TupleContextualFailure(anchor, CS, fromTT, toTT, + CS.getConstraintLocator(expr)); + return failure.diagnoseAsError(); } SmallVector FromElts; @@ -1166,10 +1165,9 @@ bool FailureDiagnosis::diagnoseGeneralConversionFailure(Constraint *constraint){ // then we have a type error. if (computeTupleShuffle(TEType->castTo()->getElements(), toTT->getElements(), sources)) { - diagnose(anchor->getLoc(), diag::tuple_types_not_convertible, - fromTT, toTT) - .highlight(anchor->getSourceRange()); - return true; + auto failure = TupleContextualFailure(anchor, CS, fromTT, toTT, + CS.getConstraintLocator(expr)); + return failure.diagnoseAsError(); } } diff --git a/lib/Sema/CSDiagnostics.cpp b/lib/Sema/CSDiagnostics.cpp index 8c8532d38f886..f29732dd8eb9c 100644 --- a/lib/Sema/CSDiagnostics.cpp +++ b/lib/Sema/CSDiagnostics.cpp @@ -1887,6 +1887,14 @@ void ContextualFailure::tryComputedPropertyFixIts(Expr *expr) const { } } +bool TupleContextualFailure::diagnoseAsError() { + auto diagnostic = isNumElementsMismatch() + ? diag::tuple_types_not_convertible_nelts + : diag::tuple_types_not_convertible; + emitDiagnostic(getAnchor()->getLoc(), diagnostic, getFromType(), getToType()); + return true; +} + bool AutoClosureForwardingFailure::diagnoseAsError() { auto path = getLocator()->getPath(); assert(!path.empty()); diff --git a/lib/Sema/CSDiagnostics.h b/lib/Sema/CSDiagnostics.h index 915a950614898..227341f11f828 100644 --- a/lib/Sema/CSDiagnostics.h +++ b/lib/Sema/CSDiagnostics.h @@ -759,6 +759,23 @@ class ContextualFailure : public FailureDiagnostic { void tryComputedPropertyFixIts(Expr *expr) const; }; +/// Diagnose mismatches relating to tuple destructuring. +class TupleContextualFailure final : public ContextualFailure { +public: + TupleContextualFailure(Expr *root, ConstraintSystem &cs, Type lhs, Type rhs, + ConstraintLocator *locator) + : ContextualFailure(root, cs, lhs, rhs, locator) {} + + bool diagnoseAsError() override; + + bool isNumElementsMismatch() const { + auto lhsTy = getFromType()->castTo(); + auto rhsTy = getToType()->castTo(); + assert(lhsTy && rhsTy); + return lhsTy->getNumElements() != rhsTy->getNumElements(); + } +}; + /// Diagnose situations when @autoclosure argument is passed to @autoclosure /// parameter directly without calling it first. class AutoClosureForwardingFailure final : public FailureDiagnostic { diff --git a/lib/Sema/CSFix.cpp b/lib/Sema/CSFix.cpp index 8237bae40aa67..5f496f3d57e24 100644 --- a/lib/Sema/CSFix.cpp +++ b/lib/Sema/CSFix.cpp @@ -229,6 +229,20 @@ ContextualMismatch *ContextualMismatch::create(ConstraintSystem &cs, Type lhs, return new (cs.getAllocator()) ContextualMismatch(cs, lhs, rhs, locator); } +bool AllowTupleTypeMismatch::diagnose(Expr *root, bool asNote) const { + auto failure = TupleContextualFailure( + root, getConstraintSystem(), getFromType(), getToType(), getLocator()); + return failure.diagnose(asNote); +} + +AllowTupleTypeMismatch * +AllowTupleTypeMismatch::create(ConstraintSystem &cs, Type lhs, Type rhs, + ConstraintLocator *locator) { + assert(lhs->is() && rhs->is() && + "lhs and rhs must be tuple types"); + return new (cs.getAllocator()) AllowTupleTypeMismatch(cs, lhs, rhs, locator); +} + bool GenericArgumentsMismatch::diagnose(Expr *root, bool asNote) const { auto failure = GenericArgumentsMismatchFailure(root, getConstraintSystem(), getActual(), getRequired(), diff --git a/lib/Sema/CSFix.h b/lib/Sema/CSFix.h index 569042c93b0e9..ebdf63c2b2521 100644 --- a/lib/Sema/CSFix.h +++ b/lib/Sema/CSFix.h @@ -136,6 +136,10 @@ enum class FixKind : uint8_t { /// referenced constructor must be required. AllowInvalidInitRef, + /// Allow a tuple to be destructured with mismatched arity, or mismatched + /// types. + AllowTupleTypeMismatch, + /// Allow an invalid member access on a value of protocol type as if /// that protocol type were a generic constraint requiring conformance /// to that protocol. @@ -494,6 +498,9 @@ class ContextualMismatch : public ConstraintFix { ConstraintLocator *locator) : ConstraintFix(cs, FixKind::ContextualMismatch, locator), LHS(lhs), RHS(rhs) {} + ContextualMismatch(ConstraintSystem &cs, FixKind kind, Type lhs, Type rhs, + ConstraintLocator *locator) + : ConstraintFix(cs, kind, locator), LHS(lhs), RHS(rhs) {} public: std::string getName() const override { return "fix contextual mismatch"; } @@ -862,6 +869,23 @@ class AllowInvalidInitRef final : public ConstraintFix { ConstraintLocator *locator); }; +class AllowTupleTypeMismatch final : public ContextualMismatch { + AllowTupleTypeMismatch(ConstraintSystem &cs, Type lhs, Type rhs, + ConstraintLocator *locator) + : ContextualMismatch(cs, FixKind::AllowTupleTypeMismatch, lhs, rhs, + locator) {} + +public: + static AllowTupleTypeMismatch *create(ConstraintSystem &cs, Type lhs, + Type rhs, ConstraintLocator *locator); + + std::string getName() const override { + return "fix tuple mismatches in type and arity"; + } + + bool diagnose(Expr *root, bool asNote = false) const override; +}; + class AllowMutatingMemberOnRValueBase final : public AllowInvalidMemberRef { AllowMutatingMemberOnRValueBase(ConstraintSystem &cs, Type baseType, ValueDecl *member, DeclName name, diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 3099f1bd1fa09..40757bff58f5e 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -2382,6 +2382,13 @@ bool ConstraintSystem::repairFailures( getConstraintLocator(locator)); conversionsOrFixes.push_back(fix); } + + if (purpose == CTP_Initialization && lhs->is() && + rhs->is()) { + auto *fix = AllowTupleTypeMismatch::create(*this, lhs, rhs, + getConstraintLocator(locator)); + conversionsOrFixes.push_back(fix); + } break; } @@ -2413,6 +2420,11 @@ bool ConstraintSystem::repairFailures( conversionsOrFixes.push_back(CollectionElementContextualMismatch::create( *this, lhs, rhs, getConstraintLocator(locator))); } + if (lhs->is() && rhs->is()) { + auto *fix = AllowTupleTypeMismatch::create(*this, lhs, rhs, + getConstraintLocator(locator)); + conversionsOrFixes.push_back(fix); + } break; } @@ -6951,6 +6963,45 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint( return matchTypes(type1, type2, matchKind, subflags, locator); } + case FixKind::AllowTupleTypeMismatch: { + auto lhs = type1->castTo(); + auto rhs = type2->castTo(); + // Create a new tuple type the size of the smaller tuple with elements + // from the larger tuple whenever either side contains a type variable. + // For example (A, $0, B, $2) and (X, Y, $1) produces: (X, $0, B). + // This allows us to guarentee that the types will match, and all + // type variables will get bound to something as long as we default + // excess types in the larger tuple to Any. In the prior example, + // when the tuples (X, Y, $1) and (X, $0, B) get matched, $0 is equated + // to Y, $1 is equated to B, and $2 is defaulted to Any. + auto lhsLarger = lhs->getNumElements() >= rhs->getNumElements(); + auto larger = lhsLarger ? lhs : rhs; + auto smaller = lhsLarger ? rhs : lhs; + llvm::SmallVector newTupleTypes; + + for (unsigned i = 0; i < larger->getNumElements(); ++i) { + auto largerElt = larger->getElement(i); + if (i < smaller->getNumElements()) { + auto smallerElt = smaller->getElement(i); + if (largerElt.getType()->isTypeVariableOrMember() || + smallerElt.getType()->isTypeVariableOrMember()) + newTupleTypes.push_back(largerElt); + else + newTupleTypes.push_back(smallerElt); + } else { + if (largerElt.getType()->isTypeVariableOrMember()) + addConstraint(ConstraintKind::Defaultable, largerElt.getType(), + getASTContext().TheAnyType, + getConstraintLocator(locator)); + } + } + auto matchingType = + TupleType::get(newTupleTypes, getASTContext())->castTo(); + if (recordFix(fix)) + return SolutionKind::Error; + return matchTupleTypes(matchingType, smaller, matchKind, subflags, locator); + } + case FixKind::InsertCall: case FixKind::RemoveReturn: case FixKind::AddConformance: diff --git a/test/Constraints/sr10728.swift b/test/Constraints/sr10728.swift index c91d85e2298fc..88e3f0b606646 100644 --- a/test/Constraints/sr10728.swift +++ b/test/Constraints/sr10728.swift @@ -19,5 +19,5 @@ struct S: P { typealias R = T3 static let foo: (T1, (R) -> T2) = bind() - // expected-error@-1 {{cannot convert value of type '(T1, (S.R) -> T3)' (aka '(Int, (Bool) -> Bool)') to specified type '(T1, (S.R) -> T2)' (aka '(Int, (Bool) -> Float)')}} + // expected-error@-1 {{tuple type '(T1, (S.R) -> T3)' (aka '(Int, (Bool) -> Bool)') is not convertible to tuple '(T1, (S.R) -> T2)' (aka '(Int, (Bool) -> Float)')}} } diff --git a/test/NameBinding/name-binding.swift b/test/NameBinding/name-binding.swift index c2f6be7a90795..e38184cc8e718 100644 --- a/test/NameBinding/name-binding.swift +++ b/test/NameBinding/name-binding.swift @@ -55,8 +55,8 @@ func test_varname_binding() { var (d, e) = (c.1, c.0) var ((), (g1, g2), h) = ((), (e, d), e) var (j, k, l) = callee1() - var (m, n) = callee1() // expected-error{{'(Int, Int, Int)' is not convertible to '(_, _)', tuples have a different number of elements}} - var (o, p, q, r) = callee1() // expected-error{{'(Int, Int, Int)' is not convertible to '(_, _, _, _)', tuples have a different number of elements}} + var (m, n) = callee1() // expected-error{{'(Int, Int, Int)' is not convertible to '(Int, Int)', tuples have a different number of elements}} + var (o, p, q, r) = callee1() // expected-error{{'(Int, Int, Int)' is not convertible to '(Int, Int, Int, Any)', tuples have a different number of elements}} } //===----------------------------------------------------------------------===// diff --git a/test/expr/expressions.swift b/test/expr/expressions.swift index 8d51cdcca5edd..da6beb87240ed 100644 --- a/test/expr/expressions.swift +++ b/test/expr/expressions.swift @@ -139,6 +139,7 @@ func funcdecl7(_ a: Int, b: (c: Int, d: Int), third: (c: Int, d: Int)) -> Int { // Error recovery. func testfunc2 (_: ((), Int) -> Int) -> Int {} +func makeTuple() -> (String, Int) { return ("foo", 42) } func errorRecovery() { testfunc2({ $0 + 1 }) // expected-error {{contextual closure type '((), Int) -> Int' expects 2 arguments, but 1 was used in closure body}} @@ -149,12 +150,14 @@ func errorRecovery() { var a: Int = .hello // expected-error {{type 'Int' has no member 'hello'}} var b: union1 = .bar // ok var c: union1 = .xyz // expected-error {{type 'union1' has no member 'xyz'}} - var d: (Int,Int,Int) = (1,2) // expected-error {{cannot convert value of type '(Int, Int)' to specified type '(Int, Int, Int)'}} - var e: (Int,Int) = (1, 2, 3) // expected-error {{cannot convert value of type '(Int, Int, Int)' to specified type '(Int, Int)'}} - var f: (Int,Int) = (1, 2, f : 3) // expected-error {{cannot convert value of type '(Int, Int, f: Int)' to specified type '(Int, Int)'}} + var d: (Int,Int,Int) = (1,2) // expected-error {{'(Int, Int)' is not convertible to '(Int, Int, Int)', tuples have a different number of elements}} + var e: (Int,Int) = (1, 2, 3) // expected-error {{'(Int, Int, Int)' is not convertible to '(Int, Int)', tuples have a different number of elements}} + var f: (Int,Int) = (1, 2, f : 3) // expected-error {{'(Int, Int, f: Int)' is not convertible to '(Int, Int)', tuples have a different number of elements}} // CrashTracer: [USER] swift at …mous_namespace::ConstraintGenerator::getTypeForPattern + 698 - var (g1, g2, g3) = (1, 2) // expected-error {{'(Int, Int)' is not convertible to '(_, _, _)', tuples have a different number of elements}} + var (g1, g2, g3) = (1, 2) // expected-error {{'(Int, Int)' is not convertible to '(Int, Int, Any)', tuples have a different number of elements}} + var (h1, h2) = (1, 2, 3) // expected-error {{'(Int, Int, Int)' is not convertible to '(Int, Int)', tuples have a different number of elements}} + var i: (Bool, Bool) = makeTuple() // expected-error {{tuple type '(String, Int)' is not convertible to tuple '(Bool, Bool)'}} } func acceptsInt(_ x: Int) {} @@ -185,7 +188,7 @@ func test4() -> ((_ arg1: Int, _ arg2: Int) -> Int) { func test5() { let a: (Int, Int) = (1,2) var - _: ((Int) -> Int, Int) = a // expected-error {{cannot convert value of type '(Int, Int)' to specified type '((Int) -> Int, Int)'}} + _: ((Int) -> Int, Int) = a // expected-error {{tuple type '(Int, Int)' is not convertible to tuple '((Int) -> Int, Int)'}} let c: (a: Int, b: Int) = (1,2)