diff --git a/include/swift/IDE/RefactoringKinds.def b/include/swift/IDE/RefactoringKinds.def index 84daad8bc9b86..b6565439a2016 100644 --- a/include/swift/IDE/RefactoringKinds.def +++ b/include/swift/IDE/RefactoringKinds.def @@ -70,6 +70,8 @@ RANGE_REFACTORING(ConvertIfLetExprToGuardExpr, "Convert To Guard Expression", co RANGE_REFACTORING(ConvertGuardExprToIfLetExpr, "Convert To IfLet Expression", convert.to.iflet.expr) +RANGE_REFACTORING(ConvertToComputedProperty, "Convert To Computed Property", convert.to.computed.property) + // These internal refactorings are designed to be helpful for working on // the compiler/standard library, etc., but are likely to be just confusing and // noise for general development. diff --git a/lib/IDE/Refactoring.cpp b/lib/IDE/Refactoring.cpp index b148785f6d070..9ce2bc7739024 100644 --- a/lib/IDE/Refactoring.cpp +++ b/lib/IDE/Refactoring.cpp @@ -3166,6 +3166,97 @@ static bool rangeStartMayNeedRename(ResolvedRangeInfo Info) { } llvm_unreachable("unhandled kind"); } + +bool RefactoringActionConvertToComputedProperty:: +isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) { + if (Info.Kind != RangeKind::SingleDecl) { + return false; + } + + if (Info.ContainedNodes.size() != 1) { + return false; + } + + auto D = Info.ContainedNodes[0].dyn_cast(); + if (!D) { + return false; + } + + auto Binding = dyn_cast(D); + if (!Binding) { + return false; + } + + auto SV = Binding->getSingleVar(); + if (!SV) { + return false; + } + + // willSet, didSet cannot be provided together with a getter + for (auto AD : SV->getAllAccessors()) { + if (AD->isObservingAccessor()) { + return false; + } + } + + // 'lazy' must not be used on a computed property + // NSCopying and IBOutlet attribute requires property to be mutable + auto Attributies = SV->getAttrs(); + if (Attributies.hasAttribute() || + Attributies.hasAttribute() || + Attributies.hasAttribute()) { + return false; + } + + // Property wrapper cannot be applied to a computed property + if (SV->hasAttachedPropertyWrapper()) { + return false; + } + + // has an initializer + return Binding->hasInitStringRepresentation(0); +} + +bool RefactoringActionConvertToComputedProperty::performChange() { + // Get an initialization + auto D = RangeInfo.ContainedNodes[0].dyn_cast(); + auto Binding = dyn_cast(D); + SmallString<128> scratch; + auto Init = Binding->getInitStringRepresentation(0, scratch); + + // Get type + auto SV = Binding->getSingleVar(); + auto SVType = SV->getType(); + auto TR = SV->getTypeReprOrParentPatternTypeRepr(); + + llvm::SmallString<64> DeclBuffer; + llvm::raw_svector_ostream OS(DeclBuffer); + llvm::StringRef Space = " "; + llvm::StringRef NewLine = "\n"; + + OS << tok::kw_var << Space; + // Add var name + OS << SV->getNameStr().str() << ":" << Space; + // For computed property must write a type of var + if (TR) { + OS << Lexer::getCharSourceRangeFromSourceRange(SM, TR->getSourceRange()).str(); + } else { + SVType.print(OS); + } + + OS << Space << tok::l_brace << NewLine; + // Add an initialization + OS << tok::kw_return << Space << Init.str() << NewLine; + OS << tok::r_brace; + + // Replace initializer to computed property + auto ReplaceStartLoc = Binding->getLoc(); + auto ReplaceEndLoc = Binding->getSourceRange().End; + auto ReplaceRange = SourceRange(ReplaceStartLoc, ReplaceEndLoc); + auto ReplaceCharSourceRange = Lexer::getCharSourceRangeFromSourceRange(SM, ReplaceRange); + EditConsumer.accept(SM, ReplaceCharSourceRange, DeclBuffer.str()); + return false; // success +} }// end of anonymous namespace StringRef swift::ide:: diff --git a/test/refactoring/ConvertToComputedProperty/Outputs/basic/L10-3.swift.expected b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L10-3.swift.expected new file mode 100644 index 0000000000000..e8eda6da2ee82 --- /dev/null +++ b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L10-3.swift.expected @@ -0,0 +1,28 @@ +struct S { + var field1 = 2 + var field2 = "2" + var field3 = String() + static var field4 = 4 + var y: Int! = 45 +} + +class C { + static var field1: S { +return S() +} + public var field2 = 2 + private dynamic var field3 = 5 + @available(macOS 10.12, *) private static dynamic var field4 = 4 + let field5 = 5 +} + + + + + + + + + + + diff --git a/test/refactoring/ConvertToComputedProperty/Outputs/basic/L11-3.swift.expected b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L11-3.swift.expected new file mode 100644 index 0000000000000..6c0e0427aa9f3 --- /dev/null +++ b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L11-3.swift.expected @@ -0,0 +1,28 @@ +struct S { + var field1 = 2 + var field2 = "2" + var field3 = String() + static var field4 = 4 + var y: Int! = 45 +} + +class C { + static var field1 = S() + public var field2: Int { +return 2 +} + private dynamic var field3 = 5 + @available(macOS 10.12, *) private static dynamic var field4 = 4 + let field5 = 5 +} + + + + + + + + + + + diff --git a/test/refactoring/ConvertToComputedProperty/Outputs/basic/L12-3.swift.expected b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L12-3.swift.expected new file mode 100644 index 0000000000000..8e04908653313 --- /dev/null +++ b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L12-3.swift.expected @@ -0,0 +1,28 @@ +struct S { + var field1 = 2 + var field2 = "2" + var field3 = String() + static var field4 = 4 + var y: Int! = 45 +} + +class C { + static var field1 = S() + public var field2 = 2 + private dynamic var field3: Int { +return 5 +} + @available(macOS 10.12, *) private static dynamic var field4 = 4 + let field5 = 5 +} + + + + + + + + + + + diff --git a/test/refactoring/ConvertToComputedProperty/Outputs/basic/L13-3.swift.expected b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L13-3.swift.expected new file mode 100644 index 0000000000000..b85ee87f74713 --- /dev/null +++ b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L13-3.swift.expected @@ -0,0 +1,28 @@ +struct S { + var field1 = 2 + var field2 = "2" + var field3 = String() + static var field4 = 4 + var y: Int! = 45 +} + +class C { + static var field1 = S() + public var field2 = 2 + private dynamic var field3 = 5 + @available(macOS 10.12, *) private static dynamic var field4: Int { +return 4 +} + let field5 = 5 +} + + + + + + + + + + + diff --git a/test/refactoring/ConvertToComputedProperty/Outputs/basic/L14-3.swift.expected b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L14-3.swift.expected new file mode 100644 index 0000000000000..d909eca08cfc0 --- /dev/null +++ b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L14-3.swift.expected @@ -0,0 +1,28 @@ +struct S { + var field1 = 2 + var field2 = "2" + var field3 = String() + static var field4 = 4 + var y: Int! = 45 +} + +class C { + static var field1 = S() + public var field2 = 2 + private dynamic var field3 = 5 + @available(macOS 10.12, *) private static dynamic var field4 = 4 + var field5: Int { +return 5 +} +} + + + + + + + + + + + diff --git a/test/refactoring/ConvertToComputedProperty/Outputs/basic/L2-3.swift.expected b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L2-3.swift.expected new file mode 100644 index 0000000000000..87f15cfc7bbfe --- /dev/null +++ b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L2-3.swift.expected @@ -0,0 +1,28 @@ +struct S { + var field1: Int { +return 2 +} + var field2 = "2" + var field3 = String() + static var field4 = 4 + var y: Int! = 45 +} + +class C { + static var field1 = S() + public var field2 = 2 + private dynamic var field3 = 5 + @available(macOS 10.12, *) private static dynamic var field4 = 4 + let field5 = 5 +} + + + + + + + + + + + diff --git a/test/refactoring/ConvertToComputedProperty/Outputs/basic/L3-3.swift.expected b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L3-3.swift.expected new file mode 100644 index 0000000000000..09b7fb68a5ebe --- /dev/null +++ b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L3-3.swift.expected @@ -0,0 +1,28 @@ +struct S { + var field1 = 2 + var field2: String { +return "2" +} + var field3 = String() + static var field4 = 4 + var y: Int! = 45 +} + +class C { + static var field1 = S() + public var field2 = 2 + private dynamic var field3 = 5 + @available(macOS 10.12, *) private static dynamic var field4 = 4 + let field5 = 5 +} + + + + + + + + + + + diff --git a/test/refactoring/ConvertToComputedProperty/Outputs/basic/L4-3.swift.expected b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L4-3.swift.expected new file mode 100644 index 0000000000000..2702ab0a321d1 --- /dev/null +++ b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L4-3.swift.expected @@ -0,0 +1,28 @@ +struct S { + var field1 = 2 + var field2 = "2" + var field3: String { +return String() +} + static var field4 = 4 + var y: Int! = 45 +} + +class C { + static var field1 = S() + public var field2 = 2 + private dynamic var field3 = 5 + @available(macOS 10.12, *) private static dynamic var field4 = 4 + let field5 = 5 +} + + + + + + + + + + + diff --git a/test/refactoring/ConvertToComputedProperty/Outputs/basic/L5-3.swift.expected b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L5-3.swift.expected new file mode 100644 index 0000000000000..2d53bc984c398 --- /dev/null +++ b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L5-3.swift.expected @@ -0,0 +1,28 @@ +struct S { + var field1 = 2 + var field2 = "2" + var field3 = String() + static var field4: Int { +return 4 +} + var y: Int! = 45 +} + +class C { + static var field1 = S() + public var field2 = 2 + private dynamic var field3 = 5 + @available(macOS 10.12, *) private static dynamic var field4 = 4 + let field5 = 5 +} + + + + + + + + + + + diff --git a/test/refactoring/ConvertToComputedProperty/Outputs/basic/L6-3.swift.expected b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L6-3.swift.expected new file mode 100644 index 0000000000000..f31ba4658f4ce --- /dev/null +++ b/test/refactoring/ConvertToComputedProperty/Outputs/basic/L6-3.swift.expected @@ -0,0 +1,28 @@ +struct S { + var field1 = 2 + var field2 = "2" + var field3 = String() + static var field4 = 4 + var y: Int! { +return 45 +} +} + +class C { + static var field1 = S() + public var field2 = 2 + private dynamic var field3 = 5 + @available(macOS 10.12, *) private static dynamic var field4 = 4 + let field5 = 5 +} + + + + + + + + + + + diff --git a/test/refactoring/ConvertToComputedProperty/basic.swift b/test/refactoring/ConvertToComputedProperty/basic.swift new file mode 100644 index 0000000000000..1a9f119290286 --- /dev/null +++ b/test/refactoring/ConvertToComputedProperty/basic.swift @@ -0,0 +1,47 @@ +struct S { + var field1 = 2 + var field2 = "2" + var field3 = String() + static var field4 = 4 + var y: Int! = 45 +} + +class C { + static var field1 = S() + public var field2 = 2 + private dynamic var field3 = 5 + @available(macOS 10.12, *) private static dynamic var field4 = 4 + let field5 = 5 +} + +// RUN: %empty-directory(%t.result) + +// RUN: %refactor -convert-to-computed-property -source-filename %s -pos=2:3 -end-pos=2:17 > %t.result/L2-3.swift +// RUN: diff -u %S/Outputs/basic/L2-3.swift.expected %t.result/L2-3.swift + +// RUN: %refactor -convert-to-computed-property -source-filename %s -pos=3:3 -end-pos=3:19 > %t.result/L3-3.swift +// RUN: diff -u %S/Outputs/basic/L3-3.swift.expected %t.result/L3-3.swift + +// RUN: %refactor -convert-to-computed-property -source-filename %s -pos=4:3 -end-pos=4:24 > %t.result/L4-3.swift +// RUN: diff -u %S/Outputs/basic/L4-3.swift.expected %t.result/L4-3.swift + +// RUN: %refactor -convert-to-computed-property -source-filename %s -pos=5:3 -end-pos=5:24 > %t.result/L5-3.swift +// RUN: diff -u %S/Outputs/basic/L5-3.swift.expected %t.result/L5-3.swift + +// RUN: %refactor -convert-to-computed-property -source-filename %s -pos=6:3 -end-pos=6:19 > %t.result/L6-3.swift +// RUN: diff -u %S/Outputs/basic/L6-3.swift.expected %t.result/L6-3.swift + +// RUN: %refactor -convert-to-computed-property -source-filename %s -pos=10:3 -end-pos=10:26 > %t.result/L10-3.swift +// RUN: diff -u %S/Outputs/basic/L10-3.swift.expected %t.result/L10-3.swift + +// RUN: %refactor -convert-to-computed-property -source-filename %s -pos=11:3 -end-pos=11:24 > %t.result/L11-3.swift +// RUN: diff -u %S/Outputs/basic/L11-3.swift.expected %t.result/L11-3.swift + +// RUN: %refactor -convert-to-computed-property -source-filename %s -pos=12:3 -end-pos=12:33 > %t.result/L12-3.swift +// RUN: diff -u %S/Outputs/basic/L12-3.swift.expected %t.result/L12-3.swift + +// RUN: %refactor -convert-to-computed-property -source-filename %s -pos=13:3 -end-pos=13:67 > %t.result/L13-3.swift +// RUN: diff -u %S/Outputs/basic/L13-3.swift.expected %t.result/L13-3.swift + +// RUN: %refactor -convert-to-computed-property -source-filename %s -pos=14:3 -end-pos=14:17 > %t.result/L14-3.swift +// RUN: diff -u %S/Outputs/basic/L14-3.swift.expected %t.result/L14-3.swift diff --git a/test/refactoring/RefactoringKind/basic.swift b/test/refactoring/RefactoringKind/basic.swift index 7d0775c976909..2ec7e032ef8e0 100644 --- a/test/refactoring/RefactoringKind/basic.swift +++ b/test/refactoring/RefactoringKind/basic.swift @@ -275,13 +275,33 @@ func testConvertToIfLetExpr(idxOpt: Int?) { print(idx) } +@propertyWrapper +struct TwelveOrLess { + private var number = 0 + var wrappedValue: Int { + get { return number } + set { number = min(newValue, 12) } + } +} + +struct S { + var field = 2 + let (x, y) = (2, 4) + @TwelveOrLess var height: Int + lazy var z = 42 + var totalSteps: Int = 0 { + willSet(newTotalSteps) { + print("About to set totalSteps to \(newTotalSteps)") + } + } +} // RUN: %refactor -source-filename %s -pos=2:1 -end-pos=5:13 | %FileCheck %s -check-prefix=CHECK1 // RUN: %refactor -source-filename %s -pos=3:1 -end-pos=5:13 | %FileCheck %s -check-prefix=CHECK1 // RUN: %refactor -source-filename %s -pos=4:1 -end-pos=5:13 | %FileCheck %s -check-prefix=CHECK1 // RUN: %refactor -source-filename %s -pos=5:1 -end-pos=5:13 | %FileCheck %s -check-prefix=CHECK1 -// RUN: %refactor -source-filename %s -pos=2:1 -end-pos=2:18 | %FileCheck %s -check-prefix=CHECK2 +// RUN: %refactor -source-filename %s -pos=2:1 -end-pos=2:18 | %FileCheck %s -check-prefix=CHECK-CONVERT-TO-COMPUTED-PROPERTY // RUN: %refactor -source-filename %s -pos=2:1 -end-pos=3:16 | %FileCheck %s -check-prefix=CHECK2 // RUN: %refactor -source-filename %s -pos=2:1 -end-pos=4:26 | %FileCheck %s -check-prefix=CHECK2 @@ -367,12 +387,16 @@ func testConvertToIfLetExpr(idxOpt: Int?) { // RUN: %refactor -source-filename %s -pos=251:3 -end-pos=251:24 | %FileCheck %s -check-prefix=CHECK-EXPAND-TERNARY-EXPRESSEXPRESSION -// RUN: %refactor -source-filename %s -pos=257:3 -end-pos=262:4 | %FileCheck %s -check-prefix=CHECK-CONVERT-TO-TERNARY-EXPRESSEXPRESSION - // RUN: %refactor -source-filename %s -pos=266:3 -end-pos=268:4 | %FileCheck %s -check-prefix=CHECK-CONVERT-TO-GUARD-EXPRESSION // RUN: %refactor -source-filename %s -pos=272:3 -end-pos=275:13 | %FileCheck %s -check-prefix=CHECK-CONVERT-TO-IFLET-EXPRESSION +// RUN: %refactor -source-filename %s -pos=288:3 -end-pos=288:16 | %FileCheck %s -check-prefix=CHECK-CONVERT-TO-COMPUTED-PROPERTY +// RUN: %refactor -source-filename %s -pos=289:3 -end-pos=289:22 | %FileCheck %s -check-prefix=CHECK-NONE +// RUN: %refactor -source-filename %s -pos=290:3 -end-pos=290:32 | %FileCheck %s -check-prefix=CHECK-CONVERT-TO-COMPUTED-PROPERTY2 +// RUN: %refactor -source-filename %s -pos=291:3 -end-pos=291:18 | %FileCheck %s -check-prefix=CHECK-CONVERT-TO-COMPUTED-PROPERTY2 +// RUN: %refactor -source-filename %s -pos=292:3 -end-pos=296:4 | %FileCheck %s -check-prefix=CHECK-NONE + // CHECK1: Action begins // CHECK1-NEXT: Extract Method // CHECK1-NEXT: Action ends @@ -423,3 +447,9 @@ func testConvertToIfLetExpr(idxOpt: Int?) { // CHECK-CONVERT-TO-GUARD-EXPRESSION: Convert To Guard Expression // CHECK-CONVERT-TO-IFLET-EXPRESSION: Convert To IfLet Expression + +// CHECK-CONVERT-TO-COMPUTED-PROPERTY: Convert To Computed Property + +// CHECK-CONVERT-TO-COMPUTED-PROPERTY2: Action begins +// CHECK-CONVERT-TO-COMPUTED-PROPERTY2-NEXT: Move To Extension +// CHECK-CONVERT-TO-COMPUTED-PROPERTY2-NEXT: Action ends \ No newline at end of file diff --git a/tools/swift-refactor/swift-refactor.cpp b/tools/swift-refactor/swift-refactor.cpp index 83417f6cf97fd..22c885b97c2cb 100644 --- a/tools/swift-refactor/swift-refactor.cpp +++ b/tools/swift-refactor/swift-refactor.cpp @@ -71,7 +71,9 @@ Action(llvm::cl::desc("kind:"), llvm::cl::init(RefactoringKind::None), "trailingclosure", "Perform trailing closure refactoring"), clEnumValN(RefactoringKind::ReplaceBodiesWithFatalError, "replace-bodies-with-fatalError", "Perform trailing closure refactoring"), - clEnumValN(RefactoringKind::MemberwiseInitLocalRefactoring, "memberwise-init", "Generate member wise initializer"))); + clEnumValN(RefactoringKind::MemberwiseInitLocalRefactoring, "memberwise-init", "Generate member wise initializer"), + clEnumValN(RefactoringKind::ConvertToComputedProperty, + "convert-to-computed-property", "Convert from field initialization to computed property"))); static llvm::cl::opt