Skip to content

Commit b397a10

Browse files
authored
Merge pull request #1075 from DougGregor/refactor-opaque-parameter-to-generic
[Refactor] Add a refactor action to replace "some" parameters with generics
2 parents 1d555c8 + 24ccc82 commit b397a10

File tree

2 files changed

+294
-0
lines changed

2 files changed

+294
-0
lines changed
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftSyntax
14+
15+
/// Describes a "some" parameter that has been rewritten into a generic
16+
/// parameter.
17+
fileprivate struct RewrittenSome {
18+
let original: ConstrainedSugarTypeSyntax
19+
let genericParam: GenericParameterSyntax
20+
let genericParamRef: SimpleTypeIdentifierSyntax
21+
}
22+
23+
/// Rewrite `some` parameters to explicit generic parameters.
24+
///
25+
/// ## Before
26+
///
27+
/// ```swift
28+
/// func someFunction(_ input: some Value) {}
29+
/// ```
30+
///
31+
/// ## After
32+
///
33+
/// ```swift
34+
/// func someFunction<T1: Value>(_ input: T1) {}
35+
/// ```
36+
fileprivate class SomeParameterRewriter: SyntaxRewriter {
37+
var rewrittenSomeParameters: [RewrittenSome] = []
38+
39+
override func visit(_ node: ConstrainedSugarTypeSyntax) -> TypeSyntax {
40+
if node.someOrAnySpecifier.text != "some" {
41+
return TypeSyntax(node)
42+
}
43+
44+
let paramName = "T\(rewrittenSomeParameters.count + 1)"
45+
let paramNameSyntax = TokenSyntax.identifier(paramName)
46+
47+
let inheritedType: TypeSyntax?
48+
let colon: TokenSyntax?
49+
if node.baseType.description != "Any" {
50+
colon = .colonToken()
51+
inheritedType = node.baseType.withLeadingTrivia(.space)
52+
} else {
53+
colon = nil
54+
inheritedType = nil
55+
}
56+
57+
let genericParam = GenericParameterSyntax(
58+
attributes: nil, name: paramNameSyntax, ellipsis: nil, colon: colon,
59+
inheritedType: inheritedType, trailingComma: nil
60+
)
61+
62+
let genericParamRef = SimpleTypeIdentifierSyntax(
63+
name: .identifier(paramName), genericArgumentClause: nil
64+
)
65+
66+
rewrittenSomeParameters.append(
67+
.init(
68+
original: node, genericParam: genericParam,
69+
genericParamRef: genericParamRef))
70+
71+
return TypeSyntax(genericParamRef)
72+
}
73+
74+
override func visit(_ node: TupleTypeSyntax) -> TypeSyntax {
75+
let newNode = super.visit(node)
76+
77+
// If this tuple type is simple parentheses around a replaced "some"
78+
// parameter, drop the parentheses.
79+
guard let newTuple = newNode.as(TupleTypeSyntax.self),
80+
newTuple.elements.count == 1,
81+
let onlyElement = newTuple.elements.first,
82+
onlyElement.name == nil,
83+
onlyElement.ellipsis == nil,
84+
let onlyIdentifierType =
85+
onlyElement.type.as(SimpleTypeIdentifierSyntax.self),
86+
rewrittenSomeParameters.first(
87+
where: { $0.genericParamRef.name.text == onlyIdentifierType.name.text }
88+
) != nil
89+
else {
90+
return newNode
91+
}
92+
93+
return TypeSyntax(onlyIdentifierType)
94+
}
95+
}
96+
97+
/// Rewrite `some` parameters to explicit generic parameters.
98+
///
99+
/// ## Before
100+
///
101+
/// ```swift
102+
/// func someFunction(_ input: some Value) {}
103+
/// ```
104+
///
105+
/// ## After
106+
///
107+
/// ```swift
108+
/// func someFunction<T1: Value>(_ input: T1) {}
109+
/// ```
110+
public struct OpaqueParameterToGeneric: RefactoringProvider {
111+
/// Replace all of the "some" parameters in the given parameter clause with
112+
/// freshly-created generic parameters.
113+
///
114+
/// - Returns: nil if there was nothing to rewrite, or a pair of the
115+
/// rewritten parameters and augmented generic parameter list.
116+
static func replaceSomeParameters(
117+
in params: ParameterClauseSyntax,
118+
augmenting genericParams: GenericParameterClauseSyntax?
119+
) -> (ParameterClauseSyntax, GenericParameterClauseSyntax)? {
120+
let rewriter = SomeParameterRewriter()
121+
let rewrittenParams = rewriter.visit(params.parameterList)
122+
123+
if rewriter.rewrittenSomeParameters.isEmpty {
124+
return nil
125+
}
126+
127+
var newGenericParams: [GenericParameterSyntax] = []
128+
if let genericParams = genericParams {
129+
newGenericParams.append(contentsOf: genericParams.genericParameterList)
130+
}
131+
132+
for rewritten in rewriter.rewrittenSomeParameters {
133+
let newGenericParam = rewritten.genericParam
134+
135+
// Add a trailing comma to the prior generic parameter, if there is one.
136+
if let lastNewGenericParam = newGenericParams.last {
137+
newGenericParams[newGenericParams.count-1] =
138+
lastNewGenericParam.withTrailingComma(.commaToken())
139+
newGenericParams.append(newGenericParam.withLeadingTrivia(.space))
140+
} else {
141+
newGenericParams.append(newGenericParam)
142+
}
143+
}
144+
145+
let newGenericParamSyntax = GenericParameterListSyntax(newGenericParams)
146+
let newGenericParamClause: GenericParameterClauseSyntax
147+
if let genericParams = genericParams {
148+
newGenericParamClause = genericParams.withGenericParameterList(
149+
newGenericParamSyntax
150+
)
151+
} else {
152+
newGenericParamClause = GenericParameterClauseSyntax(
153+
leftAngleBracket: .leftAngleToken(),
154+
genericParameterList: newGenericParamSyntax,
155+
genericWhereClause: nil,
156+
rightAngleBracket: .rightAngleToken()
157+
)
158+
}
159+
160+
return (
161+
params.withParameterList(rewrittenParams),
162+
newGenericParamClause
163+
)
164+
}
165+
166+
public static func refactor(
167+
syntax decl: DeclSyntax, in context: Void
168+
) -> DeclSyntax? {
169+
// Function declaration.
170+
if let funcSyntax = decl.as(FunctionDeclSyntax.self) {
171+
guard let (newInput, newGenericParams) = replaceSomeParameters(
172+
in: funcSyntax.signature.input,
173+
augmenting: funcSyntax.genericParameterClause
174+
) else {
175+
return nil
176+
}
177+
178+
return DeclSyntax(
179+
funcSyntax
180+
.withSignature(funcSyntax.signature.withInput(newInput))
181+
.withGenericParameterClause(newGenericParams)
182+
)
183+
}
184+
185+
// Initializer declaration.
186+
if let initSyntax = decl.as(InitializerDeclSyntax.self) {
187+
guard let (newInput, newGenericParams) = replaceSomeParameters(
188+
in: initSyntax.signature.input,
189+
augmenting: initSyntax.genericParameterClause
190+
) else {
191+
return nil
192+
}
193+
194+
return DeclSyntax(
195+
initSyntax
196+
.withSignature(initSyntax.signature.withInput(newInput))
197+
.withGenericParameterClause(newGenericParams)
198+
)
199+
}
200+
201+
// Subscript declaration.
202+
if let subscriptSyntax = decl.as(SubscriptDeclSyntax.self) {
203+
guard let (newIndices, newGenericParams) = replaceSomeParameters(
204+
in: subscriptSyntax.indices,
205+
augmenting: subscriptSyntax.genericParameterClause
206+
) else {
207+
return nil
208+
}
209+
210+
return DeclSyntax(
211+
subscriptSyntax
212+
.withIndices(newIndices)
213+
.withGenericParameterClause(newGenericParams)
214+
)
215+
}
216+
217+
return nil
218+
}
219+
}
220+
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftRefactor
14+
import SwiftSyntax
15+
import SwiftSyntaxBuilder
16+
17+
import XCTest
18+
import _SwiftSyntaxTestSupport
19+
20+
final class OpaqueParameterToGenericTest: XCTestCase {
21+
func testRefactoringFunc() throws {
22+
let baseline: DeclSyntax = """
23+
func f(
24+
x: some P,
25+
y: [some Hashable & Codable: some Any]
26+
) -> some Equatable { }
27+
"""
28+
29+
let expected: DeclSyntax = """
30+
func f<T1: P, T2: Hashable & Codable, T3>(
31+
x: T1,
32+
y: [T2: T3]
33+
) -> some Equatable { }
34+
"""
35+
36+
let refactored = try XCTUnwrap(OpaqueParameterToGeneric.refactor(syntax: baseline))
37+
38+
AssertStringsEqualWithDiff(expected.description, refactored.description)
39+
}
40+
41+
func testRefactoringInit() throws {
42+
let baseline: DeclSyntax = """
43+
init<A>(
44+
x: (some P<A>),
45+
y: [some Hashable & Codable: some Any]
46+
) { }
47+
"""
48+
49+
let expected: DeclSyntax = """
50+
init<A, T1: P<A>, T2: Hashable & Codable, T3>(
51+
x: T1,
52+
y: [T2: T3]
53+
) { }
54+
"""
55+
56+
let refactored = try XCTUnwrap(OpaqueParameterToGeneric.refactor(syntax: baseline))
57+
58+
AssertStringsEqualWithDiff(expected.description, refactored.description)
59+
}
60+
61+
func testRefactoringSubscript() throws {
62+
let baseline: DeclSyntax = """
63+
subscript(index: some Hashable) -> String
64+
"""
65+
66+
let expected: DeclSyntax = """
67+
subscript<T1: Hashable>(index: T1) -> String
68+
"""
69+
70+
let refactored = try XCTUnwrap(OpaqueParameterToGeneric.refactor(syntax: baseline))
71+
72+
AssertStringsEqualWithDiff(expected.description, refactored.description)
73+
}
74+
}

0 commit comments

Comments
 (0)