Skip to content

Migrate from strings to typed buildable nodes in SyntaxBuildableWrappers #542

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 3 commits into from
Aug 4, 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
22 changes: 11 additions & 11 deletions Sources/SwiftSyntaxBuilder/generated/BuildableBaseProtocols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public extension DeclBuildable {
/// - Parameter leadingTrivia: Replaces the last leading trivia if not nil.
///
/// Satisfies conformance to `DeclListBuildable`
func buildDeclList(format: Format, leadingTrivia: Trivia? = nil) -> [DeclSyntax] {
func buildDeclList(format: Format, leadingTrivia: Trivia? = nil ) -> [DeclSyntax] {
return [buildDecl(format: format, leadingTrivia: leadingTrivia)]
}
/// Builds a `DeclSyntax`.
Expand All @@ -45,7 +45,7 @@ public extension DeclBuildable {
/// - Returns: A new `Syntax` with the built `DeclSyntax`.
///
/// Satisfies conformance to `SyntaxBuildable`.
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil) -> Syntax {
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil ) -> Syntax {
return Syntax(buildDecl(format: format, leadingTrivia: leadingTrivia))
}
}
Expand All @@ -71,7 +71,7 @@ public extension ExprBuildable {
/// - Parameter leadingTrivia: Replaces the last leading trivia if not nil.
///
/// Satisfies conformance to `ExprListBuildable`
func buildExprList(format: Format, leadingTrivia: Trivia? = nil) -> [ExprSyntax] {
func buildExprList(format: Format, leadingTrivia: Trivia? = nil ) -> [ExprSyntax] {
return [buildExpr(format: format, leadingTrivia: leadingTrivia)]
}
/// Builds a `ExprSyntax`.
Expand All @@ -80,7 +80,7 @@ public extension ExprBuildable {
/// - Returns: A new `Syntax` with the built `ExprSyntax`.
///
/// Satisfies conformance to `SyntaxBuildable`.
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil) -> Syntax {
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil ) -> Syntax {
return Syntax(buildExpr(format: format, leadingTrivia: leadingTrivia))
}
}
Expand All @@ -106,7 +106,7 @@ public extension PatternBuildable {
/// - Parameter leadingTrivia: Replaces the last leading trivia if not nil.
///
/// Satisfies conformance to `PatternListBuildable`
func buildPatternList(format: Format, leadingTrivia: Trivia? = nil) -> [PatternSyntax] {
func buildPatternList(format: Format, leadingTrivia: Trivia? = nil ) -> [PatternSyntax] {
return [buildPattern(format: format, leadingTrivia: leadingTrivia)]
}
/// Builds a `PatternSyntax`.
Expand All @@ -115,7 +115,7 @@ public extension PatternBuildable {
/// - Returns: A new `Syntax` with the built `PatternSyntax`.
///
/// Satisfies conformance to `SyntaxBuildable`.
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil) -> Syntax {
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil ) -> Syntax {
return Syntax(buildPattern(format: format, leadingTrivia: leadingTrivia))
}
}
Expand All @@ -141,7 +141,7 @@ public extension StmtBuildable {
/// - Parameter leadingTrivia: Replaces the last leading trivia if not nil.
///
/// Satisfies conformance to `StmtListBuildable`
func buildStmtList(format: Format, leadingTrivia: Trivia? = nil) -> [StmtSyntax] {
func buildStmtList(format: Format, leadingTrivia: Trivia? = nil ) -> [StmtSyntax] {
return [buildStmt(format: format, leadingTrivia: leadingTrivia)]
}
/// Builds a `StmtSyntax`.
Expand All @@ -150,7 +150,7 @@ public extension StmtBuildable {
/// - Returns: A new `Syntax` with the built `StmtSyntax`.
///
/// Satisfies conformance to `SyntaxBuildable`.
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil) -> Syntax {
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil ) -> Syntax {
return Syntax(buildStmt(format: format, leadingTrivia: leadingTrivia))
}
}
Expand All @@ -176,7 +176,7 @@ public extension SyntaxBuildable {
/// - Parameter leadingTrivia: Replaces the last leading trivia if not nil.
///
/// Satisfies conformance to `SyntaxListBuildable`
func buildSyntaxList(format: Format, leadingTrivia: Trivia? = nil) -> [Syntax] {
func buildSyntaxList(format: Format, leadingTrivia: Trivia? = nil ) -> [Syntax] {
return [buildSyntax(format: format, leadingTrivia: leadingTrivia)]
}
}
Expand All @@ -202,7 +202,7 @@ public extension TypeBuildable {
/// - Parameter leadingTrivia: Replaces the last leading trivia if not nil.
///
/// Satisfies conformance to `TypeListBuildable`
func buildTypeList(format: Format, leadingTrivia: Trivia? = nil) -> [TypeSyntax] {
func buildTypeList(format: Format, leadingTrivia: Trivia? = nil ) -> [TypeSyntax] {
return [buildType(format: format, leadingTrivia: leadingTrivia)]
}
/// Builds a `TypeSyntax`.
Expand All @@ -211,7 +211,7 @@ public extension TypeBuildable {
/// - Returns: A new `Syntax` with the built `TypeSyntax`.
///
/// Satisfies conformance to `SyntaxBuildable`.
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil) -> Syntax {
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil ) -> Syntax {
return Syntax(buildType(format: format, leadingTrivia: leadingTrivia))
}
}
33 changes: 28 additions & 5 deletions Sources/SwiftSyntaxBuilderGeneration/SyntaxBuildableChild.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
//
//===----------------------------------------------------------------------===//

import SwiftSyntax
import SwiftSyntaxBuilder

/// Extension to the `Child` type to provide functionality specific to
/// SwiftSyntaxBuilder.
extension Child {
Expand All @@ -31,17 +34,37 @@ extension Child {
/// Generate a Swift expression that creates a proper SwiftSyntax node of type
/// `type.syntax` from a variable named `varName` of type `type.buildable` that
/// represents this child node.
func generateExprBuildSyntaxNode(varName: String, formatName: String) -> String {
func generateExprBuildSyntaxNode(varName: String, formatName: String) -> ExpressibleAsExprBuildable {
if type.isToken {
if requiresLeadingNewline {
return "\(varName).withLeadingTrivia(.newline + \(formatName)._makeIndent() + (\(varName).leadingTrivia ?? []))"
return FunctionCallExpr(MemberAccessExpr(base: varName, name: "withLeadingTrivia")) {
SequenceExpr {
MemberAccessExpr(name: "newline")
BinaryOperatorExpr("+")
FunctionCallExpr(MemberAccessExpr(base: formatName, name: "_makeIndent"))
BinaryOperatorExpr("+")
TupleExpr {
SequenceExpr {
MemberAccessExpr(base: varName, name: "leadingTrivia")
BinaryOperatorExpr("??")
ArrayExpr(elements: [])
}
}
}
}
} else {
return varName
}
} else {
let format = formatName + (isIndented ? "._indented()" : "")
let expr = "\(varName)\(type.optionalQuestionMark)"
return "\(expr).build\(type.baseName)(format: \(format), leadingTrivia: nil)"
var format: ExpressibleAsExprBuildable = formatName
if isIndented {
format = FunctionCallExpr(MemberAccessExpr(base: format, name: "_indented"))
}
let expr = type.optionalChained(expr: varName)
return FunctionCallExpr(MemberAccessExpr(base: expr, name: "build\(type.baseName)")) {
TupleExprElement(label: "format", expression: format)
TupleExprElement(label: "leadingTrivia", expression: NilLiteralExpr())
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ extension Node {

/// Assuming this node has a single child without a default value, that child.
var singleNonDefaultedChild: Child {
let nonDefaultedParams = children.filter(\.type.defaultInitialization.isEmpty)
let nonDefaultedParams = children.filter { $0.type.defaultInitialization == nil }
assert(nonDefaultedParams.count == 1)
return nonDefaultedParams[0]
}
Expand Down
123 changes: 78 additions & 45 deletions Sources/SwiftSyntaxBuilderGeneration/SyntaxBuildableType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
//
//===----------------------------------------------------------------------===//

import SwiftSyntax
import SwiftSyntaxBuilder

/// Wrapper around the syntax kind strings to provide functionality specific to
/// SwiftSyntaxBuilder. In particular, this includes the functionality to create
/// the `*Buildable`, `ExpressibleAs*` and `*Syntax` Swift types from the syntax
Expand All @@ -19,11 +22,6 @@ struct SyntaxBuildableType: Hashable {
let tokenKind: String?
let isOptional: Bool

/// If optional `?`, otherwise the empty string.
var optionalQuestionMark: String {
isOptional ? "?" : ""
}
Comment on lines -22 to -25
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This property is replaced with optionalWrapped(type:) and optionalChained(expr:), depending on whether a type or an expression is needed.


/// Whether this is a token.
var isToken: Bool {
syntaxKind == "Token"
Expand All @@ -43,17 +41,17 @@ struct SyntaxBuildableType: Hashable {
/// with fixed test), return an expression of the form ` = defaultValue`
/// that can be used as the default value for a function parameter.
/// Otherwise, return the empty string.
var defaultInitialization: String {
var defaultInitialization: ExpressibleAsExprBuildable? {
if isOptional {
return " = nil"
return NilLiteralExpr()
} else if isToken {
if let token = token, token.text != nil {
return " = TokenSyntax.`\(lowercaseFirstWord(name: token.name))`"
return MemberAccessExpr(base: "TokenSyntax", name: lowercaseFirstWord(name: token.name))
} else if tokenKind == "EOFToken" {
return " = TokenSyntax.eof"
return MemberAccessExpr(base: "TokenSyntax", name: "eof")
}
}
return ""
return nil
}

/// Whether the type is a syntax collection.
Expand All @@ -68,81 +66,98 @@ struct SyntaxBuildableType: Hashable {
syntaxKind
}

/// Return the name of the `Buildable` type that is the main entry point for building
/// Return the `Buildable` type that is the main entry point for building
/// SwiftSyntax trees using `SwiftSyntaxBuilder`.
///
/// These names look as follows:
/// - For nodes: The node name, e.g. `IdentifierExpr` (these are implemented as structs)
/// - For base kinds: `<BaseKind>Buildable`, e.g. `ExprBuildable` (these are implemented as protocols)
/// - For token: `TokenSyntax` (tokens don't have a dedicated type in SwiftSyntaxBuilder)
/// If the type is optional, this terminates with a '?'.
var buildable: String {
if isToken {
// Tokens don't have a dedicated buildable type.
return "TokenSyntax\(optionalQuestionMark)"
} else if SYNTAX_BASE_KINDS.contains(syntaxKind) {
return "\(syntaxKind)Buildable\(optionalQuestionMark)"
} else {
return "\(syntaxKind)\(optionalQuestionMark)"
}
/// If the type is optional, the type is wrapped in an `OptionalType`.
var buildable: ExpressibleAsTypeBuildable {
optionalWrapped(type: buildableBaseName)
}

/// Whether parameters of this type should be initializable by a result builder.
/// Used for certain syntax collections and block-like structures (e.g. `CodeBlock`,
/// `MemberDeclList`).
var isBuilderInitializable: Bool {
BUILDER_INITIALIZABLE_TYPES.keys.contains(nonOptional.buildable)
BUILDER_INITIALIZABLE_TYPES.keys.contains(buildableBaseName)
}

/// A type suitable for initializing this type through a result builder (e.g.
/// returns `CodeBlockItemList` for `CodeBlock`) and otherwise itself.
var builderInitializableType: Self {
let buildable = nonOptional.buildable
return Self(
syntaxKind: BUILDER_INITIALIZABLE_TYPES[buildable].flatMap { $0 } ?? buildable,
Self(
syntaxKind: BUILDER_INITIALIZABLE_TYPES[buildableBaseName].flatMap { $0 } ?? buildableBaseName,
isOptional: isOptional
)
}

/// The type from `buildable()` without any question marks attached.
/// This is used for the `create*` methods defined in the `ExpressibleAs*` protocols.
var buildableBaseName: String {
nonOptional.buildable
if isToken {
// Tokens don't have a dedicated buildable type.
return "TokenSyntax"
} else if SYNTAX_BASE_KINDS.contains(syntaxKind) {
return "\(syntaxKind)Buildable"
} else {
return syntaxKind
}
}

/// The `ExpressibleAs*` Swift type for this syntax kind. Tokens don't
/// have an `ExpressibleAs*` type, so for those this method just returns
/// `TokenSyntax`. If the type is optional, this terminates with a `?`.
var expressibleAs: String {
/// The `ExpressibleAs*` Swift type for this syntax kind without any
/// question marks attached.
var expressibleAsBaseName: String {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR factors out a few ...BaseName properties (expressibleAs, syntax, buildable). These represent a non-optional type and will still be strings (as opposed to, e.g. ExpressibleAsTypeBuildables) to make them easy to embed in string interpolations and across type and expression contexts.

Thus .xyzBaseName replaces the earlier .nonOptional.xyz idiom.

if isToken {
// Tokens don't have a dedicated ExpressibleAs type.
return buildable
return buildableBaseName
} else {
return "ExpressibleAs\(buildable)"
return "ExpressibleAs\(buildableBaseName)"
}
}

/// The `ExpressibleAs*` Swift type for this syntax kind. Tokens don't
/// have an `ExpressibleAs*` type, so for those this method just returns
/// `TokenSyntax`. If the type is optional, this terminates with a `?`.
var expressibleAs: ExpressibleAsTypeBuildable {
optionalWrapped(type: expressibleAsBaseName)
}

/// The corresponding `*Syntax` type defined in the `SwiftSyntax` module,
/// which will eventually get built from `SwiftSyntaxBuilder`. If the type
/// is optional, this terminates with a `?`.
var syntax: String {
/// without any question marks attached.
var syntaxBaseName: String {
if syntaxKind == "Syntax" {
return "Syntax\(optionalQuestionMark)"
return "Syntax"
} else {
return "\(syntaxKind)Syntax\(optionalQuestionMark)"
return "\(syntaxKind)Syntax"
}
}

/// Assuming that this is a base kind, return the corresponding `*ListBuildable` type.
var listBuildable: String {
/// The corresponding `*Syntax` type defined in the `SwiftSyntax` module,
/// which will eventually get built from `SwiftSyntaxBuilder`. If the type
/// is optional, this terminates with a `?`.
var syntax: ExpressibleAsTypeBuildable {
optionalWrapped(type: syntaxBaseName)
}

/// Assuming that this is a base kind, return the corresponding `*ListBuildable` type
/// without any question marks attached.
var listBuildableBaseName: String {
assert(SYNTAX_BASE_KINDS.contains(syntaxKind), "ListBuildable types only exist for syntax base kinds")
return "\(syntaxKind)ListBuildable\(optionalQuestionMark)"
return "\(syntaxKind)ListBuildable"
}

/// Assuming that this is a base kind, return the corresponding `*ListBuildable` type.
var listBuildable: ExpressibleAsTypeBuildable {
optionalWrapped(type: listBuildableBaseName)
}

/// Assuming that this is a collection type, the type of the result builder
/// that can be used to build the collection.
var resultBuilder: String {
"\(syntaxKind)Builder\(optionalQuestionMark)"
var resultBuilder: ExpressibleAsTypeBuildable {
optionalWrapped(type: "\(syntaxKind)Builder")
}

/// The collection types in which this type occurs as an element.
Expand All @@ -164,7 +179,7 @@ struct SyntaxBuildableType: Hashable {
/// make the `ExpressibleAs*` of this type conform to the `ExpressibleAs*`
/// protocol of the convertible types.
var convertibleToTypes: [Self] {
(SYNTAX_BUILDABLE_EXPRESSIBLE_AS_CONFORMANCES[buildable] ?? [])
(SYNTAX_BUILDABLE_EXPRESSIBLE_AS_CONFORMANCES[buildableBaseName] ?? [])
.map { Self(syntaxKind: $0) }
}

Expand Down Expand Up @@ -217,13 +232,31 @@ struct SyntaxBuildableType: Hashable {
}
}

/// Wraps a type in an optional depending on whether `isOptional` is true.
func optionalWrapped(type: ExpressibleAsTypeBuildable) -> ExpressibleAsTypeBuildable {
if isOptional {
return OptionalType(wrappedType: type)
} else {
return type
}
}

/// Wraps a type in an optional chaining depending on whether `isOptional` is true.
func optionalChained(expr: ExpressibleAsExprBuildable) -> ExpressibleAsExprBuildable {
if isOptional {
return OptionalChainingExpr(expression: expr)
} else {
return expr
}
}

/// Generate an expression that converts a variable named `varName`
/// which is of `expressibleAs` type to an object of type `buildable`.
func generateExprConvertParamTypeToStorageType(varName: String) -> String {
func generateExprConvertParamTypeToStorageType(varName: String) -> ExpressibleAsExprBuildable {
if isToken {
return varName
} else {
return "\(varName)\(optionalQuestionMark).create\(buildableBaseName)()"
return FunctionCallExpr(MemberAccessExpr(base: optionalChained(expr: varName), name: "create\(buildableBaseName)"))
}
}
}
Loading