diff --git a/lib/IDE/Refactoring.cpp b/lib/IDE/Refactoring.cpp index 5ea4e10ba28..2cc448c8167 100644 --- a/lib/IDE/Refactoring.cpp +++ b/lib/IDE/Refactoring.cpp @@ -2753,78 +2753,107 @@ bool RefactoringActionLocalizeString::performChange() { return false; } +struct MemberwiseParameter { + Identifier Name; + Type MemberType; + Expr *DefaultExpr; + + MemberwiseParameter(Identifier name, Type type, Expr *initialExpr) + : Name(name), MemberType(type), DefaultExpr(initialExpr) {} +}; + static void generateMemberwiseInit(SourceEditConsumer &EditConsumer, - SourceManager &SM, - SmallVectorImpl& memberNameVector, - SmallVectorImpl& memberTypeVector, - SourceLoc targetLocation) { - - assert(!memberTypeVector.empty()); - assert(memberTypeVector.size() == memberNameVector.size()); + SourceManager &SM, + ArrayRef memberVector, + SourceLoc targetLocation) { + assert(!memberVector.empty()); + EditConsumer.accept(SM, targetLocation, "\ninternal init("); - - for (size_t i = 0, n = memberTypeVector.size(); i < n ; i++) { - EditConsumer.accept(SM, targetLocation, memberNameVector[i] + ": " + - memberTypeVector[i]); - - if (i != memberTypeVector.size() - 1) { - EditConsumer.accept(SM, targetLocation, ", "); + auto insertMember = [&SM](const MemberwiseParameter &memberData, + llvm::raw_ostream &OS, bool wantsSeparator) { + OS << memberData.Name << ": " << memberData.MemberType.getString(); + if (auto *expr = memberData.DefaultExpr) { + if (isa(expr)) { + OS << " = nil"; + } else if (expr->getSourceRange().isValid()) { + auto range = + Lexer::getCharSourceRangeFromSourceRange( + SM, expr->getSourceRange()); + OS << " = " << SM.extractText(range); + } } + + if (wantsSeparator) { + OS << ", "; + } + }; + + // Process the initial list of members, inserting commas as appropriate. + std::string Buffer; + llvm::raw_string_ostream OS(Buffer); + for (const auto &memberData : memberVector.drop_back()) { + insertMember(memberData, OS, /*wantsSeparator*/ true); } - - EditConsumer.accept(SM, targetLocation, ") {\n"); - - for (auto varName: memberNameVector) { - EditConsumer.accept(SM, targetLocation, - "self." + varName + " = " + varName + "\n"); + + // Process the last (or perhaps, only) member. + insertMember(memberVector.back(), OS, /*wantsSeparator*/ false); + + // Synthesize the body. + OS << ") {\n"; + for (auto &member : memberVector) { + // self. = + OS << "self." << member.Name << " = " << member.Name << "\n"; } - - EditConsumer.accept(SM, targetLocation, "}\n"); + OS << "}\n"; + + // Accept the entire edit. + EditConsumer.accept(SM, targetLocation, OS.str()); } - -static SourceLoc collectMembersForInit(ResolvedCursorInfo CursorInfo, - SmallVectorImpl& memberNameVector, - SmallVectorImpl& memberTypeVector) { - + +static SourceLoc +collectMembersForInit(ResolvedCursorInfo CursorInfo, + SmallVectorImpl &memberVector) { + if (!CursorInfo.ValueD) return SourceLoc(); - ClassDecl *classDecl = dyn_cast(CursorInfo.ValueD); - if (!classDecl || classDecl->getStoredProperties().empty() || + NominalTypeDecl *nominalDecl = dyn_cast(CursorInfo.ValueD); + if (!nominalDecl || nominalDecl->getStoredProperties().empty() || CursorInfo.IsRef) { return SourceLoc(); } - - SourceLoc bracesStart = classDecl->getBraces().Start; + + SourceLoc bracesStart = nominalDecl->getBraces().Start; if (!bracesStart.isValid()) return SourceLoc(); SourceLoc targetLocation = bracesStart.getAdvancedLoc(1); if (!targetLocation.isValid()) return SourceLoc(); - - for (auto varDecl : classDecl->getStoredProperties()) { - auto parentPatternBinding = varDecl->getParentPatternBinding(); - if (!parentPatternBinding) + + for (auto varDecl : nominalDecl->getStoredProperties()) { + auto patternBinding = varDecl->getParentPatternBinding(); + if (!patternBinding) + continue; + + if (!varDecl->isMemberwiseInitialized(/*preferDeclaredProperties=*/true)) { continue; - - auto varDeclIndex = - parentPatternBinding->getPatternEntryIndexForVarDecl(varDecl); - - if (auto init = varDecl->getParentPatternBinding()->getInit(varDeclIndex)) { - if (init->getStartLoc().isValid()) - continue; } - - StringRef memberName = varDecl->getName().str(); - memberNameVector.push_back(memberName.str()); - - std::string memberType = varDecl->getType().getString(); - memberTypeVector.push_back(memberType); + + auto &entry = patternBinding->getPatternEntryForVarDecl(varDecl); + bool isExplicitlyInitialized = + entry.isInitialized() && entry.getEqualLoc().isValid(); + Expr *defaultInit = nullptr; + if (isExplicitlyInitialized || patternBinding->isDefaultInitializable()) { + defaultInit = varDecl->getParentInitializer(); + } + + memberVector.emplace_back(varDecl->getName(), + varDecl->getType(), defaultInit); } - if (memberNameVector.empty() || memberTypeVector.empty()) { + if (memberVector.empty()) { return SourceLoc(); } @@ -2834,25 +2863,18 @@ static SourceLoc collectMembersForInit(ResolvedCursorInfo CursorInfo, bool RefactoringActionMemberwiseInitLocalRefactoring:: isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag) { - SmallVector memberNameVector; - SmallVector memberTypeVector; - - return collectMembersForInit(Tok, memberNameVector, - memberTypeVector).isValid(); + SmallVector memberVector; + return collectMembersForInit(Tok, memberVector).isValid(); } bool RefactoringActionMemberwiseInitLocalRefactoring::performChange() { - SmallVector memberNameVector; - SmallVector memberTypeVector; - - SourceLoc targetLocation = collectMembersForInit(CursorInfo, memberNameVector, - memberTypeVector); + SmallVector memberVector; + SourceLoc targetLocation = collectMembersForInit(CursorInfo, memberVector); if (targetLocation.isInvalid()) return true; - generateMemberwiseInit(EditConsumer, SM, memberNameVector, - memberTypeVector, targetLocation); + generateMemberwiseInit(EditConsumer, SM, memberVector, targetLocation); return false; } diff --git a/lib/Sema/CSFix.cpp b/lib/Sema/CSFix.cpp index 76b21b842dc..a8b1a60e1fd 100644 --- a/lib/Sema/CSFix.cpp +++ b/lib/Sema/CSFix.cpp @@ -759,6 +759,26 @@ IgnoreContextualType *IgnoreContextualType::create(ConstraintSystem &cs, IgnoreContextualType(cs, resultTy, specifiedTy, locator); } +bool IgnoreAssignmentDestinationType::diagnose(Expr *root, bool asNote) const { + auto &cs = getConstraintSystem(); + auto *AE = cast(getAnchor()); + auto CTP = isa(AE->getDest()) ? CTP_SubscriptAssignSource + : CTP_AssignSource; + + ContextualFailure failure( + root, cs, CTP, getFromType(), getToType(), + cs.getConstraintLocator(AE->getSrc(), LocatorPathElt::ContextualType())); + return failure.diagnose(asNote); +} + +IgnoreAssignmentDestinationType * +IgnoreAssignmentDestinationType::create(ConstraintSystem &cs, Type sourceTy, + Type destTy, + ConstraintLocator *locator) { + return new (cs.getAllocator()) + IgnoreAssignmentDestinationType(cs, sourceTy, destTy, locator); +} + bool AllowInOutConversion::diagnose(Expr *root, bool asNote) const { auto &cs = getConstraintSystem(); InOutConversionFailure failure(root, cs, getFromType(), getToType(), diff --git a/lib/Sema/CSFix.h b/lib/Sema/CSFix.h index 3aadee4d3d0..5e235058814 100644 --- a/lib/Sema/CSFix.h +++ b/lib/Sema/CSFix.h @@ -1299,6 +1299,23 @@ public: ConstraintLocator *locator); }; +class IgnoreAssignmentDestinationType final : public ContextualMismatch { + IgnoreAssignmentDestinationType(ConstraintSystem &cs, Type sourceTy, + Type destTy, ConstraintLocator *locator) + : ContextualMismatch(cs, sourceTy, destTy, locator) {} + +public: + std::string getName() const override { + return "ignore type of the assignment destination"; + } + + bool diagnose(Expr *root, bool asNote = false) const override; + + static IgnoreAssignmentDestinationType *create(ConstraintSystem &cs, + Type sourceTy, Type destTy, + ConstraintLocator *locator); +}; + /// If this is an argument-to-parameter conversion which is associated with /// `inout` parameter, subtyping is not permitted, types have to /// be identical. diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 28c61599d82..a4ac0b781d8 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -2353,6 +2353,15 @@ bool ConstraintSystem::repairFailures( return false; }; + auto hasConversionOrRestriction = [&](ConversionRestrictionKind kind) { + return llvm::any_of(conversionsOrFixes, + [kind](const RestrictionOrFix correction) { + if (auto restriction = correction.getRestriction()) + return restriction == kind; + return false; + }); + }; + if (path.empty()) { if (!anchor) return false; @@ -2391,20 +2400,39 @@ bool ConstraintSystem::repairFailures( if (repairViaBridgingCast(*this, lhs, rhs, conversionsOrFixes, locator)) return true; + + // If we are trying to assign e.g. `Array` to `Array` let's + // give solver a chance to determine which generic parameters are + // mismatched and produce a fix for that. + if (hasConversionOrRestriction(ConversionRestrictionKind::DeepEquality)) + return false; + + // If the situation has to do with protocol composition types and + // destination doesn't have one of the conformances e.g. source is + // `X & Y` but destination is only `Y` or vice versa, there is a + // tailored "missing conformance" fix for that. + if (hasConversionOrRestriction(ConversionRestrictionKind::Existential)) + return false; + + // If this is an attempt to assign something to a value of optional type + // there is a possiblity that the problem is related to escapiness, so + // fix has to be delayed. + if (hasConversionOrRestriction( + ConversionRestrictionKind::ValueToOptional)) + return false; + + // If the destination of an assignment is l-value type + // it leaves only possible reason for failure - a type mismatch. + if (getType(AE->getDest())->is()) { + conversionsOrFixes.push_back(IgnoreAssignmentDestinationType::create( + *this, lhs, rhs, getConstraintLocator(locator))); + return true; + } } return false; } - auto hasConversionOrRestriction = [&](ConversionRestrictionKind kind) { - return llvm::any_of(conversionsOrFixes, - [kind](const RestrictionOrFix correction) { - if (auto restriction = correction.getRestriction()) - return restriction == kind; - return false; - }); - }; - auto elt = path.back(); switch (elt.getKind()) { case ConstraintLocator::LValueConversion: { diff --git a/test/Constraints/closures.swift b/test/Constraints/closures.swift index 0850eac442e..7680bab6e13 100644 --- a/test/Constraints/closures.swift +++ b/test/Constraints/closures.swift @@ -394,7 +394,7 @@ func rdar20868864(_ s: String) { func r22058555() { var firstChar: UInt8 = 0 "abc".withCString { chars in - firstChar = chars[0] // expected-error {{cannot assign value of type 'Int8' to type 'UInt8'}} + firstChar = chars[0] // expected-error {{cannot assign value of type 'Int8' to type 'UInt8'}} {{17-17=UInt8(}} {{25-25=)}} } } diff --git a/test/Generics/deduction.swift b/test/Generics/deduction.swift index ea7a3deb426..e10bf9fffaa 100644 --- a/test/Generics/deduction.swift +++ b/test/Generics/deduction.swift @@ -22,7 +22,7 @@ func useIdentity(_ x: Int, y: Float, i32: Int32) { // Deduction where the result type and input type can get different results var xx : X, yy : Y - xx = identity(yy) // expected-error{{cannot convert value of type 'Y' to expected argument type 'X'}} + xx = identity(yy) // expected-error{{cannot assign value of type 'Y' to type 'X'}} xx = identity2(yy) // expected-error{{cannot convert value of type 'Y' to expected argument type 'X'}} } diff --git a/test/ModuleInterface/Inputs/opaque-result-types-client.swift b/test/ModuleInterface/Inputs/opaque-result-types-client.swift index 4fb9870aa5d..88671bd7cc6 100644 --- a/test/ModuleInterface/Inputs/opaque-result-types-client.swift +++ b/test/ModuleInterface/Inputs/opaque-result-types-client.swift @@ -17,14 +17,14 @@ struct YourFoo: Foo {} func someTypeIsTheSame() { var a = foo(0) a = foo(0) - a = foo("") // expected-error{{cannot convert value of type 'String' to expected argument type 'Int'}} + a = foo("") // expected-error{{cannot assign value of type 'some Foo' (result of 'foo') to type 'some Foo' (result of 'foo')}} var b = foo("") - b = foo(0) // expected-error{{cannot convert value of type 'Int' to expected argument type 'String'}} + b = foo(0) // expected-error{{cannot assign value of type 'some Foo' (result of 'foo') to type 'some Foo' (result of 'foo')}} b = foo("") var c = foo(MyFoo()) - c = foo(0) // expected-error{{cannot convert value of type 'Int' to expected argument type 'MyFoo'}} + c = foo(0) // expected-error{{cannot assign value of type 'some Foo' (result of 'foo') to type 'some Foo' (result of 'foo')}} c = foo(MyFoo()) c = foo(YourFoo()) // expected-error{{cannot convert value of type 'YourFoo' to expected argument type 'MyFoo'}} diff --git a/test/refactoring/MemberwiseInit/Outputs/class_members/class_members.swift.expected b/test/refactoring/MemberwiseInit/Outputs/class_members/class_members.swift.expected deleted file mode 100644 index 5c3de898efb..00000000000 --- a/test/refactoring/MemberwiseInit/Outputs/class_members/class_members.swift.expected +++ /dev/null @@ -1,14 +0,0 @@ -class Person { -internal init(firstName: String?, lastName: String?, age: Int?) { -self.firstName = firstName -self.lastName = lastName -self.age = age -} - - var firstName: String! - var lastName: String! - var age: Int! - var planet = "Earth", solarSystem = "Milky Way" - var avgHeight = 175 -} - diff --git a/test/refactoring/MemberwiseInit/Outputs/generate_memberwise/class_members.swift.expected b/test/refactoring/MemberwiseInit/Outputs/generate_memberwise/class_members.swift.expected new file mode 100644 index 00000000000..8745fd72357 --- /dev/null +++ b/test/refactoring/MemberwiseInit/Outputs/generate_memberwise/class_members.swift.expected @@ -0,0 +1,40 @@ +class Person { +internal init(firstName: String? = nil, lastName: String? = nil, age: Int? = nil, planet: String = "Earth", solarSystem: String = "Milky Way", avgHeight: Int = 175) { +self.firstName = firstName +self.lastName = lastName +self.age = age +self.planet = planet +self.solarSystem = solarSystem +self.avgHeight = avgHeight +} + + var firstName: String! + var lastName: String! + var age: Int! + var planet = "Earth", solarSystem = "Milky Way" + var avgHeight = 175 + let line = #line, file = #file, handle = #dsohandle + lazy var idea: Idea = { fatalError() }() +} + +struct Place { + let person: Person + let street: String + let apartment: Optional + let city: String + let state: String + let postalCode: Int + let plusFour: Int? +} + +protocol Thing { + var idea: Idea { get } +} + +enum Idea { + var subject: String { fatalError() } +} + + + + diff --git a/test/refactoring/MemberwiseInit/Outputs/generate_memberwise/struct_members.swift.expected b/test/refactoring/MemberwiseInit/Outputs/generate_memberwise/struct_members.swift.expected new file mode 100644 index 00000000000..f1663171352 --- /dev/null +++ b/test/refactoring/MemberwiseInit/Outputs/generate_memberwise/struct_members.swift.expected @@ -0,0 +1,41 @@ +class Person { + var firstName: String! + var lastName: String! + var age: Int! + var planet = "Earth", solarSystem = "Milky Way" + var avgHeight = 175 + let line = #line, file = #file, handle = #dsohandle + lazy var idea: Idea = { fatalError() }() +} + +struct Place { +internal init(person: Person, street: String, apartment: Optional, city: String, state: String, postalCode: Int, plusFour: Int?) { +self.person = person +self.street = street +self.apartment = apartment +self.city = city +self.state = state +self.postalCode = postalCode +self.plusFour = plusFour +} + + let person: Person + let street: String + let apartment: Optional + let city: String + let state: String + let postalCode: Int + let plusFour: Int? +} + +protocol Thing { + var idea: Idea { get } +} + +enum Idea { + var subject: String { fatalError() } +} + + + + diff --git a/test/refactoring/MemberwiseInit/class_members.swift b/test/refactoring/MemberwiseInit/class_members.swift deleted file mode 100644 index 78081b6b8f1..00000000000 --- a/test/refactoring/MemberwiseInit/class_members.swift +++ /dev/null @@ -1,11 +0,0 @@ -class Person { - var firstName: String! - var lastName: String! - var age: Int! - var planet = "Earth", solarSystem = "Milky Way" - var avgHeight = 175 -} - -// RUN: %empty-directory(%t.result) -// RUN: %refactor -memberwise-init -source-filename %s -pos=1:8 > %t.result/class_members.swift -// RUN: diff -u %S/Outputs/class_members/class_members.swift.expected %t.result/class_members.swift diff --git a/test/refactoring/MemberwiseInit/generate_memberwise.swift b/test/refactoring/MemberwiseInit/generate_memberwise.swift new file mode 100644 index 00000000000..f63e7294dc9 --- /dev/null +++ b/test/refactoring/MemberwiseInit/generate_memberwise.swift @@ -0,0 +1,38 @@ +class Person { + var firstName: String! + var lastName: String! + var age: Int! + var planet = "Earth", solarSystem = "Milky Way" + var avgHeight = 175 + let line = #line, file = #file, handle = #dsohandle + lazy var idea: Idea = { fatalError() }() +} + +struct Place { + let person: Person + let street: String + let apartment: Optional + let city: String + let state: String + let postalCode: Int + let plusFour: Int? +} + +protocol Thing { + var idea: Idea { get } +} + +enum Idea { + var subject: String { fatalError() } +} + +// RUN: %empty-directory(%t.result) +// RUN: %refactor -memberwise-init -source-filename %s -pos=1:8 > %t.result/generate_memberwise.swift +// RUN: diff -u %S/Outputs/generate_memberwise/class_members.swift.expected %t.result/generate_memberwise.swift + +// RUN: %refactor -memberwise-init -source-filename %s -pos=11:8 > %t.result/struct_members.swift +// RUN: diff -u %S/Outputs/generate_memberwise/struct_members.swift.expected %t.result/struct_members.swift + +// RUN: not %refactor -memberwise-init -source-filename %s -pos=21:10 > %t.result/protocol_members.swift +// RUN: not %refactor -memberwise-init -source-filename %s -pos=25:6 > %t.result/enum_members.swift +