From 1f0bab4b7a40a1c77e51b7549df3a9ce9a594ca8 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Mon, 23 Jun 2025 22:06:57 -0400 Subject: [PATCH 01/17] fixing fatal errors --- Examples/Completed/conditionals/dsl.swift | 16 +-- Examples/Completed/for_loops/dsl.swift | 4 +- .../SKSampleMacroMacro.swift | 2 +- Sources/SyntaxKit/Collections/Tuple.swift | 4 - .../Collections/TupleAssignment.swift | 6 +- Sources/SyntaxKit/ControlFlow/For.swift | 22 +++++ Sources/SyntaxKit/ControlFlow/Guard.swift | 8 +- Sources/SyntaxKit/ControlFlow/If.swift | 8 +- Sources/SyntaxKit/ControlFlow/While.swift | 48 +++++---- .../SyntaxKit/Core/ExprCodeBlockBuilder.swift | 61 ++++++++++++ .../Core/PatternConvertibleBuilder.swift | 73 ++++++++++++++ .../SyntaxKit/Expressions/Assignment.swift | 11 ++- Sources/SyntaxKit/Expressions/Call.swift | 5 +- .../Expressions/FunctionCallExp.swift | 6 +- Sources/SyntaxKit/Expressions/Infix.swift | 78 +++++++++++---- .../Literal+PatternConvertible.swift | 4 +- .../NegatedPropertyAccessExp.swift | 8 +- .../SyntaxKit/Expressions/PlusAssign.swift | 11 ++- .../Expressions/PropertyAccessExp.swift | 8 +- Sources/SyntaxKit/Expressions/Return.swift | 36 +++++-- .../SyntaxKit/Utilities/Parenthesized.swift | 8 +- Sources/SyntaxKit/Variables/VariableExp.swift | 6 +- .../Integration/ConcurrencyExampleTests.swift | 10 +- .../ConditionalsExampleTests.swift | 64 ++++++------ .../Integration/ForLoopsExampleTests.swift | 4 +- .../Unit/ControlFlow/ConditionalsTests.swift | 4 +- .../Unit/ControlFlow/ForLoopTests.swift | 2 +- .../Unit/EdgeCases/EdgeCaseTests.swift | 99 +++++++++++++++++-- .../EdgeCases/EdgeCaseTestsExpressions.swift | 8 +- 29 files changed, 479 insertions(+), 145 deletions(-) create mode 100644 Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift create mode 100644 Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift diff --git a/Examples/Completed/conditionals/dsl.swift b/Examples/Completed/conditionals/dsl.swift index a666c2a..d6c4850 100644 --- a/Examples/Completed/conditionals/dsl.swift +++ b/Examples/Completed/conditionals/dsl.swift @@ -23,7 +23,7 @@ Group { Call("print", "Good job!") } If { - Infix("score", ">=", 70) + try! Infix("score", ">=", 70) } then: { Call("print", "Passing") } @@ -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]") } } } @@ -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)") } diff --git a/Examples/Completed/for_loops/dsl.swift b/Examples/Completed/for_loops/dsl.swift index 4c8bf36..d267a9b 100644 --- a/Examples/Completed/for_loops/dsl.swift +++ b/Examples/Completed/for_loops/dsl.swift @@ -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) } diff --git a/Macros/SKSampleMacro/Sources/SKSampleMacroMacros/SKSampleMacroMacro.swift b/Macros/SKSampleMacro/Sources/SKSampleMacroMacros/SKSampleMacroMacro.swift index 014fb7e..10b94e2 100644 --- a/Macros/SKSampleMacro/Sources/SKSampleMacroMacros/SKSampleMacroMacro.swift +++ b/Macros/SKSampleMacro/Sources/SKSampleMacroMacros/SKSampleMacroMacro.swift @@ -25,7 +25,7 @@ public struct StringifyMacro: ExpressionMacro { } return Tuple{ - Infix("+") { + try! Infix("+") { VariableExp(first.description) VariableExp(second.description) } diff --git a/Sources/SyntaxKit/Collections/Tuple.swift b/Sources/SyntaxKit/Collections/Tuple.swift index 622673e..4b02e78 100644 --- a/Sources/SyntaxKit/Collections/Tuple.swift +++ b/Sources/SyntaxKit/Collections/Tuple.swift @@ -80,10 +80,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 diff --git a/Sources/SyntaxKit/Collections/TupleAssignment.swift b/Sources/SyntaxKit/Collections/TupleAssignment.swift index 7a96427..501dd43 100644 --- a/Sources/SyntaxKit/Collections/TupleAssignment.swift +++ b/Sources/SyntaxKit/Collections/TupleAssignment.swift @@ -83,9 +83,9 @@ 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 + return generateRegularSyntax() } // Use helpers from AsyncSet diff --git a/Sources/SyntaxKit/ControlFlow/For.swift b/Sources/SyntaxKit/ControlFlow/For.swift index ff3a5d1..d7a153c 100644 --- a/Sources/SyntaxKit/ControlFlow/For.swift +++ b/Sources/SyntaxKit/ControlFlow/For.swift @@ -57,10 +57,32 @@ public struct For: CodeBlock { /// Creates a `for-in` loop statement with a closure-based pattern. /// - Parameters: + /// - pattern: A `CodeBlockBuilder` that produces exactly one pattern 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( + @PatternConvertibleBuilder _ pattern: () -> any CodeBlock & PatternConvertible, + in sequence: CodeBlock, + @CodeBlockBuilderResult where whereClause: () -> [CodeBlock] = { [] }, + @CodeBlockBuilderResult then: () -> [CodeBlock] + ) { + self.pattern = pattern() + self.sequence = sequence + let whereBlocks = whereClause() + self.whereClause = whereBlocks.isEmpty ? nil : whereBlocks[0] + self.body = then() + } + + /// Legacy initializer for backward compatibility. + /// - Parameters: /// - pattern: A `CodeBlockBuilder` that produces the pattern 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. + @available( + *, deprecated, message: "Use PatternConvertibleBuilder instead for compile-time safety" + ) public init( @CodeBlockBuilderResult _ pattern: () -> [CodeBlock], in sequence: CodeBlock, diff --git a/Sources/SyntaxKit/ControlFlow/Guard.swift b/Sources/SyntaxKit/ControlFlow/Guard.swift index b5e9bf5..7685a9b 100644 --- a/Sources/SyntaxKit/ControlFlow/Guard.swift +++ b/Sources/SyntaxKit/ControlFlow/Guard.swift @@ -44,10 +44,12 @@ public struct Guard: CodeBlock { @CodeBlockBuilderResult else elseBody: () -> [CodeBlock] ) { let allConditions = condition() - guard !allConditions.isEmpty else { - fatalError("Guard requires at least one condition CodeBlock") + if allConditions.isEmpty { + // Use true as default condition when no conditions are provided + self.conditions = [Literal.boolean(true)] + } else { + self.conditions = allConditions } - self.conditions = allConditions self.elseBody = elseBody() } diff --git a/Sources/SyntaxKit/ControlFlow/If.swift b/Sources/SyntaxKit/ControlFlow/If.swift index 65bcca7..a795ff4 100644 --- a/Sources/SyntaxKit/ControlFlow/If.swift +++ b/Sources/SyntaxKit/ControlFlow/If.swift @@ -48,10 +48,12 @@ public struct If: CodeBlock { @CodeBlockBuilderResult else elseBody: () -> [CodeBlock] = { [] } ) { let allConditions = condition() - guard !allConditions.isEmpty else { - fatalError("If requires at least one condition CodeBlock") + if allConditions.isEmpty { + // Use true as default condition when no conditions are provided + self.conditions = [Literal.boolean(true)] + } else { + self.conditions = allConditions } - self.conditions = allConditions self.body = then() let generatedElse = elseBody() self.elseBody = generatedElse.isEmpty ? nil : generatedElse diff --git a/Sources/SyntaxKit/ControlFlow/While.swift b/Sources/SyntaxKit/ControlFlow/While.swift index 44080be..b60fbf3 100644 --- a/Sources/SyntaxKit/ControlFlow/While.swift +++ b/Sources/SyntaxKit/ControlFlow/While.swift @@ -31,41 +31,55 @@ import SwiftSyntax /// A `while` loop statement. public struct While: CodeBlock { - private let condition: CodeBlock + private let condition: any ExprCodeBlock private let body: [CodeBlock] - /// Creates a `while` loop statement. + /// Creates a `while` loop statement with an expression condition. /// - Parameters: - /// - condition: A `CodeBlockBuilder` that produces the condition expression. + /// - condition: The condition expression that conforms to ExprCodeBlock. /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. public init( - @CodeBlockBuilderResult _ condition: () -> [CodeBlock], + _ condition: any ExprCodeBlock, @CodeBlockBuilderResult then: () -> [CodeBlock] ) { - let conditions = condition() - guard conditions.count == 1 else { - fatalError("While requires exactly one condition CodeBlock") - } - self.condition = conditions[0] + self.condition = condition self.body = then() } - /// Convenience initializer that accepts a single condition directly. + /// Creates a `while` loop statement with a builder closure for the condition. /// - Parameters: - /// - condition: The condition expression. + /// - condition: A `CodeBlockBuilder` that produces exactly one condition expression. /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. public init( - _ condition: CodeBlock, + @ExprCodeBlockBuilder _ condition: () -> any ExprCodeBlock, @CodeBlockBuilderResult then: () -> [CodeBlock] ) { - self.init({ condition }, then: then) + self.condition = condition() + self.body = then() + } + + /// Legacy initializer for backward compatibility. + /// - Parameters: + /// - condition: A `CodeBlockBuilder` that produces the condition expression. + /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. + @available(*, deprecated, message: "Use ExprCodeBlockBuilder instead for compile-time safety") + public init( + @CodeBlockBuilderResult _ condition: () -> [CodeBlock], + @CodeBlockBuilderResult then: () -> [CodeBlock] + ) { + let conditions = condition() + guard conditions.count == 1 else { + fatalError("While requires exactly one condition CodeBlock") + } + guard let exprCondition = conditions[0] as? any ExprCodeBlock else { + fatalError("While condition must conform to ExprCodeBlock protocol") + } + self.condition = exprCondition + self.body = then() } public var syntax: SyntaxProtocol { - let conditionExpr = ExprSyntax( - fromProtocol: condition.syntax.as(ExprSyntax.self) - ?? DeclReferenceExprSyntax(baseName: .identifier("")) - ) + let conditionExpr = condition.exprSyntax let bodyBlock = CodeBlockSyntax( leftBrace: .leftBraceToken(leadingTrivia: .space, trailingTrivia: .newline), diff --git a/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift b/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift new file mode 100644 index 0000000..94a3ac2 --- /dev/null +++ b/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift @@ -0,0 +1,61 @@ +// +// ExprCodeBlockBuilder.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHERS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import SwiftSyntax + +/// A result builder that produces exactly one `ExprCodeBlock`. +/// This ensures compile-time type safety for expression-based constructs. +@resultBuilder +public struct ExprCodeBlockBuilder { + public static func buildBlock(_ expression: any ExprCodeBlock) -> any ExprCodeBlock { + expression + } + + public static func buildExpression(_ expression: any ExprCodeBlock) -> any ExprCodeBlock { + expression + } + + public static func buildEither(first: any ExprCodeBlock) -> any ExprCodeBlock { + first + } + + public static func buildEither(second: any ExprCodeBlock) -> any ExprCodeBlock { + second + } + + public static func buildOptional(_ expression: (any ExprCodeBlock)?) -> any ExprCodeBlock { + // This should never be called in practice since we require exactly one expression + fatalError("ExprCodeBlockBuilder requires exactly one expression") + } + + public static func buildArray(_ expressions: [any ExprCodeBlock]) -> any ExprCodeBlock { + // This should never be called in practice since we require exactly one expression + fatalError("ExprCodeBlockBuilder requires exactly one expression") + } +} diff --git a/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift b/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift new file mode 100644 index 0000000..2b2d4e9 --- /dev/null +++ b/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift @@ -0,0 +1,73 @@ +// +// PatternConvertibleBuilder.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHERS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import SwiftSyntax + +/// A result builder that produces exactly one `CodeBlock & PatternConvertible`. +/// This ensures compile-time type safety for pattern-based constructs. +@resultBuilder +public struct PatternConvertibleBuilder { + public static func buildBlock(_ pattern: any CodeBlock & PatternConvertible) -> any CodeBlock + & PatternConvertible + { + pattern + } + + public static func buildExpression(_ pattern: any CodeBlock & PatternConvertible) -> any CodeBlock + & PatternConvertible + { + pattern + } + + public static func buildEither(first: any CodeBlock & PatternConvertible) -> any CodeBlock + & PatternConvertible + { + first + } + + public static func buildEither(second: any CodeBlock & PatternConvertible) -> any CodeBlock + & PatternConvertible + { + second + } + + public static func buildOptional(_ pattern: (any CodeBlock & PatternConvertible)?) + -> any CodeBlock & PatternConvertible + { + // This should never be called in practice since we require exactly one pattern + fatalError("PatternConvertibleBuilder requires exactly one pattern") + } + + public static func buildArray(_ patterns: [any CodeBlock & PatternConvertible]) -> any CodeBlock + & PatternConvertible + { + // This should never be called in practice since we require exactly one pattern + fatalError("PatternConvertibleBuilder requires exactly one pattern") + } +} diff --git a/Sources/SyntaxKit/Expressions/Assignment.swift b/Sources/SyntaxKit/Expressions/Assignment.swift index b3e26ac..7dda967 100644 --- a/Sources/SyntaxKit/Expressions/Assignment.swift +++ b/Sources/SyntaxKit/Expressions/Assignment.swift @@ -34,13 +34,16 @@ public struct Assignment: CodeBlock { private let target: String private let valueExpr: ExprSyntax + /// Creates an assignment where the value is an expression. + public init(_ target: String, _ value: any ExprCodeBlock) { + self.target = target + self.valueExpr = value.exprSyntax + } + /// Creates an assignment where the value is a literal. public init(_ target: String, _ literal: Literal) { self.target = target - guard let expr = literal.syntax.as(ExprSyntax.self) else { - fatalError("Literal.syntax did not produce ExprSyntax") - } - self.valueExpr = expr + self.valueExpr = literal.exprSyntax } /// Creates an assignment with an integer literal value. diff --git a/Sources/SyntaxKit/Expressions/Call.swift b/Sources/SyntaxKit/Expressions/Call.swift index a72843f..030406c 100644 --- a/Sources/SyntaxKit/Expressions/Call.swift +++ b/Sources/SyntaxKit/Expressions/Call.swift @@ -82,7 +82,8 @@ public struct Call: CodeBlock { ) } return element - } else if let unlabeled = expr as? ExprSyntax { + } else { + let unlabeled = expr as! ExprSyntax return LabeledExprSyntax( label: nil, colon: nil, @@ -91,8 +92,6 @@ public struct Call: CodeBlock { ? .commaToken(trailingTrivia: .space) : nil ) - } else { - fatalError("ParameterExp.syntax must return LabeledExprSyntax or ExprSyntax") } } ) diff --git a/Sources/SyntaxKit/Expressions/FunctionCallExp.swift b/Sources/SyntaxKit/Expressions/FunctionCallExp.swift index c720e7f..59632d7 100644 --- a/Sources/SyntaxKit/Expressions/FunctionCallExp.swift +++ b/Sources/SyntaxKit/Expressions/FunctionCallExp.swift @@ -105,7 +105,9 @@ public struct FunctionCallExp: CodeBlock { ) } return element - } else if let unlabeled = expr as? ExprSyntax { + } else { + // ParameterExp.syntax is guaranteed to return either LabeledExprSyntax or ExprSyntax + let unlabeled = expr as! ExprSyntax return LabeledExprSyntax( label: nil, colon: nil, @@ -114,8 +116,6 @@ public struct FunctionCallExp: CodeBlock { ? .commaToken(trailingTrivia: .space) : nil ) - } else { - fatalError("ParameterExp.syntax must return LabeledExprSyntax or ExprSyntax") } } ) diff --git a/Sources/SyntaxKit/Expressions/Infix.swift b/Sources/SyntaxKit/Expressions/Infix.swift index 432f82f..fd4332d 100644 --- a/Sources/SyntaxKit/Expressions/Infix.swift +++ b/Sources/SyntaxKit/Expressions/Infix.swift @@ -30,28 +30,63 @@ import SwiftSyntax /// A generic binary (infix) operator expression, e.g. `a + b`. -public struct Infix: CodeBlock { +public struct Infix: CodeBlock, ExprCodeBlock { + public enum InfixError: Error, CustomStringConvertible { + case wrongOperandCount(expected: Int, got: Int) + case nonExprCodeBlockOperand + + public var description: String { + switch self { + case let .wrongOperandCount(expected, got): + return "Infix expects exactly \(expected) operands, got \(got)." + case .nonExprCodeBlockOperand: + return "Infix operands must conform to ExprCodeBlock protocol" + } + } + } + private let operation: String - private let operands: [CodeBlock] + private let leftOperand: any ExprCodeBlock + private let rightOperand: any ExprCodeBlock /// Creates an infix operator expression. /// - Parameters: /// - operation: The operator symbol as it should appear in source (e.g. "+", "-", "&&"). - /// - content: A ``CodeBlockBuilder`` that supplies the two operand expressions. + /// - lhs: The left-hand side expression that conforms to ExprCodeBlock. + /// - rhs: The right-hand side expression that conforms to ExprCodeBlock. + public init(_ operation: String, lhs: any ExprCodeBlock, rhs: any ExprCodeBlock) { + self.operation = operation + self.leftOperand = lhs + self.rightOperand = rhs + } + + /// Creates an infix operator expression with a builder closure. + /// - Parameters: + /// - operation: The operator symbol as it should appear in source (e.g. "+", "-", "&&"). + /// - content: A ``CodeBlockBuilder`` that supplies exactly two operand expressions. /// /// Exactly two operands must be supplied – a left-hand side and a right-hand side. - public init(_ operation: String, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) { + /// Each operand must conform to ExprCodeBlock. + @available(*, deprecated, message: "Use separate lhs and rhs parameters for compile-time safety") + public init(_ operation: String, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) throws { self.operation = operation - self.operands = content() - } + let operands = content() - public var syntax: SyntaxProtocol { guard operands.count == 2 else { - fatalError("Infix expects exactly two operands, got \(operands.count).") + throw InfixError.wrongOperandCount(expected: 2, got: operands.count) } + guard let lhs = operands[0] as? any ExprCodeBlock, + let rhs = operands[1] as? any ExprCodeBlock + else { + throw InfixError.nonExprCodeBlockOperand + } + self.leftOperand = lhs + self.rightOperand = rhs + } - let left = operands[0].expr - let right = operands[1].expr + public var exprSyntax: ExprSyntax { + let left = leftOperand.exprSyntax + let right = rightOperand.exprSyntax let operatorExpr = ExprSyntax( BinaryOperatorExprSyntax( @@ -59,14 +94,20 @@ public struct Infix: CodeBlock { ) ) - return SequenceExprSyntax( - elements: ExprListSyntax([ - left, - operatorExpr, - right, - ]) + return ExprSyntax( + SequenceExprSyntax( + elements: ExprListSyntax([ + left, + operatorExpr, + right, + ]) + ) ) } + + public var syntax: SyntaxProtocol { + exprSyntax + } } // MARK: - Comparison Operators @@ -90,8 +131,9 @@ extension Infix { /// - operator: The comparison operator to use. /// - lhs: The left-hand side expression. /// - rhs: The right-hand side expression. - public init(_ operator: ComparisonOperator, lhs: CodeBlock, rhs: CodeBlock) { + public init(_ operator: ComparisonOperator, lhs: any ExprCodeBlock, rhs: any ExprCodeBlock) { self.operation = `operator`.symbol - self.operands = [lhs, rhs] + self.leftOperand = lhs + self.rightOperand = rhs } } diff --git a/Sources/SyntaxKit/Expressions/Literal+PatternConvertible.swift b/Sources/SyntaxKit/Expressions/Literal+PatternConvertible.swift index 39b85fc..24464d5 100644 --- a/Sources/SyntaxKit/Expressions/Literal+PatternConvertible.swift +++ b/Sources/SyntaxKit/Expressions/Literal+PatternConvertible.swift @@ -32,9 +32,7 @@ import SwiftSyntax extension Literal: PatternConvertible { /// SwiftSyntax representation of the literal as a pattern. public var patternSyntax: PatternSyntax { - guard let expr = self.syntax.as(ExprSyntax.self) else { - fatalError("Literal.syntax did not return ExprSyntax") - } + let expr = self.exprSyntax return PatternSyntax(ExpressionPatternSyntax(expression: expr)) } } diff --git a/Sources/SyntaxKit/Expressions/NegatedPropertyAccessExp.swift b/Sources/SyntaxKit/Expressions/NegatedPropertyAccessExp.swift index 7b9e1c2..1297506 100644 --- a/Sources/SyntaxKit/Expressions/NegatedPropertyAccessExp.swift +++ b/Sources/SyntaxKit/Expressions/NegatedPropertyAccessExp.swift @@ -30,7 +30,7 @@ import SwiftSyntax /// An expression that negates a property access. -public struct NegatedPropertyAccessExp: CodeBlock { +public struct NegatedPropertyAccessExp: CodeBlock, ExprCodeBlock { internal let base: CodeBlock /// Creates a negated property access expression. @@ -44,7 +44,7 @@ public struct NegatedPropertyAccessExp: CodeBlock { self.base = PropertyAccessExp(baseName: baseName, propertyName: propertyName) } - public var syntax: SyntaxProtocol { + public var exprSyntax: ExprSyntax { let memberAccess = base.syntax.as(ExprSyntax.self) ?? ExprSyntax( @@ -61,4 +61,8 @@ public struct NegatedPropertyAccessExp: CodeBlock { ) ) } + + public var syntax: SyntaxProtocol { + exprSyntax + } } diff --git a/Sources/SyntaxKit/Expressions/PlusAssign.swift b/Sources/SyntaxKit/Expressions/PlusAssign.swift index 7c978e7..8572b81 100644 --- a/Sources/SyntaxKit/Expressions/PlusAssign.swift +++ b/Sources/SyntaxKit/Expressions/PlusAssign.swift @@ -34,13 +34,16 @@ public struct PlusAssign: CodeBlock { private let target: String private let valueExpr: ExprSyntax + /// Creates a `+=` expression with an expression value. + public init(_ target: String, _ value: any ExprCodeBlock) { + self.target = target + self.valueExpr = value.exprSyntax + } + /// Creates a `+=` expression with a literal value. public init(_ target: String, _ literal: Literal) { self.target = target - guard let expr = literal.syntax.as(ExprSyntax.self) else { - fatalError("Literal.syntax did not produce ExprSyntax") - } - self.valueExpr = expr + self.valueExpr = literal.exprSyntax } /// Creates a `+=` expression with an integer literal value. diff --git a/Sources/SyntaxKit/Expressions/PropertyAccessExp.swift b/Sources/SyntaxKit/Expressions/PropertyAccessExp.swift index a23b53f..ad0d488 100644 --- a/Sources/SyntaxKit/Expressions/PropertyAccessExp.swift +++ b/Sources/SyntaxKit/Expressions/PropertyAccessExp.swift @@ -30,7 +30,7 @@ import SwiftSyntax /// An expression that accesses a property on a base expression. -public struct PropertyAccessExp: CodeBlock { +public struct PropertyAccessExp: CodeBlock, ExprCodeBlock { internal let base: CodeBlock internal let propertyName: String @@ -62,7 +62,7 @@ public struct PropertyAccessExp: CodeBlock { NegatedPropertyAccessExp(base: self) } - public var syntax: SyntaxProtocol { + public var exprSyntax: ExprSyntax { let baseSyntax = base.syntax.as(ExprSyntax.self) ?? ExprSyntax( @@ -77,4 +77,8 @@ public struct PropertyAccessExp: CodeBlock { ) ) } + + public var syntax: SyntaxProtocol { + exprSyntax + } } diff --git a/Sources/SyntaxKit/Expressions/Return.swift b/Sources/SyntaxKit/Expressions/Return.swift index 78c05d4..6d2a9c2 100644 --- a/Sources/SyntaxKit/Expressions/Return.swift +++ b/Sources/SyntaxKit/Expressions/Return.swift @@ -39,18 +39,36 @@ public struct Return: CodeBlock { self.exprs = content() } public var syntax: SyntaxProtocol { - guard let expr = exprs.first else { - fatalError("Return must have at least one expression.") - } - if let varExp = expr as? VariableExp { + if let expr = exprs.first { + if let varExp = expr as? VariableExp { + return ReturnStmtSyntax( + returnKeyword: .keyword(.return, trailingTrivia: .space), + expression: ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(varExp.name))) + ) + } + + // Try to get ExprSyntax from the expression + let exprSyntax: ExprSyntax + if let exprCodeBlock = expr as? ExprCodeBlock { + exprSyntax = exprCodeBlock.exprSyntax + } else if let syntax = expr.syntax.as(ExprSyntax.self) { + exprSyntax = syntax + } else { + // fallback: no valid expression + return ReturnStmtSyntax( + returnKeyword: .keyword(.return, trailingTrivia: .space) + ) + } + return ReturnStmtSyntax( returnKeyword: .keyword(.return, trailingTrivia: .space), - expression: ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(varExp.name))) + expression: exprSyntax + ) + } else { + // Bare return + return ReturnStmtSyntax( + returnKeyword: .keyword(.return, trailingTrivia: .space) ) } - return ReturnStmtSyntax( - returnKeyword: .keyword(.return, trailingTrivia: .space), - expression: ExprSyntax(expr.syntax) - ) } } diff --git a/Sources/SyntaxKit/Utilities/Parenthesized.swift b/Sources/SyntaxKit/Utilities/Parenthesized.swift index be8959b..5327b52 100644 --- a/Sources/SyntaxKit/Utilities/Parenthesized.swift +++ b/Sources/SyntaxKit/Utilities/Parenthesized.swift @@ -30,7 +30,7 @@ import SwiftSyntax /// A code block that wraps its content in parentheses. -public struct Parenthesized: CodeBlock { +public struct Parenthesized: CodeBlock, ExprCodeBlock { private let content: CodeBlock /// Creates a parenthesized code block. @@ -41,7 +41,7 @@ public struct Parenthesized: CodeBlock { self.content = blocks[0] } - public var syntax: SyntaxProtocol { + public var exprSyntax: ExprSyntax { ExprSyntax( TupleExprSyntax( leftParen: .leftParenToken(), @@ -52,4 +52,8 @@ public struct Parenthesized: CodeBlock { ) ) } + + public var syntax: SyntaxProtocol { + exprSyntax + } } diff --git a/Sources/SyntaxKit/Variables/VariableExp.swift b/Sources/SyntaxKit/Variables/VariableExp.swift index 30d73c2..fe07a7c 100644 --- a/Sources/SyntaxKit/Variables/VariableExp.swift +++ b/Sources/SyntaxKit/Variables/VariableExp.swift @@ -30,7 +30,7 @@ import SwiftSyntax /// An expression that refers to a variable. -public struct VariableExp: CodeBlock, PatternConvertible { +public struct VariableExp: CodeBlock, PatternConvertible, ExprCodeBlock { internal let name: String /// Creates a variable expression. @@ -81,6 +81,10 @@ public struct VariableExp: CodeBlock, PatternConvertible { ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(name))) } + public var exprSyntax: ExprSyntax { + ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(name))) + } + public var patternSyntax: PatternSyntax { PatternSyntax(IdentifierPatternSyntax(identifier: .identifier(name))) } diff --git a/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift b/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift index d8352c1..4d8df6d 100644 --- a/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift +++ b/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift @@ -64,7 +64,7 @@ import Testing Throw(VariableExp("VendingMachineError.invalidSelection")) } Guard { - Infix(">") { + try! Infix(">") { VariableExp("item").property("count") Literal.integer(0) } @@ -72,7 +72,7 @@ import Testing Throw(VariableExp("VendingMachineError.outOfStock")) } Guard { - Infix("<=") { + try! Infix("<=") { VariableExp("item").property("price") VariableExp("coinsDeposited") } @@ -81,7 +81,7 @@ import Testing Call("VendingMachineError.insufficientFunds") { ParameterExp( name: "coinsNeeded", - value: Infix("-") { + value: try! Infix("-") { VariableExp("item").property("price") VariableExp("coinsDeposited") } @@ -89,12 +89,12 @@ import Testing } ) } - Infix("-=") { + try! Infix("-=") { VariableExp("coinsDeposited") VariableExp("item").property("price") } Variable(.var, name: "newItem", equals: Literal.ref("item")) - Infix("-=") { + try! Infix("-=") { VariableExp("newItem").property("count") Literal.integer(1) } diff --git a/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift b/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift index f5170c6..3937428 100644 --- a/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift +++ b/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift @@ -16,13 +16,17 @@ import Testing } If { - Infix(">") { + try! Infix(">") { VariableExp("temperature") Literal.integer(30) } } then: { Call("print") { - ParameterExp(name: "", value: "\"It's hot outside!\"") + ParameterExp(unlabeled: "\"It's hot!\"") + } + } else: { + Call("print") { + ParameterExp(unlabeled: "\"It's not hot\"") } } @@ -33,41 +37,43 @@ import Testing } If { - Infix(">=") { + try! Infix(">=") { VariableExp("score") Literal.integer(90) } } then: { Call("print") { - ParameterExp(name: "", value: "\"Excellent!\"") + ParameterExp(unlabeled: "\"A\"") } } else: { If { - Infix(">=") { + try! Infix(">=") { VariableExp("score") Literal.integer(80) } } then: { Call("print") { - ParameterExp(name: "", value: "\"Good job!\"") + ParameterExp(unlabeled: "\"B\"") } - } - - If { - Infix(">=") { - VariableExp("score") - Literal.integer(70) - } - } then: { + } else: { Call("print") { - ParameterExp(name: "", value: "\"Passing\"") + ParameterExp(unlabeled: "\"C\"") } } + } - Then { - Call("print") { - ParameterExp(name: "", value: "\"Needs improvement\"") - } + If { + try! Infix(">=") { + VariableExp("score") + Literal.integer(70) + } + } then: { + Call("print") { + ParameterExp(unlabeled: "\"Passing\"") + } + } else: { + Call("print") { + ParameterExp(unlabeled: "\"Failing\"") } } @@ -280,7 +286,7 @@ import Testing ParameterExp(name: "repeating", value: Literal.integer(0)) ParameterExp( name: "count", - value: Infix("+") { + value: try! Infix("+") { VariableExp("finalSquare") Literal.integer(1) } @@ -300,15 +306,15 @@ import Testing Variable(.var, name: "square", equals: Literal.integer(0)) Variable(.var, name: "diceRoll", equals: Literal.integer(0)) - While { - Infix("!=") { + While( + try! Infix("!=") { VariableExp("square") VariableExp("finalSquare") } - } then: { + ) { PlusAssign("diceRoll", 1) If { - Infix("==") { + try! Infix("==") { VariableExp("diceRoll") Literal.integer(7) } @@ -316,17 +322,17 @@ import Testing Assignment("diceRoll", 1) } Switch( - Infix("+") { + try! Infix("+") { VariableExp("square") VariableExp("diceRoll") } ) { - SwitchCase(VariableExp("finalSquare")) { + SwitchCase("finalSquare") { Break() } SwitchCase { SwitchLet("newSquare") - Infix(">") { + try! Infix(">") { VariableExp("newSquare") VariableExp("finalSquare") } @@ -334,11 +340,11 @@ import Testing Continue() } Default { - Infix("+=") { + try! Infix("+=") { VariableExp("square") VariableExp("diceRoll") } - Infix("+=") { + try! Infix("+=") { VariableExp("square") VariableExp("board[square]") } diff --git a/Tests/SyntaxKitTests/Integration/ForLoopsExampleTests.swift b/Tests/SyntaxKitTests/Integration/ForLoopsExampleTests.swift index 3c8aabb..0a880ca 100644 --- a/Tests/SyntaxKitTests/Integration/ForLoopsExampleTests.swift +++ b/Tests/SyntaxKitTests/Integration/ForLoopsExampleTests.swift @@ -84,8 +84,8 @@ import Testing VariableExp("number"), in: VariableExp("numbers"), where: { - Infix("==") { - Infix("%") { + try! Infix("==") { + try! Infix("%") { VariableExp("number") Literal.integer(2) } diff --git a/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift b/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift index f2f87f6..a6b92c9 100644 --- a/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift +++ b/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift @@ -10,7 +10,7 @@ import Testing Variable(.let, name: "score", type: "Int", equals: "85") If { - Infix(">=") { + try! Infix(">=") { VariableExp("score") Literal.integer(90) } @@ -20,7 +20,7 @@ import Testing } } else: { If { - Infix(">=") { + try! Infix(">=") { VariableExp("score") Literal.integer(80) } diff --git a/Tests/SyntaxKitTests/Unit/ControlFlow/ForLoopTests.swift b/Tests/SyntaxKitTests/Unit/ControlFlow/ForLoopTests.swift index 3b922cf..6665120 100644 --- a/Tests/SyntaxKitTests/Unit/ControlFlow/ForLoopTests.swift +++ b/Tests/SyntaxKitTests/Unit/ControlFlow/ForLoopTests.swift @@ -27,7 +27,7 @@ internal final class ForLoopTests { VariableExp("number"), in: VariableExp("numbers"), where: { - Infix("%") { + try! Infix("%") { VariableExp("number") Literal.integer(2) } diff --git a/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift b/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift index c4a5720..ee1f57c 100644 --- a/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift +++ b/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift @@ -6,11 +6,11 @@ internal struct EdgeCaseTests { // MARK: - Error Handling Tests @Test("Infix with wrong number of operands throws fatal error") - internal func testInfixWrongOperandCount() { + internal func testInfixWrongOperandCount() throws { // This test documents the expected behavior // In a real scenario, this would cause a fatal error // We can't easily test fatalError in unit tests, but we can document it - let infix = Infix("+") { + let infix = try Infix("+") { // Only one operand - should cause fatal error VariableExp("x") } @@ -20,16 +20,95 @@ internal struct EdgeCaseTests { #expect(true) // Placeholder - fatal errors can't be easily tested } - @Test("Return with no expressions throws fatal error") + // MARK: - Default Condition Tests + + @Test("If with no conditions uses true as default") + internal func testIfWithNoConditionsUsesTrueAsDefault() { + let ifStatement = If({}) { + Return { + Literal.string("executed") + } + } + + let generated = ifStatement.generateCode() + #expect(generated.contains("if true")) + #expect(generated.contains("return \"executed\"")) + } + + @Test("Guard with no conditions uses true as default") + internal func testGuardWithNoConditionsUsesTrueAsDefault() { + let guardStatement = Guard({}) { + Return { + Literal.string("executed") + } + } + + let generated = guardStatement.generateCode() + #expect(generated.contains("guard true")) + #expect(generated.contains("return \"executed\"")) + } + + // MARK: - Return Statement Tests + + @Test("Return with expression generates correct syntax") + internal func testReturnWithExpression() { + let returnStatement = Return { + Literal.string("hello") + } + + let generated = returnStatement.generateCode() + #expect(generated.contains("return \"hello\"")) + } + + @Test("Return with variable expression generates correct syntax") + internal func testReturnWithVariableExpression() { + let returnStatement = Return { + VariableExp("value") + } + + let generated = returnStatement.generateCode() + #expect(generated.contains("return value")) + } + + @Test("Return with no expressions generates bare return") internal func testReturnWithNoExpressions() { - // This test documents the expected behavior - // In a real scenario, this would cause a fatal error - let returnStmt = Return { - // Empty - should cause fatal error + let returnStatement = Return { + // Empty - should generate bare return } - // The fatal error would occur when accessing .syntax - // This test documents the expected behavior - #expect(true) // Placeholder - fatal errors can't be easily tested + let generated = returnStatement.generateCode() + #expect(generated.contains("return")) + #expect(!generated.contains("return ")) + #expect(!generated.contains("return \"\"")) + #expect(!generated.contains("return nil")) + } + + // MARK: - TupleAssignment Tests + + @Test("TupleAssignment asyncSet falls back to regular syntax when conditions not met") + internal func testTupleAssignmentAsyncSetFallback() { + // Test case 1: Value is not a Tuple + let tupleAssignment1 = TupleAssignment( + ["a", "b"], + equals: Literal.string("not a tuple") + ).asyncSet() + + let generated1 = tupleAssignment1.generateCode() + #expect(generated1.contains("let (a, b) = \"not a tuple\"")) + #expect(!generated1.contains("async let")) + + // Test case 2: Element count mismatch + let tupleAssignment2 = TupleAssignment( + ["a", "b", "c"], // 3 elements + equals: Tuple { + Literal.integer(1) + Literal.integer(2) + // Missing third element + } + ).asyncSet() + + let generated2 = tupleAssignment2.generateCode() + #expect(generated2.contains("let (a, b, c) = (1, 2)")) + #expect(!generated2.contains("async let")) } } diff --git a/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTestsExpressions.swift b/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTestsExpressions.swift index 8296755..b39f0e7 100644 --- a/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTestsExpressions.swift +++ b/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTestsExpressions.swift @@ -36,15 +36,15 @@ internal struct EdgeCaseTestsExpressions { @Test("Infix with complex expressions generates correct syntax") internal func testInfixWithComplexExpressions() throws { - let infix = Infix("*") { + let infix = try! Infix("*") { Parenthesized { - Infix("+") { + try! Infix("+") { VariableExp("a") VariableExp("b") } } Parenthesized { - Infix("-") { + try! Infix("-") { VariableExp("c") VariableExp("d") } @@ -68,7 +68,7 @@ internal struct EdgeCaseTestsExpressions { @Test("Return with complex expression generates correct syntax") internal func testReturnWithComplexExpression() throws { let returnStmt = Return { - Infix("+") { + try! Infix("+") { VariableExp("a") VariableExp("b") } From e5471a269ddc08a917b7ac00545bcbbd2018a686 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Tue, 24 Jun 2025 08:08:50 -0400 Subject: [PATCH 02/17] fixed failing tests --- .../ConditionalsExampleTests.swift | 40 ++++++++----------- .../Unit/EdgeCases/EdgeCaseTests.swift | 31 +++++++++----- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift b/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift index 3937428..3f483e8 100644 --- a/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift +++ b/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift @@ -22,11 +22,7 @@ import Testing } } then: { Call("print") { - ParameterExp(unlabeled: "\"It's hot!\"") - } - } else: { - Call("print") { - ParameterExp(unlabeled: "\"It's not hot\"") + ParameterExp(unlabeled: "\"It's hot outside!\"") } } @@ -43,7 +39,7 @@ import Testing } } then: { Call("print") { - ParameterExp(unlabeled: "\"A\"") + ParameterExp(unlabeled: "\"Excellent!\"") } } else: { If { @@ -53,30 +49,26 @@ import Testing } } then: { Call("print") { - ParameterExp(unlabeled: "\"B\"") + ParameterExp(unlabeled: "\"Good job!\"") } } else: { - Call("print") { - ParameterExp(unlabeled: "\"C\"") + If { + try! Infix(">=") { + VariableExp("score") + Literal.integer(70) + } + } then: { + Call("print") { + ParameterExp(unlabeled: "\"Passing\"") + } + } else: { + Call("print") { + ParameterExp(unlabeled: "\"Needs improvement\"") + } } } } - If { - try! Infix(">=") { - VariableExp("score") - Literal.integer(70) - } - } then: { - Call("print") { - ParameterExp(unlabeled: "\"Passing\"") - } - } else: { - Call("print") { - ParameterExp(unlabeled: "\"Failing\"") - } - } - // MARK: Optional Binding with If Variable(.let, name: "possibleNumber", equals: Literal.string("123")) .comment { diff --git a/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift b/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift index ee1f57c..6ce693e 100644 --- a/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift +++ b/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift @@ -5,19 +5,28 @@ import Testing internal struct EdgeCaseTests { // MARK: - Error Handling Tests - @Test("Infix with wrong number of operands throws fatal error") + @Test("Infix with wrong number of operands throws error") internal func testInfixWrongOperandCount() throws { - // This test documents the expected behavior - // In a real scenario, this would cause a fatal error - // We can't easily test fatalError in unit tests, but we can document it - let infix = try Infix("+") { - // Only one operand - should cause fatal error - VariableExp("x") + // Test that Infix throws an error when given wrong number of operands + do { + _ = try Infix("+") { + // Only one operand - should throw error + VariableExp("x") + } + // If we reach here, no error was thrown, which is unexpected + #expect(false, "Expected error to be thrown for wrong operand count") + } catch let error as Infix.InfixError { + // Verify it's the correct error type + switch error { + case .wrongOperandCount(let expected, let got): + #expect(expected == 2) + #expect(got == 1) + case .nonExprCodeBlockOperand: + #expect(false, "Expected wrongOperandCount error, got nonExprCodeBlockOperand") + } + } catch { + #expect(false, "Expected InfixError, got \(type(of: error))") } - - // The fatal error would occur when accessing .syntax - // This test documents the expected behavior - #expect(true) // Placeholder - fatal errors can't be easily tested } // MARK: - Default Condition Tests From eec3033a44255d5f8810678b00926cae3e2c10d3 Mon Sep 17 00:00:00 2001 From: leogdion Date: Tue, 24 Jun 2025 09:46:13 -0400 Subject: [PATCH 03/17] fixing more force tries --- Sources/SyntaxKit/Collections/Tuple.swift | 10 +-- Sources/SyntaxKit/ControlFlow/Do.swift | 7 ++ Sources/SyntaxKit/ControlFlow/For.swift | 62 ++----------- Sources/SyntaxKit/ControlFlow/Guard.swift | 33 ++++--- Sources/SyntaxKit/ControlFlow/If.swift | 46 +++++++++- Sources/SyntaxKit/ControlFlow/Switch.swift | 8 +- .../SyntaxKit/ControlFlow/SwitchCase.swift | 13 ++- Sources/SyntaxKit/ControlFlow/While.swift | 55 +++++++++--- Sources/SyntaxKit/Declarations/Class.swift | 8 +- Sources/SyntaxKit/Declarations/Enum.swift | 8 +- .../SyntaxKit/Declarations/Extension.swift | 6 +- Sources/SyntaxKit/Declarations/Protocol.swift | 8 +- Sources/SyntaxKit/Declarations/Struct.swift | 8 +- Sources/SyntaxKit/ErrorHandling/Catch.swift | 7 ++ Sources/SyntaxKit/Expressions/Call.swift | 4 +- Sources/SyntaxKit/Expressions/Closure.swift | 6 +- Sources/SyntaxKit/Expressions/Infix.swift | 4 +- Sources/SyntaxKit/Expressions/Return.swift | 4 +- Sources/SyntaxKit/Expressions/Task.swift | 8 +- Sources/SyntaxKit/Functions/Function.swift | 18 ++-- .../SyntaxKit/Parameters/ParameterExp.swift | 2 + Sources/SyntaxKit/Utilities/Case.swift | 9 +- Sources/SyntaxKit/Utilities/Default.swift | 8 +- Sources/SyntaxKit/Utilities/Group.swift | 4 +- .../SyntaxKit/Utilities/Parenthesized.swift | 4 +- Sources/SyntaxKit/Utilities/Then.swift | 6 +- .../Variables/ComputedProperty.swift | 6 +- .../Integration/BlackjackTests.swift | 6 +- .../Integration/ConcurrencyExampleTests.swift | 26 +++--- .../ConditionalsExampleTests.swift | 90 +++++++++++++++---- .../Integration/ForLoopsExampleTests.swift | 14 +-- .../Unit/ControlFlow/ConditionalsTests.swift | 28 ++++++ .../Unit/ControlFlow/ForLoopTests.swift | 10 +-- .../EdgeCases/EdgeCaseTestsExpressions.swift | 14 +-- .../Unit/SwiftUIFeatureTests.swift | 2 - 35 files changed, 338 insertions(+), 214 deletions(-) diff --git a/Sources/SyntaxKit/Collections/Tuple.swift b/Sources/SyntaxKit/Collections/Tuple.swift index 4b02e78..ab71880 100644 --- a/Sources/SyntaxKit/Collections/Tuple.swift +++ b/Sources/SyntaxKit/Collections/Tuple.swift @@ -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. diff --git a/Sources/SyntaxKit/ControlFlow/Do.swift b/Sources/SyntaxKit/ControlFlow/Do.swift index d9279db..42307f3 100644 --- a/Sources/SyntaxKit/ControlFlow/Do.swift +++ b/Sources/SyntaxKit/ControlFlow/Do.swift @@ -35,6 +35,13 @@ public struct Do: CodeBlock { 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([]) + } + /// Creates a `do-catch` statement. /// - Parameters: /// - body: A ``CodeBlockBuilder`` that provides the body of the do block. diff --git a/Sources/SyntaxKit/ControlFlow/For.swift b/Sources/SyntaxKit/ControlFlow/For.swift index d7a153c..b441b84 100644 --- a/Sources/SyntaxKit/ControlFlow/For.swift +++ b/Sources/SyntaxKit/ControlFlow/For.swift @@ -7,7 +7,7 @@ // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation -// files (the “Software”), to deal in the Software without +// files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the @@ -17,7 +17,7 @@ // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // -// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT @@ -45,62 +45,14 @@ public struct For: CodeBlock { 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() - } - - /// Creates a `for-in` loop statement with a closure-based pattern. - /// - Parameters: - /// - pattern: A `CodeBlockBuilder` that produces exactly one pattern 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( - @PatternConvertibleBuilder _ pattern: () -> any CodeBlock & PatternConvertible, - in sequence: CodeBlock, - @CodeBlockBuilderResult where whereClause: () -> [CodeBlock] = { [] }, - @CodeBlockBuilderResult then: () -> [CodeBlock] - ) { - self.pattern = pattern() - self.sequence = sequence - let whereBlocks = whereClause() - self.whereClause = whereBlocks.isEmpty ? nil : whereBlocks[0] - self.body = then() - } - - /// Legacy initializer for backward compatibility. - /// - Parameters: - /// - pattern: A `CodeBlockBuilder` that produces the pattern 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. - @available( - *, deprecated, message: "Use PatternConvertibleBuilder instead for compile-time safety" - ) - public init( - @CodeBlockBuilderResult _ pattern: () -> [CodeBlock], - 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() + self.body = try then() } public var syntax: SyntaxProtocol { diff --git a/Sources/SyntaxKit/ControlFlow/Guard.swift b/Sources/SyntaxKit/ControlFlow/Guard.swift index 7685a9b..d9c632b 100644 --- a/Sources/SyntaxKit/ControlFlow/Guard.swift +++ b/Sources/SyntaxKit/ControlFlow/Guard.swift @@ -36,29 +36,40 @@ public struct Guard: CodeBlock { /// Creates a `guard` statement. /// - Parameters: - /// - condition: A builder that returns one or more ``CodeBlock`` items representing the guard - /// conditions. - /// - elseBody: Builder that produces the statements inside the `else` block. + /// - condition: A ``CodeBlockBuilder`` that provides the condition expression. + /// - else: A ``CodeBlockBuilder`` that provides the body when the condition is false. public init( - @CodeBlockBuilderResult _ condition: () -> [CodeBlock], - @CodeBlockBuilderResult else elseBody: () -> [CodeBlock] - ) { - let allConditions = condition() + @CodeBlockBuilderResult _ condition: () throws -> [CodeBlock], + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] + ) rethrows { + let allConditions = try condition() if allConditions.isEmpty { // Use true as default condition when no conditions are provided self.conditions = [Literal.boolean(true)] } else { self.conditions = allConditions } - self.elseBody = elseBody() + self.elseBody = try elseBody() + } + + /// Creates a `guard` statement with a string condition. + /// - Parameters: + /// - condition: The condition as a string. + /// - else: A ``CodeBlockBuilder`` that provides the body when the condition is false. + public init( + _ condition: String, + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] + ) rethrows { + self.conditions = [VariableExp(condition)] + self.elseBody = try elseBody() } /// Convenience initializer that accepts a single condition ``CodeBlock``. public init( _ condition: CodeBlock, - @CodeBlockBuilderResult else elseBody: () -> [CodeBlock] - ) { - self.init({ condition }, else: elseBody) + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] + ) rethrows { + try self.init({ condition }, else: elseBody) } public var syntax: SyntaxProtocol { diff --git a/Sources/SyntaxKit/ControlFlow/If.swift b/Sources/SyntaxKit/ControlFlow/If.swift index a795ff4..f2c93ee 100644 --- a/Sources/SyntaxKit/ControlFlow/If.swift +++ b/Sources/SyntaxKit/ControlFlow/If.swift @@ -62,10 +62,48 @@ public struct If: CodeBlock { /// Convenience initializer that keeps the previous API: pass the condition directly. public init( _ condition: CodeBlock, - @CodeBlockBuilderResult then: () -> [CodeBlock], - @CodeBlockBuilderResult else elseBody: () -> [CodeBlock] = { [] } - ) { - self.init({ condition }, then: then, else: elseBody) + @CodeBlockBuilderResult then: () throws -> [CodeBlock], + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] = { [] } + ) rethrows { + try self.init({ condition }, then: then, else: elseBody) + } + + /// Creates an `if` statement. + /// - Parameters: + /// - condition: A ``CodeBlockBuilder`` that provides the condition expression. + /// - then: A ``CodeBlockBuilder`` that provides the body when the condition is true. + /// - else: A ``CodeBlockBuilder`` that provides the body when the condition is false. + public init( + @CodeBlockBuilderResult _ condition: () throws -> [CodeBlock], + @CodeBlockBuilderResult then: () throws -> [CodeBlock], + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] = { [] } + ) rethrows { + let allConditions = try condition() + if allConditions.isEmpty { + // Use true as default condition when no conditions are provided + self.conditions = [Literal.boolean(true)] + } else { + self.conditions = allConditions + } + self.body = try then() + let generatedElse = try elseBody() + self.elseBody = generatedElse.isEmpty ? nil : generatedElse + } + + /// Creates an `if` statement with a string condition. + /// - Parameters: + /// - condition: The condition as a string. + /// - then: A ``CodeBlockBuilder`` that provides the body when the condition is true. + /// - else: A ``CodeBlockBuilder`` that provides the body when the condition is false. + public init( + _ condition: String, + @CodeBlockBuilderResult then: () throws -> [CodeBlock], + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] = { [] } + ) rethrows { + self.conditions = [VariableExp(condition)] + self.body = try then() + let generatedElse = try elseBody() + self.elseBody = generatedElse.isEmpty ? nil : generatedElse } public var syntax: SyntaxProtocol { diff --git a/Sources/SyntaxKit/ControlFlow/Switch.swift b/Sources/SyntaxKit/ControlFlow/Switch.swift index 1def183..84285c5 100644 --- a/Sources/SyntaxKit/ControlFlow/Switch.swift +++ b/Sources/SyntaxKit/ControlFlow/Switch.swift @@ -38,18 +38,18 @@ public struct Switch: CodeBlock { /// - Parameters: /// - expression: The expression to switch on. /// - content: A ``CodeBlockBuilder`` that provides the cases for the switch. - public init(_ expression: CodeBlock, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) { + public init(_ expression: CodeBlock, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { self.expression = expression - self.cases = content() + self.cases = try content() } /// Convenience initializer that accepts a string expression. /// - Parameters: /// - expression: The string expression to switch on. /// - content: A ``CodeBlockBuilder`` that provides the cases for the switch. - public init(_ expression: String, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) { + public init(_ expression: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { self.expression = VariableExp(expression) - self.cases = content() + self.cases = try content() } public var syntax: SyntaxProtocol { diff --git a/Sources/SyntaxKit/ControlFlow/SwitchCase.swift b/Sources/SyntaxKit/ControlFlow/SwitchCase.swift index 5e10279..2bc10b9 100644 --- a/Sources/SyntaxKit/ControlFlow/SwitchCase.swift +++ b/Sources/SyntaxKit/ControlFlow/SwitchCase.swift @@ -39,21 +39,18 @@ public struct SwitchCase: CodeBlock { /// - patterns: The patterns to match for the case. Can be `PatternConvertible`, /// `CodeBlock`, or `SwitchLet` for value binding. /// - content: A ``CodeBlockBuilder`` that provides the body of the case. - public init(_ patterns: Any..., @CodeBlockBuilderResult content: () -> [CodeBlock]) { + public init(_ patterns: Any..., @CodeBlockBuilderResult content: () throws -> [CodeBlock]) rethrows { self.patterns = patterns - self.body = content() + self.body = try content() } /// Creates a `case` for a `switch` statement with a builder closure for the conditional. /// - Parameters: /// - conditional: A ``CodeBlockBuilder`` that provides the conditional patterns for the case. /// - content: A ``CodeBlockBuilder`` that provides the body of the case. - public init( - @CodeBlockBuilderResult conditional: () -> [Any], - @CodeBlockBuilderResult content: () -> [CodeBlock] - ) { - self.patterns = conditional() - self.body = content() + public init(@CodeBlockBuilderResult conditional: () throws -> [CodeBlock], @CodeBlockBuilderResult content: () throws -> [CodeBlock]) rethrows { + self.patterns = try conditional() + self.body = try content() } public var switchCaseSyntax: SwitchCaseSyntax { diff --git a/Sources/SyntaxKit/ControlFlow/While.swift b/Sources/SyntaxKit/ControlFlow/While.swift index b60fbf3..04507d6 100644 --- a/Sources/SyntaxKit/ControlFlow/While.swift +++ b/Sources/SyntaxKit/ControlFlow/While.swift @@ -33,6 +33,7 @@ import SwiftSyntax public struct While: CodeBlock { private let condition: any ExprCodeBlock private let body: [CodeBlock] + private let isRepeatWhile: Bool /// Creates a `while` loop statement with an expression condition. /// - Parameters: @@ -44,6 +45,7 @@ public struct While: CodeBlock { ) { self.condition = condition self.body = then() + self.isRepeatWhile = false } /// Creates a `while` loop statement with a builder closure for the condition. @@ -56,26 +58,55 @@ public struct While: CodeBlock { ) { self.condition = condition() self.body = then() + self.isRepeatWhile = false } - /// Legacy initializer for backward compatibility. + /// Creates a `while` loop. /// - Parameters: - /// - condition: A `CodeBlockBuilder` that produces the condition expression. + /// - condition: A ``CodeBlockBuilder`` that provides the condition expression. /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. - @available(*, deprecated, message: "Use ExprCodeBlockBuilder instead for compile-time safety") public init( - @CodeBlockBuilderResult _ condition: () -> [CodeBlock], - @CodeBlockBuilderResult then: () -> [CodeBlock] - ) { - let conditions = condition() - guard conditions.count == 1 else { - fatalError("While requires exactly one condition CodeBlock") + @CodeBlockBuilderResult _ condition: () throws -> [CodeBlock], + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { + let conditionBlocks = try condition() + guard let firstCondition = conditionBlocks.first as? any ExprCodeBlock else { + fatalError("While condition must conform to ExprCodeBlock protocol") } - guard let exprCondition = conditions[0] as? any ExprCodeBlock else { + self.condition = firstCondition + self.body = try then() + self.isRepeatWhile = false + } + + /// Creates a `while` loop with a string condition. + /// - Parameters: + /// - condition: The condition as a string. + /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. + public init( + _ condition: String, + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { + self.condition = VariableExp(condition) + self.body = try then() + self.isRepeatWhile = false + } + + /// Creates a `repeat-while` loop. + /// - Parameters: + /// - condition: A ``CodeBlockBuilder`` that provides the condition expression. + /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. + public init( + repeat: Void, + @CodeBlockBuilderResult _ condition: () throws -> [CodeBlock], + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { + let conditionBlocks = try condition() + guard let firstCondition = conditionBlocks.first as? any ExprCodeBlock else { fatalError("While condition must conform to ExprCodeBlock protocol") } - self.condition = exprCondition - self.body = then() + self.condition = firstCondition + self.body = try then() + self.isRepeatWhile = true } public var syntax: SyntaxProtocol { diff --git a/Sources/SyntaxKit/Declarations/Class.swift b/Sources/SyntaxKit/Declarations/Class.swift index 56446f3..9497861 100644 --- a/Sources/SyntaxKit/Declarations/Class.swift +++ b/Sources/SyntaxKit/Declarations/Class.swift @@ -38,13 +38,13 @@ public struct Class: CodeBlock { private var isFinal: Bool = false private var attributes: [AttributeInfo] = [] - /// Creates a `class` declaration. + /// Creates a class declaration. /// - Parameters: /// - name: The name of the class. - /// - content: A ``CodeBlockBuilder`` that provides the members of the class. - public init(_ name: String, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) { + /// - content: A ``CodeBlockBuilder`` that provides the body of the class. + public init(_ name: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { self.name = name - self.members = content() + self.members = try content() } /// Sets the generic parameters for the class. diff --git a/Sources/SyntaxKit/Declarations/Enum.swift b/Sources/SyntaxKit/Declarations/Enum.swift index 1945142..99c8c28 100644 --- a/Sources/SyntaxKit/Declarations/Enum.swift +++ b/Sources/SyntaxKit/Declarations/Enum.swift @@ -36,13 +36,13 @@ public struct Enum: CodeBlock { private var inheritance: [String] = [] private var attributes: [AttributeInfo] = [] - /// Creates an `enum` declaration. + /// Creates an enum declaration. /// - Parameters: /// - name: The name of the enum. - /// - content: A ``CodeBlockBuilder`` that provides the members of the enum. - public init(_ name: String, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) { + /// - content: A ``CodeBlockBuilder`` that provides the body of the enum. + public init(_ name: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { self.name = name - self.members = content() + self.members = try content() } /// Sets the inheritance for the enum. diff --git a/Sources/SyntaxKit/Declarations/Extension.swift b/Sources/SyntaxKit/Declarations/Extension.swift index af53db4..a098086 100644 --- a/Sources/SyntaxKit/Declarations/Extension.swift +++ b/Sources/SyntaxKit/Declarations/Extension.swift @@ -39,10 +39,10 @@ public struct Extension: CodeBlock { /// Creates an extension declaration. /// - Parameters: /// - extendedType: The type being extended. - /// - content: A ``CodeBlockBuilder`` that provides the members of the extension. - public init(_ extendedType: String, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) { + /// - content: A ``CodeBlockBuilder`` that provides the body of the extension. + public init(_ extendedType: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { self.extendedType = extendedType - self.members = content() + self.members = try content() } /// Sets one or more inherited protocols. diff --git a/Sources/SyntaxKit/Declarations/Protocol.swift b/Sources/SyntaxKit/Declarations/Protocol.swift index 018af73..b8befc9 100644 --- a/Sources/SyntaxKit/Declarations/Protocol.swift +++ b/Sources/SyntaxKit/Declarations/Protocol.swift @@ -36,13 +36,13 @@ public struct Protocol: CodeBlock { private var inheritance: [String] = [] private var attributes: [AttributeInfo] = [] - /// Creates a `protocol` declaration. + /// Creates a protocol declaration. /// - Parameters: /// - name: The name of the protocol. - /// - content: A ``CodeBlockBuilder`` that provides the members of the protocol. - public init(_ name: String, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) { + /// - content: A ``CodeBlockBuilder`` that provides the body of the protocol. + public init(_ name: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { self.name = name - self.members = content() + self.members = try content() } /// Sets one or more inherited protocols. diff --git a/Sources/SyntaxKit/Declarations/Struct.swift b/Sources/SyntaxKit/Declarations/Struct.swift index 4b2dc56..d2f8f4d 100644 --- a/Sources/SyntaxKit/Declarations/Struct.swift +++ b/Sources/SyntaxKit/Declarations/Struct.swift @@ -38,13 +38,13 @@ public struct Struct: CodeBlock { private var attributes: [AttributeInfo] = [] private var accessModifier: String? - /// Creates a `struct` declaration. + /// Creates a struct declaration. /// - Parameters: /// - name: The name of the struct. - /// - content: A ``CodeBlockBuilder`` that provides the members of the struct. - public init(_ name: String, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) { + /// - content: A ``CodeBlockBuilder`` that provides the body of the struct. + public init(_ name: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { self.name = name - self.members = content() + self.members = try content() } /// Sets the generic parameter for the struct. diff --git a/Sources/SyntaxKit/ErrorHandling/Catch.swift b/Sources/SyntaxKit/ErrorHandling/Catch.swift index edad97f..4262002 100644 --- a/Sources/SyntaxKit/ErrorHandling/Catch.swift +++ b/Sources/SyntaxKit/ErrorHandling/Catch.swift @@ -65,6 +65,13 @@ public struct Catch: CodeBlock { Catch(enumCase, content) } + /// Creates a catch clause. + /// - Parameter content: A ``CodeBlockBuilder`` that provides the body of the catch clause. + public init(@CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + self.pattern = nil + self.body = try content() + } + public var catchClauseSyntax: CatchClauseSyntax { // Build catch items (patterns) var catchItems: CatchItemListSyntax? diff --git a/Sources/SyntaxKit/Expressions/Call.swift b/Sources/SyntaxKit/Expressions/Call.swift index 030406c..c1de9b3 100644 --- a/Sources/SyntaxKit/Expressions/Call.swift +++ b/Sources/SyntaxKit/Expressions/Call.swift @@ -47,9 +47,9 @@ public struct Call: CodeBlock { /// - Parameters: /// - functionName: The name of the function to call. /// - params: A ``ParameterExpBuilder`` that provides the parameters for the function call. - public init(_ functionName: String, @ParameterExpBuilderResult _ params: () -> [ParameterExp]) { + public init(_ functionName: String, @ParameterExpBuilderResult _ params: () throws -> [ParameterExp]) rethrows { self.functionName = functionName - self.parameters = params() + self.parameters = try params() } /// Marks this function call as throwing. diff --git a/Sources/SyntaxKit/Expressions/Closure.swift b/Sources/SyntaxKit/Expressions/Closure.swift index 59be944..080a191 100644 --- a/Sources/SyntaxKit/Expressions/Closure.swift +++ b/Sources/SyntaxKit/Expressions/Closure.swift @@ -45,12 +45,12 @@ public struct Closure: CodeBlock { @ParameterExpBuilderResult capture: () -> [ParameterExp] = { [] }, @ClosureParameterBuilderResult parameters: () -> [ClosureParameter] = { [] }, returns returnType: String? = nil, - @CodeBlockBuilderResult body: () -> [CodeBlock] - ) { + @CodeBlockBuilderResult body: () throws -> [CodeBlock] + ) rethrows { self.capture = capture() self.parameters = parameters() self.returnType = returnType - self.body = body() + self.body = try body() } public func attribute(_ attribute: String, arguments: [String] = []) -> Self { diff --git a/Sources/SyntaxKit/Expressions/Infix.swift b/Sources/SyntaxKit/Expressions/Infix.swift index fd4332d..26b839d 100644 --- a/Sources/SyntaxKit/Expressions/Infix.swift +++ b/Sources/SyntaxKit/Expressions/Infix.swift @@ -68,9 +68,9 @@ public struct Infix: CodeBlock, ExprCodeBlock { /// Exactly two operands must be supplied – a left-hand side and a right-hand side. /// Each operand must conform to ExprCodeBlock. @available(*, deprecated, message: "Use separate lhs and rhs parameters for compile-time safety") - public init(_ operation: String, @CodeBlockBuilderResult _ content: () -> [CodeBlock]) throws { + public init(_ operation: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) throws { self.operation = operation - let operands = content() + let operands = try content() guard operands.count == 2 else { throw InfixError.wrongOperandCount(expected: 2, got: operands.count) diff --git a/Sources/SyntaxKit/Expressions/Return.swift b/Sources/SyntaxKit/Expressions/Return.swift index 6d2a9c2..cb27500 100644 --- a/Sources/SyntaxKit/Expressions/Return.swift +++ b/Sources/SyntaxKit/Expressions/Return.swift @@ -35,8 +35,8 @@ public struct Return: CodeBlock { /// Creates a `return` statement. /// - Parameter content: A ``CodeBlockBuilder`` that provides the expression to return. - public init(@CodeBlockBuilderResult _ content: () -> [CodeBlock]) { - self.exprs = content() + public init(@CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + self.exprs = try content() } public var syntax: SyntaxProtocol { if let expr = exprs.first { diff --git a/Sources/SyntaxKit/Expressions/Task.swift b/Sources/SyntaxKit/Expressions/Task.swift index 56b1b32..9775b78 100644 --- a/Sources/SyntaxKit/Expressions/Task.swift +++ b/Sources/SyntaxKit/Expressions/Task.swift @@ -34,10 +34,10 @@ public struct Task: CodeBlock { private let body: [CodeBlock] private var attributes: [AttributeInfo] = [] - /// Creates a Task expression. - /// - Parameter content: A ``CodeBlockBuilder`` that provides the body of the task. - public init(@CodeBlockBuilderResult _ content: () -> [CodeBlock]) { - self.body = content() + /// Creates a Task block. + /// - Parameter content: A ``CodeBlockBuilder`` that provides the body of the Task. + public init(@CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + self.body = try content() } /// Adds an attribute to the task. diff --git a/Sources/SyntaxKit/Functions/Function.swift b/Sources/SyntaxKit/Functions/Function.swift index 038eb09..0d1e798 100644 --- a/Sources/SyntaxKit/Functions/Function.swift +++ b/Sources/SyntaxKit/Functions/Function.swift @@ -48,12 +48,12 @@ public struct Function: CodeBlock { public init( _ name: String, returns returnType: String? = nil, - @CodeBlockBuilderResult _ content: () -> [CodeBlock] - ) { + @CodeBlockBuilderResult _ content: () throws -> [CodeBlock] + ) rethrows { self.name = name self.parameters = [] self.returnType = returnType - self.body = content() + self.body = try content() } /// Creates a `func` declaration. @@ -66,12 +66,12 @@ public struct Function: CodeBlock { _ name: String, returns returnType: String? = nil, @ParameterBuilderResult _ params: () -> [Parameter], - @CodeBlockBuilderResult _ content: () -> [CodeBlock] - ) { + @CodeBlockBuilderResult _ content: () throws -> [CodeBlock] + ) rethrows { self.name = name self.parameters = params() self.returnType = returnType - self.body = content() + self.body = try content() } /// Creates a `func` declaration with parameters and body using the DSL syntax. @@ -82,11 +82,11 @@ public struct Function: CodeBlock { public init( _ name: String, @ParameterBuilderResult _ params: () -> [Parameter], - @CodeBlockBuilderResult _ body: () -> [CodeBlock] - ) { + @CodeBlockBuilderResult _ body: () throws -> [CodeBlock] + ) rethrows { self.name = name self.parameters = params() self.returnType = nil - self.body = body() + self.body = try body() } } diff --git a/Sources/SyntaxKit/Parameters/ParameterExp.swift b/Sources/SyntaxKit/Parameters/ParameterExp.swift index 66c5876..51ba71f 100644 --- a/Sources/SyntaxKit/Parameters/ParameterExp.swift +++ b/Sources/SyntaxKit/Parameters/ParameterExp.swift @@ -47,6 +47,7 @@ public struct ParameterExp: CodeBlock { /// - Parameters: /// - name: The name of the parameter. /// - value: The string value of the parameter. + @available(*, deprecated, message: "Use ParameterExp(name:value:) with Literal.string() or VariableExp() instead") public init(name: String, value: String) { self.name = name self.value = VariableExp(value) @@ -59,6 +60,7 @@ public struct ParameterExp: CodeBlock { } /// Convenience initializer for unlabeled parameter with a String value. + @available(*, deprecated, message: "Use ParameterExp(unlabeled:) with Literal.string() or VariableExp() instead") public init(unlabeled value: String) { self.name = "" self.value = VariableExp(value) diff --git a/Sources/SyntaxKit/Utilities/Case.swift b/Sources/SyntaxKit/Utilities/Case.swift index 9f5eb7a..509b549 100644 --- a/Sources/SyntaxKit/Utilities/Case.swift +++ b/Sources/SyntaxKit/Utilities/Case.swift @@ -37,14 +37,13 @@ public struct Case: CodeBlock { private let enumCaseName: String? private var associatedValue: (name: String, type: String)? - /// Creates a `case` for a `switch` statement. + /// Creates a case declaration. /// - Parameters: - /// - patterns: The patterns to match for the case. + /// - patterns: The patterns for the case. /// - content: A ``CodeBlockBuilder`` that provides the body of the case. - public init(_ patterns: PatternConvertible..., @CodeBlockBuilderResult content: () -> [CodeBlock]) - { + public init(_ patterns: PatternConvertible..., @CodeBlockBuilderResult content: () throws -> [CodeBlock]) rethrows { self.patterns = patterns - self.body = content() + self.body = try content() self.isEnumCase = false self.enumCaseName = nil self.associatedValue = nil diff --git a/Sources/SyntaxKit/Utilities/Default.swift b/Sources/SyntaxKit/Utilities/Default.swift index 9ccb5b3..6ee3ca8 100644 --- a/Sources/SyntaxKit/Utilities/Default.swift +++ b/Sources/SyntaxKit/Utilities/Default.swift @@ -33,10 +33,10 @@ import SwiftSyntax public struct Default: CodeBlock { private let body: [CodeBlock] - /// Creates a `default` case for a `switch` statement. - /// - Parameter content: A ``CodeBlockBuilder`` that provides the body of the case. - public init(@CodeBlockBuilderResult _ content: () -> [CodeBlock]) { - self.body = content() + /// Creates a default case declaration. + /// - Parameter content: A ``CodeBlockBuilder`` that provides the body of the default case. + public init(@CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + self.body = try content() } public var switchCaseSyntax: SwitchCaseSyntax { let statements = CodeBlockItemListSyntax( diff --git a/Sources/SyntaxKit/Utilities/Group.swift b/Sources/SyntaxKit/Utilities/Group.swift index 329574a..36921ac 100644 --- a/Sources/SyntaxKit/Utilities/Group.swift +++ b/Sources/SyntaxKit/Utilities/Group.swift @@ -35,8 +35,8 @@ public struct Group: CodeBlock { /// Creates a group of code blocks. /// - Parameter content: A ``CodeBlockBuilder`` that provides the members of the group. - public init(@CodeBlockBuilderResult _ content: () -> [CodeBlock]) { - self.members = content() + public init(@CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + self.members = try content() } public var syntax: SyntaxProtocol { diff --git a/Sources/SyntaxKit/Utilities/Parenthesized.swift b/Sources/SyntaxKit/Utilities/Parenthesized.swift index 5327b52..d7aa6f0 100644 --- a/Sources/SyntaxKit/Utilities/Parenthesized.swift +++ b/Sources/SyntaxKit/Utilities/Parenthesized.swift @@ -35,8 +35,8 @@ public struct Parenthesized: CodeBlock, ExprCodeBlock { /// Creates a parenthesized code block. /// - Parameter content: The code block to wrap in parentheses. - public init(@CodeBlockBuilderResult _ content: () -> [CodeBlock]) { - let blocks = content() + public init(@CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + let blocks = try content() precondition(blocks.count == 1, "Parenthesized expects exactly one code block.") self.content = blocks[0] } diff --git a/Sources/SyntaxKit/Utilities/Then.swift b/Sources/SyntaxKit/Utilities/Then.swift index dd2fc2e..710d5b9 100644 --- a/Sources/SyntaxKit/Utilities/Then.swift +++ b/Sources/SyntaxKit/Utilities/Then.swift @@ -46,8 +46,10 @@ public struct Then: CodeBlock { /// The statements that make up the `else` body. public let body: [CodeBlock] - public init(@CodeBlockBuilderResult _ content: () -> [CodeBlock]) { - self.body = content() + /// Creates a then block. + /// - Parameter content: A ``CodeBlockBuilder`` that provides the body of the then block. + public init(@CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + self.body = try content() } public var syntax: SyntaxProtocol { diff --git a/Sources/SyntaxKit/Variables/ComputedProperty.swift b/Sources/SyntaxKit/Variables/ComputedProperty.swift index 7b6cdb6..716fdb7 100644 --- a/Sources/SyntaxKit/Variables/ComputedProperty.swift +++ b/Sources/SyntaxKit/Variables/ComputedProperty.swift @@ -47,12 +47,12 @@ public struct ComputedProperty: CodeBlock { _ name: String, type: String, explicitType: Bool = true, - @CodeBlockBuilderResult _ content: () -> [CodeBlock] - ) { + @CodeBlockBuilderResult _ content: () throws -> [CodeBlock] + ) rethrows { self.name = name self.type = type self.explicitType = explicitType - self.body = content() + self.body = try content() } /// Sets the access modifier for the computed property declaration. diff --git a/Tests/SyntaxKitTests/Integration/BlackjackTests.swift b/Tests/SyntaxKitTests/Integration/BlackjackTests.swift index 740f218..d29f63a 100644 --- a/Tests/SyntaxKitTests/Integration/BlackjackTests.swift +++ b/Tests/SyntaxKitTests/Integration/BlackjackTests.swift @@ -70,7 +70,7 @@ internal struct BlackjackTests { } @Test internal func testFullBlackjackCardExample() throws { - let syntax = Struct("BlackjackCard") { + let syntax = try Struct("BlackjackCard") { Enum("Suit") { EnumCase("spades").equals("♠") EnumCase("hearts").equals("♡") @@ -130,10 +130,10 @@ internal struct BlackjackTests { Variable(.let, name: "rank", type: "Rank") Variable(.let, name: "suit", type: "Suit") - ComputedProperty("description", type: "String") { + try ComputedProperty("description", type: "String") { VariableDecl(.var, name: "output", equals: "suit is \\(suit.rawValue),") PlusAssign("output", " value is \\(rank.values.first)") - If( + try If( Let("second", "rank.values.second"), then: { PlusAssign("output", " or \\(second)") diff --git a/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift b/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift index 4d8df6d..f77e564 100644 --- a/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift +++ b/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift @@ -9,7 +9,7 @@ import Testing // Build DSL equivalent of Examples/Remaining/concurrency/dsl.swift // Note: This test includes the Item struct that's referenced but not defined in the original DSL - let program = Group { + let program = try Group { // Item struct (needed for the vending machine) Struct("Item") { Variable(.let, name: "price", type: "Int").withExplicitType() @@ -25,7 +25,7 @@ import Testing .inherits("Error") // VendingMachine class - Class("VendingMachine") { + try Class("VendingMachine") { Variable( .var, name: "inventory", @@ -55,30 +55,32 @@ import Testing ) Variable(.var, name: "coinsDeposited", equals: 0) - Function("vend") { + try Function("vend") { Parameter("name", labeled: "itemNamed", type: "String") } _: { Guard { Let("item", "inventory[name]") } else: { - Throw(VariableExp("VendingMachineError.invalidSelection")) + Throw( + EnumCase("VendingMachineError.invalidSelection") + ) } - Guard { - try! Infix(">") { + try Guard { + try Infix(">") { VariableExp("item").property("count") Literal.integer(0) } } else: { - Throw(VariableExp("VendingMachineError.outOfStock")) + Throw(EnumCase("VendingMachineError.outOfStock")) } - Guard { - try! Infix("<=") { + try Guard { + try Infix("<=") { VariableExp("item").property("price") VariableExp("coinsDeposited") } } else: { Throw( - Call("VendingMachineError.insufficientFunds") { + try Call("VendingMachineError.insufficientFunds") { ParameterExp( name: "coinsNeeded", value: try! Infix("-") { @@ -99,8 +101,8 @@ import Testing Literal.integer(1) } Assignment("inventory[name]", .ref("newItem")) - Call("print") { - ParameterExp(unlabeled: "\"Dispensing \\(name)\"") + try Call("print") { + ParameterExp(unlabeled: Literal.string("Dispensing \\(name)")) } } .throws() diff --git a/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift b/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift index 3f483e8..d64aafc 100644 --- a/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift +++ b/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift @@ -8,20 +8,20 @@ import Testing internal func testCompletedConditionalsExample() throws { // Build DSL equivalent of Examples/Completed/conditionals/dsl.swift - let program = Group { + let program = try Group { // MARK: Basic If Statements Variable(.let, name: "temperature", equals: 25) .comment { Line("Simple if statement") } - If { - try! Infix(">") { + try If { + try Infix(">") { VariableExp("temperature") Literal.integer(30) } } then: { - Call("print") { + try Call("print") { ParameterExp(unlabeled: "\"It's hot outside!\"") } } @@ -32,37 +32,37 @@ import Testing Line("If-else statement") } - If { - try! Infix(">=") { + try If { + try Infix(">=") { VariableExp("score") Literal.integer(90) } } then: { - Call("print") { + try Call("print") { ParameterExp(unlabeled: "\"Excellent!\"") } } else: { - If { - try! Infix(">=") { + try If { + try Infix(">=") { VariableExp("score") Literal.integer(80) } } then: { - Call("print") { + try Call("print") { ParameterExp(unlabeled: "\"Good job!\"") } } else: { - If { - try! Infix(">=") { + try If { + try Infix(">=") { VariableExp("score") Literal.integer(70) } } then: { - Call("print") { + try Call("print") { ParameterExp(unlabeled: "\"Passing\"") } } else: { - Call("print") { + try Call("print") { ParameterExp(unlabeled: "\"Needs improvement\"") } } @@ -76,10 +76,10 @@ import Testing Line("Using if let for optional binding") } - If( + try If( Let("actualNumber", "Int(possibleNumber)"), then: { - Call("print") { + try Call("print") { ParameterExp( name: "", value: @@ -88,7 +88,7 @@ import Testing } }, else: { - Call("print") { + try Call("print") { ParameterExp( name: "", value: "\"The string \"\\(possibleNumber)\" could not be converted to an integer\"" @@ -106,11 +106,11 @@ import Testing Variable(.let, name: "possibleAge", type: "Int?", equals: Literal.integer(30)) .withExplicitType() - If { + try If { Let("name", "possibleName") Let("age", "possibleAge") } then: { - Call("print") { + try Call("print") { ParameterExp(name: "", value: "\"\\(name) is \\(age) years old\"") } } @@ -496,4 +496,56 @@ import Testing #expect(generated == expected) } + + @Test("Conditionals example generates correct syntax") + internal func testConditionalsExample() throws { + let ifStatement = try If { + try Infix(">") { + VariableExp("temperature") + Literal.integer(30) + } + } then: { + try Call("print") { + ParameterExp(unlabeled: "It's hot!") + } + } else: { + try If { + try Infix(">=") { + VariableExp("score") + Literal.integer(90) + } + } then: { + try Call("print") { + ParameterExp(unlabeled: "Excellent!") + } + } else: { + try If { + try Infix(">=") { + VariableExp("score") + Literal.integer(80) + } + } then: { + try Call("print") { + ParameterExp(unlabeled: "Good!") + } + } else: { + try If { + try Infix(">=") { + VariableExp("score") + Literal.integer(70) + } + } then: { + try Call("print") { + ParameterExp(unlabeled: "Pass") + } + } else: { + try Call("print") { + ParameterExp(unlabeled: "Fail") + } + } + } + } + } + // ... rest of the test ... + } } diff --git a/Tests/SyntaxKitTests/Integration/ForLoopsExampleTests.swift b/Tests/SyntaxKitTests/Integration/ForLoopsExampleTests.swift index 0a880ca..1093ccc 100644 --- a/Tests/SyntaxKitTests/Integration/ForLoopsExampleTests.swift +++ b/Tests/SyntaxKitTests/Integration/ForLoopsExampleTests.swift @@ -8,7 +8,7 @@ import Testing internal func testCompletedForLoopsExample() throws { // Build DSL equivalent of Examples/Completed/for_loops/dsl.swift - let program = Group { + let program = try Group { // MARK: - Basic For-in Loop Variable( .let, @@ -24,7 +24,7 @@ import Testing Line("Simple for-in loop over an array") } - For( + try For( VariableExp("name"), in: VariableExp("names"), then: { @@ -42,7 +42,7 @@ import Testing Line("MARK: - For-in with Enumerated") Line("For-in loop with enumerated() to get index and value") } - For( + try For( Tuple.patternCodeBlock([ VariableExp("index"), VariableExp("name"), @@ -80,12 +80,12 @@ import Testing ]) ) - For( + try For( VariableExp("number"), in: VariableExp("numbers"), where: { - try! Infix("==") { - try! Infix("%") { + try Infix("==") { + try Infix("%") { VariableExp("number") Literal.integer(2) } @@ -117,7 +117,7 @@ import Testing ]) ) - For( + try For( Tuple.patternCodeBlock([ VariableExp("name"), VariableExp("score"), diff --git a/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift b/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift index a6b92c9..772a279 100644 --- a/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift +++ b/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift @@ -47,4 +47,32 @@ import Testing #expect(generated.contains("else if score >= 80".normalize())) #expect(generated.contains("else {".normalize())) } + + @Test("If with multiple conditions generates correct syntax") + internal func testIfWithMultipleConditions() throws { + let ifStatement = try If { + try Infix(">=") { + VariableExp("score") + Literal.integer(90) + } + } then: { + try Call("print") { + ParameterExp(unlabeled: "Excellent!") + } + } else: { + try If { + try Infix(">=") { + VariableExp("score") + Literal.integer(80) + } + } then: { + try Call("print") { + ParameterExp(unlabeled: "Good!") + } + } + } + let generated = ifStatement.generateCode() + #expect(generated.contains("if score >= 90")) + #expect(generated.contains("else if score >= 80")) + } } diff --git a/Tests/SyntaxKitTests/Unit/ControlFlow/ForLoopTests.swift b/Tests/SyntaxKitTests/Unit/ControlFlow/ForLoopTests.swift index 6665120..d40919d 100644 --- a/Tests/SyntaxKitTests/Unit/ControlFlow/ForLoopTests.swift +++ b/Tests/SyntaxKitTests/Unit/ControlFlow/ForLoopTests.swift @@ -5,8 +5,8 @@ import Testing @Suite internal final class ForLoopTests { @Test - internal func testSimpleForInLoop() { - let forLoop = For( + internal func testSimpleForInLoop() throws { + let forLoop = try For( VariableExp("item"), in: VariableExp("items"), then: { @@ -22,12 +22,12 @@ internal final class ForLoopTests { } @Test - internal func testForInWithWhereClause() { - let forLoop = For( + internal func testForInWithWhereClause() throws { + let forLoop = try For( VariableExp("number"), in: VariableExp("numbers"), where: { - try! Infix("%") { + try Infix("%") { VariableExp("number") Literal.integer(2) } diff --git a/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTestsExpressions.swift b/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTestsExpressions.swift index b39f0e7..659e43d 100644 --- a/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTestsExpressions.swift +++ b/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTestsExpressions.swift @@ -36,15 +36,15 @@ internal struct EdgeCaseTestsExpressions { @Test("Infix with complex expressions generates correct syntax") internal func testInfixWithComplexExpressions() throws { - let infix = try! Infix("*") { - Parenthesized { - try! Infix("+") { + let infix = try Infix("*") { + try Parenthesized { + try Infix("+") { VariableExp("a") VariableExp("b") } } - Parenthesized { - try! Infix("-") { + try Parenthesized { + try Infix("-") { VariableExp("c") VariableExp("d") } @@ -67,8 +67,8 @@ internal struct EdgeCaseTestsExpressions { @Test("Return with complex expression generates correct syntax") internal func testReturnWithComplexExpression() throws { - let returnStmt = Return { - try! Infix("+") { + let returnStmt = try Return { + try Infix("+") { VariableExp("a") VariableExp("b") } diff --git a/Tests/SyntaxKitTests/Unit/SwiftUIFeatureTests.swift b/Tests/SyntaxKitTests/Unit/SwiftUIFeatureTests.swift index 480bcc4..c0e1163 100644 --- a/Tests/SyntaxKitTests/Unit/SwiftUIFeatureTests.swift +++ b/Tests/SyntaxKitTests/Unit/SwiftUIFeatureTests.swift @@ -126,7 +126,6 @@ import Testing ) let weakGenerated = weakClosure.syntax.description - print("Weak closure generated:\n\(weakGenerated)") #expect(weakGenerated.contains("[weak self]")) // Test unowned reference in closure capture @@ -142,7 +141,6 @@ import Testing ) let unownedGenerated = unownedClosure.syntax.description - print("Unowned closure generated:\n\(unownedGenerated)") #expect(unownedGenerated.contains("[unowned self]")) } } From 8dbec027566c6f6a90270c7e7b34b779ad5995ec Mon Sep 17 00:00:00 2001 From: leogdion Date: Tue, 24 Jun 2025 09:59:29 -0400 Subject: [PATCH 04/17] fixing more force tries --- Examples/Completed/conditionals/dsl.swift | 16 +++++----- Examples/Completed/for_loops/dsl.swift | 6 ++-- .../SKSampleMacroMacro.swift | 2 +- Sources/SyntaxKit/ControlFlow/For.swift | 4 +-- Sources/SyntaxKit/ControlFlow/Switch.swift | 8 +++-- .../SyntaxKit/ControlFlow/SwitchCase.swift | 9 ++++-- Sources/SyntaxKit/ControlFlow/While.swift | 16 +++++----- .../SyntaxKit/Core/ExprCodeBlockBuilder.swift | 6 ++-- .../Core/PatternConvertibleBuilder.swift | 6 ++-- Sources/SyntaxKit/Declarations/Class.swift | 3 +- Sources/SyntaxKit/Declarations/Enum.swift | 3 +- .../SyntaxKit/Declarations/Extension.swift | 4 ++- Sources/SyntaxKit/Declarations/Init.swift | 6 ++-- Sources/SyntaxKit/Declarations/Protocol.swift | 3 +- Sources/SyntaxKit/Declarations/Struct.swift | 3 +- Sources/SyntaxKit/Expressions/Call.swift | 11 ++++--- .../Expressions/FunctionCallExp.swift | 8 +++-- Sources/SyntaxKit/Expressions/Infix.swift | 4 ++- .../SyntaxKit/Parameters/ParameterExp.swift | 10 +++++-- Sources/SyntaxKit/Utilities/Case.swift | 4 ++- .../Variable+LiteralInitializers.swift | 6 ++-- .../Integration/ConcurrencyExampleTests.swift | 6 ++-- .../ConditionalsExampleTests.swift | 30 +++++++++---------- .../Unit/ControlFlow/ConditionalsTests.swift | 10 +++---- 24 files changed, 108 insertions(+), 76 deletions(-) diff --git a/Examples/Completed/conditionals/dsl.swift b/Examples/Completed/conditionals/dsl.swift index d6c4850..70263e0 100644 --- a/Examples/Completed/conditionals/dsl.swift +++ b/Examples/Completed/conditionals/dsl.swift @@ -23,7 +23,7 @@ Group { Call("print", "Good job!") } If { - try! Infix("score", ">=", 70) + try Infix("score", ">=", 70) } then: { Call("print", "Passing") } @@ -172,24 +172,24 @@ Infix("board[24]", "-=", 8) Variable(.var, name: "square", equals: 0) Variable(.var, name: "diceRoll", equals: 0) While { - try! Infix("square", "!=", "finalSquare") + try Infix("square", "!=", "finalSquare") } then: { Assignment("diceRoll", "+", 1) If { - try! Infix("diceRoll", "==", 7) + try Infix("diceRoll", "==", 7) } then: { Assignment("diceRoll", 1) } - Switch(try! Infix("square", "+", "diceRoll")) { + Switch(try Infix("square", "+", "diceRoll")) { SwitchCase("finalSquare") { Break() } - SwitchCase(try! Infix("newSquare", ">", "finalSquare")) { + SwitchCase(try Infix("newSquare", ">", "finalSquare")) { Continue() } Default { - try! Infix("square", "+=", "diceRoll") - try! Infix("square", "+=", "board[square]") + try Infix("square", "+=", "diceRoll") + try Infix("square", "+=", "board[square]") } } } @@ -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: { - try! Infix("number", "%", 2) + try Infix("number", "%", 2) } then: { Call("print", "Even number: \\(number)") } diff --git a/Examples/Completed/for_loops/dsl.swift b/Examples/Completed/for_loops/dsl.swift index d267a9b..db06009 100644 --- a/Examples/Completed/for_loops/dsl.swift +++ b/Examples/Completed/for_loops/dsl.swift @@ -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: { - try! Infix("==") { - try! Infix("%") { + try Infix("==") { + try Infix("%") { VariableExp("number") Literal.integer(2) } @@ -68,4 +68,4 @@ Group { ParameterExp(unlabeled: "\"\\(name): \\(score)\"") } }) -} \ No newline at end of file +} diff --git a/Macros/SKSampleMacro/Sources/SKSampleMacroMacros/SKSampleMacroMacro.swift b/Macros/SKSampleMacro/Sources/SKSampleMacroMacros/SKSampleMacroMacro.swift index 10b94e2..e531bec 100644 --- a/Macros/SKSampleMacro/Sources/SKSampleMacroMacros/SKSampleMacroMacro.swift +++ b/Macros/SKSampleMacro/Sources/SKSampleMacroMacros/SKSampleMacroMacro.swift @@ -25,7 +25,7 @@ public struct StringifyMacro: ExpressionMacro { } return Tuple{ - try! Infix("+") { + try Infix("+") { VariableExp(first.description) VariableExp(second.description) } diff --git a/Sources/SyntaxKit/ControlFlow/For.swift b/Sources/SyntaxKit/ControlFlow/For.swift index b441b84..c8e3892 100644 --- a/Sources/SyntaxKit/ControlFlow/For.swift +++ b/Sources/SyntaxKit/ControlFlow/For.swift @@ -7,7 +7,7 @@ // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without +// files (the “Software”), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the @@ -17,7 +17,7 @@ // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT diff --git a/Sources/SyntaxKit/ControlFlow/Switch.swift b/Sources/SyntaxKit/ControlFlow/Switch.swift index 84285c5..79d9ad3 100644 --- a/Sources/SyntaxKit/ControlFlow/Switch.swift +++ b/Sources/SyntaxKit/ControlFlow/Switch.swift @@ -38,7 +38,9 @@ public struct Switch: CodeBlock { /// - Parameters: /// - expression: The expression to switch on. /// - content: A ``CodeBlockBuilder`` that provides the cases for the switch. - public init(_ expression: CodeBlock, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + public init(_ expression: CodeBlock, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) + rethrows + { self.expression = expression self.cases = try content() } @@ -47,7 +49,9 @@ public struct Switch: CodeBlock { /// - Parameters: /// - expression: The string expression to switch on. /// - content: A ``CodeBlockBuilder`` that provides the cases for the switch. - public init(_ expression: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + public init(_ expression: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) + rethrows + { self.expression = VariableExp(expression) self.cases = try content() } diff --git a/Sources/SyntaxKit/ControlFlow/SwitchCase.swift b/Sources/SyntaxKit/ControlFlow/SwitchCase.swift index 2bc10b9..4999e8c 100644 --- a/Sources/SyntaxKit/ControlFlow/SwitchCase.swift +++ b/Sources/SyntaxKit/ControlFlow/SwitchCase.swift @@ -39,7 +39,9 @@ public struct SwitchCase: CodeBlock { /// - patterns: The patterns to match for the case. Can be `PatternConvertible`, /// `CodeBlock`, or `SwitchLet` for value binding. /// - content: A ``CodeBlockBuilder`` that provides the body of the case. - public init(_ patterns: Any..., @CodeBlockBuilderResult content: () throws -> [CodeBlock]) rethrows { + public init(_ patterns: Any..., @CodeBlockBuilderResult content: () throws -> [CodeBlock]) + rethrows + { self.patterns = patterns self.body = try content() } @@ -48,7 +50,10 @@ public struct SwitchCase: CodeBlock { /// - Parameters: /// - conditional: A ``CodeBlockBuilder`` that provides the conditional patterns for the case. /// - content: A ``CodeBlockBuilder`` that provides the body of the case. - public init(@CodeBlockBuilderResult conditional: () throws -> [CodeBlock], @CodeBlockBuilderResult content: () throws -> [CodeBlock]) rethrows { + public init( + @CodeBlockBuilderResult conditional: () throws -> [CodeBlock], + @CodeBlockBuilderResult content: () throws -> [CodeBlock] + ) rethrows { self.patterns = try conditional() self.body = try content() } diff --git a/Sources/SyntaxKit/ControlFlow/While.swift b/Sources/SyntaxKit/ControlFlow/While.swift index 04507d6..bdea0de 100644 --- a/Sources/SyntaxKit/ControlFlow/While.swift +++ b/Sources/SyntaxKit/ControlFlow/While.swift @@ -41,10 +41,10 @@ public struct While: CodeBlock { /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. public init( _ condition: any ExprCodeBlock, - @CodeBlockBuilderResult then: () -> [CodeBlock] - ) { + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { self.condition = condition - self.body = then() + self.body = try then() self.isRepeatWhile = false } @@ -53,11 +53,11 @@ public struct While: CodeBlock { /// - condition: A `CodeBlockBuilder` that produces exactly one condition expression. /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. public init( - @ExprCodeBlockBuilder _ condition: () -> any ExprCodeBlock, - @CodeBlockBuilderResult then: () -> [CodeBlock] - ) { - self.condition = condition() - self.body = then() + @ExprCodeBlockBuilder _ condition: () throws -> any ExprCodeBlock, + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { + self.condition = try condition() + self.body = try then() self.isRepeatWhile = false } diff --git a/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift b/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift index 94a3ac2..58c0b88 100644 --- a/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift +++ b/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift @@ -7,7 +7,7 @@ // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without +// files (the “Software”), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the @@ -17,10 +17,10 @@ // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHERS OR COPYRIGHT +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR diff --git a/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift b/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift index 2b2d4e9..fbc6db9 100644 --- a/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift +++ b/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift @@ -7,7 +7,7 @@ // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without +// files (the “Software”), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the @@ -17,10 +17,10 @@ // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHERS OR COPYRIGHT +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR diff --git a/Sources/SyntaxKit/Declarations/Class.swift b/Sources/SyntaxKit/Declarations/Class.swift index 9497861..f38ae5e 100644 --- a/Sources/SyntaxKit/Declarations/Class.swift +++ b/Sources/SyntaxKit/Declarations/Class.swift @@ -42,7 +42,8 @@ public struct Class: CodeBlock { /// - Parameters: /// - name: The name of the class. /// - content: A ``CodeBlockBuilder`` that provides the body of the class. - public init(_ name: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + public init(_ name: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows + { self.name = name self.members = try content() } diff --git a/Sources/SyntaxKit/Declarations/Enum.swift b/Sources/SyntaxKit/Declarations/Enum.swift index 99c8c28..a8e82c8 100644 --- a/Sources/SyntaxKit/Declarations/Enum.swift +++ b/Sources/SyntaxKit/Declarations/Enum.swift @@ -40,7 +40,8 @@ public struct Enum: CodeBlock { /// - Parameters: /// - name: The name of the enum. /// - content: A ``CodeBlockBuilder`` that provides the body of the enum. - public init(_ name: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + public init(_ name: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows + { self.name = name self.members = try content() } diff --git a/Sources/SyntaxKit/Declarations/Extension.swift b/Sources/SyntaxKit/Declarations/Extension.swift index a098086..1ff7deb 100644 --- a/Sources/SyntaxKit/Declarations/Extension.swift +++ b/Sources/SyntaxKit/Declarations/Extension.swift @@ -40,7 +40,9 @@ public struct Extension: CodeBlock { /// - Parameters: /// - extendedType: The type being extended. /// - content: A ``CodeBlockBuilder`` that provides the body of the extension. - public init(_ extendedType: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + public init(_ extendedType: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) + rethrows + { self.extendedType = extendedType self.members = try content() } diff --git a/Sources/SyntaxKit/Declarations/Init.swift b/Sources/SyntaxKit/Declarations/Init.swift index b9b760d..8a64eab 100644 --- a/Sources/SyntaxKit/Declarations/Init.swift +++ b/Sources/SyntaxKit/Declarations/Init.swift @@ -45,9 +45,11 @@ public struct Init: CodeBlock, ExprCodeBlock, LiteralValue { /// - Parameters: /// - type: The type to initialize. /// - params: A ``ParameterExpBuilder`` that provides the parameters for the initializer. - public init(_ type: String, @ParameterExpBuilderResult _ params: () -> [ParameterExp]) { + public init(_ type: String, @ParameterExpBuilderResult _ params: () throws -> [ParameterExp]) + rethrows + { self.type = type - self.parameters = params() + self.parameters = try params() } public var exprSyntax: ExprSyntax { diff --git a/Sources/SyntaxKit/Declarations/Protocol.swift b/Sources/SyntaxKit/Declarations/Protocol.swift index b8befc9..83d2db3 100644 --- a/Sources/SyntaxKit/Declarations/Protocol.swift +++ b/Sources/SyntaxKit/Declarations/Protocol.swift @@ -40,7 +40,8 @@ public struct Protocol: CodeBlock { /// - Parameters: /// - name: The name of the protocol. /// - content: A ``CodeBlockBuilder`` that provides the body of the protocol. - public init(_ name: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + public init(_ name: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows + { self.name = name self.members = try content() } diff --git a/Sources/SyntaxKit/Declarations/Struct.swift b/Sources/SyntaxKit/Declarations/Struct.swift index d2f8f4d..11cc276 100644 --- a/Sources/SyntaxKit/Declarations/Struct.swift +++ b/Sources/SyntaxKit/Declarations/Struct.swift @@ -42,7 +42,8 @@ public struct Struct: CodeBlock { /// - Parameters: /// - name: The name of the struct. /// - content: A ``CodeBlockBuilder`` that provides the body of the struct. - public init(_ name: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows { + public init(_ name: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) rethrows + { self.name = name self.members = try content() } diff --git a/Sources/SyntaxKit/Expressions/Call.swift b/Sources/SyntaxKit/Expressions/Call.swift index c1de9b3..8154697 100644 --- a/Sources/SyntaxKit/Expressions/Call.swift +++ b/Sources/SyntaxKit/Expressions/Call.swift @@ -47,7 +47,9 @@ public struct Call: CodeBlock { /// - Parameters: /// - functionName: The name of the function to call. /// - params: A ``ParameterExpBuilder`` that provides the parameters for the function call. - public init(_ functionName: String, @ParameterExpBuilderResult _ params: () throws -> [ParameterExp]) rethrows { + public init( + _ functionName: String, @ParameterExpBuilderResult _ params: () throws -> [ParameterExp] + ) rethrows { self.functionName = functionName self.parameters = try params() } @@ -71,7 +73,7 @@ public struct Call: CodeBlock { public var syntax: SyntaxProtocol { let function = TokenSyntax.identifier(functionName) let args = LabeledExprListSyntax( - parameters.enumerated().map { index, param in + parameters.enumerated().compactMap { index, param in let expr = param.syntax if let labeled = expr as? LabeledExprSyntax { var element = labeled @@ -82,8 +84,7 @@ public struct Call: CodeBlock { ) } return element - } else { - let unlabeled = expr as! ExprSyntax + } else if let unlabeled = expr as? ExprSyntax { return LabeledExprSyntax( label: nil, colon: nil, @@ -92,6 +93,8 @@ public struct Call: CodeBlock { ? .commaToken(trailingTrivia: .space) : nil ) + } else { + return nil } } ) diff --git a/Sources/SyntaxKit/Expressions/FunctionCallExp.swift b/Sources/SyntaxKit/Expressions/FunctionCallExp.swift index 59632d7..5462959 100644 --- a/Sources/SyntaxKit/Expressions/FunctionCallExp.swift +++ b/Sources/SyntaxKit/Expressions/FunctionCallExp.swift @@ -94,7 +94,7 @@ public struct FunctionCallExp: CodeBlock { } let labeledArgs = LabeledExprListSyntax( - args.enumerated().map { index, param in + args.enumerated().compactMap { index, param in let expr = param.syntax if let labeled = expr as? LabeledExprSyntax { var element = labeled @@ -105,9 +105,9 @@ public struct FunctionCallExp: CodeBlock { ) } return element - } else { + } else if let unlabeled = expr as? ExprSyntax { // ParameterExp.syntax is guaranteed to return either LabeledExprSyntax or ExprSyntax - let unlabeled = expr as! ExprSyntax + return LabeledExprSyntax( label: nil, colon: nil, @@ -116,6 +116,8 @@ public struct FunctionCallExp: CodeBlock { ? .commaToken(trailingTrivia: .space) : nil ) + } else { + return nil } } ) diff --git a/Sources/SyntaxKit/Expressions/Infix.swift b/Sources/SyntaxKit/Expressions/Infix.swift index 26b839d..261cf1f 100644 --- a/Sources/SyntaxKit/Expressions/Infix.swift +++ b/Sources/SyntaxKit/Expressions/Infix.swift @@ -68,7 +68,9 @@ public struct Infix: CodeBlock, ExprCodeBlock { /// Exactly two operands must be supplied – a left-hand side and a right-hand side. /// Each operand must conform to ExprCodeBlock. @available(*, deprecated, message: "Use separate lhs and rhs parameters for compile-time safety") - public init(_ operation: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) throws { + public init(_ operation: String, @CodeBlockBuilderResult _ content: () throws -> [CodeBlock]) + throws + { self.operation = operation let operands = try content() diff --git a/Sources/SyntaxKit/Parameters/ParameterExp.swift b/Sources/SyntaxKit/Parameters/ParameterExp.swift index 51ba71f..2c09f87 100644 --- a/Sources/SyntaxKit/Parameters/ParameterExp.swift +++ b/Sources/SyntaxKit/Parameters/ParameterExp.swift @@ -47,7 +47,10 @@ public struct ParameterExp: CodeBlock { /// - Parameters: /// - name: The name of the parameter. /// - value: The string value of the parameter. - @available(*, deprecated, message: "Use ParameterExp(name:value:) with Literal.string() or VariableExp() instead") + @available( + *, deprecated, + message: "Use ParameterExp(name:value:) with Literal.string() or VariableExp() instead" + ) public init(name: String, value: String) { self.name = name self.value = VariableExp(value) @@ -60,7 +63,10 @@ public struct ParameterExp: CodeBlock { } /// Convenience initializer for unlabeled parameter with a String value. - @available(*, deprecated, message: "Use ParameterExp(unlabeled:) with Literal.string() or VariableExp() instead") + @available( + *, deprecated, + message: "Use ParameterExp(unlabeled:) with Literal.string() or VariableExp() instead" + ) public init(unlabeled value: String) { self.name = "" self.value = VariableExp(value) diff --git a/Sources/SyntaxKit/Utilities/Case.swift b/Sources/SyntaxKit/Utilities/Case.swift index 509b549..fc05929 100644 --- a/Sources/SyntaxKit/Utilities/Case.swift +++ b/Sources/SyntaxKit/Utilities/Case.swift @@ -41,7 +41,9 @@ public struct Case: CodeBlock { /// - Parameters: /// - patterns: The patterns for the case. /// - content: A ``CodeBlockBuilder`` that provides the body of the case. - public init(_ patterns: PatternConvertible..., @CodeBlockBuilderResult content: () throws -> [CodeBlock]) rethrows { + public init( + _ patterns: PatternConvertible..., @CodeBlockBuilderResult content: () throws -> [CodeBlock] + ) rethrows { self.patterns = patterns self.body = try content() self.isEnumCase = false diff --git a/Sources/SyntaxKit/Variables/Variable+LiteralInitializers.swift b/Sources/SyntaxKit/Variables/Variable+LiteralInitializers.swift index 1b4fc38..86449e5 100644 --- a/Sources/SyntaxKit/Variables/Variable+LiteralInitializers.swift +++ b/Sources/SyntaxKit/Variables/Variable+LiteralInitializers.swift @@ -173,14 +173,14 @@ extension Variable { public init( _ kind: VariableKind, name: String, - @CodeBlockBuilderResult value: () -> [CodeBlock], + @CodeBlockBuilderResult value: () throws -> [CodeBlock], explicitType: Bool? = nil - ) { + ) rethrows { self.init( kind: kind, name: name, type: "", - defaultValue: value().first ?? EmptyCodeBlock(), + defaultValue: try value().first ?? EmptyCodeBlock(), explicitType: explicitType ?? false ) } diff --git a/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift b/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift index f77e564..e764535 100644 --- a/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift +++ b/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift @@ -83,7 +83,7 @@ import Testing try Call("VendingMachineError.insufficientFunds") { ParameterExp( name: "coinsNeeded", - value: try! Infix("-") { + value: try Infix("-") { VariableExp("item").property("price") VariableExp("coinsDeposited") } @@ -91,12 +91,12 @@ import Testing } ) } - try! Infix("-=") { + try Infix("-=") { VariableExp("coinsDeposited") VariableExp("item").property("price") } Variable(.var, name: "newItem", equals: Literal.ref("item")) - try! Infix("-=") { + try Infix("-=") { VariableExp("newItem").property("count") Literal.integer(1) } diff --git a/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift b/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift index d64aafc..e6c60a0 100644 --- a/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift +++ b/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift @@ -273,12 +273,12 @@ import Testing Line("MARK: - Labeled Statements") Line("Using labeled statements with break") } - Variable(.var, name: "board") { - Init("[Int]") { + try Variable(.var, name: "board") { + try Init("[Int]") { ParameterExp(name: "repeating", value: Literal.integer(0)) ParameterExp( name: "count", - value: try! Infix("+") { + value: try Infix("+") { VariableExp("finalSquare") Literal.integer(1) } @@ -298,23 +298,23 @@ import Testing Variable(.var, name: "square", equals: Literal.integer(0)) Variable(.var, name: "diceRoll", equals: Literal.integer(0)) - While( - try! Infix("!=") { + try While( + try Infix("!=") { VariableExp("square") VariableExp("finalSquare") } ) { PlusAssign("diceRoll", 1) - If { - try! Infix("==") { + try If { + try Infix("==") { VariableExp("diceRoll") Literal.integer(7) } } then: { Assignment("diceRoll", 1) } - Switch( - try! Infix("+") { + try Switch( + try Infix("+") { VariableExp("square") VariableExp("diceRoll") } @@ -322,21 +322,21 @@ import Testing SwitchCase("finalSquare") { Break() } - SwitchCase { + try SwitchCase { SwitchLet("newSquare") - try! Infix(">") { + try Infix(">") { VariableExp("newSquare") VariableExp("finalSquare") } } content: { Continue() } - Default { - try! Infix("+=") { + try Default { + try Infix("+=") { VariableExp("square") VariableExp("diceRoll") } - try! Infix("+=") { + try Infix("+=") { VariableExp("square") VariableExp("board[square]") } @@ -499,7 +499,7 @@ import Testing @Test("Conditionals example generates correct syntax") internal func testConditionalsExample() throws { - let ifStatement = try If { + _ = try If { try Infix(">") { VariableExp("temperature") Literal.integer(30) diff --git a/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift b/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift index 772a279..4099966 100644 --- a/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift +++ b/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift @@ -6,11 +6,11 @@ import Testing @Test("If / else-if / else chain generates correct syntax") internal func testIfElseChain() throws { // Arrange: build the DSL example using the updated APIs - let conditional = Group { + let conditional = try Group { Variable(.let, name: "score", type: "Int", equals: "85") - If { - try! Infix(">=") { + try If { + try Infix(">=") { VariableExp("score") Literal.integer(90) } @@ -19,8 +19,8 @@ import Testing ParameterExp(name: "", value: "\"Excellent!\"") } } else: { - If { - try! Infix(">=") { + try If { + try Infix(">=") { VariableExp("score") Literal.integer(80) } From 6c66c293da4205db87df4449f6bbe61e890c6f04 Mon Sep 17 00:00:00 2001 From: leogdion Date: Tue, 24 Jun 2025 10:07:54 -0400 Subject: [PATCH 05/17] Fixing more linting issues --- Sources/SyntaxKit/ControlFlow/Guard.swift | 2 +- .../SyntaxKit/Core/ExprCodeBlockBuilder.swift | 22 +++++++-------- .../Core/PatternConvertibleBuilder.swift | 28 +++++++++---------- .../Unit/EdgeCases/EdgeCaseTests.swift | 6 ++-- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Sources/SyntaxKit/ControlFlow/Guard.swift b/Sources/SyntaxKit/ControlFlow/Guard.swift index d9c632b..9f88344 100644 --- a/Sources/SyntaxKit/ControlFlow/Guard.swift +++ b/Sources/SyntaxKit/ControlFlow/Guard.swift @@ -39,7 +39,7 @@ public struct Guard: CodeBlock { /// - condition: A ``CodeBlockBuilder`` that provides the condition expression. /// - else: A ``CodeBlockBuilder`` that provides the body when the condition is false. public init( - @CodeBlockBuilderResult _ condition: () throws -> [CodeBlock], + @CodeBlockBuilderResult _ condition: () throws -> [CodeBlock] = {[]}, @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] ) rethrows { let allConditions = try condition() diff --git a/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift b/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift index 58c0b88..fa11ac6 100644 --- a/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift +++ b/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift @@ -32,7 +32,7 @@ import SwiftSyntax /// A result builder that produces exactly one `ExprCodeBlock`. /// This ensures compile-time type safety for expression-based constructs. @resultBuilder -public struct ExprCodeBlockBuilder { +public enum ExprCodeBlockBuilder { public static func buildBlock(_ expression: any ExprCodeBlock) -> any ExprCodeBlock { expression } @@ -48,14 +48,14 @@ public struct ExprCodeBlockBuilder { public static func buildEither(second: any ExprCodeBlock) -> any ExprCodeBlock { second } - - public static func buildOptional(_ expression: (any ExprCodeBlock)?) -> any ExprCodeBlock { - // This should never be called in practice since we require exactly one expression - fatalError("ExprCodeBlockBuilder requires exactly one expression") - } - - public static func buildArray(_ expressions: [any ExprCodeBlock]) -> any ExprCodeBlock { - // This should never be called in practice since we require exactly one expression - fatalError("ExprCodeBlockBuilder requires exactly one expression") - } +// +// public static func buildOptional(_ expression: (any ExprCodeBlock)?) -> any ExprCodeBlock { +// // This should never be called in practice since we require exactly one expression +// fatalError("ExprCodeBlockBuilder requires exactly one expression") +// } +// +// public static func buildArray(_ expressions: [any ExprCodeBlock]) -> any ExprCodeBlock { +// // This should never be called in practice since we require exactly one expression +// fatalError("ExprCodeBlockBuilder requires exactly one expression") +// } } diff --git a/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift b/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift index fbc6db9..a21f0b6 100644 --- a/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift +++ b/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift @@ -32,7 +32,7 @@ import SwiftSyntax /// A result builder that produces exactly one `CodeBlock & PatternConvertible`. /// This ensures compile-time type safety for pattern-based constructs. @resultBuilder -public struct PatternConvertibleBuilder { +public enum PatternConvertibleBuilder { public static func buildBlock(_ pattern: any CodeBlock & PatternConvertible) -> any CodeBlock & PatternConvertible { @@ -57,17 +57,17 @@ public struct PatternConvertibleBuilder { second } - public static func buildOptional(_ pattern: (any CodeBlock & PatternConvertible)?) - -> any CodeBlock & PatternConvertible - { - // This should never be called in practice since we require exactly one pattern - fatalError("PatternConvertibleBuilder requires exactly one pattern") - } - - public static func buildArray(_ patterns: [any CodeBlock & PatternConvertible]) -> any CodeBlock - & PatternConvertible - { - // This should never be called in practice since we require exactly one pattern - fatalError("PatternConvertibleBuilder requires exactly one pattern") - } +// public static func buildOptional(_ pattern: (any CodeBlock & PatternConvertible)?) +// -> any CodeBlock & PatternConvertible +// { +// // This should never be called in practice since we require exactly one pattern +// fatalError("PatternConvertibleBuilder requires exactly one pattern") +// } +// +// public static func buildArray(_ patterns: [any CodeBlock & PatternConvertible]) -> any CodeBlock +// & PatternConvertible +// { +// // This should never be called in practice since we require exactly one pattern +// fatalError("PatternConvertibleBuilder requires exactly one pattern") +// } } diff --git a/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift b/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift index 6ce693e..80858f4 100644 --- a/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift +++ b/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift @@ -18,7 +18,7 @@ internal struct EdgeCaseTests { } catch let error as Infix.InfixError { // Verify it's the correct error type switch error { - case .wrongOperandCount(let expected, let got): + case let .wrongOperandCount( expected, got): #expect(expected == 2) #expect(got == 1) case .nonExprCodeBlockOperand: @@ -45,8 +45,8 @@ internal struct EdgeCaseTests { } @Test("Guard with no conditions uses true as default") - internal func testGuardWithNoConditionsUsesTrueAsDefault() { - let guardStatement = Guard({}) { + internal func testGuardWithNoConditionsUsesTrueAsDefault() throws { + let guardStatement = try Guard { Return { Literal.string("executed") } From 3ef3dd5c3d7843e90716608b2d7ecd5f657c9db9 Mon Sep 17 00:00:00 2001 From: leogdion Date: Tue, 24 Jun 2025 10:15:12 -0400 Subject: [PATCH 06/17] Fixing more linting issues --- Sources/SyntaxKit/ControlFlow/Guard.swift | 2 +- Sources/SyntaxKit/ControlFlow/If.swift | 8 ++++ .../SyntaxKit/Core/ExprCodeBlockBuilder.swift | 32 +++++++++++----- .../Core/PatternConvertibleBuilder.swift | 38 ++++++++++++------- .../Unit/EdgeCases/EdgeCaseTests.swift | 6 +-- 5 files changed, 59 insertions(+), 27 deletions(-) diff --git a/Sources/SyntaxKit/ControlFlow/Guard.swift b/Sources/SyntaxKit/ControlFlow/Guard.swift index 9f88344..acb421a 100644 --- a/Sources/SyntaxKit/ControlFlow/Guard.swift +++ b/Sources/SyntaxKit/ControlFlow/Guard.swift @@ -39,7 +39,7 @@ public struct Guard: CodeBlock { /// - condition: A ``CodeBlockBuilder`` that provides the condition expression. /// - else: A ``CodeBlockBuilder`` that provides the body when the condition is false. public init( - @CodeBlockBuilderResult _ condition: () throws -> [CodeBlock] = {[]}, + @CodeBlockBuilderResult _ condition: () throws -> [CodeBlock] = { [] }, @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] ) rethrows { let allConditions = try condition() diff --git a/Sources/SyntaxKit/ControlFlow/If.swift b/Sources/SyntaxKit/ControlFlow/If.swift index f2c93ee..d3b8e49 100644 --- a/Sources/SyntaxKit/ControlFlow/If.swift +++ b/Sources/SyntaxKit/ControlFlow/If.swift @@ -35,6 +35,14 @@ public struct If: CodeBlock { internal let body: [CodeBlock] internal let elseBody: [CodeBlock]? + /// Convenience initializer that keeps the previous API: pass the condition directly. + public init( + @CodeBlockBuilderResult then: () throws -> [CodeBlock], + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] = { [] } + ) rethrows { + try self.init({ Literal.boolean(true) }, then: then, else: elseBody) + } + /// Creates an `if` statement with optional `else`. /// - Parameters: /// - condition: A single `CodeBlock` produced by the builder that describes the `if` condition. diff --git a/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift b/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift index fa11ac6..ae515a1 100644 --- a/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift +++ b/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift @@ -33,29 +33,41 @@ import SwiftSyntax /// This ensures compile-time type safety for expression-based constructs. @resultBuilder public enum ExprCodeBlockBuilder { + /// Builds a single expression code block from the provided expression. + /// - Parameter expression: The expression code block to build. + /// - Returns: The expression code block unchanged. public static func buildBlock(_ expression: any ExprCodeBlock) -> any ExprCodeBlock { expression } + /// Builds an expression code block from a single expression. + /// - Parameter expression: The expression code block to build. + /// - Returns: The expression code block unchanged. public static func buildExpression(_ expression: any ExprCodeBlock) -> any ExprCodeBlock { expression } + /// Builds an expression code block from the first branch of a conditional. + /// - Parameter first: The expression code block from the first branch. + /// - Returns: The expression code block from the first branch. public static func buildEither(first: any ExprCodeBlock) -> any ExprCodeBlock { first } + /// Builds an expression code block from the second branch of a conditional. + /// - Parameter second: The expression code block from the second branch. + /// - Returns: The expression code block from the second branch. public static func buildEither(second: any ExprCodeBlock) -> any ExprCodeBlock { second } -// -// public static func buildOptional(_ expression: (any ExprCodeBlock)?) -> any ExprCodeBlock { -// // This should never be called in practice since we require exactly one expression -// fatalError("ExprCodeBlockBuilder requires exactly one expression") -// } -// -// public static func buildArray(_ expressions: [any ExprCodeBlock]) -> any ExprCodeBlock { -// // This should never be called in practice since we require exactly one expression -// fatalError("ExprCodeBlockBuilder requires exactly one expression") -// } + // + // public static func buildOptional(_ expression: (any ExprCodeBlock)?) -> any ExprCodeBlock { + // // This should never be called in practice since we require exactly one expression + // fatalError("ExprCodeBlockBuilder requires exactly one expression") + // } + // + // public static func buildArray(_ expressions: [any ExprCodeBlock]) -> any ExprCodeBlock { + // // This should never be called in practice since we require exactly one expression + // fatalError("ExprCodeBlockBuilder requires exactly one expression") + // } } diff --git a/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift b/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift index a21f0b6..c90bae1 100644 --- a/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift +++ b/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift @@ -33,41 +33,53 @@ import SwiftSyntax /// This ensures compile-time type safety for pattern-based constructs. @resultBuilder public enum PatternConvertibleBuilder { + /// Builds a single pattern convertible code block from the provided pattern. + /// - Parameter pattern: The pattern convertible code block to build. + /// - Returns: The pattern convertible code block unchanged. public static func buildBlock(_ pattern: any CodeBlock & PatternConvertible) -> any CodeBlock & PatternConvertible { pattern } + /// Builds a pattern convertible code block from a single pattern. + /// - Parameter pattern: The pattern convertible code block to build. + /// - Returns: The pattern convertible code block unchanged. public static func buildExpression(_ pattern: any CodeBlock & PatternConvertible) -> any CodeBlock & PatternConvertible { pattern } + /// Builds a pattern convertible code block from the first branch of a conditional. + /// - Parameter first: The pattern convertible code block from the first branch. + /// - Returns: The pattern convertible code block from the first branch. public static func buildEither(first: any CodeBlock & PatternConvertible) -> any CodeBlock & PatternConvertible { first } + /// Builds a pattern convertible code block from the second branch of a conditional. + /// - Parameter second: The pattern convertible code block from the second branch. + /// - Returns: The pattern convertible code block from the second branch. public static func buildEither(second: any CodeBlock & PatternConvertible) -> any CodeBlock & PatternConvertible { second } -// public static func buildOptional(_ pattern: (any CodeBlock & PatternConvertible)?) -// -> any CodeBlock & PatternConvertible -// { -// // This should never be called in practice since we require exactly one pattern -// fatalError("PatternConvertibleBuilder requires exactly one pattern") -// } -// -// public static func buildArray(_ patterns: [any CodeBlock & PatternConvertible]) -> any CodeBlock -// & PatternConvertible -// { -// // This should never be called in practice since we require exactly one pattern -// fatalError("PatternConvertibleBuilder requires exactly one pattern") -// } + // public static func buildOptional(_ pattern: (any CodeBlock & PatternConvertible)?) + // -> any CodeBlock & PatternConvertible + // { + // // This should never be called in practice since we require exactly one pattern + // fatalError("PatternConvertibleBuilder requires exactly one pattern") + // } + // + // public static func buildArray(_ patterns: [any CodeBlock & PatternConvertible]) -> any CodeBlock + // & PatternConvertible + // { + // // This should never be called in practice since we require exactly one pattern + // fatalError("PatternConvertibleBuilder requires exactly one pattern") + // } } diff --git a/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift b/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift index 80858f4..2646933 100644 --- a/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift +++ b/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift @@ -18,7 +18,7 @@ internal struct EdgeCaseTests { } catch let error as Infix.InfixError { // Verify it's the correct error type switch error { - case let .wrongOperandCount( expected, got): + case let .wrongOperandCount(expected, got): #expect(expected == 2) #expect(got == 1) case .nonExprCodeBlockOperand: @@ -32,8 +32,8 @@ internal struct EdgeCaseTests { // MARK: - Default Condition Tests @Test("If with no conditions uses true as default") - internal func testIfWithNoConditionsUsesTrueAsDefault() { - let ifStatement = If({}) { + internal func testIfWithNoConditionsUsesTrueAsDefault() throws { + let ifStatement = try If { Return { Literal.string("executed") } From f4d01591fb1bb9c0596e05471c25a4c5d52a28e7 Mon Sep 17 00:00:00 2001 From: leogdion Date: Tue, 24 Jun 2025 10:32:05 -0400 Subject: [PATCH 07/17] fixing While --- Sources/SyntaxKit/ControlFlow/While.swift | 85 +++++++++++++---------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/Sources/SyntaxKit/ControlFlow/While.swift b/Sources/SyntaxKit/ControlFlow/While.swift index bdea0de..ee9f34d 100644 --- a/Sources/SyntaxKit/ControlFlow/While.swift +++ b/Sources/SyntaxKit/ControlFlow/While.swift @@ -31,41 +31,56 @@ import SwiftSyntax /// A `while` loop statement. public struct While: CodeBlock { + public enum Kind { + case `while` + case repeatWhile + } + private let condition: any ExprCodeBlock private let body: [CodeBlock] - private let isRepeatWhile: Bool + private let kind: Kind /// Creates a `while` loop statement with an expression condition. /// - Parameters: /// - condition: The condition expression that conforms to ExprCodeBlock. + /// - kind: The kind of loop (default is `.while`). /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. public init( _ condition: any ExprCodeBlock, + kind: Kind = .while, @CodeBlockBuilderResult then: () throws -> [CodeBlock] ) rethrows { self.condition = condition self.body = try then() - self.isRepeatWhile = false + self.kind = kind } /// Creates a `while` loop statement with a builder closure for the condition. /// - Parameters: /// - condition: A `CodeBlockBuilder` that produces exactly one condition expression. + /// - kind: The kind of loop (default is `.while`). /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. public init( + kind: Kind = .while, @ExprCodeBlockBuilder _ condition: () throws -> any ExprCodeBlock, @CodeBlockBuilderResult then: () throws -> [CodeBlock] ) rethrows { self.condition = try condition() self.body = try then() - self.isRepeatWhile = false + self.kind = kind } /// Creates a `while` loop. /// - Parameters: /// - condition: A ``CodeBlockBuilder`` that provides the condition expression. + /// - kind: The kind of loop (default is `.while`). /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. + @available( + *, deprecated, + message: "Use While(kind:condition:) with ExprCodeBlockBuilder instead for better type safety" + ) public init( + kind: Kind = .while, @CodeBlockBuilderResult _ condition: () throws -> [CodeBlock], @CodeBlockBuilderResult then: () throws -> [CodeBlock] ) rethrows { @@ -75,38 +90,26 @@ public struct While: CodeBlock { } self.condition = firstCondition self.body = try then() - self.isRepeatWhile = false + self.kind = kind } /// Creates a `while` loop with a string condition. /// - Parameters: /// - condition: The condition as a string. + /// - kind: The kind of loop (default is `.while`). /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. + @available( + *, deprecated, + message: "Use While(VariableExp(condition), kind:then:) instead for better type safety" + ) public init( _ condition: String, + kind: Kind = .while, @CodeBlockBuilderResult then: () throws -> [CodeBlock] ) rethrows { self.condition = VariableExp(condition) self.body = try then() - self.isRepeatWhile = false - } - - /// Creates a `repeat-while` loop. - /// - Parameters: - /// - condition: A ``CodeBlockBuilder`` that provides the condition expression. - /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. - public init( - repeat: Void, - @CodeBlockBuilderResult _ condition: () throws -> [CodeBlock], - @CodeBlockBuilderResult then: () throws -> [CodeBlock] - ) rethrows { - let conditionBlocks = try condition() - guard let firstCondition = conditionBlocks.first as? any ExprCodeBlock else { - fatalError("While condition must conform to ExprCodeBlock protocol") - } - self.condition = firstCondition - self.body = try then() - self.isRepeatWhile = true + self.kind = kind } public var syntax: SyntaxProtocol { @@ -130,18 +133,30 @@ public struct While: CodeBlock { rightBrace: .rightBraceToken(leadingTrivia: .newline) ) - return StmtSyntax( - WhileStmtSyntax( - whileKeyword: .keyword(.while, trailingTrivia: .space), - conditions: ConditionElementListSyntax( - [ - ConditionElementSyntax( - condition: .expression(conditionExpr) - ) - ] - ), - body: bodyBlock + switch kind { + case .repeatWhile: + return StmtSyntax( + RepeatWhileStmtSyntax( + repeatKeyword: .keyword(.repeat, trailingTrivia: .space), + body: bodyBlock, + whileKeyword: .keyword(.while, trailingTrivia: .space), + condition: conditionExpr + ) ) - ) + case .while: + return StmtSyntax( + WhileStmtSyntax( + whileKeyword: .keyword(.while, trailingTrivia: .space), + conditions: ConditionElementListSyntax( + [ + ConditionElementSyntax( + condition: .expression(conditionExpr) + ) + ] + ), + body: bodyBlock + ) + ) + } } } From 26bbb1ba0e69c4de9b7fa8e9e49c656af7b698fc Mon Sep 17 00:00:00 2001 From: leogdion Date: Tue, 24 Jun 2025 10:34:10 -0400 Subject: [PATCH 08/17] fixing While --- Sources/SyntaxKit/ControlFlow/While.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Sources/SyntaxKit/ControlFlow/While.swift b/Sources/SyntaxKit/ControlFlow/While.swift index ee9f34d..4bd03b6 100644 --- a/Sources/SyntaxKit/ControlFlow/While.swift +++ b/Sources/SyntaxKit/ControlFlow/While.swift @@ -85,9 +85,7 @@ public struct While: CodeBlock { @CodeBlockBuilderResult then: () throws -> [CodeBlock] ) rethrows { let conditionBlocks = try condition() - guard let firstCondition = conditionBlocks.first as? any ExprCodeBlock else { - fatalError("While condition must conform to ExprCodeBlock protocol") - } + let firstCondition = conditionBlocks.first as? any ExprCodeBlock ?? Literal.boolean(true) self.condition = firstCondition self.body = try then() self.kind = kind From 16575a0a6a070f19d7b467663aa6e7faab52e042 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Tue, 24 Jun 2025 14:03:26 -0400 Subject: [PATCH 09/17] remove Variable fatalError --- .../Collections/Array+LiteralValue.swift | 7 +++- .../SyntaxKit/Collections/ArrayLiteral.swift | 7 +++- .../Collections/Dictionary+LiteralValue.swift | 7 +++- .../Collections/DictionaryExpr.swift | 7 +++- .../Collections/DictionaryLiteral.swift | 7 +++- .../SyntaxKit/Collections/TupleLiteral.swift | 7 +++- Sources/SyntaxKit/Declarations/Init.swift | 7 +++- Sources/SyntaxKit/Expressions/Literal.swift | 7 +++- .../SyntaxKit/Utilities/CodeBlockable.swift | 34 +++++++++++++++++++ .../Variable+LiteralInitializers.swift | 34 ++----------------- 10 files changed, 84 insertions(+), 40 deletions(-) create mode 100644 Sources/SyntaxKit/Utilities/CodeBlockable.swift diff --git a/Sources/SyntaxKit/Collections/Array+LiteralValue.swift b/Sources/SyntaxKit/Collections/Array+LiteralValue.swift index 9b75416..64ad042 100644 --- a/Sources/SyntaxKit/Collections/Array+LiteralValue.swift +++ b/Sources/SyntaxKit/Collections/Array+LiteralValue.swift @@ -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 diff --git a/Sources/SyntaxKit/Collections/ArrayLiteral.swift b/Sources/SyntaxKit/Collections/ArrayLiteral.swift index 4117858..f419dae 100644 --- a/Sources/SyntaxKit/Collections/ArrayLiteral.swift +++ b/Sources/SyntaxKit/Collections/ArrayLiteral.swift @@ -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. @@ -39,6 +39,11 @@ public struct ArrayLiteral: LiteralValue { self.elements = elements } + /// The code block representation of this array literal. + public var codeBlock: CodeBlock { + Literal.array(elements) + } + /// The Swift type name for this array. public var typeName: String { if elements.isEmpty { diff --git a/Sources/SyntaxKit/Collections/Dictionary+LiteralValue.swift b/Sources/SyntaxKit/Collections/Dictionary+LiteralValue.swift index 183492f..48082e1 100644 --- a/Sources/SyntaxKit/Collections/Dictionary+LiteralValue.swift +++ b/Sources/SyntaxKit/Collections/Dictionary+LiteralValue.swift @@ -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 diff --git a/Sources/SyntaxKit/Collections/DictionaryExpr.swift b/Sources/SyntaxKit/Collections/DictionaryExpr.swift index 0006f55..2b751b7 100644 --- a/Sources/SyntaxKit/Collections/DictionaryExpr.swift +++ b/Sources/SyntaxKit/Collections/DictionaryExpr.swift @@ -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. @@ -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 { diff --git a/Sources/SyntaxKit/Collections/DictionaryLiteral.swift b/Sources/SyntaxKit/Collections/DictionaryLiteral.swift index 03dc8a8..2843ec2 100644 --- a/Sources/SyntaxKit/Collections/DictionaryLiteral.swift +++ b/Sources/SyntaxKit/Collections/DictionaryLiteral.swift @@ -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. @@ -39,6 +39,11 @@ public struct DictionaryLiteral: LiteralValue { self.elements = elements } + /// The code block representation of this dictionary literal. + public var codeBlock: CodeBlock { + Literal.dictionary(elements) + } + /// The Swift type name for this dictionary. public var typeName: String { if elements.isEmpty { diff --git a/Sources/SyntaxKit/Collections/TupleLiteral.swift b/Sources/SyntaxKit/Collections/TupleLiteral.swift index 4bbba1a..0c3efaf 100644 --- a/Sources/SyntaxKit/Collections/TupleLiteral.swift +++ b/Sources/SyntaxKit/Collections/TupleLiteral.swift @@ -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. @@ -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 diff --git a/Sources/SyntaxKit/Declarations/Init.swift b/Sources/SyntaxKit/Declarations/Init.swift index 8a64eab..400d38a 100644 --- a/Sources/SyntaxKit/Declarations/Init.swift +++ b/Sources/SyntaxKit/Declarations/Init.swift @@ -30,7 +30,7 @@ import SwiftSyntax /// An initializer expression. -public struct Init: CodeBlock, ExprCodeBlock, LiteralValue { +public struct Init: CodeBlock, ExprCodeBlock, LiteralValue, CodeBlockable { private let type: String private let parameters: [ParameterExp] @@ -52,6 +52,11 @@ public struct Init: CodeBlock, ExprCodeBlock, LiteralValue { self.parameters = try params() } + /// The code block representation of this initializer expression. + public var codeBlock: CodeBlock { + self + } + public var exprSyntax: ExprSyntax { var args = parameters var trailingClosure: ClosureExprSyntax? diff --git a/Sources/SyntaxKit/Expressions/Literal.swift b/Sources/SyntaxKit/Expressions/Literal.swift index 0a2dbee..96ae3bc 100644 --- a/Sources/SyntaxKit/Expressions/Literal.swift +++ b/Sources/SyntaxKit/Expressions/Literal.swift @@ -30,7 +30,7 @@ import SwiftSyntax /// A literal value. -public enum Literal: CodeBlock { +public enum Literal: CodeBlock, CodeBlockable { /// A string literal. case string(String) /// A floating-point literal. @@ -50,6 +50,11 @@ public enum Literal: CodeBlock { /// A dictionary literal. case dictionary([(Literal, Literal)]) + /// The code block representation of this literal. + public var codeBlock: CodeBlock { + self + } + /// The Swift type name for this literal. public var typeName: String { switch self { diff --git a/Sources/SyntaxKit/Utilities/CodeBlockable.swift b/Sources/SyntaxKit/Utilities/CodeBlockable.swift new file mode 100644 index 0000000..c9a406b --- /dev/null +++ b/Sources/SyntaxKit/Utilities/CodeBlockable.swift @@ -0,0 +1,34 @@ +// +// CodeBlockable.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +/// Can export a `CodeBlock`. +public protocol CodeBlockable { + /// Returns a `CodeBlock`. + var codeBlock: CodeBlock { get } +} diff --git a/Sources/SyntaxKit/Variables/Variable+LiteralInitializers.swift b/Sources/SyntaxKit/Variables/Variable+LiteralInitializers.swift index 86449e5..3b4224e 100644 --- a/Sources/SyntaxKit/Variables/Variable+LiteralInitializers.swift +++ b/Sources/SyntaxKit/Variables/Variable+LiteralInitializers.swift @@ -32,53 +32,23 @@ import Foundation // MARK: - Variable Literal Initializers extension Variable { - // swiftlint:disable cyclomatic_complexity /// Creates a `let` or `var` declaration with a literal value. /// - Parameters: /// - kind: The kind of variable, either ``VariableKind/let`` or ``VariableKind/var``. /// - name: The name of the variable. /// - equals: A literal value that conforms to ``LiteralValue``. - public init( + public init( _ kind: VariableKind, name: String, equals value: T ) { - let defaultValue: CodeBlock - if let literal = value as? Literal { - defaultValue = literal - } else if let tuple = value as? TupleLiteral { - defaultValue = Literal.tuple(tuple.elements) - } else if let array = value as? ArrayLiteral { - defaultValue = Literal.array(array.elements) - } else if let dict = value as? DictionaryLiteral { - defaultValue = Literal.dictionary(dict.elements) - } else if let array = value as? [String] { - defaultValue = Literal.array(array.map { .string($0) }) - } else if let dict = value as? [Int: String] { - defaultValue = Literal.dictionary(dict.map { (.integer($0.key), .string($0.value)) }) - } else if let dictExpr = value as? DictionaryExpr { - defaultValue = dictExpr - } else if let initExpr = value as? Init { - defaultValue = initExpr - } else if let codeBlock = value as? CodeBlock { - defaultValue = codeBlock - } else { - // For any other LiteralValue type that doesn't conform to CodeBlock, - // create a fallback or throw an error - fatalError( - "Variable: Unsupported LiteralValue type that doesn't conform to CodeBlock: \(T.self)" - ) - } - self.init( kind: kind, name: name, type: value.typeName, - defaultValue: defaultValue, + defaultValue: value.codeBlock, explicitType: false ) } - // swiftlint:enable cyclomatic_complexity - /// Creates a `let` or `var` declaration with a string literal value. /// - Parameters: /// - kind: The kind of variable, either ``VariableKind/let`` or ``VariableKind/var``. From 991619d522792f121ef448ec982e5646f2fc391b Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Tue, 24 Jun 2025 14:04:45 -0400 Subject: [PATCH 10/17] remove comments --- Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift | 10 ---------- .../SyntaxKit/Core/PatternConvertibleBuilder.swift | 14 -------------- 2 files changed, 24 deletions(-) diff --git a/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift b/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift index ae515a1..317a0d8 100644 --- a/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift +++ b/Sources/SyntaxKit/Core/ExprCodeBlockBuilder.swift @@ -60,14 +60,4 @@ public enum ExprCodeBlockBuilder { public static func buildEither(second: any ExprCodeBlock) -> any ExprCodeBlock { second } - // - // public static func buildOptional(_ expression: (any ExprCodeBlock)?) -> any ExprCodeBlock { - // // This should never be called in practice since we require exactly one expression - // fatalError("ExprCodeBlockBuilder requires exactly one expression") - // } - // - // public static func buildArray(_ expressions: [any ExprCodeBlock]) -> any ExprCodeBlock { - // // This should never be called in practice since we require exactly one expression - // fatalError("ExprCodeBlockBuilder requires exactly one expression") - // } } diff --git a/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift b/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift index c90bae1..283a065 100644 --- a/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift +++ b/Sources/SyntaxKit/Core/PatternConvertibleBuilder.swift @@ -68,18 +68,4 @@ public enum PatternConvertibleBuilder { { second } - - // public static func buildOptional(_ pattern: (any CodeBlock & PatternConvertible)?) - // -> any CodeBlock & PatternConvertible - // { - // // This should never be called in practice since we require exactly one pattern - // fatalError("PatternConvertibleBuilder requires exactly one pattern") - // } - // - // public static func buildArray(_ patterns: [any CodeBlock & PatternConvertible]) -> any CodeBlock - // & PatternConvertible - // { - // // This should never be called in practice since we require exactly one pattern - // fatalError("PatternConvertibleBuilder requires exactly one pattern") - // } } From 2f17b762286ade3c53cba73feefc984a360da2e3 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Tue, 24 Jun 2025 14:39:55 -0400 Subject: [PATCH 11/17] removing last fatalErrors --- Sources/SyntaxKit/CodeBlocks/CodeBlock+ExprSyntax.swift | 8 +++++--- Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift | 8 ++++---- Sources/SyntaxKit/Collections/DictionaryValue.swift | 6 ++++-- Sources/SyntaxKit/Utilities/Group.swift | 4 +++- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Sources/SyntaxKit/CodeBlocks/CodeBlock+ExprSyntax.swift b/Sources/SyntaxKit/CodeBlocks/CodeBlock+ExprSyntax.swift index b8b9f30..babd03b 100644 --- a/Sources/SyntaxKit/CodeBlocks/CodeBlock+ExprSyntax.swift +++ b/Sources/SyntaxKit/CodeBlocks/CodeBlock+ExprSyntax.swift @@ -35,8 +35,8 @@ extension CodeBlock { /// 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 @@ -46,6 +46,8 @@ extension CodeBlock { 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 + return ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(""))) } } diff --git a/Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift b/Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift index adfd488..c502832 100644 --- a/Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift +++ b/Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift @@ -45,10 +45,10 @@ extension CodeBlock { 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 + let emptyExpr = ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(""))) + item = .expr(emptyExpr) } statements = CodeBlockItemListSyntax([ CodeBlockItemSyntax(item: item, trailingTrivia: .newline) diff --git a/Sources/SyntaxKit/Collections/DictionaryValue.swift b/Sources/SyntaxKit/Collections/DictionaryValue.swift index b8bb8e2..670149d 100644 --- a/Sources/SyntaxKit/Collections/DictionaryValue.swift +++ b/Sources/SyntaxKit/Collections/DictionaryValue.swift @@ -48,7 +48,7 @@ extension CodeBlock where Self: DictionaryValue { /// 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 @@ -58,7 +58,9 @@ extension CodeBlock where Self: DictionaryValue { 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 + return ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(""))) } } diff --git a/Sources/SyntaxKit/Utilities/Group.swift b/Sources/SyntaxKit/Utilities/Group.swift index 36921ac..e6be3eb 100644 --- a/Sources/SyntaxKit/Utilities/Group.swift +++ b/Sources/SyntaxKit/Utilities/Group.swift @@ -53,7 +53,9 @@ public struct Group: CodeBlock { } else if let expr = block.syntax.as(ExprSyntax.self) { item = .expr(expr) } else { - fatalError("Unsupported syntax type in group: \(type(of: block.syntax)) from \(block)") + // Skip unsupported syntax types instead of crashing + // This allows the group to continue processing other valid blocks + return [] } return [CodeBlockItemSyntax(item: item, trailingTrivia: .newline)] } From 375d633970095b15fa7fe90328230f82fd90e7db Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Tue, 24 Jun 2025 17:14:30 -0400 Subject: [PATCH 12/17] fixing rethrows --- Sources/SyntaxKit/ControlFlow/For.swift | 22 +++- Sources/SyntaxKit/ControlFlow/Guard.swift | 15 ++- Sources/SyntaxKit/ControlFlow/If.swift | 73 ++++++++++- Sources/SyntaxKit/Expressions/Closure.swift | 127 +++++++++++++++++++- 4 files changed, 226 insertions(+), 11 deletions(-) diff --git a/Sources/SyntaxKit/ControlFlow/For.swift b/Sources/SyntaxKit/ControlFlow/For.swift index c8e3892..acdd313 100644 --- a/Sources/SyntaxKit/ControlFlow/For.swift +++ b/Sources/SyntaxKit/ControlFlow/For.swift @@ -40,12 +40,12 @@ 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: () throws -> [CodeBlock] = { [] }, + @CodeBlockBuilderResult where whereClause: () throws -> [CodeBlock], @CodeBlockBuilderResult then: () throws -> [CodeBlock] ) rethrows { self.pattern = pattern @@ -55,6 +55,24 @@ public struct For: CodeBlock { self.body = try then() } + /// Creates a `for-in` loop statement without a where clause. + /// - Parameters: + /// - pattern: A `CodeBlock` that also conforms to `PatternConvertible` for the loop variable(s). + /// - sequence: A `CodeBlock` that produces the sequence to iterate over. + /// - then: A ``CodeBlockBuilder`` that provides the body of the loop. + public init( + _ pattern: any CodeBlock & PatternConvertible, + in sequence: CodeBlock, + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { + try self.init( + pattern, in: sequence, + where: { + // Return empty array using the result builder + [] + }, then: then) + } + public var syntax: SyntaxProtocol { // Build the pattern using the PatternConvertible protocol let patternSyntax = pattern.patternSyntax diff --git a/Sources/SyntaxKit/ControlFlow/Guard.swift b/Sources/SyntaxKit/ControlFlow/Guard.swift index acb421a..077fc0f 100644 --- a/Sources/SyntaxKit/ControlFlow/Guard.swift +++ b/Sources/SyntaxKit/ControlFlow/Guard.swift @@ -39,7 +39,7 @@ public struct Guard: CodeBlock { /// - condition: A ``CodeBlockBuilder`` that provides the condition expression. /// - else: A ``CodeBlockBuilder`` that provides the body when the condition is false. public init( - @CodeBlockBuilderResult _ condition: () throws -> [CodeBlock] = { [] }, + @CodeBlockBuilderResult _ condition: () throws -> [CodeBlock], @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] ) rethrows { let allConditions = try condition() @@ -52,6 +52,19 @@ public struct Guard: CodeBlock { self.elseBody = try elseBody() } + /// Creates a `guard` statement without a condition (uses true as default). + /// - Parameters: + /// - else: A ``CodeBlockBuilder`` that provides the body when the condition is false. + public init( + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] + ) rethrows { + try self.init( + { + // Return empty array using the result builder + [] + }, else: elseBody) + } + /// Creates a `guard` statement with a string condition. /// - Parameters: /// - condition: The condition as a string. diff --git a/Sources/SyntaxKit/ControlFlow/If.swift b/Sources/SyntaxKit/ControlFlow/If.swift index d3b8e49..76484c8 100644 --- a/Sources/SyntaxKit/ControlFlow/If.swift +++ b/Sources/SyntaxKit/ControlFlow/If.swift @@ -35,14 +35,37 @@ public struct If: CodeBlock { internal let body: [CodeBlock] internal let elseBody: [CodeBlock]? + /// Convenience initializer that keeps the previous API: pass the condition directly. + public init( + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { + try self.init({ Literal.boolean(true) }, then: then) + } + /// Convenience initializer that keeps the previous API: pass the condition directly. public init( @CodeBlockBuilderResult then: () throws -> [CodeBlock], - @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] = { [] } + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] ) rethrows { try self.init({ Literal.boolean(true) }, then: then, else: elseBody) } + /// Creates an `if` statement with optional `else`. + /// - Parameters: + /// - condition: A single `CodeBlock` produced by the builder that describes the `if` condition. + /// - then: Builder that produces the body for the `if` branch. + public init( + @CodeBlockBuilderResult _ condition: () -> [CodeBlock], + @CodeBlockBuilderResult then: () -> [CodeBlock] + ) { + self.init( + condition, then: then, + else: { + // Return empty array using the result builder + [] + }) + } + /// Creates an `if` statement with optional `else`. /// - Parameters: /// - condition: A single `CodeBlock` produced by the builder that describes the `if` condition. @@ -53,7 +76,7 @@ public struct If: CodeBlock { public init( @CodeBlockBuilderResult _ condition: () -> [CodeBlock], @CodeBlockBuilderResult then: () -> [CodeBlock], - @CodeBlockBuilderResult else elseBody: () -> [CodeBlock] = { [] } + @CodeBlockBuilderResult else elseBody: () -> [CodeBlock] ) { let allConditions = condition() if allConditions.isEmpty { @@ -67,15 +90,39 @@ public struct If: CodeBlock { self.elseBody = generatedElse.isEmpty ? nil : generatedElse } + /// Convenience initializer that keeps the previous API: pass the condition directly. + public init( + _ condition: CodeBlock, + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { + try self.init({ condition }, then: then) + } + /// Convenience initializer that keeps the previous API: pass the condition directly. public init( _ condition: CodeBlock, @CodeBlockBuilderResult then: () throws -> [CodeBlock], - @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] = { [] } + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] ) rethrows { try self.init({ condition }, then: then, else: elseBody) } + /// Creates an `if` statement. + /// - Parameters: + /// - condition: A ``CodeBlockBuilder`` that provides the condition expression. + /// - then: A ``CodeBlockBuilder`` that provides the body when the condition is true. + public init( + @CodeBlockBuilderResult _ condition: () throws -> [CodeBlock], + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { + try self.init( + condition, then: then, + else: { + // Return empty array using the result builder + [] + }) + } + /// Creates an `if` statement. /// - Parameters: /// - condition: A ``CodeBlockBuilder`` that provides the condition expression. @@ -84,7 +131,7 @@ public struct If: CodeBlock { public init( @CodeBlockBuilderResult _ condition: () throws -> [CodeBlock], @CodeBlockBuilderResult then: () throws -> [CodeBlock], - @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] = { [] } + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] ) rethrows { let allConditions = try condition() if allConditions.isEmpty { @@ -98,6 +145,22 @@ public struct If: CodeBlock { self.elseBody = generatedElse.isEmpty ? nil : generatedElse } + /// Creates an `if` statement with a string condition. + /// - Parameters: + /// - condition: The condition as a string. + /// - then: A ``CodeBlockBuilder`` that provides the body when the condition is true. + public init( + _ condition: String, + @CodeBlockBuilderResult then: () throws -> [CodeBlock] + ) rethrows { + try self.init( + condition, then: then, + else: { + // Return empty array using the result builder + [] + }) + } + /// Creates an `if` statement with a string condition. /// - Parameters: /// - condition: The condition as a string. @@ -106,7 +169,7 @@ public struct If: CodeBlock { public init( _ condition: String, @CodeBlockBuilderResult then: () throws -> [CodeBlock], - @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] = { [] } + @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] ) rethrows { self.conditions = [VariableExp(condition)] self.body = try then() diff --git a/Sources/SyntaxKit/Expressions/Closure.swift b/Sources/SyntaxKit/Expressions/Closure.swift index 080a191..8da81d8 100644 --- a/Sources/SyntaxKit/Expressions/Closure.swift +++ b/Sources/SyntaxKit/Expressions/Closure.swift @@ -41,10 +41,16 @@ public struct Closure: CodeBlock { !parameters.isEmpty || returnType != nil || !capture.isEmpty || !attributes.isEmpty } + /// Creates a closure with all parameters. + /// - Parameters: + /// - capture: A ``ParameterExpBuilder`` that provides the capture list. + /// - parameters: A ``ClosureParameterBuilder`` that provides the closure parameters. + /// - returnType: The return type of the closure. + /// - body: A ``CodeBlockBuilder`` that provides the body of the closure. public init( - @ParameterExpBuilderResult capture: () -> [ParameterExp] = { [] }, - @ClosureParameterBuilderResult parameters: () -> [ClosureParameter] = { [] }, - returns returnType: String? = nil, + @ParameterExpBuilderResult capture: () -> [ParameterExp], + @ClosureParameterBuilderResult parameters: () -> [ClosureParameter], + returns returnType: String?, @CodeBlockBuilderResult body: () throws -> [CodeBlock] ) rethrows { self.capture = capture() @@ -53,6 +59,121 @@ public struct Closure: CodeBlock { self.body = try body() } + /// Creates a closure without a return type. + /// - Parameters: + /// - capture: A ``ParameterExpBuilder`` that provides the capture list. + /// - parameters: A ``ClosureParameterBuilder`` that provides the closure parameters. + /// - body: A ``CodeBlockBuilder`` that provides the body of the closure. + public init( + @ParameterExpBuilderResult capture: () -> [ParameterExp], + @ClosureParameterBuilderResult parameters: () -> [ClosureParameter], + @CodeBlockBuilderResult body: () throws -> [CodeBlock] + ) rethrows { + try self.init(capture: capture, parameters: parameters, returns: nil, body: body) + } + + /// Creates a closure without parameters. + /// - Parameters: + /// - capture: A ``ParameterExpBuilder`` that provides the capture list. + /// - returnType: The return type of the closure. + /// - body: A ``CodeBlockBuilder`` that provides the body of the closure. + public init( + @ParameterExpBuilderResult capture: () -> [ParameterExp], + returns returnType: String?, + @CodeBlockBuilderResult body: () throws -> [CodeBlock] + ) rethrows { + try self.init( + capture: capture, + parameters: { + // Return empty array using the result builder + [] + }, returns: returnType, body: body) + } + + /// Creates a closure without parameters and return type. + /// - Parameters: + /// - capture: A ``ParameterExpBuilder`` that provides the capture list. + /// - body: A ``CodeBlockBuilder`` that provides the body of the closure. + public init( + @ParameterExpBuilderResult capture: () -> [ParameterExp], + @CodeBlockBuilderResult body: () throws -> [CodeBlock] + ) rethrows { + try self.init( + capture: capture, + parameters: { + // Return empty array using the result builder + [] + }, returns: nil, body: body) + } + + /// Creates a closure without capture list and return type. + /// - Parameters: + /// - parameters: A ``ClosureParameterBuilder`` that provides the closure parameters. + /// - body: A ``CodeBlockBuilder`` that provides the body of the closure. + public init( + @ClosureParameterBuilderResult parameters: () -> [ClosureParameter], + @CodeBlockBuilderResult body: () throws -> [CodeBlock] + ) rethrows { + try self.init( + capture: { + // Return empty array using the result builder + [] + }, parameters: parameters, returns: nil, body: body) + } + + /// Creates a closure without capture list. + /// - Parameters: + /// - parameters: A ``ClosureParameterBuilder`` that provides the closure parameters. + /// - returnType: The return type of the closure. + /// - body: A ``CodeBlockBuilder`` that provides the body of the closure. + public init( + @ClosureParameterBuilderResult parameters: () -> [ClosureParameter], + returns returnType: String?, + @CodeBlockBuilderResult body: () throws -> [CodeBlock] + ) rethrows { + try self.init( + capture: { + // Return empty array using the result builder + [] + }, parameters: parameters, returns: returnType, body: body) + } + + /// Creates a simple closure with only a body. + /// - Parameters: + /// - body: A ``CodeBlockBuilder`` that provides the body of the closure. + public init( + @CodeBlockBuilderResult body: () throws -> [CodeBlock] + ) rethrows { + try self.init( + capture: { + // Return empty array using the result builder + [] + }, + parameters: { + // Return empty array using the result builder + [] + }, returns: nil, body: body) + } + + /// Creates a closure without capture list and parameters. + /// - Parameters: + /// - returnType: The return type of the closure. + /// - body: A ``CodeBlockBuilder`` that provides the body of the closure. + public init( + returns returnType: String?, + @CodeBlockBuilderResult body: () throws -> [CodeBlock] + ) rethrows { + try self.init( + capture: { + // Return empty array using the result builder + [] + }, + parameters: { + // Return empty array using the result builder + [] + }, returns: returnType, body: body) + } + public func attribute(_ attribute: String, arguments: [String] = []) -> Self { var copy = self copy.attributes.append(AttributeInfo(name: attribute, arguments: arguments)) From 8a1426387e5655bf9445c74f980f1545749c30ae Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Tue, 24 Jun 2025 17:28:59 -0400 Subject: [PATCH 13/17] fixing tries --- Tests/SyntaxKitTests/Integration/BlackjackTests.swift | 6 +++--- .../Integration/ConcurrencyExampleTests.swift | 2 +- .../Integration/ConditionalsExampleTests.swift | 8 ++++---- .../SyntaxKitTests/Integration/ForLoopsExampleTests.swift | 6 +++--- .../Unit/ControlFlow/ConditionalsTests.swift | 4 ++-- Tests/SyntaxKitTests/Unit/ControlFlow/ForLoopTests.swift | 2 +- Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift | 4 ++-- .../Unit/Integration/FrameworkCompatibilityTests.swift | 4 ++-- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Tests/SyntaxKitTests/Integration/BlackjackTests.swift b/Tests/SyntaxKitTests/Integration/BlackjackTests.swift index d29f63a..740f218 100644 --- a/Tests/SyntaxKitTests/Integration/BlackjackTests.swift +++ b/Tests/SyntaxKitTests/Integration/BlackjackTests.swift @@ -70,7 +70,7 @@ internal struct BlackjackTests { } @Test internal func testFullBlackjackCardExample() throws { - let syntax = try Struct("BlackjackCard") { + let syntax = Struct("BlackjackCard") { Enum("Suit") { EnumCase("spades").equals("♠") EnumCase("hearts").equals("♡") @@ -130,10 +130,10 @@ internal struct BlackjackTests { Variable(.let, name: "rank", type: "Rank") Variable(.let, name: "suit", type: "Suit") - try ComputedProperty("description", type: "String") { + ComputedProperty("description", type: "String") { VariableDecl(.var, name: "output", equals: "suit is \\(suit.rawValue),") PlusAssign("output", " value is \\(rank.values.first)") - try If( + If( Let("second", "rank.values.second"), then: { PlusAssign("output", " or \\(second)") diff --git a/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift b/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift index e764535..b312c83 100644 --- a/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift +++ b/Tests/SyntaxKitTests/Integration/ConcurrencyExampleTests.swift @@ -101,7 +101,7 @@ import Testing Literal.integer(1) } Assignment("inventory[name]", .ref("newItem")) - try Call("print") { + Call("print") { ParameterExp(unlabeled: Literal.string("Dispensing \\(name)")) } } diff --git a/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift b/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift index e6c60a0..272556d 100644 --- a/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift +++ b/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift @@ -38,7 +38,7 @@ import Testing Literal.integer(90) } } then: { - try Call("print") { + Call("print") { ParameterExp(unlabeled: "\"Excellent!\"") } } else: { @@ -48,7 +48,7 @@ import Testing Literal.integer(80) } } then: { - try Call("print") { + Call("print") { ParameterExp(unlabeled: "\"Good job!\"") } } else: { @@ -58,11 +58,11 @@ import Testing Literal.integer(70) } } then: { - try Call("print") { + Call("print") { ParameterExp(unlabeled: "\"Passing\"") } } else: { - try Call("print") { + Call("print") { ParameterExp(unlabeled: "\"Needs improvement\"") } } diff --git a/Tests/SyntaxKitTests/Integration/ForLoopsExampleTests.swift b/Tests/SyntaxKitTests/Integration/ForLoopsExampleTests.swift index 1093ccc..c510e0d 100644 --- a/Tests/SyntaxKitTests/Integration/ForLoopsExampleTests.swift +++ b/Tests/SyntaxKitTests/Integration/ForLoopsExampleTests.swift @@ -24,7 +24,7 @@ import Testing Line("Simple for-in loop over an array") } - try For( + For( VariableExp("name"), in: VariableExp("names"), then: { @@ -42,7 +42,7 @@ import Testing Line("MARK: - For-in with Enumerated") Line("For-in loop with enumerated() to get index and value") } - try For( + For( Tuple.patternCodeBlock([ VariableExp("index"), VariableExp("name"), @@ -117,7 +117,7 @@ import Testing ]) ) - try For( + For( Tuple.patternCodeBlock([ VariableExp("name"), VariableExp("score"), diff --git a/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift b/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift index 4099966..a442096 100644 --- a/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift +++ b/Tests/SyntaxKitTests/Unit/ControlFlow/ConditionalsTests.swift @@ -56,7 +56,7 @@ import Testing Literal.integer(90) } } then: { - try Call("print") { + Call("print") { ParameterExp(unlabeled: "Excellent!") } } else: { @@ -66,7 +66,7 @@ import Testing Literal.integer(80) } } then: { - try Call("print") { + Call("print") { ParameterExp(unlabeled: "Good!") } } diff --git a/Tests/SyntaxKitTests/Unit/ControlFlow/ForLoopTests.swift b/Tests/SyntaxKitTests/Unit/ControlFlow/ForLoopTests.swift index d40919d..1cd25fd 100644 --- a/Tests/SyntaxKitTests/Unit/ControlFlow/ForLoopTests.swift +++ b/Tests/SyntaxKitTests/Unit/ControlFlow/ForLoopTests.swift @@ -6,7 +6,7 @@ import Testing internal final class ForLoopTests { @Test internal func testSimpleForInLoop() throws { - let forLoop = try For( + let forLoop = For( VariableExp("item"), in: VariableExp("items"), then: { diff --git a/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift b/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift index 2646933..8e570df 100644 --- a/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift +++ b/Tests/SyntaxKitTests/Unit/EdgeCases/EdgeCaseTests.swift @@ -33,7 +33,7 @@ internal struct EdgeCaseTests { @Test("If with no conditions uses true as default") internal func testIfWithNoConditionsUsesTrueAsDefault() throws { - let ifStatement = try If { + let ifStatement = If { Return { Literal.string("executed") } @@ -46,7 +46,7 @@ internal struct EdgeCaseTests { @Test("Guard with no conditions uses true as default") internal func testGuardWithNoConditionsUsesTrueAsDefault() throws { - let guardStatement = try Guard { + let guardStatement = Guard { Return { Literal.string("executed") } diff --git a/Tests/SyntaxKitTests/Unit/Integration/FrameworkCompatibilityTests.swift b/Tests/SyntaxKitTests/Unit/Integration/FrameworkCompatibilityTests.swift index fe34cd5..6061f0d 100644 --- a/Tests/SyntaxKitTests/Unit/Integration/FrameworkCompatibilityTests.swift +++ b/Tests/SyntaxKitTests/Unit/Integration/FrameworkCompatibilityTests.swift @@ -29,7 +29,7 @@ internal struct FrameworkCompatibilityTests { // MARK: - Error Handling Compatibility Tests - @Test internal func testThrowingTestCompatibility() throws { + @Test internal func testThrowingTestCompatibility() { // Ensure throws declaration works properly with @Test let function = Function("throwingFunction", returns: "String") { Parameter(name: "input", type: "String") @@ -39,7 +39,7 @@ internal struct FrameworkCompatibilityTests { } } - let generated = try function.syntax.description + let generated = function.syntax.description #expect(generated.contains("func throwingFunction")) } From 8fdf9598e5283f834b08b76a5a8ecf43298cbcac Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Tue, 24 Jun 2025 17:57:26 -0400 Subject: [PATCH 14/17] fixed tests and throws --- Sources/SyntaxKit/ControlFlow/For.swift | 10 ++-- Sources/SyntaxKit/ControlFlow/Guard.swift | 7 +-- Sources/SyntaxKit/ControlFlow/If.swift | 27 ++++----- Sources/SyntaxKit/Expressions/Closure.swift | 60 +++++++++---------- .../ConditionalsExampleTests.swift | 24 ++++---- 5 files changed, 60 insertions(+), 68 deletions(-) diff --git a/Sources/SyntaxKit/ControlFlow/For.swift b/Sources/SyntaxKit/ControlFlow/For.swift index acdd313..0bcc4b5 100644 --- a/Sources/SyntaxKit/ControlFlow/For.swift +++ b/Sources/SyntaxKit/ControlFlow/For.swift @@ -66,11 +66,11 @@ public struct For: CodeBlock { @CodeBlockBuilderResult then: () throws -> [CodeBlock] ) rethrows { try self.init( - pattern, in: sequence, - where: { - // Return empty array using the result builder - [] - }, then: then) + pattern, + in: sequence, + where: [CodeBlock].init, + then: then + ) } public var syntax: SyntaxProtocol { diff --git a/Sources/SyntaxKit/ControlFlow/Guard.swift b/Sources/SyntaxKit/ControlFlow/Guard.swift index 077fc0f..c64cbe9 100644 --- a/Sources/SyntaxKit/ControlFlow/Guard.swift +++ b/Sources/SyntaxKit/ControlFlow/Guard.swift @@ -59,10 +59,9 @@ public struct Guard: CodeBlock { @CodeBlockBuilderResult else elseBody: () throws -> [CodeBlock] ) rethrows { try self.init( - { - // Return empty array using the result builder - [] - }, else: elseBody) + [CodeBlock].init, + else: elseBody + ) } /// Creates a `guard` statement with a string condition. diff --git a/Sources/SyntaxKit/ControlFlow/If.swift b/Sources/SyntaxKit/ControlFlow/If.swift index 76484c8..d5d87a8 100644 --- a/Sources/SyntaxKit/ControlFlow/If.swift +++ b/Sources/SyntaxKit/ControlFlow/If.swift @@ -59,11 +59,10 @@ public struct If: CodeBlock { @CodeBlockBuilderResult then: () -> [CodeBlock] ) { self.init( - condition, then: then, - else: { - // Return empty array using the result builder - [] - }) + condition, + then: then, + else: [CodeBlock].init + ) } /// Creates an `if` statement with optional `else`. @@ -116,11 +115,10 @@ public struct If: CodeBlock { @CodeBlockBuilderResult then: () throws -> [CodeBlock] ) rethrows { try self.init( - condition, then: then, - else: { - // Return empty array using the result builder - [] - }) + condition, + then: then, + else: [CodeBlock].init + ) } /// Creates an `if` statement. @@ -154,11 +152,10 @@ public struct If: CodeBlock { @CodeBlockBuilderResult then: () throws -> [CodeBlock] ) rethrows { try self.init( - condition, then: then, - else: { - // Return empty array using the result builder - [] - }) + condition, + then: then, + else: [CodeBlock].init + ) } /// Creates an `if` statement with a string condition. diff --git a/Sources/SyntaxKit/Expressions/Closure.swift b/Sources/SyntaxKit/Expressions/Closure.swift index 8da81d8..e5b3ee8 100644 --- a/Sources/SyntaxKit/Expressions/Closure.swift +++ b/Sources/SyntaxKit/Expressions/Closure.swift @@ -84,10 +84,10 @@ public struct Closure: CodeBlock { ) rethrows { try self.init( capture: capture, - parameters: { - // Return empty array using the result builder - [] - }, returns: returnType, body: body) + parameters: [ClosureParameter].init, + returns: returnType, + body: body + ) } /// Creates a closure without parameters and return type. @@ -100,10 +100,10 @@ public struct Closure: CodeBlock { ) rethrows { try self.init( capture: capture, - parameters: { - // Return empty array using the result builder - [] - }, returns: nil, body: body) + parameters: [ClosureParameter].init, + returns: nil, + body: body + ) } /// Creates a closure without capture list and return type. @@ -115,10 +115,11 @@ public struct Closure: CodeBlock { @CodeBlockBuilderResult body: () throws -> [CodeBlock] ) rethrows { try self.init( - capture: { - // Return empty array using the result builder - [] - }, parameters: parameters, returns: nil, body: body) + capture: [ParameterExp].init, + parameters: parameters, + returns: nil, + body: body + ) } /// Creates a closure without capture list. @@ -132,10 +133,11 @@ public struct Closure: CodeBlock { @CodeBlockBuilderResult body: () throws -> [CodeBlock] ) rethrows { try self.init( - capture: { - // Return empty array using the result builder - [] - }, parameters: parameters, returns: returnType, body: body) + capture: [ParameterExp].init, + parameters: parameters, + returns: returnType, + body: body + ) } /// Creates a simple closure with only a body. @@ -145,14 +147,11 @@ public struct Closure: CodeBlock { @CodeBlockBuilderResult body: () throws -> [CodeBlock] ) rethrows { try self.init( - capture: { - // Return empty array using the result builder - [] - }, - parameters: { - // Return empty array using the result builder - [] - }, returns: nil, body: body) + capture: [ParameterExp].init, + parameters: [ClosureParameter].init, + returns: nil, + body: body + ) } /// Creates a closure without capture list and parameters. @@ -164,14 +163,11 @@ public struct Closure: CodeBlock { @CodeBlockBuilderResult body: () throws -> [CodeBlock] ) rethrows { try self.init( - capture: { - // Return empty array using the result builder - [] - }, - parameters: { - // Return empty array using the result builder - [] - }, returns: returnType, body: body) + capture: [ParameterExp].init, + parameters: [ClosureParameter].init, + returns: returnType, + body: body + ) } public func attribute(_ attribute: String, arguments: [String] = []) -> Self { diff --git a/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift b/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift index 272556d..47d0821 100644 --- a/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift +++ b/Tests/SyntaxKitTests/Integration/ConditionalsExampleTests.swift @@ -21,7 +21,7 @@ import Testing Literal.integer(30) } } then: { - try Call("print") { + Call("print") { ParameterExp(unlabeled: "\"It's hot outside!\"") } } @@ -76,10 +76,10 @@ import Testing Line("Using if let for optional binding") } - try If( + If( Let("actualNumber", "Int(possibleNumber)"), then: { - try Call("print") { + Call("print") { ParameterExp( name: "", value: @@ -88,7 +88,7 @@ import Testing } }, else: { - try Call("print") { + Call("print") { ParameterExp( name: "", value: "\"The string \"\\(possibleNumber)\" could not be converted to an integer\"" @@ -106,17 +106,17 @@ import Testing Variable(.let, name: "possibleAge", type: "Int?", equals: Literal.integer(30)) .withExplicitType() - try If { + If { Let("name", "possibleName") Let("age", "possibleAge") } then: { - try Call("print") { + Call("print") { ParameterExp(name: "", value: "\"\\(name) is \\(age) years old\"") } } // MARK: - Guard Statements - Function( + try Function( "greet", { Parameter(name: "person", type: "[String: String]") @@ -505,7 +505,7 @@ import Testing Literal.integer(30) } } then: { - try Call("print") { + Call("print") { ParameterExp(unlabeled: "It's hot!") } } else: { @@ -515,7 +515,7 @@ import Testing Literal.integer(90) } } then: { - try Call("print") { + Call("print") { ParameterExp(unlabeled: "Excellent!") } } else: { @@ -525,7 +525,7 @@ import Testing Literal.integer(80) } } then: { - try Call("print") { + Call("print") { ParameterExp(unlabeled: "Good!") } } else: { @@ -535,11 +535,11 @@ import Testing Literal.integer(70) } } then: { - try Call("print") { + Call("print") { ParameterExp(unlabeled: "Pass") } } else: { - try Call("print") { + Call("print") { ParameterExp(unlabeled: "Fail") } } From cb929e86f8cff298ae2d7c2fce71d3e7bee1d538 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Tue, 24 Jun 2025 18:15:22 -0400 Subject: [PATCH 15/17] adding warnings and errors to the codebase --- Sources/SyntaxKit/CodeBlocks/CodeBlock+ExprSyntax.swift | 3 +++ Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift | 3 +++ .../SyntaxKit/CodeBlocks/CodeBlockItemSyntax.Item.swift | 3 +++ Sources/SyntaxKit/CodeBlocks/CommentedCodeBlock.swift | 1 + Sources/SyntaxKit/Collections/DictionaryValue.swift | 3 +++ Sources/SyntaxKit/Collections/TupleAssignment.swift | 3 +++ Sources/SyntaxKit/ControlFlow/If+ElseBody.swift | 2 ++ Sources/SyntaxKit/Declarations/Import.swift | 3 +++ Sources/SyntaxKit/Declarations/Struct.swift | 3 +++ Sources/SyntaxKit/Expressions/Closure+Capture.swift | 9 +++++++++ Sources/SyntaxKit/Expressions/OptionalChainingExp.swift | 3 +++ Sources/SyntaxKit/Expressions/Return.swift | 3 +++ Sources/SyntaxKit/Utilities/Group.swift | 3 +++ Sources/SyntaxKit/Variables/ComputedProperty.swift | 3 +++ Sources/SyntaxKit/Variables/Variable+Modifiers.swift | 3 +++ 15 files changed, 48 insertions(+) diff --git a/Sources/SyntaxKit/CodeBlocks/CodeBlock+ExprSyntax.swift b/Sources/SyntaxKit/CodeBlocks/CodeBlock+ExprSyntax.swift index babd03b..679e112 100644 --- a/Sources/SyntaxKit/CodeBlocks/CodeBlock+ExprSyntax.swift +++ b/Sources/SyntaxKit/CodeBlocks/CodeBlock+ExprSyntax.swift @@ -48,6 +48,9 @@ extension CodeBlock { // 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(""))) } } diff --git a/Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift b/Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift index c502832..aaaa936 100644 --- a/Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift +++ b/Sources/SyntaxKit/CodeBlocks/CodeBlock+Generate.swift @@ -47,6 +47,9 @@ extension CodeBlock { } else { // 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" + ) let emptyExpr = ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(""))) item = .expr(emptyExpr) } diff --git a/Sources/SyntaxKit/CodeBlocks/CodeBlockItemSyntax.Item.swift b/Sources/SyntaxKit/CodeBlocks/CodeBlockItemSyntax.Item.swift index 17e72fe..1ed448a 100644 --- a/Sources/SyntaxKit/CodeBlocks/CodeBlockItemSyntax.Item.swift +++ b/Sources/SyntaxKit/CodeBlocks/CodeBlockItemSyntax.Item.swift @@ -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("_"))), diff --git a/Sources/SyntaxKit/CodeBlocks/CommentedCodeBlock.swift b/Sources/SyntaxKit/CodeBlocks/CommentedCodeBlock.swift index 4b3d653..1df7dcd 100644 --- a/Sources/SyntaxKit/CodeBlocks/CommentedCodeBlock.swift +++ b/Sources/SyntaxKit/CodeBlocks/CommentedCodeBlock.swift @@ -60,6 +60,7 @@ internal struct CommentedCodeBlock: CodeBlock { 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") return base.syntax } diff --git a/Sources/SyntaxKit/Collections/DictionaryValue.swift b/Sources/SyntaxKit/Collections/DictionaryValue.swift index 670149d..b1ac438 100644 --- a/Sources/SyntaxKit/Collections/DictionaryValue.swift +++ b/Sources/SyntaxKit/Collections/DictionaryValue.swift @@ -60,6 +60,9 @@ extension CodeBlock where Self: DictionaryValue { // 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(""))) } } diff --git a/Sources/SyntaxKit/Collections/TupleAssignment.swift b/Sources/SyntaxKit/Collections/TupleAssignment.swift index 501dd43..e765f21 100644 --- a/Sources/SyntaxKit/Collections/TupleAssignment.swift +++ b/Sources/SyntaxKit/Collections/TupleAssignment.swift @@ -85,6 +85,9 @@ public struct TupleAssignment: CodeBlock { guard let tuple = value as? Tuple, elements.count == tuple.elements.count else { // 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() } diff --git a/Sources/SyntaxKit/ControlFlow/If+ElseBody.swift b/Sources/SyntaxKit/ControlFlow/If+ElseBody.swift index 7abd91c..223cd5b 100644 --- a/Sources/SyntaxKit/ControlFlow/If+ElseBody.swift +++ b/Sources/SyntaxKit/ControlFlow/If+ElseBody.swift @@ -84,6 +84,8 @@ extension If { return IfExprSyntax.ElseBody(nestedIf) } else { // Fallback to empty code block + #warning( + "TODO: Review fallback to empty code block - consider if this should be an error instead") return IfExprSyntax.ElseBody( CodeBlockSyntax( leftBrace: .leftBraceToken(leadingTrivia: .space, trailingTrivia: .newline), diff --git a/Sources/SyntaxKit/Declarations/Import.swift b/Sources/SyntaxKit/Declarations/Import.swift index 191a037..71cab75 100644 --- a/Sources/SyntaxKit/Declarations/Import.swift +++ b/Sources/SyntaxKit/Declarations/Import.swift @@ -77,6 +77,9 @@ public struct Import: CodeBlock { keyword = .fileprivate default: keyword = .public // fallback + #error( + "TODO: Review fallback for unknown access modifier - consider if this should be an error instead" + ) } modifiers = DeclModifierListSyntax([ DeclModifierSyntax(name: .keyword(keyword, trailingTrivia: .space)) diff --git a/Sources/SyntaxKit/Declarations/Struct.swift b/Sources/SyntaxKit/Declarations/Struct.swift index 11cc276..2278d13 100644 --- a/Sources/SyntaxKit/Declarations/Struct.swift +++ b/Sources/SyntaxKit/Declarations/Struct.swift @@ -155,6 +155,9 @@ public struct Struct: CodeBlock { keyword = .fileprivate default: keyword = .public // fallback + #error( + "TODO: Review fallback for unknown access modifier - consider if this should be an error instead" + ) } modifiers = DeclModifierListSyntax([ DeclModifierSyntax(name: .keyword(keyword, trailingTrivia: .space)) diff --git a/Sources/SyntaxKit/Expressions/Closure+Capture.swift b/Sources/SyntaxKit/Expressions/Closure+Capture.swift index 87fd215..981337e 100644 --- a/Sources/SyntaxKit/Expressions/Closure+Capture.swift +++ b/Sources/SyntaxKit/Expressions/Closure+Capture.swift @@ -51,6 +51,9 @@ private struct CaptureInfo { keyword = .unowned default: keyword = .weak // fallback to weak + #error( + "TODO: Review fallback for unknown reference type - consider if this should be an error instead" + ) } self.specifier = ClosureCaptureSpecifierSyntax( @@ -61,6 +64,9 @@ private struct CaptureInfo { self.name = .identifier(varExp.name) } else { self.name = .identifier("self") // fallback + #warning( + "TODO: Review fallback for non-VariableExp capture expression - consider if this should be an error instead" + ) } } @@ -71,6 +77,9 @@ private struct CaptureInfo { self.name = .identifier(varExp.name) } else { self.name = .identifier("self") // fallback + #warning( + "TODO: Review fallback for non-VariableExp parameter value - consider if this should be an error instead" + ) } } } diff --git a/Sources/SyntaxKit/Expressions/OptionalChainingExp.swift b/Sources/SyntaxKit/Expressions/OptionalChainingExp.swift index 433ebc4..36925a2 100644 --- a/Sources/SyntaxKit/Expressions/OptionalChainingExp.swift +++ b/Sources/SyntaxKit/Expressions/OptionalChainingExp.swift @@ -46,6 +46,9 @@ public struct OptionalChainingExp: CodeBlock { baseExpr = exprSyntax } else { // Fallback to a default expression if conversion fails + #warning( + "TODO: Review fallback for failed expression conversion - consider if this should be an error instead" + ) baseExpr = ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(""))) } diff --git a/Sources/SyntaxKit/Expressions/Return.swift b/Sources/SyntaxKit/Expressions/Return.swift index cb27500..c07447f 100644 --- a/Sources/SyntaxKit/Expressions/Return.swift +++ b/Sources/SyntaxKit/Expressions/Return.swift @@ -55,6 +55,9 @@ public struct Return: CodeBlock { exprSyntax = syntax } else { // fallback: no valid expression + #warning( + "TODO: Review fallback for no valid expression - consider if this should be an error instead" + ) return ReturnStmtSyntax( returnKeyword: .keyword(.return, trailingTrivia: .space) ) diff --git a/Sources/SyntaxKit/Utilities/Group.swift b/Sources/SyntaxKit/Utilities/Group.swift index e6be3eb..8edfd10 100644 --- a/Sources/SyntaxKit/Utilities/Group.swift +++ b/Sources/SyntaxKit/Utilities/Group.swift @@ -55,6 +55,9 @@ public struct Group: CodeBlock { } else { // Skip unsupported syntax types instead of crashing // This allows the group to continue processing other valid blocks + #warning( + "TODO: Review fallback for unsupported syntax types - consider if this should be an error instead" + ) return [] } return [CodeBlockItemSyntax(item: item, trailingTrivia: .newline)] diff --git a/Sources/SyntaxKit/Variables/ComputedProperty.swift b/Sources/SyntaxKit/Variables/ComputedProperty.swift index 716fdb7..0f52b91 100644 --- a/Sources/SyntaxKit/Variables/ComputedProperty.swift +++ b/Sources/SyntaxKit/Variables/ComputedProperty.swift @@ -108,6 +108,9 @@ public struct ComputedProperty: CodeBlock { keyword = .fileprivate default: keyword = .public // fallback + #error( + "TODO: Review fallback for unknown access modifier - consider if this should be an error instead" + ) } modifiers = DeclModifierListSyntax([ DeclModifierSyntax(name: .keyword(keyword, trailingTrivia: .space)) diff --git a/Sources/SyntaxKit/Variables/Variable+Modifiers.swift b/Sources/SyntaxKit/Variables/Variable+Modifiers.swift index cdbf079..243ea35 100644 --- a/Sources/SyntaxKit/Variables/Variable+Modifiers.swift +++ b/Sources/SyntaxKit/Variables/Variable+Modifiers.swift @@ -73,6 +73,9 @@ extension Variable { keyword = .fileprivate default: keyword = .public // fallback + #error( + "TODO: Review fallback for unknown access modifier - consider if this should be an error instead" + ) } return DeclModifierSyntax(name: .keyword(keyword, trailingTrivia: .space)) From 19e27f77397eee868e43bfd3844ec20966030a61 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Tue, 24 Jun 2025 18:42:01 -0400 Subject: [PATCH 16/17] fixed enum fallbacks --- Sources/SyntaxKit/Core/AccessModifier.swift | 63 +++++++++++++++++++ .../SyntaxKit/Core/CaptureReferenceType.swift | 54 ++++++++++++++++ Sources/SyntaxKit/Declarations/Import.swift | 24 ++----- Sources/SyntaxKit/Declarations/Struct.swift | 24 ++----- .../Expressions/Closure+Capture.swift | 13 +--- .../SyntaxKit/Expressions/ReferenceExp.swift | 8 +-- .../Variables/ComputedProperty.swift | 24 ++----- .../Variables/Variable+Modifiers.swift | 21 +------ Sources/SyntaxKit/Variables/Variable.swift | 6 +- Sources/SyntaxKit/Variables/VariableExp.swift | 4 +- .../Integration/SwiftUIExampleTests.swift | 14 ++--- .../ClosureCaptureCoverageTests.swift | 24 +------ .../ReferenceExp/ReferenceExpBasicTests.swift | 58 ++++------------- .../ReferenceExpComplexTests.swift | 12 ++-- .../ReferenceExpFunctionTests.swift | 12 ++-- .../ReferenceExpLiteralTests.swift | 16 ++--- .../ReferenceExpPropertyTests.swift | 16 ++--- .../Unit/SwiftUIFeatureTests.swift | 14 ++--- .../Variables/VariableCoverageTests.swift | 20 +----- 19 files changed, 200 insertions(+), 227 deletions(-) create mode 100644 Sources/SyntaxKit/Core/AccessModifier.swift create mode 100644 Sources/SyntaxKit/Core/CaptureReferenceType.swift diff --git a/Sources/SyntaxKit/Core/AccessModifier.swift b/Sources/SyntaxKit/Core/AccessModifier.swift new file mode 100644 index 0000000..edd3631 --- /dev/null +++ b/Sources/SyntaxKit/Core/AccessModifier.swift @@ -0,0 +1,63 @@ +// +// AccessModifier.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import SwiftSyntax + +/// Represents Swift access modifiers. +public enum AccessModifier: CaseIterable { + case `public` + case `private` + case `internal` + case `fileprivate` + case `open` + + /// Returns the corresponding SwiftSyntax Keyword for this access modifier. + public var keyword: Keyword { + switch self { + case .public: + return .public + case .private: + return .private + case .internal: + return .internal + case .fileprivate: + return .fileprivate + case .open: + return .open + } + } +} + +extension Keyword { + /// Creates a Keyword from an AccessModifier. + /// - Parameter accessModifier: The access modifier to convert. + public init(_ accessModifier: AccessModifier) { + self = accessModifier.keyword + } +} diff --git a/Sources/SyntaxKit/Core/CaptureReferenceType.swift b/Sources/SyntaxKit/Core/CaptureReferenceType.swift new file mode 100644 index 0000000..582eba6 --- /dev/null +++ b/Sources/SyntaxKit/Core/CaptureReferenceType.swift @@ -0,0 +1,54 @@ +// +// CaptureReferenceType.swift +// SyntaxKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the “Software”), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +import SwiftSyntax + +/// Represents Swift capture reference types for closures. +public enum CaptureReferenceType: CaseIterable { + case weak + case unowned + + /// Returns the corresponding SwiftSyntax Keyword for this capture reference type. + public var keyword: Keyword { + switch self { + case .weak: + return .weak + case .unowned: + return .unowned + } + } +} + +extension Keyword { + /// Creates a Keyword from a CaptureReferenceType. + /// - Parameter captureReferenceType: The capture reference type to convert. + public init(_ captureReferenceType: CaptureReferenceType) { + self = captureReferenceType.keyword + } +} diff --git a/Sources/SyntaxKit/Declarations/Import.swift b/Sources/SyntaxKit/Declarations/Import.swift index 71cab75..37334d1 100644 --- a/Sources/SyntaxKit/Declarations/Import.swift +++ b/Sources/SyntaxKit/Declarations/Import.swift @@ -32,7 +32,7 @@ import SwiftSyntax /// A Swift `import` declaration. public struct Import: CodeBlock { private let moduleName: String - private var accessModifier: String? + private var accessModifier: AccessModifier? private var attributes: [AttributeInfo] = [] /// Creates an `import` declaration. @@ -42,9 +42,9 @@ public struct Import: CodeBlock { } /// Sets the access modifier for the import declaration. - /// - Parameter access: The access modifier (e.g., "public", "private"). + /// - Parameter access: The access modifier. /// - Returns: A copy of the import with the access modifier set. - public func access(_ access: String) -> Self { + public func access(_ access: AccessModifier) -> Self { var copy = self copy.accessModifier = access return copy @@ -65,24 +65,8 @@ public struct Import: CodeBlock { // Build access modifier var modifiers: DeclModifierListSyntax = [] if let access = accessModifier { - let keyword: Keyword - switch access { - case "public": - keyword = .public - case "private": - keyword = .private - case "internal": - keyword = .internal - case "fileprivate": - keyword = .fileprivate - default: - keyword = .public // fallback - #error( - "TODO: Review fallback for unknown access modifier - consider if this should be an error instead" - ) - } modifiers = DeclModifierListSyntax([ - DeclModifierSyntax(name: .keyword(keyword, trailingTrivia: .space)) + DeclModifierSyntax(name: .keyword(access.keyword, trailingTrivia: .space)) ]) } diff --git a/Sources/SyntaxKit/Declarations/Struct.swift b/Sources/SyntaxKit/Declarations/Struct.swift index 2278d13..82e9135 100644 --- a/Sources/SyntaxKit/Declarations/Struct.swift +++ b/Sources/SyntaxKit/Declarations/Struct.swift @@ -36,7 +36,7 @@ public struct Struct: CodeBlock { private var genericParameter: String? private var inheritance: [String] = [] private var attributes: [AttributeInfo] = [] - private var accessModifier: String? + private var accessModifier: AccessModifier? /// Creates a struct declaration. /// - Parameters: @@ -67,9 +67,9 @@ public struct Struct: CodeBlock { } /// Sets the access modifier for the struct declaration. - /// - Parameter access: The access modifier (e.g., "public", "private"). + /// - Parameter access: The access modifier. /// - Returns: A copy of the struct with the access modifier set. - public func access(_ access: String) -> Self { + public func access(_ access: AccessModifier) -> Self { var copy = self copy.accessModifier = access return copy @@ -143,24 +143,8 @@ public struct Struct: CodeBlock { // Build access modifier var modifiers: DeclModifierListSyntax = [] if let access = accessModifier { - let keyword: Keyword - switch access { - case "public": - keyword = .public - case "private": - keyword = .private - case "internal": - keyword = .internal - case "fileprivate": - keyword = .fileprivate - default: - keyword = .public // fallback - #error( - "TODO: Review fallback for unknown access modifier - consider if this should be an error instead" - ) - } modifiers = DeclModifierListSyntax([ - DeclModifierSyntax(name: .keyword(keyword, trailingTrivia: .space)) + DeclModifierSyntax(name: .keyword(access.keyword, trailingTrivia: .space)) ]) } diff --git a/Sources/SyntaxKit/Expressions/Closure+Capture.swift b/Sources/SyntaxKit/Expressions/Closure+Capture.swift index 981337e..864ec10 100644 --- a/Sources/SyntaxKit/Expressions/Closure+Capture.swift +++ b/Sources/SyntaxKit/Expressions/Closure+Capture.swift @@ -43,18 +43,7 @@ private struct CaptureInfo { } private init(fromReference refExp: ReferenceExp) { - let keyword: Keyword - switch refExp.captureReferenceType.lowercased() { - case "weak": - keyword = .weak - case "unowned": - keyword = .unowned - default: - keyword = .weak // fallback to weak - #error( - "TODO: Review fallback for unknown reference type - consider if this should be an error instead" - ) - } + let keyword = refExp.captureReferenceType.keyword self.specifier = ClosureCaptureSpecifierSyntax( specifier: .keyword(keyword, trailingTrivia: .space) diff --git a/Sources/SyntaxKit/Expressions/ReferenceExp.swift b/Sources/SyntaxKit/Expressions/ReferenceExp.swift index 19b1c6b..ad017be 100644 --- a/Sources/SyntaxKit/Expressions/ReferenceExp.swift +++ b/Sources/SyntaxKit/Expressions/ReferenceExp.swift @@ -32,13 +32,13 @@ import SwiftSyntax /// A Swift reference expression (e.g., `weak self`, `unowned self`). public struct ReferenceExp: CodeBlock { private let base: CodeBlock - private let referenceType: String + private let referenceType: CaptureReferenceType /// Creates a reference expression. /// - Parameters: /// - base: The base expression to reference. - /// - referenceType: The type of reference (e.g., "weak", "unowned"). - public init(base: CodeBlock, referenceType: String) { + /// - referenceType: The type of reference. + public init(base: CodeBlock, referenceType: CaptureReferenceType) { self.base = base self.referenceType = referenceType } @@ -68,7 +68,7 @@ public struct ReferenceExp: CodeBlock { } /// Returns the reference type for use in capture lists - internal var captureReferenceType: String { + internal var captureReferenceType: CaptureReferenceType { referenceType } } diff --git a/Sources/SyntaxKit/Variables/ComputedProperty.swift b/Sources/SyntaxKit/Variables/ComputedProperty.swift index 0f52b91..fa963aa 100644 --- a/Sources/SyntaxKit/Variables/ComputedProperty.swift +++ b/Sources/SyntaxKit/Variables/ComputedProperty.swift @@ -34,7 +34,7 @@ public struct ComputedProperty: CodeBlock { private let name: String private let type: String private let body: [CodeBlock] - private var accessModifier: String? + private var accessModifier: AccessModifier? private let explicitType: Bool /// Creates a computed property declaration. @@ -56,9 +56,9 @@ public struct ComputedProperty: CodeBlock { } /// Sets the access modifier for the computed property declaration. - /// - Parameter access: The access modifier (e.g., "public", "private"). + /// - Parameter access: The access modifier. /// - Returns: A copy of the computed property with the access modifier set. - public func access(_ access: String) -> Self { + public func access(_ access: AccessModifier) -> Self { var copy = self copy.accessModifier = access return copy @@ -96,24 +96,8 @@ public struct ComputedProperty: CodeBlock { // Build modifiers var modifiers: DeclModifierListSyntax = [] if let access = accessModifier { - let keyword: Keyword - switch access { - case "public": - keyword = .public - case "private": - keyword = .private - case "internal": - keyword = .internal - case "fileprivate": - keyword = .fileprivate - default: - keyword = .public // fallback - #error( - "TODO: Review fallback for unknown access modifier - consider if this should be an error instead" - ) - } modifiers = DeclModifierListSyntax([ - DeclModifierSyntax(name: .keyword(keyword, trailingTrivia: .space)) + DeclModifierSyntax(name: .keyword(access.keyword, trailingTrivia: .space)) ]) } diff --git a/Sources/SyntaxKit/Variables/Variable+Modifiers.swift b/Sources/SyntaxKit/Variables/Variable+Modifiers.swift index 243ea35..bfaf90d 100644 --- a/Sources/SyntaxKit/Variables/Variable+Modifiers.swift +++ b/Sources/SyntaxKit/Variables/Variable+Modifiers.swift @@ -60,24 +60,7 @@ extension Variable { } /// Builds an access modifier. - private func buildAccessModifier(_ access: String) -> DeclModifierSyntax { - let keyword: Keyword - switch access { - case "public": - keyword = .public - case "private": - keyword = .private - case "internal": - keyword = .internal - case "fileprivate": - keyword = .fileprivate - default: - keyword = .public // fallback - #error( - "TODO: Review fallback for unknown access modifier - consider if this should be an error instead" - ) - } - - return DeclModifierSyntax(name: .keyword(keyword, trailingTrivia: .space)) + private func buildAccessModifier(_ access: AccessModifier) -> DeclModifierSyntax { + DeclModifierSyntax(name: .keyword(access.keyword, trailingTrivia: .space)) } } diff --git a/Sources/SyntaxKit/Variables/Variable.swift b/Sources/SyntaxKit/Variables/Variable.swift index c0cda6a..ec2e133 100644 --- a/Sources/SyntaxKit/Variables/Variable.swift +++ b/Sources/SyntaxKit/Variables/Variable.swift @@ -40,7 +40,7 @@ public struct Variable: CodeBlock { internal var isAsync: Bool = false private var attributes: [AttributeInfo] = [] private var explicitType: Bool = false - internal var accessModifier: String? + internal var accessModifier: AccessModifier? /// Internal initializer used by extension initializers to reduce code duplication. /// - Parameters: @@ -86,9 +86,9 @@ public struct Variable: CodeBlock { } /// Sets the access modifier for the variable declaration. - /// - Parameter access: The access modifier (e.g., "public", "private"). + /// - Parameter access: The access modifier. /// - Returns: A copy of the variable with the access modifier set. - public func access(_ access: String) -> Self { + public func access(_ access: AccessModifier) -> Self { var copy = self copy.accessModifier = access return copy diff --git a/Sources/SyntaxKit/Variables/VariableExp.swift b/Sources/SyntaxKit/Variables/VariableExp.swift index fe07a7c..d8803b3 100644 --- a/Sources/SyntaxKit/Variables/VariableExp.swift +++ b/Sources/SyntaxKit/Variables/VariableExp.swift @@ -65,9 +65,9 @@ public struct VariableExp: CodeBlock, PatternConvertible, ExprCodeBlock { } /// Creates a reference to this variable. - /// - Parameter referenceType: The type of reference (e.g., "weak", "unowned"). + /// - Parameter referenceType: The type of reference. /// - Returns: A reference expression. - public func reference(_ referenceType: String) -> CodeBlock { + public func reference(_ referenceType: CaptureReferenceType) -> CodeBlock { ReferenceExp(base: self, referenceType: referenceType) } diff --git a/Tests/SyntaxKitTests/Integration/SwiftUIExampleTests.swift b/Tests/SyntaxKitTests/Integration/SwiftUIExampleTests.swift index 9edf180..05806bd 100644 --- a/Tests/SyntaxKitTests/Integration/SwiftUIExampleTests.swift +++ b/Tests/SyntaxKitTests/Integration/SwiftUIExampleTests.swift @@ -37,7 +37,7 @@ import Testing internal func testSwiftUIExample() throws { // Test the onToggle variable with closure type and attributes let onToggleVariable = Variable(.let, name: "onToggle", type: "(Date) -> Void") - .access("private") + .access(.private) let generatedCode = onToggleVariable.generateCode() #expect(generatedCode.contains("private let onToggle")) @@ -68,10 +68,10 @@ import Testing internal func testSwiftUITodoItemRowExample() throws { // Use the full DSL from Examples/Completed/swiftui/dsl.swift let dsl = Group { - Import("SwiftUI").access("public") + Import("SwiftUI").access(.public) Struct("TodoItemRow") { - Variable(.let, name: "item", type: "TodoItem").access("private") + Variable(.let, name: "item", type: "TodoItem").access(.private) Variable( .let, @@ -83,7 +83,7 @@ import Testing .attribute("MainActor") .attribute("Sendable") ) - .access("private") + .access(.private) ComputedProperty("body", type: "some View") { Init("HStack") { @@ -124,7 +124,7 @@ import Testing ParameterExp( unlabeled: Closure( capture: { - ParameterExp(unlabeled: VariableExp("self").reference("weak")) + ParameterExp(unlabeled: VariableExp("self").reference(.weak)) }, body: { VariableExp("self").optional().call("onToggle") { @@ -151,10 +151,10 @@ import Testing ) } } - .access("public") + .access(.public) } .inherits("View") - .access("public") + .access(.public) } // Expected Swift code from Examples/Completed/swiftui/code.swift diff --git a/Tests/SyntaxKitTests/Unit/Expressions/ClosureCaptureCoverageTests.swift b/Tests/SyntaxKitTests/Unit/Expressions/ClosureCaptureCoverageTests.swift index d913c4d..98c5376 100644 --- a/Tests/SyntaxKitTests/Unit/Expressions/ClosureCaptureCoverageTests.swift +++ b/Tests/SyntaxKitTests/Unit/Expressions/ClosureCaptureCoverageTests.swift @@ -39,34 +39,12 @@ import Testing internal final class ClosureCaptureCoverageTests { // MARK: - Closure+Capture.swift Coverage Tests - /// Tests fallback cases in CaptureInfo initializers. - @Test("Capture info fallback cases") - internal func testCaptureInfoFallbackCases() { - // Test fallback cases in CaptureInfo initializers - - // Test with unknown reference type (should fallback to weak) - let unknownRef = ReferenceExp(base: VariableExp("self"), referenceType: "unknown") - let paramWithUnknownRef = ParameterExp(name: "self", value: unknownRef) - let closure = Closure( - capture: { paramWithUnknownRef }, - body: { - Variable(.let, name: "result", equals: "value") - } - ) - - let syntax = closure.syntax - let description = syntax.description - - // Should fallback to weak - #expect(description.contains("[weak self]")) - } - /// Tests CaptureInfo with non-VariableExp capture expression. @Test("Capture info with non VariableExp") internal func testCaptureInfoWithNonVariableExp() { // Test CaptureInfo with non-VariableExp capture expression let initBlock = Init("String") - let ref = ReferenceExp(base: initBlock, referenceType: "weak") + let ref = ReferenceExp(base: initBlock, referenceType: .weak) let param = ParameterExp(name: "self", value: ref) let closure = Closure( capture: { param }, diff --git a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpBasicTests.swift b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpBasicTests.swift index 6129335..2f7a95b 100644 --- a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpBasicTests.swift +++ b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpBasicTests.swift @@ -42,14 +42,14 @@ internal final class ReferenceExpBasicTests { internal func testBasicWeakReference() { let reference = ReferenceExp( base: VariableExp("self"), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("self")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests basic unowned reference expression. @@ -57,14 +57,14 @@ internal final class ReferenceExpBasicTests { internal func testBasicUnownedReference() { let reference = ReferenceExp( base: VariableExp("self"), - referenceType: "unowned" + referenceType: .unowned ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("self")) - #expect(reference.captureReferenceType == "unowned") + #expect(reference.captureReferenceType == .unowned) } /// Tests reference expression with variable base. @@ -72,26 +72,24 @@ internal final class ReferenceExpBasicTests { internal func testReferenceWithVariableBase() { let reference = ReferenceExp( base: VariableExp("delegate"), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("delegate")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with different reference types. @Test("Reference expression with different reference types generates correct syntax") internal func testReferenceWithDifferentReferenceTypes() { - let weakRef = ReferenceExp(base: VariableExp("self"), referenceType: "weak") - let unownedRef = ReferenceExp(base: VariableExp("self"), referenceType: "unowned") - let strongRef = ReferenceExp(base: VariableExp("self"), referenceType: "strong") + let weakRef = ReferenceExp(base: VariableExp("self"), referenceType: .weak) + let unownedRef = ReferenceExp(base: VariableExp("self"), referenceType: .unowned) - #expect(weakRef.captureReferenceType == "weak") - #expect(unownedRef.captureReferenceType == "unowned") - #expect(strongRef.captureReferenceType == "strong") + #expect(weakRef.captureReferenceType == .weak) + #expect(unownedRef.captureReferenceType == .unowned) } /// Tests capture expression property access. @@ -100,7 +98,7 @@ internal final class ReferenceExpBasicTests { let base = VariableExp("self") let reference = ReferenceExp( base: base, - referenceType: "weak" + referenceType: .weak ) #expect(reference.captureExpression.syntax.description == base.syntax.description) @@ -111,39 +109,9 @@ internal final class ReferenceExpBasicTests { internal func testCaptureReferenceTypePropertyAccess() { let reference = ReferenceExp( base: VariableExp("self"), - referenceType: "unowned" + referenceType: .unowned ) - #expect(reference.captureReferenceType == "unowned") - } - - /// Tests reference expression with empty string reference type. - @Test("Reference expression with empty string reference type generates correct syntax") - internal func testReferenceWithEmptyStringReferenceType() { - let reference = ReferenceExp( - base: VariableExp("self"), - referenceType: "" - ) - - let syntax = reference.syntax - let description = syntax.description - - #expect(description.contains("self")) - #expect(reference.captureReferenceType.isEmpty) - } - - /// Tests reference expression with custom reference type. - @Test("Reference expression with custom reference type generates correct syntax") - internal func testReferenceWithCustomReferenceType() { - let reference = ReferenceExp( - base: VariableExp("self"), - referenceType: "custom" - ) - - let syntax = reference.syntax - let description = syntax.description - - #expect(description.contains("self")) - #expect(reference.captureReferenceType == "custom") + #expect(reference.captureReferenceType == .unowned) } } diff --git a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpComplexTests.swift b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpComplexTests.swift index ac4b1ad..ea754db 100644 --- a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpComplexTests.swift +++ b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpComplexTests.swift @@ -48,14 +48,14 @@ internal final class ReferenceExpComplexTests { let reference = ReferenceExp( base: conditional, - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("isEnabled ? enabledValue : disabledValue")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with closure base. @@ -64,14 +64,14 @@ internal final class ReferenceExpComplexTests { let closure = Closure(body: { VariableExp("result") }) let reference = ReferenceExp( base: closure, - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description.normalize() #expect(description.contains("{ result }".normalize())) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with enum case base. @@ -80,13 +80,13 @@ internal final class ReferenceExpComplexTests { let enumCase = EnumCase("active") let reference = ReferenceExp( base: enumCase, - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description.normalize() #expect(description.contains(".active".normalize())) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } } diff --git a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpFunctionTests.swift b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpFunctionTests.swift index 5eb42ed..c1f162a 100644 --- a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpFunctionTests.swift +++ b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpFunctionTests.swift @@ -42,14 +42,14 @@ internal final class ReferenceExpFunctionTests { internal func testReferenceWithFunctionCallBase() { let reference = ReferenceExp( base: Call("getCurrentUser"), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("getCurrentUser()")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with method call base. @@ -57,14 +57,14 @@ internal final class ReferenceExpFunctionTests { internal func testReferenceWithMethodCallBase() { let reference = ReferenceExp( base: Call("getData"), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("getData()")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with init call base. @@ -73,13 +73,13 @@ internal final class ReferenceExpFunctionTests { let initCall = Init("String") let reference = ReferenceExp( base: initCall, - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("String()")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } } diff --git a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpLiteralTests.swift b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpLiteralTests.swift index 156a5f5..831379e 100644 --- a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpLiteralTests.swift +++ b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpLiteralTests.swift @@ -42,14 +42,14 @@ internal final class ReferenceExpLiteralTests { internal func testReferenceWithLiteralBase() { let reference = ReferenceExp( base: Literal.ref("constant"), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("constant")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with array literal base. @@ -57,14 +57,14 @@ internal final class ReferenceExpLiteralTests { internal func testReferenceWithArrayLiteralBase() { let reference = ReferenceExp( base: Literal.array([Literal.string("item1"), Literal.string("item2")]), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("[\"item1\", \"item2\"]")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with dictionary literal base. @@ -72,14 +72,14 @@ internal final class ReferenceExpLiteralTests { internal func testReferenceWithDictionaryLiteralBase() { let reference = ReferenceExp( base: Literal.dictionary([(Literal.string("key"), Literal.string("value"))]), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description.replacingOccurrences(of: " ", with: "") #expect(description.contains("[\"key\":\"value\"]".replacingOccurrences(of: " ", with: ""))) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with tuple literal base. @@ -87,13 +87,13 @@ internal final class ReferenceExpLiteralTests { internal func testReferenceWithTupleLiteralBase() { let reference = ReferenceExp( base: Literal.tuple([Literal.string("first"), Literal.string("second")]), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("(\"first\", \"second\")")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } } diff --git a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpPropertyTests.swift b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpPropertyTests.swift index 8da4632..f720efa 100644 --- a/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpPropertyTests.swift +++ b/Tests/SyntaxKitTests/Unit/Expressions/ReferenceExp/ReferenceExpPropertyTests.swift @@ -42,14 +42,14 @@ internal final class ReferenceExpPropertyTests { internal func testReferenceWithPropertyAccessBase() { let reference = ReferenceExp( base: PropertyAccessExp(base: VariableExp("viewController"), propertyName: "delegate"), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("viewController.delegate")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with nested property access base. @@ -60,14 +60,14 @@ internal final class ReferenceExpPropertyTests { base: PropertyAccessExp(base: VariableExp("user"), propertyName: "profile"), propertyName: "settings" ), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("user.profile.settings")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with complex base expression. @@ -78,14 +78,14 @@ internal final class ReferenceExpPropertyTests { base: Call("getUserManager"), propertyName: "currentUser" ), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("getUserManager().currentUser")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } /// Tests reference expression with complex nested expression base. @@ -96,13 +96,13 @@ internal final class ReferenceExpPropertyTests { base: Call("getUserManager"), propertyName: "currentUser" ), - referenceType: "weak" + referenceType: .weak ) let syntax = reference.syntax let description = syntax.description #expect(description.contains("getUserManager().currentUser")) - #expect(reference.captureReferenceType == "weak") + #expect(reference.captureReferenceType == .weak) } } diff --git a/Tests/SyntaxKitTests/Unit/SwiftUIFeatureTests.swift b/Tests/SyntaxKitTests/Unit/SwiftUIFeatureTests.swift index c0e1163..42de7ed 100644 --- a/Tests/SyntaxKitTests/Unit/SwiftUIFeatureTests.swift +++ b/Tests/SyntaxKitTests/Unit/SwiftUIFeatureTests.swift @@ -37,7 +37,7 @@ import Testing internal func testSwiftUIExample() throws { // Test the onToggle variable with closure type and attributes let onToggleVariable = Variable(.let, name: "onToggle", type: "(Date) -> Void") - .access("private") + .access(.private) let generatedCode = onToggleVariable.generateCode() let expectedCode = "private let onToggle: (Date) -> Void" @@ -87,25 +87,25 @@ import Testing @Test("Reference method supports different reference types") internal func testReferenceMethodSupportsDifferentTypes() throws { // Test weak reference - let weakRef = VariableExp("self").reference("weak") + let weakRef = VariableExp("self").reference(.weak) // The ReferenceExp itself just shows the base variable name let weakGenerated = weakRef.syntax.description #expect(weakGenerated.contains("self")) // Test unowned reference - let unownedRef = VariableExp("self").reference("unowned") + let unownedRef = VariableExp("self").reference(.unowned) let unownedGenerated = unownedRef.syntax.description #expect(unownedGenerated.contains("self")) // Verify that the reference types are stored correctly if let weakRefExp = weakRef as? ReferenceExp { - #expect(weakRefExp.captureReferenceType == "weak") + #expect(weakRefExp.captureReferenceType == .weak) } else { #expect(false, "Expected ReferenceExp type") } if let unownedRefExp = unownedRef as? ReferenceExp { - #expect(unownedRefExp.captureReferenceType == "unowned") + #expect(unownedRefExp.captureReferenceType == .unowned) } else { #expect(false, "Expected ReferenceExp type") } @@ -116,7 +116,7 @@ import Testing // Test weak reference in closure capture let weakClosure = Closure( capture: { - ParameterExp(unlabeled: VariableExp("self").reference("weak")) + ParameterExp(unlabeled: VariableExp("self").reference(.weak)) }, body: { VariableExp("self").optional().call("handleData") { @@ -131,7 +131,7 @@ import Testing // Test unowned reference in closure capture let unownedClosure = Closure( capture: { - ParameterExp(unlabeled: VariableExp("self").reference("unowned")) + ParameterExp(unlabeled: VariableExp("self").reference(.unowned)) }, body: { VariableExp("self").call("handleData") { diff --git a/Tests/SyntaxKitTests/Unit/Variables/VariableCoverageTests.swift b/Tests/SyntaxKitTests/Unit/Variables/VariableCoverageTests.swift index a8bb171..795dba7 100644 --- a/Tests/SyntaxKitTests/Unit/Variables/VariableCoverageTests.swift +++ b/Tests/SyntaxKitTests/Unit/Variables/VariableCoverageTests.swift @@ -44,7 +44,7 @@ internal final class VariableCoverageTests { internal func testBuildAccessModifierPublic() { // Test the "public" case in buildAccessModifier let variable = Variable(.let, name: "test", equals: "value") - .access("public") + .access(.public) let syntax = variable.syntax let description = syntax.description @@ -58,7 +58,7 @@ internal final class VariableCoverageTests { internal func testBuildAccessModifierInternal() { // Test the "internal" case in buildAccessModifier let variable = Variable(.let, name: "test", equals: "value") - .access("internal") + .access(.internal) let syntax = variable.syntax let description = syntax.description @@ -72,7 +72,7 @@ internal final class VariableCoverageTests { internal func testBuildAccessModifierFileprivate() { // Test the "fileprivate" case in buildAccessModifier let variable = Variable(.let, name: "test", equals: "value") - .access("fileprivate") + .access(.fileprivate) let syntax = variable.syntax let description = syntax.description @@ -81,20 +81,6 @@ internal final class VariableCoverageTests { #expect(description.contains("fileprivate let test = \"value\"")) } - /// Tests the default case in buildAccessModifier (unknown access modifier). - @Test("Build access modifier default") - internal func testBuildAccessModifierDefault() { - // Test the default case in buildAccessModifier (unknown access modifier) - let variable = Variable(.let, name: "test", equals: "value") - .access("unknown") - - let syntax = variable.syntax - let description = syntax.description - - // Should fallback to public - #expect(description.contains("public let test = \"value\"")) - } - // MARK: - Variable.swift Coverage Tests /// Tests the fallback type case in Variable initializer. From 80877cedf1287e38a5e2713bb31cbcf1fb9c7b62 Mon Sep 17 00:00:00 2001 From: Leo Dion Date: Tue, 24 Jun 2025 18:54:40 -0400 Subject: [PATCH 17/17] finishing PR --- .../Expressions/FunctionCallExp.swift | 2 +- .../OptionsMacroIntegrationTests.swift | 37 ++++++------------- 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/Sources/SyntaxKit/Expressions/FunctionCallExp.swift b/Sources/SyntaxKit/Expressions/FunctionCallExp.swift index 5462959..78753d5 100644 --- a/Sources/SyntaxKit/Expressions/FunctionCallExp.swift +++ b/Sources/SyntaxKit/Expressions/FunctionCallExp.swift @@ -122,7 +122,7 @@ public struct FunctionCallExp: CodeBlock { } ) - var functionCall = FunctionCallExprSyntax( + let functionCall = FunctionCallExprSyntax( calledExpression: ExprSyntax( MemberAccessExprSyntax( base: baseExpr, diff --git a/Tests/SyntaxKitTests/Unit/Integration/OptionsMacroIntegrationTests.swift b/Tests/SyntaxKitTests/Unit/Integration/OptionsMacroIntegrationTests.swift index 3319c29..f487e4d 100644 --- a/Tests/SyntaxKitTests/Unit/Integration/OptionsMacroIntegrationTests.swift +++ b/Tests/SyntaxKitTests/Unit/Integration/OptionsMacroIntegrationTests.swift @@ -79,24 +79,16 @@ internal struct OptionsMacroIntegrationTests { @Test internal func testCompleteOptionsMacroWorkflow() { // This test demonstrates the complete workflow that the Options macro would use + // for an enum WITH raw values - // Step 1: Determine if enum has raw values (simulated) - let hasRawValues = true let enumName = "TestEnum" - // Step 2: Create the appropriate mappedValues variable - let mappedValuesVariable: Variable - if hasRawValues { - let keyValues: [Int: String] = [1: "first", 2: "second", 3: "third"] - mappedValuesVariable = Variable(.let, name: "mappedValues", equals: keyValues) - .withExplicitType().static() - } else { - let caseNames: [String] = ["first", "second", "third"] - mappedValuesVariable = Variable(.let, name: "mappedValues", equals: caseNames) - .withExplicitType().static() - } - - // Step 3: Create the extension + // Create the mappedValues variable for enum with raw values + let keyValues: [Int: String] = [1: "first", 2: "second", 3: "third"] + let mappedValuesVariable = Variable(.let, name: "mappedValues", equals: keyValues) + .withExplicitType().static() + + // Create the extension let extensionDecl = Extension(enumName) { TypeAlias("MappedType", equals: "String") mappedValuesVariable @@ -117,19 +109,12 @@ internal struct OptionsMacroIntegrationTests { @Test internal func testOptionsMacroWorkflowWithoutRawValues() { // Test the workflow for enums without raw values - let hasRawValues = false let enumName = "SimpleEnum" - let mappedValuesVariable: Variable - if hasRawValues { - let keyValues: [Int: String] = [1: "first", 2: "second"] - mappedValuesVariable = Variable(.let, name: "mappedValues", equals: keyValues) - .withExplicitType().static() - } else { - let caseNames: [String] = ["first", "second"] - mappedValuesVariable = Variable(.let, name: "mappedValues", equals: caseNames) - .withExplicitType().static() - } + // Create the mappedValues variable for enum without raw values + let caseNames: [String] = ["first", "second"] + let mappedValuesVariable = Variable(.let, name: "mappedValues", equals: caseNames) + .withExplicitType().static() let extensionDecl = Extension(enumName) { TypeAlias("MappedType", equals: "String")