Skip to content

Commit 2d43d17

Browse files
committed
Significantly restrict nodes expressible by string interpolation
With this change the following nodes will be the only ones that are expressible by string interpolation: - All base nodes - `SourceFileSyntax` - `AccessorDeclSyntax` - `SwitchCase` Most importantly, this removes the implicit expressibility by string literals for all decl/stmt/... nodes, which parsed the node as a decl/stmt/... and then cast it to the expected type. New types now need to explicitly opt in to be expressible by string literals. rdar://104090218
1 parent 3ca3c0b commit 2d43d17

32 files changed

+235
-656
lines changed

CodeGeneration/Sources/SyntaxSupport/Node.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public class Node {
2424
public let description: String?
2525
public let baseKind: String
2626
public let parserFunction: String?
27+
public let expressibleByStringLiteral: Bool
2728
public let traits: [String]
2829
public let children: [Child]
2930
public let nonUnexpectedChildren: [Child]
@@ -83,6 +84,7 @@ public class Node {
8384
kind: String,
8485
traits: [String] = [],
8586
parserFunction: String? = nil,
87+
expressibleByStringLiteral: Bool = false,
8688
children: [Child] = [],
8789
element: String = "",
8890
elementName: String? = nil,
@@ -95,6 +97,7 @@ public class Node {
9597
self.nameForDiagnostics = nameForDiagnostics
9698
self.description = description
9799
self.parserFunction = parserFunction
100+
self.expressibleByStringLiteral = expressibleByStringLiteral
98101
self.traits = traits
99102
self.baseKind = kind
100103

CodeGeneration/Sources/SyntaxSupport/gyb_generated/AttributeNodes.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public let ATTRIBUTE_NODES: [Node] = [
1818
description: "An `@` attribute.",
1919
kind: "Syntax",
2020
parserFunction: "parseAttribute",
21+
expressibleByStringLiteral: true,
2122
children: [
2223
Child(name: "AtSignToken",
2324
kind: "AtSignToken",

CodeGeneration/Sources/SyntaxSupport/gyb_generated/CommonNodes.swift

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,32 @@ public let COMMON_NODES: [Node] = [
1616
Node(name: "Decl",
1717
nameForDiagnostics: "declaration",
1818
kind: "Syntax",
19-
parserFunction: "parseDeclaration"),
19+
parserFunction: "parseDeclaration",
20+
expressibleByStringLiteral: true),
2021

2122
Node(name: "Expr",
2223
nameForDiagnostics: "expression",
2324
kind: "Syntax",
24-
parserFunction: "parseExpression"),
25+
parserFunction: "parseExpression",
26+
expressibleByStringLiteral: true),
2527

2628
Node(name: "Stmt",
2729
nameForDiagnostics: "statement",
2830
kind: "Syntax",
29-
parserFunction: "parseStatement"),
31+
parserFunction: "parseStatement",
32+
expressibleByStringLiteral: true),
3033

3134
Node(name: "Type",
3235
nameForDiagnostics: "type",
3336
kind: "Syntax",
34-
parserFunction: "parseType"),
37+
parserFunction: "parseType",
38+
expressibleByStringLiteral: true),
3539

3640
Node(name: "Pattern",
3741
nameForDiagnostics: "pattern",
3842
kind: "Syntax",
39-
parserFunction: "parsePattern"),
43+
parserFunction: "parsePattern",
44+
expressibleByStringLiteral: true),
4045

4146
Node(name: "Missing",
4247
nameForDiagnostics: nil,

CodeGeneration/Sources/SyntaxSupport/gyb_generated/DeclNodes.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,7 @@ public let DECL_NODES: [Node] = [
721721
"WithStatements"
722722
],
723723
parserFunction: "parseSourceFile",
724+
expressibleByStringLiteral: true,
724725
children: [
725726
Child(name: "Statements",
726727
kind: "CodeBlockItemList",
@@ -1063,6 +1064,7 @@ public let DECL_NODES: [Node] = [
10631064
"Attributed"
10641065
],
10651066
parserFunction: "parseAccessorDecl",
1067+
expressibleByStringLiteral: true,
10661068
children: [
10671069
Child(name: "Attributes",
10681070
kind: "AttributeList",

CodeGeneration/Sources/SyntaxSupport/gyb_generated/StmtNodes.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,7 @@ public let STMT_NODES: [Node] = [
571571
"WithStatements"
572572
],
573573
parserFunction: "parseSwitchCase",
574+
expressibleByStringLiteral: true,
574575
children: [
575576
Child(name: "UnknownAttr",
576577
kind: "Attribute",

CodeGeneration/Sources/SyntaxSupport/gyb_helpers/utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ def make_swift_node(node):
2222

2323
if node.parser_function:
2424
parameters += ['parserFunction: "%s"' % node.parser_function]
25+
if node.expressible_by_string_literal:
26+
parameters += ['expressibleByStringLiteral: true']
2527

2628
if node.non_unexpected_children:
2729
children = []

CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntaxbuilder/SyntaxExpressibleByStringInterpolationConformancesFile.swift

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -35,38 +35,8 @@ let syntaxExpressibleByStringInterpolationConformancesFile = SourceFileSyntax {
3535
""")
3636
}
3737

38-
for node in SYNTAX_NODES {
39-
if node.isBase {
40-
ExtensionDeclSyntax("extension \(raw: node.name)Protocol") {
41-
InitializerDeclSyntax(
42-
"""
43-
public init(stringInterpolationOrThrow stringInterpolation: SyntaxStringInterpolation) throws {
44-
self = try performParse(source: stringInterpolation.sourceText, parse: { parser in
45-
let node = \(raw: node.name).parse(from: &parser)
46-
guard let result = node.as(Self.self) else {
47-
throw SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: Self.self, actualType: node.kind.syntaxNodeType)
48-
}
49-
return result
50-
})
51-
}
52-
""")
53-
}
54-
}
55-
56-
if node.parserFunction != nil {
57-
ExtensionDeclSyntax("extension \(raw: node.name): SyntaxExpressibleByStringInterpolation") {
58-
InitializerDeclSyntax(
59-
"""
60-
public init(stringInterpolationOrThrow stringInterpolation: SyntaxStringInterpolation) throws {
61-
self = try performParse(source: stringInterpolation.sourceText, parse: { parser in
62-
return Self.parse(from: &parser)
63-
})
64-
}
65-
""")
66-
}
67-
} else if !node.isMissing && node.baseType.baseName != "Syntax" && node.baseType.baseName != "SyntaxCollection" {
68-
ExtensionDeclSyntax("extension \(raw: node.name): SyntaxExpressibleByStringInterpolation { }")
69-
}
38+
for node in SYNTAX_NODES where node.expressibleByStringLiteral {
39+
ExtensionDeclSyntax("extension \(raw: node.name): SyntaxExpressibleByStringInterpolation {}")
7040
}
7141

7242
FunctionDeclSyntax(

Sources/SwiftSyntaxBuilder/ConvenienceInitializers.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ extension VariableDeclSyntax {
408408
attributes: AttributeListSyntax? = nil,
409409
modifiers: ModifierListSyntax? = nil,
410410
_ letOrVarKeyword: Keyword,
411-
name: IdentifierPatternSyntax,
411+
name: PatternSyntax,
412412
type: TypeAnnotationSyntax? = nil,
413413
initializer: InitializerClauseSyntax? = nil
414414
) {
@@ -431,7 +431,7 @@ extension VariableDeclSyntax {
431431
leadingTrivia: Trivia = [],
432432
attributes: AttributeListSyntax? = nil,
433433
modifiers: ModifierListSyntax? = nil,
434-
name: IdentifierPatternSyntax,
434+
name: PatternSyntax,
435435
type: TypeAnnotationSyntax,
436436
@CodeBlockItemListBuilder accessor: () -> CodeBlockItemListSyntax
437437
) {

Sources/SwiftSyntaxBuilder/Syntax+StringInterpolation.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ enum SyntaxStringInterpolationError: Error, CustomStringConvertible {
150150
case producedInvalidNodeType(expectedType: SyntaxProtocol.Type, actualType: SyntaxProtocol.Type)
151151
case diagnostics([Diagnostic], tree: Syntax)
152152

153+
static func producedInvalidNodeType<S: SyntaxProtocol>(expectedType: SyntaxProtocol.Type, actualNode: S) -> Self {
154+
return .producedInvalidNodeType(expectedType: expectedType, actualType: type(of: actualNode))
155+
}
156+
153157
var description: String {
154158
switch self {
155159
case .producedInvalidNodeType(expectedType: let expectedType, actualType: let actualType):

Sources/SwiftSyntaxBuilder/SyntaxNodeWithBody.swift

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
import SwiftSyntax
1414

15+
// MARK: - Error
16+
1517
// MARK: - PartialSyntaxNode
1618

1719
/// A type that is expressible by string interpolation the same way that syntax
@@ -39,16 +41,22 @@ extension SyntaxStringInterpolation {
3941

4042
public protocol HasTrailingCodeBlock {
4143
var body: CodeBlockSyntax { get set }
44+
45+
init(_ signature: PartialSyntaxNodeString, @CodeBlockItemListBuilder bodyBuilder: () -> CodeBlockItemListSyntax) throws
4246
}
4347

44-
public extension HasTrailingCodeBlock where Self: SyntaxExpressibleByStringInterpolation {
45-
init(_ signature: PartialSyntaxNodeString, @CodeBlockItemListBuilder bodyBuilder: () -> CodeBlockItemListSyntax) {
46-
self = "\(signature) {}"
48+
public extension HasTrailingCodeBlock where Self: StmtSyntaxProtocol {
49+
init(_ signature: PartialSyntaxNodeString, @CodeBlockItemListBuilder bodyBuilder: () -> CodeBlockItemListSyntax) throws {
50+
let stmt = StmtSyntax("\(signature) {}")
51+
guard let castedStmt = stmt.as(Self.self) else {
52+
throw SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: IfStmtSyntax.self, actualNode: stmt)
53+
}
54+
self = castedStmt
4755
self.body = CodeBlockSyntax(statements: bodyBuilder())
4856
}
4957
}
5058

51-
extension CatchClauseSyntax: HasTrailingCodeBlock {}
59+
//extension CatchClauseSyntax: HasTrailingCodeBlock {}
5260
extension DeferStmtSyntax: HasTrailingCodeBlock {}
5361
extension DoStmtSyntax: HasTrailingCodeBlock {}
5462
extension ForInStmtSyntax: HasTrailingCodeBlock {}
@@ -59,11 +67,17 @@ extension WhileStmtSyntax: HasTrailingCodeBlock {}
5967

6068
public protocol HasTrailingOptionalCodeBlock {
6169
var body: CodeBlockSyntax? { get set }
70+
71+
init(_ signature: PartialSyntaxNodeString, @CodeBlockItemListBuilder bodyBuilder: () -> CodeBlockItemListSyntax) throws
6272
}
6373

64-
public extension HasTrailingOptionalCodeBlock where Self: SyntaxExpressibleByStringInterpolation {
65-
init(_ signature: PartialSyntaxNodeString, @CodeBlockItemListBuilder bodyBuilder: () -> CodeBlockItemListSyntax) {
66-
self = "\(signature) {}"
74+
public extension HasTrailingOptionalCodeBlock where Self: DeclSyntaxProtocol {
75+
init(_ signature: PartialSyntaxNodeString, @CodeBlockItemListBuilder bodyBuilder: () -> CodeBlockItemListSyntax) throws {
76+
let decl = DeclSyntax("\(signature) {}")
77+
guard let castedDecl = decl.as(Self.self) else {
78+
throw SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: IfStmtSyntax.self, actualNode: decl)
79+
}
80+
self = castedDecl
6781
self.body = CodeBlockSyntax(statements: bodyBuilder())
6882
}
6983
}
@@ -77,11 +91,17 @@ extension InitializerDeclSyntax: HasTrailingOptionalCodeBlock {}
7791

7892
public protocol HasTrailingMemberDeclBlock {
7993
var members: MemberDeclBlockSyntax { get set }
94+
95+
init(_ signature: PartialSyntaxNodeString, @MemberDeclListBuilder membersBuilder: () -> MemberDeclListSyntax) throws
8096
}
8197

82-
public extension HasTrailingMemberDeclBlock where Self: SyntaxExpressibleByStringInterpolation {
83-
init(_ signature: PartialSyntaxNodeString, @MemberDeclListBuilder membersBuilder: () -> MemberDeclListSyntax) {
84-
self = "\(signature) {}"
98+
public extension HasTrailingMemberDeclBlock where Self: DeclSyntaxProtocol {
99+
init(_ signature: PartialSyntaxNodeString, @MemberDeclListBuilder membersBuilder: () -> MemberDeclListSyntax) throws {
100+
let decl = DeclSyntax("\(signature) {}")
101+
guard let castedDecl = decl.as(Self.self) else {
102+
throw SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: IfStmtSyntax.self, actualNode: decl)
103+
}
104+
self = castedDecl
85105
self.members = MemberDeclBlockSyntax(members: membersBuilder())
86106
}
87107
}
@@ -98,15 +118,23 @@ extension StructDeclSyntax: HasTrailingMemberDeclBlock {}
98118
// So we cannot conform to `HasTrailingCodeBlock`
99119

100120
public extension IfStmtSyntax {
101-
init(_ signature: PartialSyntaxNodeString, @CodeBlockItemListBuilder bodyBuilder: () -> CodeBlockItemListSyntax, @CodeBlockItemListBuilder `else` elseBuilder: () -> CodeBlockItemListSyntax? = { nil }) {
102-
self = "\(signature) {}"
121+
init(_ signature: PartialSyntaxNodeString, @CodeBlockItemListBuilder bodyBuilder: () -> CodeBlockItemListSyntax, @CodeBlockItemListBuilder `else` elseBuilder: () -> CodeBlockItemListSyntax? = { nil }) throws {
122+
let stmt = StmtSyntax("\(signature) {}")
123+
guard let ifStmt = stmt.as(IfStmtSyntax.self) else {
124+
throw SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: IfStmtSyntax.self, actualNode: stmt)
125+
}
126+
self = ifStmt
103127
self.body = CodeBlockSyntax(statements: bodyBuilder())
104128
self.elseBody = elseBuilder().map { .codeBlock(CodeBlockSyntax(statements: $0)) }
105129
self.elseKeyword = elseBody != nil ? .keyword(.else) : nil
106130
}
107131

108-
init(_ signature: PartialSyntaxNodeString, @CodeBlockItemListBuilder bodyBuilder: () -> CodeBlockItemListSyntax, elseIf: IfStmtSyntax) {
109-
self = "\(signature) {}"
132+
init(_ signature: PartialSyntaxNodeString, @CodeBlockItemListBuilder bodyBuilder: () -> CodeBlockItemListSyntax, elseIf: IfStmtSyntax) throws {
133+
let stmt = StmtSyntax("\(signature) {}")
134+
guard let ifStmt = stmt.as(IfStmtSyntax.self) else {
135+
throw SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: IfStmtSyntax.self, actualNode: stmt)
136+
}
137+
self = ifStmt
110138
self.body = CodeBlockSyntax(statements: bodyBuilder())
111139
self.elseBody = .ifStmt(elseIf)
112140
self.elseKeyword = elseBody != nil ? .keyword(.else) : nil
@@ -118,8 +146,12 @@ public extension IfStmtSyntax {
118146
// So we cannot conform to `HasTrailingCodeBlock` or `HasTrailingMemberDeclBlock`
119147

120148
public extension SwitchStmtSyntax {
121-
init(_ signature: PartialSyntaxNodeString, @SwitchCaseListBuilder casesBuilder: () -> SwitchCaseListSyntax = { SwitchCaseListSyntax([]) }) {
122-
self = "\(signature) {}"
149+
init(_ signature: PartialSyntaxNodeString, @SwitchCaseListBuilder casesBuilder: () -> SwitchCaseListSyntax = { SwitchCaseListSyntax([]) }) throws {
150+
let stmt = StmtSyntax("\(signature) {}")
151+
guard let castedStmt = stmt.as(Self.self) else {
152+
throw SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: IfStmtSyntax.self, actualNode: stmt)
153+
}
154+
self = castedStmt
123155
self.cases = casesBuilder()
124156
}
125157
}

0 commit comments

Comments
 (0)