Skip to content

Commit 8706e0f

Browse files
authored
Merge pull request #2813 from AppAppWorks/improve-identifier-backticking
[CodeGeneration] Improve identifier backticking
2 parents 92b9fa1 + d0558dc commit 8706e0f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+407
-348
lines changed

CodeGeneration/Sources/SyntaxSupport/Child.swift

+8-7
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import SwiftSyntax
1414

1515
/// The kind of token a node can contain. Either a token of a specific kind or a
1616
/// keyword with the given text.
17-
public enum TokenChoice: Equatable {
17+
public enum TokenChoice: Equatable, IdentifierConvertible {
1818
case keyword(Keyword)
1919
case token(Token)
2020

@@ -25,12 +25,13 @@ public enum TokenChoice: Equatable {
2525
}
2626
}
2727

28-
public var varOrCaseName: TokenSyntax {
28+
/// The name of this token choice as an identifier.
29+
public var identifier: TokenSyntax {
2930
switch self {
3031
case .keyword(let keyword):
31-
return keyword.spec.varOrCaseName
32+
return keyword.spec.identifier
3233
case .token(let token):
33-
return token.spec.varOrCaseName
34+
return token.spec.identifier
3435
}
3536
}
3637
}
@@ -79,7 +80,7 @@ public enum ChildKind {
7980

8081
/// A child of a node, that may be declared optional or a token with a
8182
/// restricted subset of acceptable kinds or texts.
82-
public class Child {
83+
public class Child: IdentifierConvertible {
8384
/// The name of the child.
8485
///
8586
/// The first character of the name is always uppercase.
@@ -145,8 +146,8 @@ public class Child {
145146
}
146147
}
147148

148-
/// A name of this child that's suitable to be used for variable or enum case names.
149-
public var varOrCaseName: TokenSyntax {
149+
/// A name of this child as an identifier.
150+
public var identifier: TokenSyntax {
150151
return .identifier(lowercaseFirstWord(name: name))
151152
}
152153

CodeGeneration/Sources/SyntaxSupport/GrammarGenerator.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ struct GrammarGenerator {
2828
if let tokenText = tokenSpec.text {
2929
return "`\(tokenText)`"
3030
} else {
31-
return "`<\(tokenSpec.varOrCaseName)>`"
31+
return "`<\(tokenSpec.identifier)>`"
3232
}
3333
}
3434
}
@@ -62,7 +62,7 @@ struct GrammarGenerator {
6262
return
6363
children
6464
.filter { !$0.isUnexpectedNodes }
65-
.map { " - `\($0.varOrCaseName)`: \(generator.grammar(for: $0))" }
65+
.map { " - `\($0.identifier)`: \(generator.grammar(for: $0))" }
6666
.joined(separator: "\n")
6767
}
6868

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 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+
/// Instances of a conforming type should provide an identifier to be used in code generation.
16+
public protocol IdentifierConvertible {
17+
/// The name of the instance as an identifer.
18+
var identifier: TokenSyntax {
19+
get
20+
}
21+
}
22+
23+
public extension IdentifierConvertible {
24+
/// ``identifier`` escaped as a base name suitable for call sites.
25+
var baseCallName: TokenSyntax {
26+
identifier.declNameOrVarCallName
27+
}
28+
29+
/// ``identifier`` escaped as an enum case name suitable for call sites.
30+
var enumCaseCallName: TokenSyntax {
31+
memberCallName
32+
}
33+
34+
/// ``identifier`` escaped as a member name suitable for call sites.
35+
var memberCallName: TokenSyntax {
36+
identifier.nonVarCallNameOrLabelDeclName
37+
}
38+
39+
/// ``identifier`` escaped as an enum case name suitable for declaration sites.
40+
var enumCaseDeclName: TokenSyntax {
41+
identifier.declNameOrVarCallName
42+
}
43+
44+
/// ``identifier`` escaped as a function name suitable for declaration sites.
45+
var funcDeclName: TokenSyntax {
46+
identifier.declNameOrVarCallName
47+
}
48+
49+
/// ``identifier`` escaped as an argument label name suitable for declaration sites.
50+
var labelDeclName: TokenSyntax {
51+
identifier.nonVarCallNameOrLabelDeclName
52+
}
53+
54+
/// ``identifier`` escaped as a variable name suitable for declaration sites.
55+
var varDeclName: TokenSyntax {
56+
identifier.declNameOrVarCallName
57+
}
58+
}

CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift

+4-8
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import SwiftSyntax
1414

15-
public struct KeywordSpec {
15+
public struct KeywordSpec: IdentifierConvertible {
1616
/// The name of the keyword.
1717
public let name: String
1818

@@ -29,13 +29,9 @@ public struct KeywordSpec {
2929
/// API generated should be marked as SPI
3030
public var isExperimental: Bool { experimentalFeature != nil }
3131

32-
/// The name of this keyword that's suitable to be used for variable or enum case names.
33-
public var varOrCaseName: TokenSyntax {
34-
if name == "init" {
35-
return "`init`"
36-
} else {
37-
return TokenSyntax.identifier(name)
38-
}
32+
/// The name of this keyword as an identifier.
33+
public var identifier: TokenSyntax {
34+
TokenSyntax.identifier(name)
3935
}
4036

4137
/// The attributes that should be printed on any API for the generated keyword.

CodeGeneration/Sources/SyntaxSupport/Node.swift

+5-6
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import SwiftSyntax
2121
/// but fixed types.
2222
/// - Collection nodes contains an arbitrary number of children but all those
2323
/// children are of the same type.
24-
public class Node {
24+
public class Node: IdentifierConvertible {
2525
fileprivate enum Data {
2626
case layout(children: [Child], traits: [String])
2727
case collection(choices: [SyntaxNodeKind])
@@ -61,10 +61,9 @@ public class Node {
6161
/// API generated should be SPI.
6262
public var isExperimental: Bool { experimentalFeature != nil }
6363

64-
/// A name for this node that is suitable to be used as a variables or enum
65-
/// case's name.
66-
public var varOrCaseName: TokenSyntax {
67-
return kind.varOrCaseName
64+
/// A name for this node as an identifier.
65+
public var identifier: TokenSyntax {
66+
return kind.identifier
6867
}
6968

7069
/// If this is a layout node, return a view of the node that provides access
@@ -229,7 +228,7 @@ public class Node {
229228
let list =
230229
childIn
231230
.map {
232-
if let childName = $0.child?.varOrCaseName {
231+
if let childName = $0.child?.identifier {
233232
// This will repeat the syntax type before and after the dot, which is
234233
// a little unfortunate, but it's the only way I found to get docc to
235234
// generate a fully-qualified type + member.

CodeGeneration/Sources/SyntaxSupport/String+Extensions.swift

-9
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,6 @@ extension StringProtocol {
2323
}
2424
return prefix(1).uppercased() + dropFirst()
2525
}
26-
public var backtickedIfNeeded: String {
27-
if Keyword.allCases.map(\.spec).contains(where: {
28-
$0.name == self && ($0.isLexerClassified || $0.name == "Type" || $0.name == "Protocol")
29-
}) {
30-
return "`\(self)`"
31-
} else {
32-
return String(self)
33-
}
34-
}
3526
}
3627

3728
extension String {

CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift

+3-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import SwiftSyntaxBuilder
1717
///
1818
/// Using the cases of this enum, children of syntax nodes can refer the syntax
1919
/// node that defines their layout.
20-
public enum SyntaxNodeKind: String, CaseIterable {
20+
public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible {
2121
// Please keep this list sorted alphabetically
2222

2323
case _canImportExpr
@@ -337,9 +337,8 @@ public enum SyntaxNodeKind: String, CaseIterable {
337337
}
338338
}
339339

340-
/// A name for this node that is suitable to be used as a variables or enum
341-
/// case's name.
342-
public var varOrCaseName: TokenSyntax {
340+
/// A name for this node as an identifier.
341+
public var identifier: TokenSyntax {
343342
return .identifier(rawValue)
344343
}
345344

CodeGeneration/Sources/SyntaxSupport/TokenSpec.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@
1313
import SwiftSyntax
1414

1515
/// Represents the specification for a Token in the TokenSyntax file.
16-
public struct TokenSpec {
16+
public struct TokenSpec: IdentifierConvertible {
1717
public enum Kind {
1818
case punctuation
1919
/// The `keyword` TokenKind that contains the actual keyword as an associated value
2020
case keyword
2121
case other
2222
}
2323

24-
/// The name of the token, suitable for use in variable or enum case names.
25-
public let varOrCaseName: TokenSyntax
24+
/// The name of the token as an identifier.
25+
public let identifier: TokenSyntax
2626

2727
/// The experimental feature the token is part of, or `nil` if this isn't
2828
/// for an experimental feature.
@@ -66,7 +66,7 @@ public struct TokenSpec {
6666
text: String? = nil,
6767
kind: Kind
6868
) {
69-
self.varOrCaseName = .identifier(name)
69+
self.identifier = .identifier(name)
7070
self.experimentalFeature = experimentalFeature
7171
self.nameForDiagnostics = nameForDiagnostics
7272
self.text = text

CodeGeneration/Sources/SyntaxSupport/Utils.swift

+19-4
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,29 @@ extension Collection {
6969
}
7070
}
7171

72+
private extension Keyword {
73+
static let backticksNeeded: Set<String> = Set(
74+
Keyword.allCases.map(\.spec).filter {
75+
$0.isLexerClassified || $0.name == "Type" || $0.name == "Protocol"
76+
}.map(\.name)
77+
)
78+
}
79+
7280
extension TokenSyntax {
73-
public var backtickedIfNeeded: TokenSyntax {
74-
if Keyword.allCases.map(\.spec).contains(where: {
75-
$0.name == self.description && ($0.isLexerClassified || $0.name == "Type" || $0.name == "Protocol")
76-
}) {
81+
public var declNameOrVarCallName: Self {
82+
if Keyword.backticksNeeded.contains(self.description) {
7783
return "`\(self)`"
7884
} else {
7985
return self
8086
}
8187
}
88+
89+
public var nonVarCallNameOrLabelDeclName: Self {
90+
switch self.tokenKind {
91+
case .keyword(.`init`), .identifier("init"):
92+
return "`init`"
93+
default:
94+
return self
95+
}
96+
}
8297
}

CodeGeneration/Sources/Utils/SyntaxBuildableChild.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,12 @@ extension Child {
7272
return buildableType.defaultValue
7373
}
7474
if token.text != nil {
75-
return ExprSyntax(".\(token.varOrCaseName)Token()")
75+
return ExprSyntax(".\(token.identifier)Token()")
7676
}
7777
if case .token(let choices, _, _) = kind,
7878
case .keyword(let keyword) = choices.only
7979
{
80-
return ExprSyntax(".\(token.varOrCaseName)(.\(keyword.spec.varOrCaseName))")
80+
return ExprSyntax(".\(token.memberCallName)(.\(keyword.spec.memberCallName))")
8181
}
8282
return nil
8383
}

CodeGeneration/Sources/Utils/SyntaxBuildableType.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public struct SyntaxBuildableType: Hashable {
6464
return ExprSyntax(NilLiteralExprSyntax())
6565
} else if let token = token {
6666
if token.text != nil {
67-
return ExprSyntax(".\(token.varOrCaseName)Token()")
67+
return ExprSyntax(".\(token.identifier)Token()")
6868
}
6969
}
7070
return nil

CodeGeneration/Sources/generate-swift-syntax/LayoutNode+Extensions.swift

+10-10
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ extension LayoutNode {
4444
if useDeprecatedChildName, let deprecatedVarName = child.deprecatedVarName {
4545
parameterName = deprecatedVarName
4646
} else {
47-
parameterName = child.varOrCaseName
47+
parameterName = child.labelDeclName
4848
}
4949

5050
return FunctionParameterSyntax(
@@ -80,7 +80,7 @@ extension LayoutNode {
8080
if child.documentationAbstract.isEmpty {
8181
return nil
8282
}
83-
return " - \(child.varOrCaseName): \(child.documentationAbstract)"
83+
return " - \(child.identifier): \(child.documentationAbstract)"
8484
}
8585

8686
let formattedParams = """
@@ -116,7 +116,7 @@ extension LayoutNode {
116116
if useDeprecatedChildName, let deprecatedVarName = child.deprecatedVarName {
117117
childName = deprecatedVarName
118118
} else {
119-
childName = child.varOrCaseName
119+
childName = child.identifier
120120
}
121121

122122
if child.buildableType.isBuilderInitializable {
@@ -127,11 +127,11 @@ extension LayoutNode {
127127
let param = Node.from(type: child.buildableType).layoutNode!.singleNonDefaultedChild
128128
if child.isOptional {
129129
produceExpr = ExprSyntax(
130-
"\(childName)Builder().map { \(child.buildableType.syntaxBaseName)(\(param.varOrCaseName): $0) }"
130+
"\(childName)Builder().map { \(child.buildableType.syntaxBaseName)(\(param.labelDeclName): $0) }"
131131
)
132132
} else {
133133
produceExpr = ExprSyntax(
134-
"\(child.buildableType.syntaxBaseName)(\(param.varOrCaseName): \(childName)Builder())"
134+
"\(child.buildableType.syntaxBaseName)(\(param.labelDeclName): \(childName)Builder())"
135135
)
136136
}
137137
} else {
@@ -149,7 +149,7 @@ extension LayoutNode {
149149
)
150150
normalParameters.append(
151151
FunctionParameterSyntax(
152-
firstName: childName,
152+
firstName: childName.nonVarCallNameOrLabelDeclName,
153153
colon: .colonToken(),
154154
type: child.parameterType,
155155
defaultValue: child.defaultInitialization
@@ -158,7 +158,7 @@ extension LayoutNode {
158158
}
159159
delegatedInitArgs.append(
160160
LabeledExprSyntax(
161-
label: child.isUnexpectedNodes ? nil : child.varOrCaseName,
161+
label: child.isUnexpectedNodes ? nil : child.labelDeclName,
162162
colon: child.isUnexpectedNodes ? nil : .colonToken(),
163163
expression: produceExpr
164164
)
@@ -202,11 +202,11 @@ fileprivate func convertFromSyntaxProtocolToSyntaxType(
202202
if useDeprecatedChildName, let deprecatedVarName = child.deprecatedVarName {
203203
childName = deprecatedVarName
204204
} else {
205-
childName = child.varOrCaseName
205+
childName = child.identifier
206206
}
207207

208208
if child.buildableType.isBaseType && !child.kind.isNodeChoices {
209-
return ExprSyntax("\(child.buildableType.syntaxBaseName)(fromProtocol: \(childName.backtickedIfNeeded))")
209+
return ExprSyntax("\(child.buildableType.syntaxBaseName)(fromProtocol: \(childName.declNameOrVarCallName))")
210210
}
211-
return ExprSyntax("\(childName.backtickedIfNeeded)")
211+
return ExprSyntax("\(childName.declNameOrVarCallName)")
212212
}

CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/IsLexerClassifiedFile.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ let isLexerClassifiedFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
4141
try! SwitchExprSyntax("switch self") {
4242
for keyword in Keyword.allCases {
4343
if keyword.spec.isLexerClassified {
44-
SwitchCaseSyntax("case .\(keyword.spec.varOrCaseName): return true")
44+
SwitchCaseSyntax("case .\(keyword.spec.enumCaseCallName): return true")
4545
}
4646
}
4747
SwitchCaseSyntax("default: return false")

0 commit comments

Comments
 (0)