diff --git a/Sources/VexilMacros/FlagContainerMacro.swift b/Sources/VexilMacros/FlagContainerMacro.swift index 733623f7..abbc9e1a 100644 --- a/Sources/VexilMacros/FlagContainerMacro.swift +++ b/Sources/VexilMacros/FlagContainerMacro.swift @@ -146,7 +146,7 @@ extension FlagContainerMacro: ExtensionMacro { extendedType: type, inheritanceClause: .init(inheritedTypes: [ .init(type: TypeSyntax(stringLiteral: "Equatable")) ]) ) { - var variables = declaration.memberBlock.variables + var variables = declaration.memberBlock.storedVariables if variables.isEmpty == false { try FunctionDeclSyntax("func ==(lhs: \(type), rhs: \(type)) -> Bool") { if let lastBinding = variables.removeLast().bindings.first?.pattern { diff --git a/Sources/VexilMacros/Utilities/SimpleVariables.swift b/Sources/VexilMacros/Utilities/SimpleVariables.swift index 96526ed6..5077c5d2 100644 --- a/Sources/VexilMacros/Utilities/SimpleVariables.swift +++ b/Sources/VexilMacros/Utilities/SimpleVariables.swift @@ -22,6 +22,29 @@ extension MemberBlockSyntax { } } + var storedVariables: [VariableDeclSyntax] { + variables.filter { variable in + // Only simple properties + guard variable.bindings.count == 1, let binding = variable.bindings.first else { + return false + } + + // If it has no accessor block it's stored + guard let accessorBlock = binding.accessorBlock else { + return true + } + + // If there is any kind of getter then its computed + switch accessorBlock.accessors { + case .getter: + return false + + case let .accessors(accessors): + return accessors.allSatisfy({ $0.accessorSpecifier.tokenKind != .keyword(.get) }) + } + } + } + } extension VariableDeclSyntax { diff --git a/Tests/VexilMacroTests/EquatableFlagContainerMacroTests.swift b/Tests/VexilMacroTests/EquatableFlagContainerMacroTests.swift index 1bba4beb..b532b875 100644 --- a/Tests/VexilMacroTests/EquatableFlagContainerMacroTests.swift +++ b/Tests/VexilMacroTests/EquatableFlagContainerMacroTests.swift @@ -64,6 +64,19 @@ final class EquatableFlagContainerMacroTests: XCTestCase { struct TestFlags { @Flag(default: false, description: "Some Flag") var someFlag: Bool + + var someComputedNotEquatableProperty: (any Error)? { + nil + } + + var someComputedPropertyWithAGetter: (any Error)? { + get { fatalError() } + set { fatalError() } + } + + var otherStoredProperty: Int { + didSet { fatalError() } + } } """, expandedSource: """ @@ -85,6 +98,19 @@ final class EquatableFlagContainerMacroTests: XCTestCase { lookup: _flagLookup ) } + + var someComputedNotEquatableProperty: (any Error)? { + nil + } + + var someComputedPropertyWithAGetter: (any Error)? { + get { fatalError() } + set { fatalError() } + } + + var otherStoredProperty: Int { + didSet { fatalError() } + } fileprivate let _flagKeyPath: FlagKeyPath @@ -120,7 +146,8 @@ final class EquatableFlagContainerMacroTests: XCTestCase { extension TestFlags: Equatable { static func ==(lhs: TestFlags, rhs: TestFlags) -> Bool { - lhs.someFlag == rhs.someFlag + lhs.someFlag == rhs.someFlag && + lhs.otherStoredProperty == rhs.otherStoredProperty } } """,