Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions Sources/MacroToolkit/DeclGroup/Enum.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ public struct Enum: DeclGroupProtocol, RepresentableBySyntax {
_syntax.name.withoutTrivia().text
}

public var rawRepresentableType: EnumRawRepresentableType? {
EnumRawRepresentableType(possibleRawType: _syntax.inheritanceClause?.inheritedTypes.first)
}

/// Initializes an `Enum` instance with the given syntax node.
///
/// - Parameter syntax: The syntax node representing the `enum` declaration.
Expand All @@ -19,12 +23,17 @@ public struct Enum: DeclGroupProtocol, RepresentableBySyntax {

/// The `enum`'s cases.
public var cases: [EnumCase] {
_syntax.memberBlock.members
var lastSeen: EnumCase?
return _syntax.memberBlock.members
.compactMap { member in
member.decl.as(EnumCaseDeclSyntax.self)
}
.flatMap { syntax in
syntax.elements.map(EnumCase.init)
syntax.elements.map {
let next = EnumCase($0, rawRepresentableType: rawRepresentableType, precedingCase: lastSeen)
lastSeen = next
return next
}
}
}
}
42 changes: 28 additions & 14 deletions Sources/MacroToolkit/EnumCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,41 @@ import SwiftSyntax
public struct EnumCase {
public var _syntax: EnumCaseElementSyntax

public init(_ syntax: EnumCaseElementSyntax) {
public init(_ syntax: EnumCaseElementSyntax, rawRepresentableType: EnumRawRepresentableType? = nil, precedingCase: EnumCase? = nil) {
_syntax = syntax
value = {
if let rawValue = _syntax.rawValue {
return .rawValue(rawValue)
} else if let associatedValue = _syntax.parameterClause {
let parameters = Array(associatedValue.parameters)
.map(EnumCaseAssociatedValueParameter.init)
return .associatedValue(parameters)
} else if let rawRepresentableType {
switch rawRepresentableType {
case .string: return .inferredRawValue(.init(value: "\"\(raw: _syntax.name.text)\"" as ExprSyntax))
case .character: return nil // Characters cannot be inferred
case .number:
// Raw representable conformance is only synthesized when using integer literals (eg 1),
// not float literals (eg 1.0).
let previousValue: Int? = switch precedingCase?.value {
case .rawValue(let v), .inferredRawValue(let v): IntegerLiteral(v.value)?.value
default: nil
}
return .inferredRawValue(.init(value: "\(raw: (previousValue ?? -1) + 1)" as ExprSyntax))
}
} else {
return nil
}
}()
}

/// The case's name
public var identifier: String {
_syntax.name.withoutTrivia().description
}

/// The value associated with the enum case (either associated or raw).
public var value: EnumCaseValue? {
if let rawValue = _syntax.rawValue {
return .rawValue(rawValue)
} else if let associatedValue = _syntax.parameterClause {
let parameters = Array(associatedValue.parameters)
.map(EnumCaseAssociatedValueParameter.init)
return .associatedValue(parameters)
} else {
return nil
}
}

/// The value associated with the enum case (either associated, raw or inferred).
public var value: EnumCaseValue?

public func withoutValue() -> Self {
EnumCase(_syntax.with(\.rawValue, nil).with(\.parameterClause, nil))
Expand Down
1 change: 1 addition & 0 deletions Sources/MacroToolkit/EnumCaseValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ import SwiftSyntax
public enum EnumCaseValue {
case associatedValue([EnumCaseAssociatedValueParameter])
case rawValue(InitializerClauseSyntax)
case inferredRawValue(InitializerClauseSyntax)
}
21 changes: 21 additions & 0 deletions Sources/MacroToolkit/EnumRawRepresentableType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import SwiftSyntax

/// Enum raw values can be strings, characters, or any of the integer or floating-point number types.
public enum EnumRawRepresentableType {
case string
case character
case number

init?(possibleRawType syntax: InheritedTypeSyntax?) {
switch syntax?.type.as(IdentifierTypeSyntax.self)?.name.text {
case "String": self = .string
case "Character": self = .character
case "Int", "Int8", "Int16", "Int32", "Int64", "Int128",
"UInt", "UInt8", "UInt16", "UInt32", "UInt64", "UInt128",
"Float", "Float16", "Float32", "Float64",
"Double", "CGFloat", "NSNumber":
self = .number
default: return nil
}
}
}
80 changes: 80 additions & 0 deletions Tests/MacroToolkitTests/DeclGroupTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,84 @@ final class DeclGroupTests: XCTestCase {
XCTAssertEqual(testClass.accessLevel, .public)
XCTAssertEqual(testClass.declarationContext, nil)
}

func testEnumRawTypeInferredValueString() throws {
let decl: DeclSyntax = """
enum TestEnum: String { case caseOne, caseTwo, caseThree = "case3" }
"""
let enumDecl = decl.as(EnumDeclSyntax.self)!
let testEnum = Enum(enumDecl)

XCTAssertEqual(testEnum.identifier, "TestEnum")
XCTAssertEqual(testEnum.members.count, 1)
XCTAssertEqual(testEnum.cases.count, 3)
XCTAssertEqual(testEnum.rawRepresentableType, .string)
let literals = testEnum.cases.map {
switch $0.value {
case .rawValue(let i), .inferredRawValue(let i): return StringLiteral(i.value)?.value
default: return nil
}
}
XCTAssertEqual(literals, ["caseOne", "caseTwo", "case3"])
}

func testEnumRawTypeInferredValueInt() throws {
let decl: DeclSyntax = """
enum TestEnum: Int { case caseOne = 1, caseTwo, caseThree }
"""
let enumDecl = decl.as(EnumDeclSyntax.self)!
let testEnum = Enum(enumDecl)

XCTAssertEqual(testEnum.identifier, "TestEnum")
XCTAssertEqual(testEnum.members.count, 1)
XCTAssertEqual(testEnum.cases.count, 3)
XCTAssertEqual(testEnum.rawRepresentableType, .number)
let literals = testEnum.cases.map {
switch $0.value {
case .rawValue(let i), .inferredRawValue(let i): return IntegerLiteral(i.value)?.value
default: return nil
}
}
XCTAssertEqual(literals, [1, 2, 3])
}

func testEnumRawTypeInferredValueNegativeInt() throws {
let decl: DeclSyntax = """
enum TestEnum: Int { case a = -1, b, c }
"""
let enumDecl = decl.as(EnumDeclSyntax.self)!
let testEnum = Enum(enumDecl)

XCTAssertEqual(testEnum.identifier, "TestEnum")
XCTAssertEqual(testEnum.members.count, 1)
XCTAssertEqual(testEnum.cases.count, 3)
XCTAssertEqual(testEnum.rawRepresentableType, .number)
let literals = testEnum.cases.map {
switch $0.value {
case .rawValue(let i), .inferredRawValue(let i): return IntegerLiteral(i.value)?.value
default: return nil
}
}
XCTAssertEqual(literals, [-1, 0, 1])
}

func testEnumRawTypeInferredValueDouble() throws {
let decl: DeclSyntax = """
enum TestEnum: Double { case caseOne = 1, caseTwo, caseThree }
"""
let enumDecl = decl.as(EnumDeclSyntax.self)!
let testEnum = Enum(enumDecl)

XCTAssertEqual(testEnum.identifier, "TestEnum")
XCTAssertEqual(testEnum.members.count, 1)
XCTAssertEqual(testEnum.cases.count, 3)
XCTAssertEqual(testEnum.rawRepresentableType, .number)
let literals = testEnum.cases.map {
switch $0.value {
case .rawValue(let i), .inferredRawValue(let i): return IntegerLiteral(i.value)?.value
default: return nil
}
}
XCTAssertEqual(literals, [1, 2, 3])
}
}