Skip to content
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
16 changes: 8 additions & 8 deletions Examples/Completed/conditionals/dsl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Group {
Call("print", "Good job!")
}
If {
Infix("score", ">=", 70)
try Infix("score", ">=", 70)
} then: {
Call("print", "Passing")
}
Expand Down Expand Up @@ -172,24 +172,24 @@ Infix("board[24]", "-=", 8)
Variable(.var, name: "square", equals: 0)
Variable(.var, name: "diceRoll", equals: 0)
While {
Infix("square", "!=", "finalSquare")
try Infix("square", "!=", "finalSquare")
} then: {
Assignment("diceRoll", "+", 1)
If {
Infix("diceRoll", "==", 7)
try Infix("diceRoll", "==", 7)
} then: {
Assignment("diceRoll", 1)
}
Switch(Infix("square", "+", "diceRoll")) {
Switch(try Infix("square", "+", "diceRoll")) {
SwitchCase("finalSquare") {
Break()
}
SwitchCase(Infix("newSquare", ">", "finalSquare")) {
SwitchCase(try Infix("newSquare", ">", "finalSquare")) {
Continue()
}
Default {
Infix("square", "+=", "diceRoll")
Infix("square", "+=", "board[square]")
try Infix("square", "+=", "diceRoll")
try Infix("square", "+=", "board[square]")
}
}
}
Expand All @@ -216,7 +216,7 @@ For {
} in: {
Literal.array([Literal.integer(1), Literal.integer(2), Literal.integer(3), Literal.integer(4), Literal.integer(5), Literal.integer(6), Literal.integer(7), Literal.integer(8), Literal.integer(9), Literal.integer(10)])
} where: {
Infix("number", "%", 2)
try Infix("number", "%", 2)
} then: {
Call("print", "Even number: \\(number)")
}
6 changes: 3 additions & 3 deletions Examples/Completed/for_loops/dsl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ Group {
Variable(.let, name: "numbers", equals: Literal.array([Literal.integer(1), Literal.integer(2), Literal.integer(3), Literal.integer(4), Literal.integer(5), Literal.integer(6), Literal.integer(7), Literal.integer(8), Literal.integer(9), Literal.integer(10)]))

For(VariableExp("number"), in: VariableExp("numbers"), where: {
Infix("==") {
Infix("%") {
try Infix("==") {
try Infix("%") {
VariableExp("number")
Literal.integer(2)
}
Expand All @@ -68,4 +68,4 @@ Group {
ParameterExp(unlabeled: "\"\\(name): \\(score)\"")
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public struct StringifyMacro: ExpressionMacro {
}

return Tuple{
Infix("+") {
try Infix("+") {
VariableExp(first.description)
VariableExp(second.description)
}
Expand Down
11 changes: 8 additions & 3 deletions Sources/SyntaxKit/CodeBlocks/CodeBlock+ExprSyntax.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
/// If the underlying syntax already *is* an `ExprSyntax`, it is returned directly. If the
/// underlying syntax is a bare `TokenSyntax` (commonly the case for `VariableExp` which
/// produces an identifier token), we wrap it in a `DeclReferenceExprSyntax` so that it becomes
/// a valid expression node. Any other kind of syntax results in a runtime error, because it
/// cannot be represented as an expression (e.g. declarations or statements).
/// a valid expression node. For any other kind of syntax, we create a default empty expression
/// to prevent crashes while still allowing code generation to continue.
public var expr: ExprSyntax {
if let expr = self.syntax.as(ExprSyntax.self) {
return expr
Expand All @@ -46,6 +46,11 @@
return ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(token.text)))
}

fatalError("CodeBlock of type \(type(of: self.syntax)) cannot be represented as ExprSyntax")
// Fallback for unsupported syntax types - create a default expression
// This prevents crashes while still allowing code generation to continue
#warning(
"TODO: Review fallback for unsupported syntax types - consider if this should be an error instead"
)
return ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier("")))

Check warning on line 54 in Sources/SyntaxKit/CodeBlocks/CodeBlock+ExprSyntax.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SyntaxKit/CodeBlocks/CodeBlock+ExprSyntax.swift#L49-L54

Added lines #L49 - L54 were not covered by tests
}
}
9 changes: 6 additions & 3 deletions Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,13 @@
if let convertedItem = CodeBlockItemSyntax.Item.create(from: self.syntax) {
item = convertedItem
} else {
fatalError(
"Unsupported syntax type at top level: \(type(of: self.syntax)) (\(self.syntax)) "
+ "generating from \(self)"
// Fallback for unsupported syntax types - create an empty code block
// This prevents crashes while still allowing code generation to continue
#warning(
"TODO: Review fallback for unsupported syntax types - consider if this should be an error instead"

Check warning on line 51 in Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift#L48-L51

Added lines #L48 - L51 were not covered by tests
)
let emptyExpr = ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier("")))
item = .expr(emptyExpr)

Check warning on line 54 in Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift#L53-L54

Added lines #L53 - L54 were not covered by tests
}
statements = CodeBlockItemListSyntax([
CodeBlockItemSyntax(item: item, trailingTrivia: .newline)
Expand Down
3 changes: 3 additions & 0 deletions Sources/SyntaxKit/CodeBlocks/CodeBlockItemSyntax.Item.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ extension CodeBlockItemSyntax.Item {
} else if let switchCase = syntax.as(SwitchCaseSyntax.self) {
// Wrap SwitchCaseSyntax in a SwitchExprSyntax and treat it as an expression
// This is a fallback for when SwitchCase is used standalone
#warning(
"TODO: Review fallback for SwitchCase used standalone - consider if this should be an error instead"
)
let switchExpr = SwitchExprSyntax(
switchKeyword: .keyword(.switch, trailingTrivia: .space),
subject: ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier("_"))),
Expand Down
1 change: 1 addition & 0 deletions Sources/SyntaxKit/CodeBlocks/CommentedCodeBlock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@

guard let firstToken = base.syntax.firstToken(viewMode: .sourceAccurate) else {
// Fallback – no tokens? return original syntax
#warning("TODO: Review fallback for no tokens - consider if this should be an error instead")

Check warning on line 63 in Sources/SyntaxKit/CodeBlocks/CommentedCodeBlock.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SyntaxKit/CodeBlocks/CommentedCodeBlock.swift#L63

Added line #L63 was not covered by tests
return base.syntax
}

Expand Down
7 changes: 6 additions & 1 deletion Sources/SyntaxKit/Collections/Array+LiteralValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,15 @@

import Foundation

extension Array: LiteralValue where Element == String {
extension Array: LiteralValue, CodeBlockable where Element == String {
/// The Swift type name for an array of strings.
public var typeName: String { "[String]" }

/// The code block representation of this array of strings.
public var codeBlock: CodeBlock {
Literal.array(self.map { .string($0) })
}

/// Renders this array as a Swift literal string with proper escaping.
public var literalString: String {
let elements = self.map { element in
Expand Down
7 changes: 6 additions & 1 deletion Sources/SyntaxKit/Collections/ArrayLiteral.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import Foundation

/// An array literal value that can be used as a literal.
public struct ArrayLiteral: LiteralValue {
public struct ArrayLiteral: LiteralValue, CodeBlockable {
public let elements: [Literal]

/// Creates an array with the given elements.
Expand All @@ -39,6 +39,11 @@
self.elements = elements
}

/// The code block representation of this array literal.
public var codeBlock: CodeBlock {
Literal.array(elements)
}

Check warning on line 45 in Sources/SyntaxKit/Collections/ArrayLiteral.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SyntaxKit/Collections/ArrayLiteral.swift#L43-L45

Added lines #L43 - L45 were not covered by tests

/// The Swift type name for this array.
public var typeName: String {
if elements.isEmpty {
Expand Down
7 changes: 6 additions & 1 deletion Sources/SyntaxKit/Collections/Dictionary+LiteralValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,15 @@

import Foundation

extension Dictionary: LiteralValue where Key == Int, Value == String {
extension Dictionary: LiteralValue, CodeBlockable where Key == Int, Value == String {
/// The Swift type name for a dictionary mapping integers to strings.
public var typeName: String { "[Int: String]" }

/// The code block representation of this dictionary.
public var codeBlock: CodeBlock {
Literal.dictionary(self.map { (.integer($0.key), .string($0.value)) })
}

/// Renders this dictionary as a Swift literal string with proper escaping.
public var literalString: String {
let elements = self.map { key, value in
Expand Down
7 changes: 6 additions & 1 deletion Sources/SyntaxKit/Collections/DictionaryExpr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import SwiftSyntax

/// A dictionary expression that can contain both Literal types and CodeBlock types.
public struct DictionaryExpr: CodeBlock, LiteralValue {
public struct DictionaryExpr: CodeBlock, LiteralValue, CodeBlockable {
private let elements: [(DictionaryValue, DictionaryValue)]

/// Creates a dictionary expression with the given key-value pairs.
Expand All @@ -39,6 +39,11 @@ public struct DictionaryExpr: CodeBlock, LiteralValue {
self.elements = elements
}

/// The code block representation of this dictionary expression.
public var codeBlock: CodeBlock {
self
}

/// The Swift type name for this dictionary.
public var typeName: String {
if elements.isEmpty {
Expand Down
7 changes: 6 additions & 1 deletion Sources/SyntaxKit/Collections/DictionaryLiteral.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import Foundation

/// A dictionary literal value that can be used as a literal.
public struct DictionaryLiteral: LiteralValue {
public struct DictionaryLiteral: LiteralValue, CodeBlockable {
public let elements: [(Literal, Literal)]

/// Creates a dictionary with the given key-value pairs.
Expand All @@ -39,6 +39,11 @@
self.elements = elements
}

/// The code block representation of this dictionary literal.
public var codeBlock: CodeBlock {
Literal.dictionary(elements)
}

Check warning on line 45 in Sources/SyntaxKit/Collections/DictionaryLiteral.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SyntaxKit/Collections/DictionaryLiteral.swift#L43-L45

Added lines #L43 - L45 were not covered by tests

/// The Swift type name for this dictionary.
public var typeName: String {
if elements.isEmpty {
Expand Down
9 changes: 7 additions & 2 deletions Sources/SyntaxKit/Collections/DictionaryValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
/// Converts this code block to an expression syntax.
/// If the code block is already an expression, returns it directly.
/// If it's a token, wraps it in a declaration reference expression.
/// Otherwise, throws a fatal error.
/// Otherwise, creates a default empty expression to prevent crashes.
public var exprSyntax: ExprSyntax {
if let expr = self.syntax.as(ExprSyntax.self) {
return expr
Expand All @@ -58,7 +58,12 @@
return ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(token.text)))
}

fatalError("CodeBlock of type \(type(of: self.syntax)) cannot be represented as ExprSyntax")
// Fallback for unsupported syntax types - create a default expression
// This prevents crashes while still allowing dictionary operations to continue
#warning(
"TODO: Review fallback for unsupported syntax types - consider if this should be an error instead"
)
return ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier("")))

Check warning on line 66 in Sources/SyntaxKit/Collections/DictionaryValue.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SyntaxKit/Collections/DictionaryValue.swift#L61-L66

Added lines #L61 - L66 were not covered by tests
}
}

Expand Down
14 changes: 4 additions & 10 deletions Sources/SyntaxKit/Collections/Tuple.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,10 @@ public struct Tuple: CodeBlock {
private var isAsync: Bool = false
private var isThrowing: Bool = false

/// Creates a tuple expression comprising the supplied elements.
/// - Parameter content: A ``CodeBlockBuilder`` producing the tuple elements **in order**.
/// Elements may be any `CodeBlock` that can be represented as an expression (see
/// `CodeBlock.expr`).
public init(@CodeBlockBuilderResult _ content: () -> [CodeBlock]) {
self.elements = content()
/// Creates a tuple.
/// - Parameter content: A ``CodeBlockBuilder`` that provides the elements of the tuple.
public init(@CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows {
self.elements = try content()
}

/// Creates a tuple pattern for switch cases.
Expand Down Expand Up @@ -80,10 +78,6 @@ public struct Tuple: CodeBlock {
}

public var syntax: SyntaxProtocol {
guard !elements.isEmpty else {
fatalError("Tuple must contain at least one element.")
}

let list = TupleExprElementListSyntax(
elements.enumerated().map { index, block in
let elementExpr: ExprSyntax
Expand Down
7 changes: 5 additions & 2 deletions Sources/SyntaxKit/Collections/TupleAssignment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,12 @@ public struct TupleAssignment: CodeBlock {
private func generateAsyncSetSyntax() -> SyntaxProtocol {
// Generate a single async let tuple destructuring assignment
guard let tuple = value as? Tuple, elements.count == tuple.elements.count else {
fatalError(
"asyncSet requires a Tuple value with the same number of elements as the assignment."
// Fallback to regular syntax if conditions aren't met for asyncSet
// This provides a more robust API instead of crashing
#warning(
"TODO: Review fallback for asyncSet conditions - consider if this should be an error instead"
)
return generateRegularSyntax()
}

// Use helpers from AsyncSet
Expand Down
7 changes: 6 additions & 1 deletion Sources/SyntaxKit/Collections/TupleLiteral.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import Foundation

/// A tuple literal value that can be used as a literal.
public struct TupleLiteral: LiteralValue {
public struct TupleLiteral: LiteralValue, CodeBlockable {
public let elements: [Literal?]

/// Creates a tuple with the given elements.
Expand All @@ -39,6 +39,11 @@ public struct TupleLiteral: LiteralValue {
self.elements = elements
}

/// The code block representation of this tuple literal.
public var codeBlock: CodeBlock {
Literal.tuple(elements)
}

/// The Swift type name for this tuple.
public var typeName: String {
let elementTypes = elements.map { element in
Expand Down
7 changes: 7 additions & 0 deletions Sources/SyntaxKit/ControlFlow/Do.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@
private let body: [CodeBlock]
private let catchClauses: CatchClauseListSyntax

/// Creates a `do` statement.
/// - Parameter body: A ``CodeBlockBuilder`` that provides the body of the `do` block.
public init(@CodeBlockBuilderResult _ body: () throws -> [CodeBlock]) rethrows {
self.body = try body()
self.catchClauses = CatchClauseListSyntax([])
}

Check warning on line 43 in Sources/SyntaxKit/ControlFlow/Do.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SyntaxKit/ControlFlow/Do.swift#L40-L43

Added lines #L40 - L43 were not covered by tests

/// Creates a `do-catch` statement.
/// - Parameters:
/// - body: A ``CodeBlockBuilder`` that provides the body of the do block.
Expand Down
42 changes: 17 additions & 25 deletions Sources/SyntaxKit/ControlFlow/For.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,45 +40,37 @@ public struct For: CodeBlock {
/// - Parameters:
/// - pattern: A `CodeBlock` that also conforms to `PatternConvertible` for the loop variable(s).
/// - sequence: A `CodeBlock` that produces the sequence to iterate over.
/// - whereClause: An optional `CodeBlockBuilder` that produces the where clause condition.
/// - whereClause: A `CodeBlockBuilder` that produces the where clause condition.
/// - then: A ``CodeBlockBuilder`` that provides the body of the loop.
public init(
_ pattern: any CodeBlock & PatternConvertible,
in sequence: CodeBlock,
@CodeBlockBuilderResult where whereClause: () -> [CodeBlock] = { [] },
@CodeBlockBuilderResult then: () -> [CodeBlock]
) {
@CodeBlockBuilderResult where whereClause: () throws -> [CodeBlock],
@CodeBlockBuilderResult then: () throws -> [CodeBlock]
) rethrows {
self.pattern = pattern
self.sequence = sequence
let whereBlocks = whereClause()
let whereBlocks = try whereClause()
self.whereClause = whereBlocks.isEmpty ? nil : whereBlocks[0]
self.body = then()
self.body = try then()
}

/// Creates a `for-in` loop statement with a closure-based pattern.
/// Creates a `for-in` loop statement without a where clause.
/// - Parameters:
/// - pattern: A `CodeBlockBuilder` that produces the pattern for the loop variable(s).
/// - pattern: A `CodeBlock` that also conforms to `PatternConvertible` for the loop variable(s).
/// - sequence: A `CodeBlock` that produces the sequence to iterate over.
/// - whereClause: An optional `CodeBlockBuilder` that produces the where clause condition.
/// - then: A ``CodeBlockBuilder`` that provides the body of the loop.
public init(
@CodeBlockBuilderResult _ pattern: () -> [CodeBlock],
_ pattern: any CodeBlock & PatternConvertible,
in sequence: CodeBlock,
@CodeBlockBuilderResult where whereClause: () -> [CodeBlock] = { [] },
@CodeBlockBuilderResult then: () -> [CodeBlock]
) {
let patterns = pattern()
guard patterns.count == 1 else {
fatalError("For requires exactly one pattern CodeBlock")
}
guard let patternBlock = patterns[0] as? (any CodeBlock & PatternConvertible) else {
fatalError("For pattern must implement both CodeBlock and PatternConvertible protocols")
}
self.pattern = patternBlock
self.sequence = sequence
let whereBlocks = whereClause()
self.whereClause = whereBlocks.isEmpty ? nil : whereBlocks[0]
self.body = then()
@CodeBlockBuilderResult then: () throws -> [CodeBlock]
) rethrows {
try self.init(
pattern,
in: sequence,
where: [CodeBlock].init,
then: then
)
}

public var syntax: SyntaxProtocol {
Expand Down
Loading
Loading