Skip to content

[Refactoring] Several improvements to memberwise initializer generation #58718

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 37 additions & 26 deletions lib/IDE/Refactoring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3147,26 +3147,24 @@ bool RefactoringActionLocalizeString::performChange() {
}

struct MemberwiseParameter {
Identifier Name;
CharSourceRange NameRange;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if Identifier should have a bit for whether it needs backticks or not. I think there's a bunch of bugs involving not adding backticks and it'd be nice if we instead had a method in Identifier that would get the actual text for the name.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think it should because we use pointer equality to compare Identifiers and we want to consider two identifiers identical even if one has backticks and the other doesn’t. In general, I think the usual compilation doesn’t care about backticks, it’s only us who care.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true. Just seems so wrong that we have to extract text here.

Type MemberType;
Expr *DefaultExpr;

MemberwiseParameter(Identifier name, Type type, Expr *initialExpr)
: Name(name), MemberType(type), DefaultExpr(initialExpr) {}
MemberwiseParameter(CharSourceRange nameRange, Type type, Expr *initialExpr)
: NameRange(nameRange), MemberType(type), DefaultExpr(initialExpr) {}
};

static void generateMemberwiseInit(SourceEditConsumer &EditConsumer,
SourceManager &SM,
ArrayRef<MemberwiseParameter> memberVector,
SourceLoc targetLocation) {

assert(!memberVector.empty());

EditConsumer.accept(SM, targetLocation, "\ninternal init(");
auto insertMember = [&SM](const MemberwiseParameter &memberData,
raw_ostream &OS, bool wantsSeparator) {
{
OS << memberData.Name << ": ";
OS << SM.extractText(memberData.NameRange) << ": ";
// Unconditionally print '@escaping' if we print out a function type -
// the assignments we generate below will escape this parameter.
if (isa<AnyFunctionType>(memberData.MemberType->getCanonicalType())) {
Expand All @@ -3175,16 +3173,19 @@ static void generateMemberwiseInit(SourceEditConsumer &EditConsumer,
OS << memberData.MemberType.getString();
}

bool HasAddedDefault = false;
if (auto *expr = memberData.DefaultExpr) {
if (isa<NilLiteralExpr>(expr)) {
OS << " = nil";
} else if (expr->getSourceRange().isValid()) {
if (expr->getSourceRange().isValid()) {
auto range =
Lexer::getCharSourceRangeFromSourceRange(
SM, expr->getSourceRange());
OS << " = " << SM.extractText(range);
HasAddedDefault = true;
}
}
if (!HasAddedDefault && memberData.MemberType->isOptional()) {
OS << " = nil";
}

if (wantsSeparator) {
OS << ", ";
Expand All @@ -3194,18 +3195,17 @@ static void generateMemberwiseInit(SourceEditConsumer &EditConsumer,
// 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);
for (const auto &memberData : llvm::enumerate(memberVector)) {
bool wantsSeparator = (memberData.index() != memberVector.size() - 1);
insertMember(memberData.value(), OS, wantsSeparator);
}

// Process the last (or perhaps, only) member.
insertMember(memberVector.back(), OS, /*wantsSeparator*/ false);

// Synthesize the body.
OS << ") {\n";
for (auto &member : memberVector) {
// self.<property> = <property>
OS << "self." << member.Name << " = " << member.Name << "\n";
auto name = SM.extractText(member.NameRange);
OS << "self." << name << " = " << name << "\n";
}
OS << "}\n";

Expand Down Expand Up @@ -3234,30 +3234,41 @@ collectMembersForInit(const ResolvedCursorInfo &CursorInfo,
if (!targetLocation.isValid())
return SourceLoc();

for (auto varDecl : nominalDecl->getStoredProperties()) {
auto patternBinding = varDecl->getParentPatternBinding();
if (!patternBinding)
SourceManager &SM = nominalDecl->getASTContext().SourceMgr;

for (auto member : nominalDecl->getMembers()) {
auto varDecl = dyn_cast<VarDecl>(member);
if (!varDecl) {
continue;
}
if (varDecl->getAttrs().hasAttribute<LazyAttr>()) {
// Exclude lazy members from the memberwise initializer. This is
// inconsistent with the implicitly synthesized memberwise initializer but
// we think it makes more sense because otherwise the lazy variable's
// initializer gets evaluated eagerly.
continue;
}

if (!varDecl->isMemberwiseInitialized(/*preferDeclaredProperties=*/true)) {
continue;
}

auto patternBinding = varDecl->getParentPatternBinding();
if (!patternBinding)
continue;

const auto i = patternBinding->getPatternEntryIndexForVarDecl(varDecl);
Expr *defaultInit = nullptr;
if (patternBinding->isExplicitlyInitialized(i) ||
patternBinding->isDefaultInitializable()) {
defaultInit = varDecl->getParentInitializer();
defaultInit = patternBinding->getOriginalInit(i);
}

memberVector.emplace_back(varDecl->getName(),
varDecl->getType(), defaultInit);
auto NameRange =
Lexer::getCharSourceRangeFromSourceRange(SM, varDecl->getNameLoc());
memberVector.emplace_back(NameRange, varDecl->getType(), defaultInit);
}

if (memberVector.empty()) {
return SourceLoc();
}


return targetLocation;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class Person {
internal init(firstName: String? = nil, lastName: String? = nil, age: Int? = nil, planet: String = "Earth", solarSystem: String = "Milky Way", avgHeight: Int = 175, location: @escaping () -> Place = { fatalError() }, secondLocation: (() -> Place)? = nil) {
internal init(firstName: String? = nil, lastName: String? = nil, age: Int? = nil, planet: String = "Earth", solarSystem: String = "Milky Way", avgHeight: Int = 175, location: @escaping () -> Place = { fatalError() }, secondLocation: (() -> Place)? = nil, wrapped: String = "", didSet: String = "ds") {
self.firstName = firstName
self.lastName = lastName
self.age = age
Expand All @@ -8,6 +8,8 @@ self.solarSystem = solarSystem
self.avgHeight = avgHeight
self.location = location
self.secondLocation = secondLocation
self.wrapped = wrapped
self.didSet = didSet
}

var firstName: String!
Expand All @@ -19,6 +21,15 @@ self.secondLocation = secondLocation
lazy var idea: Idea = { fatalError() }()
var location: () -> Place = { fatalError() }
var secondLocation: (() -> Place)!
@MyWrapper var wrapped: String = ""
var computed: String { "hi" }
var getSet: String {
get { "hi" }
set {}
}
var didSet: String = "ds" {
didSet { print("didSet") }
}
}

struct Place {
Expand All @@ -31,6 +42,8 @@ struct Place {
let postalCode: Int
let plusFour: Int?
let callback: Callback
@MyWrapper var wrapped: String
var `protocol`: String
}

protocol Thing {
Expand All @@ -41,6 +54,17 @@ enum Idea {
var subject: String { fatalError() }
}

struct OnlyComputed {
lazy var idea: Idea = { fatalError() }()
var computed: String { "hi" }
}

@propertyWrapper
struct MyWrapper {
let wrappedValue: String
}





Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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() }()
var location: () -> Place = { fatalError() }
var secondLocation: (() -> Place)!
@MyWrapper var wrapped: String = ""
var computed: String { "hi" }
var getSet: String {
get { "hi" }
set {}
}
var didSet: String = "ds" {
didSet { print("didSet") }
}
}

struct Place {
typealias Callback = () -> ()
let person: Person
let street: String
let apartment: Optional<String>
let city: String
let state: String
let postalCode: Int
let plusFour: Int?
let callback: Callback
@MyWrapper var wrapped: String
var `protocol`: String
}

protocol Thing {
var idea: Idea { get }
}

enum Idea {
var subject: String { fatalError() }
}

struct OnlyComputed {
internal init() {
}

lazy var idea: Idea = { fatalError() }()
var computed: String { "hi" }
}

@propertyWrapper
struct MyWrapper {
let wrappedValue: String
}





Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,19 @@ class Person {
lazy var idea: Idea = { fatalError() }()
var location: () -> Place = { fatalError() }
var secondLocation: (() -> Place)!
@MyWrapper var wrapped: String = ""
var computed: String { "hi" }
var getSet: String {
get { "hi" }
set {}
}
var didSet: String = "ds" {
didSet { print("didSet") }
}
}

struct Place {
internal init(person: Person, street: String, apartment: Optional<String>, city: String, state: String, postalCode: Int, plusFour: Int?, callback: @escaping Place.Callback) {
internal init(person: Person, street: String, apartment: Optional<String> = nil, city: String, state: String, postalCode: Int, plusFour: Int? = nil, callback: @escaping Place.Callback, wrapped: String, `protocol`: String) {
self.person = person
self.street = street
self.apartment = apartment
Expand All @@ -20,6 +29,8 @@ self.state = state
self.postalCode = postalCode
self.plusFour = plusFour
self.callback = callback
self.wrapped = wrapped
self.`protocol` = `protocol`
}

typealias Callback = () -> ()
Expand All @@ -31,6 +42,8 @@ self.callback = callback
let postalCode: Int
let plusFour: Int?
let callback: Callback
@MyWrapper var wrapped: String
var `protocol`: String
}

protocol Thing {
Expand All @@ -41,6 +54,17 @@ enum Idea {
var subject: String { fatalError() }
}

struct OnlyComputed {
lazy var idea: Idea = { fatalError() }()
var computed: String { "hi" }
}

@propertyWrapper
struct MyWrapper {
let wrappedValue: String
}





30 changes: 27 additions & 3 deletions test/refactoring/MemberwiseInit/generate_memberwise.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ class Person {
lazy var idea: Idea = { fatalError() }()
var location: () -> Place = { fatalError() }
var secondLocation: (() -> Place)!
@MyWrapper var wrapped: String = ""
var computed: String { "hi" }
var getSet: String {
get { "hi" }
set {}
}
var didSet: String = "ds" {
didSet { print("didSet") }
}
}

struct Place {
Expand All @@ -20,6 +29,8 @@ struct Place {
let postalCode: Int
let plusFour: Int?
let callback: Callback
@MyWrapper var wrapped: String
var `protocol`: String
}

protocol Thing {
Expand All @@ -30,13 +41,26 @@ enum Idea {
var subject: String { fatalError() }
}

struct OnlyComputed {
lazy var idea: Idea = { fatalError() }()
var computed: String { "hi" }
}

@propertyWrapper
struct MyWrapper {
let wrappedValue: String
}

// 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=13:8 > %t.result/struct_members.swift
// RUN: %refactor -memberwise-init -source-filename %s -pos=22: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
// RUN: %refactor -memberwise-init -source-filename %s -pos=44:8 > %t.result/only_computed_members.swift
// RUN: diff -u %S/Outputs/generate_memberwise/only_computed_members.swift.expected %t.result/only_computed_members.swift

// RUN: not %refactor -memberwise-init -source-filename %s -pos=36:10 > %t.result/protocol_members.swift
// RUN: not %refactor -memberwise-init -source-filename %s -pos=40:6 > %t.result/enum_members.swift