Skip to content

Refactor Syntax initializer #1092

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 1 commit into from
Dec 15, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,35 +25,36 @@ let buildableNodesFile = SourceFile {
let type = node.type
let hasTrailingComma = node.traits.contains("WithTrailingComma")

let docComment: SwiftSyntax.Trivia = node.documentation.isEmpty ? [] : .docLineComment("/// \(node.documentation)") + .newline
ExtensionDecl(
leadingTrivia: docComment,
extendedType: SimpleTypeIdentifier(name: .identifier(type.shorthandName)),
inheritanceClause: hasTrailingComma ? TypeInheritanceClause { InheritedType(typeName: Type("HasTrailingComma")) } : nil
) {
// Generate initializers
createDefaultInitializer(node: node)
if let convenienceInit = createConvenienceInitializer(node: node) {
convenienceInit
}
let convenienceInit = createConvenienceInitializer(node: node)
if convenienceInit != nil || hasTrailingComma {
let docComment: SwiftSyntax.Trivia = node.documentation.isEmpty ? [] : .docLineComment("/// \(node.documentation)") + .newline
ExtensionDecl(
leadingTrivia: docComment,
extendedType: SimpleTypeIdentifier(name: .identifier(type.shorthandName)),
inheritanceClause: hasTrailingComma ? TypeInheritanceClause { InheritedType(typeName: Type("HasTrailingComma")) } : nil
) {
if let convenienceInit = convenienceInit {
convenienceInit
}

if hasTrailingComma {
VariableDecl(
if hasTrailingComma {
VariableDecl(
"""
var hasTrailingComma: Bool {
return trailingComma != nil
}
"""
)
)

FunctionDecl(
FunctionDecl(
"""
/// Conformance to `HasTrailingComma`.
public func withTrailingComma(_ withComma: Bool) -> Self {
return withTrailingComma(withComma ? .commaToken() : nil)
}
"""
)
)
}
}
}
}
Expand All @@ -67,57 +68,6 @@ private func convertFromSyntaxProtocolToSyntaxType(child: Child) -> Expr {
}
}

/// Create the default initializer for the given node.
private func createDefaultInitializer(node: Node) -> InitializerDecl {
let type = node.type
return InitializerDecl(
leadingTrivia: ([
"/// Creates a `\(type.shorthandName)` using the provided parameters.",
"/// - Parameters:",
] + node.children.map { child in
"/// - \(child.swiftName): \(child.documentation)"
}).map { .docLineComment($0) + .newline }.reduce([], +),
// FIXME: If all parameters are specified, the SwiftSyntaxBuilder initializer is ambigious
// with the memberwise initializer in SwiftSyntax.
// Hot-fix this by preferring the overload in SwiftSyntax. In the long term, consider sinking
// this initializer to SwiftSyntax.
attributes: AttributeList { CustomAttribute("_disfavoredOverload").withTrailingTrivia(.space) },
modifiers: [DeclModifier(name: .public)],
signature: FunctionSignature(
input: ParameterClause {
for trivia in ["leadingTrivia", "trailingTrivia"] {
FunctionParameter("\(trivia): Trivia = []", for: .functionParameters)
}
for child in node.children {
FunctionParameter(
firstName: .identifier(child.swiftName),
colon: .colon,
type: child.parameterType,
defaultArgument: child.type.defaultInitialization.map { InitializerClause(value: $0) }
)
}
}
)
) {
for child in node.children {
if let assertStmt = child.generateAssertStmtTextChoices(varName: child.swiftName) {
assertStmt
}
}
let nodeConstructorCall = FunctionCallExpr(callee: Expr(IdentifierExpr(identifier: .identifier(type.syntaxBaseName)))) {
for child in node.children {
TupleExprElement(
label: child.isUnexpectedNodes ? nil : child.swiftName,
expression: convertFromSyntaxProtocolToSyntaxType(child: child)
)
}
}
SequenceExpr("self = \(nodeConstructorCall)")
SequenceExpr("self.leadingTrivia = leadingTrivia + (self.leadingTrivia ?? [])")
SequenceExpr("self.trailingTrivia = trailingTrivia + (self.trailingTrivia ?? [])")
}
}

/// Create a builder-based convenience initializer, if needed.
private func createConvenienceInitializer(node: Node) -> InitializerDecl? {
// Only create the convenience initializer if at least one parameter
Expand Down Expand Up @@ -192,28 +142,24 @@ private func createConvenienceInitializer(node: Node) -> InitializerDecl? {
"/// - Initializing syntax collections using result builders",
"/// - Initializing tokens without default text using strings",
].map { .docLineComment($0) + .newline }.reduce([], +),
// FIXME: If all parameters are specified, the SwiftSyntaxBuilder initializer is ambigious
// with the memberwise initializer in SwiftSyntax.
// Hot-fix this by preferring the overload in SwiftSyntax. In the long term, consider sinking
// this initializer to SwiftSyntax.
attributes: AttributeList { CustomAttribute("_disfavoredOverload").withTrailingTrivia(.space) },
modifiers: [DeclModifier(name: .public)],
signature: FunctionSignature(
input: ParameterClause {
FunctionParameter("leadingTrivia: Trivia = []", for: .functionParameters)
FunctionParameter("leadingTrivia: Trivia? = nil", for: .functionParameters)
for param in normalParameters + builderParameters {
param
}
FunctionParameter("trailingTrivia: Trivia? = nil", for: .functionParameters)
}
)
) {
FunctionCallExpr(callee: "self.init") {
TupleExprElement(label: "leadingTrivia", expression: "leadingTrivia")
for arg in delegatedInitArgs {
arg
}
TupleExprElement(label: "trailingTrivia", expression: "trailingTrivia")
}

SequenceExpr("self.leadingTrivia = leadingTrivia + (self.leadingTrivia ?? [])")
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,24 @@ let syntaxExpressibleByStringInterpolationConformancesFile = SourceFile {
}

for node in SYNTAX_NODES {
if node.parserFunction != nil && node.isBase {
if node.isBase {
ExtensionDecl("extension \(node.name)Protocol") {
InitializerDecl(
"""
public init(stringInterpolationOrThrow stringInterpolation: SyntaxStringInterpolation) throws {
self = try performParse(source: stringInterpolation.sourceText, parse: { parser in
let node = \(raw: node.name).parse(from: &parser)
guard let result = node.as(Self.self) else {
throw SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: Self.self, actualType: node.kind.syntaxNodeType)
}
return result
})
}
self = try performParse(source: stringInterpolation.sourceText, parse: { parser in
let node = \(raw: node.name).parse(from: &parser)
guard let result = node.as(Self.self) else {
throw SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: Self.self, actualType: node.kind.syntaxNodeType)
}
return result
})
}
""")

}

}

if node.parserFunction != nil {
ExtensionDecl("extension \(node.name): SyntaxExpressibleByStringInterpolation") {
InitializerDecl(
"""
Expand All @@ -63,7 +64,7 @@ let syntaxExpressibleByStringInterpolationConformancesFile = SourceFile {
}
""")
}
} else if node.parserFunction != nil || (node.baseType.baseName != "Syntax" && node.baseType.baseName != "SyntaxCollection") {
} else if !node.isMissing && node.baseType.baseName != "Syntax" && node.baseType.baseName != "SyntaxCollection" {
ExtensionDecl("extension \(raw: node.name): SyntaxExpressibleByStringInterpolation { }")
}
}
Expand Down
3 changes: 1 addition & 2 deletions Sources/SwiftOperators/OperatorTable+Defaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,7 @@ extension OperatorTable {

Operator(
kind: .infix,
name: "~>",
precedenceGroup: nil
name: "~>"
)
]

Expand Down
8 changes: 3 additions & 5 deletions Sources/SwiftOperators/SyntaxSynthesis.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ extension Operator {
/// semantic definition.
public func synthesizedSyntax() -> OperatorDeclSyntax {
let modifiers = ModifierListSyntax(
[DeclModifierSyntax(name: .identifier("\(kind)"), detail: nil)]
[DeclModifierSyntax(name: .identifier("\(kind)"))]
)
let operatorKeyword = TokenSyntax.operatorKeyword(leadingTrivia: .space)
let identifierSyntax =
Expand All @@ -31,7 +31,7 @@ extension Operator {
}

return OperatorDeclSyntax(
attributes: nil, modifiers: modifiers, operatorKeyword: operatorKeyword,
modifiers: modifiers, operatorKeyword: operatorKeyword,
identifier: identifierSyntax,
operatorPrecedenceAndTypes: precedenceGroupSyntax
)
Expand All @@ -55,8 +55,7 @@ extension PrecedenceRelation {
otherNames: PrecedenceGroupNameListSyntax(
[
PrecedenceGroupNameElementSyntax(
name: .identifier(groupName, leadingTrivia: .space),
trailingComma: nil)
name: .identifier(groupName, leadingTrivia: .space))
]
)
)
Expand Down Expand Up @@ -126,7 +125,6 @@ extension PrecedenceGroup {
)

return PrecedenceGroupDeclSyntax(
attributes: nil, modifiers: nil,
precedencegroupKeyword: precedencegroupKeyword,
identifier: identifierSyntax, leftBrace: leftBrace,
groupAttributes: PrecedenceGroupAttributeListSyntax(groupAttributes),
Expand Down
28 changes: 5 additions & 23 deletions Sources/SwiftParserDiagnostics/PresenceUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,6 @@ class PresentMaker: SyntaxRewriter {
modifiers: node.modifiers,
structKeyword: .structKeyword(presence: .missing),
identifier: .identifier("<#declaration#>", leadingTrivia: leadingTriviaBeforePlaceholder),
genericParameterClause: nil,
inheritanceClause: nil,
genericWhereClause: nil,
members: MemberDeclBlockSyntax(
leftBrace: .leftBraceToken(presence: .missing),
members: MemberDeclListSyntax([]),
Expand All @@ -89,39 +86,24 @@ class PresentMaker: SyntaxRewriter {
}

override func visit(_ node: MissingExprSyntax) -> ExprSyntax {
return ExprSyntax(IdentifierExprSyntax(
identifier: .identifier("<#expression#>"),
declNameArguments: nil
))
return ExprSyntax(IdentifierExprSyntax(identifier: .identifier("<#expression#>")))
}

override func visit(_ node: MissingPatternSyntax) -> PatternSyntax {
return PatternSyntax(IdentifierPatternSyntax(
identifier: .identifier("<#pattern#>")
))
return PatternSyntax(IdentifierPatternSyntax(identifier: .identifier("<#pattern#>")))
}

override func visit(_ node: MissingStmtSyntax) -> StmtSyntax {
return StmtSyntax(ExpressionStmtSyntax(
expression: ExprSyntax(IdentifierExprSyntax(
identifier: .identifier("<#statement#>"),
declNameArguments: nil
))
))
expression: IdentifierExprSyntax(identifier: .identifier("<#statement#>"))))
}

override func visit(_ node: MissingTypeSyntax) -> TypeSyntax {
return TypeSyntax(SimpleTypeIdentifierSyntax(
name: .identifier("<#type#>"),
genericArgumentClause: nil
))
return TypeSyntax(SimpleTypeIdentifierSyntax(name: .identifier("<#type#>")))
}

override func visit(_ node: MissingSyntax) -> Syntax {
return Syntax(IdentifierExprSyntax(
identifier: .identifier("<#syntax#>"),
declNameArguments: nil
))
return Syntax(IdentifierExprSyntax(identifier: .identifier("<#syntax#>")))
}
}

Expand Down
19 changes: 17 additions & 2 deletions Sources/SwiftSyntax/Raw/RawSyntax.swift
Original file line number Diff line number Diff line change
Expand Up @@ -692,9 +692,24 @@ extension RawSyntax {
static func makeLayout<C: Collection>(
kind: SyntaxKind,
from collection: C,
arena: SyntaxArena
arena: SyntaxArena,
leadingTrivia: Trivia? = nil,
trailingTrivia: Trivia? = nil
) -> RawSyntax where C.Element == RawSyntax? {
.makeLayout(kind: kind, uninitializedCount: collection.count, arena: arena) {
if leadingTrivia != nil || trailingTrivia != nil {
var layout = Array(collection)
if let leadingTrivia = leadingTrivia,
let idx = layout.firstIndex(where: {$0 != nil}) {
layout[idx] = layout[idx]!.withLeadingTrivia(leadingTrivia, arena: arena)
}
if let trailingTrivia = trailingTrivia,
let idx = layout.lastIndex(where: {$0 != nil}) {
layout[idx] = layout[idx]!.withTrailingTrivia(trailingTrivia, arena: arena)
}
return .makeLayout(kind: kind, from: layout, arena: arena)
}

return .makeLayout(kind: kind, uninitializedCount: collection.count, arena: arena) {
_ = $0.initialize(from: collection)
}
}
Expand Down
63 changes: 44 additions & 19 deletions Sources/SwiftSyntax/SyntaxNodes.swift.gyb.template
Original file line number Diff line number Diff line change
Expand Up @@ -113,35 +113,60 @@ public struct ${node.name}: ${base_type}Protocol, SyntaxHashable {
self._syntaxNode = Syntax(data)
}

public init(
% for (index, child) in enumerate(node.children):
% comma = ',' if index != len(node.children) - 1 else ''
% param_type = child.name if child.node_choices else child.type_name
% if child.is_optional:
% param_type = param_type + "?"
% if child.is_unexpected_nodes():
_ ${child.swift_name}: ${param_type} = nil${comma}
% else:
${child.swift_name}: ${param_type}${comma}
% end
% end
) {
${node.generate_initializer_decl(optional_base_as_missing=False, current_indentation=" ")} {
% if node.children:
let layout: [RawSyntax?] = [
% for child in node.children:
% if child.is_optional:
% for child in node.children:
% if child.is_optional:
${child.swift_name}?.raw,
% else:
% else:
${child.swift_name}.raw,
% end
% end
% end
]
% end
let data: SyntaxData = withExtendedLifetime(SyntaxArena()) { arena in
let raw = RawSyntax.makeLayout(kind: SyntaxKind.${node.swift_syntax_kind},
from: layout, arena: arena)
% if node.children:
let raw = RawSyntax.makeLayout(
kind: SyntaxKind.${node.swift_syntax_kind}, from: layout, arena: arena,
leadingTrivia: leadingTrivia, trailingTrivia: trailingTrivia)
% else:
let raw = RawSyntax.makeEmptyLayout(kind: SyntaxKind.${node.swift_syntax_kind}, arena: arena)
% end
return SyntaxData.forRoot(raw)
}
self.init(data)
}
% if node.has_optional_base_type_child():

% # TODO: Remove when we no longer support compiling in Swift 5.6. Change the
% # above constructor to use `Optional<BaseType.none` instead.
/// This initializer exists solely because Swift 5.6 does not support
/// `Optional<ConcreteType>.none` as a default value of a generic parameter.
/// The above initializer thus defaults to `nil` instead, but that means it
/// is not actually callable when either not passing the defaulted parameter,
/// or passing `nil`.
///
/// Hack around that limitation using this initializer, which takes a
/// `Missing*` syntax node instead. `Missing*` is used over the base type as
/// the base type would allow implicit conversion from a string literal,
/// which the above initializer doesn't support.
${node.generate_initializer_decl(optional_base_as_missing=True, current_indentation=" ")} {
self.init(
leadingTrivia: leadingTrivia,
% for child in node.children:
% if child.has_optional_base_type():
${child.swift_name}: Optional<${child.type_name}>.none,
% elif child.is_unexpected_nodes():
${child.swift_name},
% else:
${child.swift_name}: ${child.swift_name},
% end
% end
trailingTrivia: trailingTrivia
)
}
% end
% for (idx, child) in enumerate(node.children):
% child_node = NODE_MAP.get(child.syntax_kind)
%
Expand Down
Loading