Skip to content

protocol ListBuilder #2451

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 4 commits into from
Feb 3, 2024
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
9 changes: 0 additions & 9 deletions CodeGeneration/Sources/Utils/SyntaxBuildableType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,6 @@ public struct SyntaxBuildableType: Hashable {
}
}

public var parameterType: TypeSyntax {
return optionalWrapped(type: parameterBaseType)
}

/// Assuming that this is a collection type, the non-optional type of the result builder
/// that can be used to build the collection.
public var resultBuilderType: TypeSyntax {
Expand All @@ -161,11 +157,6 @@ public struct SyntaxBuildableType: Hashable {
}
}

/// Whether this type has the `WithTrailingComma` trait.
public var hasWithTrailingCommaTrait: Bool {
SYNTAX_NODES.compactMap(\.layoutNode).contains { $0.type == self && $0.traits.contains("WithTrailingComma") }
}

/// If this type is not a base kind, its base type (see `SyntaxBuildableNode.base_type()`),
/// otherwise `nil`.
public var baseType: Self? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,161 +20,34 @@ let resultBuildersFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {

for node in SYNTAX_NODES.compactMap(\.collectionNode) {
let type = SyntaxBuildableType(kind: .node(kind: node.kind))
let elementType = node.collectionElementType
let expressionType: TypeSyntax = node.elementChoices.count == 1 ? elementType.parameterType : TypeSyntax("\(type.buildable).Element")

try! StructDeclSyntax(
"""

// MARK: - \(type.resultBuilderType)

@resultBuilder
\(node.node.apiAttributes())\
public struct \(type.resultBuilderType)
public struct \(type.resultBuilderType): ListBuilder
"""
) {
DeclSyntax(
"""
/// The type of individual statement expressions in the transformed function,
/// which defaults to Component if buildExpression() is not provided.
public typealias Expression = \(expressionType)
"""
)

DeclSyntax(
"""
/// The type of a partial result, which will be carried through all of the
/// build methods.
public typealias Component = [Expression]
"""
)

DeclSyntax(
"""
/// The type of the final returned result, which defaults to Component if
/// buildFinalResult() is not provided.
public typealias FinalResult = \(type.buildable)
"""
)

DeclSyntax(
"""
/// Required by every result builder to build combined results from
/// statement blocks.
public static func buildBlock(_ components: Self.Component...) -> Self.Component {
return components.flatMap { $0 }
}
"""
)

DeclSyntax(
"""
/// If declared, provides contextual type information for statement
/// expressions to translate them into partial results.
public static func buildExpression(_ expression: Self.Expression) -> Self.Component {
return [expression]
}
public typealias FinalResult = \(type.syntaxBaseName)
"""
)

if node.elementChoices.count > 1 {
for elementChoice in node.elementChoices {
DeclSyntax(
"""
/// If declared, provides contextual type information for statement
/// expressions to translate them into partial results.
public static func buildExpression(_ expression: \(elementChoice.syntaxType)) -> Self.Component {
return buildExpression(.init(expression))
public static func buildExpression(_ expression: \(elementChoice.syntaxType)) -> Component {
buildExpression(.init(expression))
}
"""
)
}
}

DeclSyntax(
"""
/// Add all the elements of `expression` to this result builder, effectively flattening them.
///
/// - Note: This overload is disfavored to resolve an ambiguity when both the final result and
/// the elements are expressible by string interpolation. In that case we favor creating a
/// single element from the string literal.
@_disfavoredOverload
public static func buildExpression(_ expression: Self.FinalResult) -> Self.Component {
return expression.map { $0 }
}
"""
)

DeclSyntax(
"""
/// Enables support for `if` statements that do not have an `else`.
public static func buildOptional(_ component: Self.Component?) -> Self.Component {
return component ?? []
}
"""
)

DeclSyntax(
"""
/// With buildEither(second:), enables support for 'if-else' and 'switch'
/// statements by folding conditional results into a single result.
public static func buildEither(first component: Self.Component) -> Self.Component {
return component
}
"""
)

DeclSyntax(
"""
/// With buildEither(first:), enables support for 'if-else' and 'switch'
/// statements by folding conditional results into a single result.
public static func buildEither(second component: Self.Component) -> Self.Component {
return component
}
"""
)

DeclSyntax(
"""
/// Enables support for 'for..in' loops by combining the
/// results of all iterations into a single result.
public static func buildArray(_ components: [Self.Component]) -> Self.Component {
return components.flatMap { $0 }
}
"""
)

DeclSyntax(
"""
/// If declared, this will be called on the partial result of an 'if'
/// #available' block to allow the result builder to erase type
/// information.
public static func buildLimitedAvailability(_ component: Self.Component) -> Self.Component {
return component
}
"""
)

try FunctionDeclSyntax(
"""
/// If declared, this will be called on the partial result from the outermost
/// block statement to produce the final returned result.
public static func buildFinalResult(_ component: Component) -> FinalResult
"""
) {
if elementType.isToken {
StmtSyntax("return .init(component)")
} else if elementType.hasWithTrailingCommaTrait {
DeclSyntax("let lastIndex = component.count - 1")

StmtSyntax(
"""
return .init(component.enumerated().map { index, source in
return index < lastIndex ? source.ensuringTrailingComma() : source
})
"""
)
} else {
StmtSyntax("return .init(component)")
}
}
}

DeclSyntax(
Expand Down
1 change: 1 addition & 0 deletions Sources/SwiftSyntaxBuilder/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ add_swift_syntax_library(SwiftSyntaxBuilder
ConvenienceInitializers.swift
DeclSyntaxParseable.swift
Indenter.swift
ListBuilder.swift
ResultBuilderExtensions.swift
SwiftSyntaxBuilderCompatibility.swift
Syntax+StringInterpolation.swift
Expand Down
60 changes: 38 additions & 22 deletions Sources/SwiftSyntaxBuilder/ConvenienceInitializers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
@_spi(RawSyntax) import SwiftParser
@_spi(RawSyntax) import SwiftSyntax

// MARK: - ArrayElementList
// MARK: - ArrayElementListSyntax

extension ArrayElementListSyntax {
public init(expressions: [ExprSyntax]) {
Expand All @@ -30,15 +30,15 @@ extension ArrayElementListSyntax {
}
}

// MARK: - ArrayExpr
// MARK: - ArrayExprSyntax

extension ArrayExprSyntax {
public init(expressions: [ExprSyntax]) {
self.init(elements: ArrayElementListSyntax(expressions: expressions))
}
}

// MARK: - CustomAttribute
// MARK: - AttributeSyntax

extension AttributeSyntax {
/// A convenience initializer that allows passing in arguments using a result builder
Expand All @@ -58,15 +58,15 @@ extension AttributeSyntax {
}
}

// MARK: - BinaryOperatorExpr
// MARK: - BinaryOperatorExprSyntax

extension BinaryOperatorExprSyntax {
public init(text: String) {
self.init(operator: .binaryOperator(text))
}
}

// MARK: - BooleanLiteralExpr
// MARK: - BooleanLiteralExprSyntax

extension BooleanLiteralExprSyntax: ExpressibleByBooleanLiteral {
public init(_ value: Bool) {
Expand All @@ -78,7 +78,7 @@ extension BooleanLiteralExprSyntax: ExpressibleByBooleanLiteral {
}
}

// MARK: - CatchClause
// MARK: - CatchClauseSyntax

extension CatchClauseSyntax {
/// A convenience initializer that calculates spacing around the `catch` keyword.
Expand All @@ -96,7 +96,7 @@ extension CatchClauseSyntax {
}
}

// MARK: - DictionaryExpr
// MARK: - DictionaryExprSyntax

extension DictionaryExprSyntax {
/// A convenience initializer that allows passing in members using a result builder
Expand All @@ -115,7 +115,15 @@ extension DictionaryExprSyntax {
}
}

// MARK: - Expr
// MARK: - ExprListSyntax

extension ExprListSyntax {
public init(_ elements: [ExprSyntaxProtocol]) {
self.init(elements.map { ExprSyntax(fromProtocol: $0) } as [ExprSyntax])
}
}

// MARK: - ExprSyntax

extension ExprSyntax {
/// Returns a syntax tree for an expression that represents the value of the
Expand Down Expand Up @@ -163,7 +171,7 @@ extension FloatLiteralExprSyntax: ExpressibleByFloatLiteral {
}
}

// MARK: - FunctionCallExpr
// MARK: - FunctionCallExprSyntax

extension FunctionCallExprSyntax {
/// A convenience initializer that allows passing in arguments using a result builder
Expand All @@ -188,7 +196,7 @@ extension FunctionCallExprSyntax {
}
}

// MARK: - IntegerLiteralExpr
// MARK: - IntegerLiteralExprSyntax

extension IntegerLiteralExprSyntax: ExpressibleByIntegerLiteral {
public init(_ value: Int) {
Expand All @@ -200,7 +208,21 @@ extension IntegerLiteralExprSyntax: ExpressibleByIntegerLiteral {
}
}

// MARK: - StringLiteralExpr
// MARK: - LabeledExprSyntax

extension LabeledExprSyntax {
/// A convenience initializer that allows passing in label as an optional string.
/// The presence of the colon will be inferred based on the presence of the label.
public init(label: String? = nil, expression: some ExprSyntaxProtocol) {
self.init(
label: label.map { .identifier($0) },
colon: label == nil ? nil : .colonToken(trailingTrivia: .space),
expression: expression
)
}
}

// MARK: - StringLiteralExprSyntax

extension String {
/// Replace literal newlines with "\r", "\n", "\u{2028}", and ASCII control characters with "\0", "\u{7}"
Expand Down Expand Up @@ -338,21 +360,15 @@ extension StringLiteralExprSyntax {
}
}

// MARK: - TupleExprElement
// MARK: - UnexpectedNodesSyntax

extension LabeledExprSyntax {
/// A convenience initializer that allows passing in label as an optional string.
/// The presence of the colon will be inferred based on the presence of the label.
public init(label: String? = nil, expression: some ExprSyntaxProtocol) {
self.init(
label: label.map { .identifier($0) },
colon: label == nil ? nil : .colonToken(trailingTrivia: .space),
expression: expression
)
extension UnexpectedNodesSyntax {
public init(_ elements: [SyntaxProtocol]) {
self.init(elements.map { Syntax(fromProtocol: $0) } as [Syntax])
}
}

// MARK: - VariableDecl
// MARK: - VariableDeclSyntax

extension VariableDeclSyntax {
/// Creates an optionally initialized property.
Expand Down
Loading