From fffb5b5ff81e22a56828f6c2595eb5b6609268eb Mon Sep 17 00:00:00 2001 From: Alessio Nossa Date: Sun, 7 Apr 2024 17:50:10 +0200 Subject: [PATCH 01/17] Add normalized types --- .../NormalizedCompositionType.swift | 12 ++++++++ .../NormalizedFunctionType.swift | 25 +++++++++++++++++ ...lizedImplicitlyUnwrappedOptionalType.swift | 12 ++++++++ .../NormalizedMemberType.swift | 15 ++++++++++ .../NormalizedMissingType.swift | 13 +++++++++ .../NormalizedPackExpansionType.swift | 12 ++++++++ .../NormalizedPackReferenceType.swift | 12 ++++++++ .../NormalizedSimpleType.swift | 28 +++++++++++++++++++ .../NormalizedSomeOrAnyType.swift | 15 ++++++++++ .../NormalizedSuppressedType.swift | 12 ++++++++ .../NormalizedTypes/NormalizedTupleType.swift | 19 +++++++++++++ 11 files changed, 175 insertions(+) create mode 100644 Sources/MacroToolkit/NormalizedTypes/NormalizedCompositionType.swift create mode 100644 Sources/MacroToolkit/NormalizedTypes/NormalizedFunctionType.swift create mode 100644 Sources/MacroToolkit/NormalizedTypes/NormalizedImplicitlyUnwrappedOptionalType.swift create mode 100644 Sources/MacroToolkit/NormalizedTypes/NormalizedMemberType.swift create mode 100644 Sources/MacroToolkit/NormalizedTypes/NormalizedMissingType.swift create mode 100644 Sources/MacroToolkit/NormalizedTypes/NormalizedPackExpansionType.swift create mode 100644 Sources/MacroToolkit/NormalizedTypes/NormalizedPackReferenceType.swift create mode 100644 Sources/MacroToolkit/NormalizedTypes/NormalizedSimpleType.swift create mode 100644 Sources/MacroToolkit/NormalizedTypes/NormalizedSomeOrAnyType.swift create mode 100644 Sources/MacroToolkit/NormalizedTypes/NormalizedSuppressedType.swift create mode 100644 Sources/MacroToolkit/NormalizedTypes/NormalizedTupleType.swift diff --git a/Sources/MacroToolkit/NormalizedTypes/NormalizedCompositionType.swift b/Sources/MacroToolkit/NormalizedTypes/NormalizedCompositionType.swift new file mode 100644 index 0000000..dabfe3b --- /dev/null +++ b/Sources/MacroToolkit/NormalizedTypes/NormalizedCompositionType.swift @@ -0,0 +1,12 @@ +import SwiftSyntax + +/// Wraps a composition type (e.g. `ProtocolA & ProtocolB`). +public struct NormalizedCompositionType: TypeProtocol { + public var _baseSyntax: CompositionTypeSyntax + public var _attributedSyntax: AttributedTypeSyntax? + + public init(_ syntax: CompositionTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { + _baseSyntax = syntax + _attributedSyntax = attributedSyntax + } +} diff --git a/Sources/MacroToolkit/NormalizedTypes/NormalizedFunctionType.swift b/Sources/MacroToolkit/NormalizedTypes/NormalizedFunctionType.swift new file mode 100644 index 0000000..34e2218 --- /dev/null +++ b/Sources/MacroToolkit/NormalizedTypes/NormalizedFunctionType.swift @@ -0,0 +1,25 @@ +import SwiftSyntax + +/// Wraps a function type (e.g. `(Int, Double) -> Bool`). +public struct NormalizedFunctionType: TypeProtocol { + // TODO: Should give access to attributes such as `@escaping`. + public var _baseSyntax: FunctionTypeSyntax + public var _attributedSyntax: AttributedTypeSyntax? + + /// Don't supply the `attributedSyntax` parameter, use the `attributedSyntax` initializer instead. + /// It only exists because of protocol conformance. + public init(_ syntax: FunctionTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { + _baseSyntax = syntax + _attributedSyntax = attributedSyntax + } + + /// The return type that the function type describes. + public var returnType: NormalizedType { + NormalizedType(_baseSyntax.returnClause.type) + } + + /// The types of the parameters the function type describes. + public var parameters: [NormalizedType] { + _baseSyntax.parameters.map(\.type).map(NormalizedType.init) + } +} diff --git a/Sources/MacroToolkit/NormalizedTypes/NormalizedImplicitlyUnwrappedOptionalType.swift b/Sources/MacroToolkit/NormalizedTypes/NormalizedImplicitlyUnwrappedOptionalType.swift new file mode 100644 index 0000000..9cc58f6 --- /dev/null +++ b/Sources/MacroToolkit/NormalizedTypes/NormalizedImplicitlyUnwrappedOptionalType.swift @@ -0,0 +1,12 @@ +import SwiftSyntax + +/// Wraps an implicitly unwrapped optional type (e.g. `Int!`). +public struct NormalizedImplicitlyUnwrappedOptionalType: TypeProtocol { + public var _baseSyntax: ImplicitlyUnwrappedOptionalTypeSyntax + public var _attributedSyntax: AttributedTypeSyntax? + + public init(_ syntax: ImplicitlyUnwrappedOptionalTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { + _baseSyntax = syntax + _attributedSyntax = attributedSyntax + } +} diff --git a/Sources/MacroToolkit/NormalizedTypes/NormalizedMemberType.swift b/Sources/MacroToolkit/NormalizedTypes/NormalizedMemberType.swift new file mode 100644 index 0000000..30d8b87 --- /dev/null +++ b/Sources/MacroToolkit/NormalizedTypes/NormalizedMemberType.swift @@ -0,0 +1,15 @@ +import SwiftSyntax + +/// Wraps a member type (e.g. `Array.Element`). +public struct NormalizedMemberType: TypeProtocol { + public var _baseSyntax: MemberTypeSyntax + public var _attributedSyntax: AttributedTypeSyntax? + + public init( + _ syntax: MemberTypeSyntax, + attributedSyntax: AttributedTypeSyntax? = nil + ) { + _baseSyntax = syntax + _attributedSyntax = attributedSyntax + } +} diff --git a/Sources/MacroToolkit/NormalizedTypes/NormalizedMissingType.swift b/Sources/MacroToolkit/NormalizedTypes/NormalizedMissingType.swift new file mode 100644 index 0000000..d59677e --- /dev/null +++ b/Sources/MacroToolkit/NormalizedTypes/NormalizedMissingType.swift @@ -0,0 +1,13 @@ +import SwiftSyntax + +/// Wraps a missing type (i.e. a type that was missing in the source but the resilient parser +/// has added a placeholder for). +public struct NormalizedMissingType: TypeProtocol { + public var _baseSyntax: MissingTypeSyntax + public var _attributedSyntax: AttributedTypeSyntax? + + public init(_ syntax: MissingTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { + _baseSyntax = syntax + _attributedSyntax = attributedSyntax + } +} diff --git a/Sources/MacroToolkit/NormalizedTypes/NormalizedPackExpansionType.swift b/Sources/MacroToolkit/NormalizedTypes/NormalizedPackExpansionType.swift new file mode 100644 index 0000000..2a9fa05 --- /dev/null +++ b/Sources/MacroToolkit/NormalizedTypes/NormalizedPackExpansionType.swift @@ -0,0 +1,12 @@ +import SwiftSyntax + +/// Wraps a pack expansion type (e.g. `repeat each V`). +public struct NormalizedPackExpansionType: TypeProtocol { + public var _baseSyntax: PackExpansionTypeSyntax + public var _attributedSyntax: AttributedTypeSyntax? + + public init(_ syntax: PackExpansionTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { + _baseSyntax = syntax + _attributedSyntax = attributedSyntax + } +} diff --git a/Sources/MacroToolkit/NormalizedTypes/NormalizedPackReferenceType.swift b/Sources/MacroToolkit/NormalizedTypes/NormalizedPackReferenceType.swift new file mode 100644 index 0000000..8a20dc4 --- /dev/null +++ b/Sources/MacroToolkit/NormalizedTypes/NormalizedPackReferenceType.swift @@ -0,0 +1,12 @@ +import SwiftSyntax + +/// Wraps an implicitly unwrapped optional type (e.g. `each V`). +public struct NormalizedPackReferenceType: TypeProtocol { + public var _baseSyntax: PackElementTypeSyntax + public var _attributedSyntax: AttributedTypeSyntax? + + public init(_ syntax: PackElementTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { + _baseSyntax = syntax + _attributedSyntax = attributedSyntax + } +} diff --git a/Sources/MacroToolkit/NormalizedTypes/NormalizedSimpleType.swift b/Sources/MacroToolkit/NormalizedTypes/NormalizedSimpleType.swift new file mode 100644 index 0000000..7b18cea --- /dev/null +++ b/Sources/MacroToolkit/NormalizedTypes/NormalizedSimpleType.swift @@ -0,0 +1,28 @@ +import SwiftSyntax + +/// Wraps a simple type (e.g. `Result`). +public struct NormalizedSimpleType: TypeProtocol { + public var _baseSyntax: IdentifierTypeSyntax + public var _attributedSyntax: AttributedTypeSyntax? + + public init( + _ syntax: IdentifierTypeSyntax, + attributedSyntax: AttributedTypeSyntax? = nil + ) { + _baseSyntax = syntax + _attributedSyntax = attributedSyntax + } + + /// The base type's name (e.g. for `Array` it would be `"Array"`). + public var name: String { + _baseSyntax.name.description + } + + /// The type's generic arguments if any were supplied (e.g. for + /// `Dictionary` it would be `["Int", "String"]`). + public var genericArguments: [NormalizedType]? { + _baseSyntax.genericArgumentClause.map { clause in + clause.arguments.map(\.argument).map(NormalizedType.init) + } + } +} diff --git a/Sources/MacroToolkit/NormalizedTypes/NormalizedSomeOrAnyType.swift b/Sources/MacroToolkit/NormalizedTypes/NormalizedSomeOrAnyType.swift new file mode 100644 index 0000000..5d04b99 --- /dev/null +++ b/Sources/MacroToolkit/NormalizedTypes/NormalizedSomeOrAnyType.swift @@ -0,0 +1,15 @@ +import SwiftSyntax + +/// Wraps a `some` or `any` type (i.e. `any Protocol` or `some Protocol`). +public struct NormalizedSomeOrAnyType: TypeProtocol { + public var _baseSyntax: SomeOrAnyTypeSyntax + public var _attributedSyntax: AttributedTypeSyntax? + + public init( + _ syntax: SomeOrAnyTypeSyntax, + attributedSyntax: AttributedTypeSyntax? = nil + ) { + _baseSyntax = syntax + _attributedSyntax = attributedSyntax + } +} diff --git a/Sources/MacroToolkit/NormalizedTypes/NormalizedSuppressedType.swift b/Sources/MacroToolkit/NormalizedTypes/NormalizedSuppressedType.swift new file mode 100644 index 0000000..84fc389 --- /dev/null +++ b/Sources/MacroToolkit/NormalizedTypes/NormalizedSuppressedType.swift @@ -0,0 +1,12 @@ +import SwiftSyntax + +/// Wraps a suppressed type from a conformance clause (e.g. `~Copyable`). +public struct NormalizedSuppressedType: TypeProtocol { + public var _baseSyntax: SuppressedTypeSyntax + public var _attributedSyntax: AttributedTypeSyntax? + + public init(_ syntax: SuppressedTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { + _baseSyntax = syntax + _attributedSyntax = attributedSyntax + } +} diff --git a/Sources/MacroToolkit/NormalizedTypes/NormalizedTupleType.swift b/Sources/MacroToolkit/NormalizedTypes/NormalizedTupleType.swift new file mode 100644 index 0000000..c62b054 --- /dev/null +++ b/Sources/MacroToolkit/NormalizedTypes/NormalizedTupleType.swift @@ -0,0 +1,19 @@ +import SwiftSyntax + +/// Wraps a tuple type (e.g. `(Int, String)`). +public struct NormalizedTupleType: TypeProtocol { + public var _baseSyntax: TupleTypeSyntax + public var _attributedSyntax: AttributedTypeSyntax? + + public init(_ syntax: TupleTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { + _baseSyntax = syntax + _attributedSyntax = attributedSyntax + } + + var elements: [NormalizedType] { + // TODO: Handle labels and the possible ellipsis + _baseSyntax.elements.map { element in + NormalizedType(element.type) + } + } +} From 644ed704c21d9d1c094340f2252d7713d487c1b9 Mon Sep 17 00:00:00 2001 From: Alessio Nossa Date: Sun, 7 Apr 2024 17:50:40 +0200 Subject: [PATCH 02/17] Add NormalizedType --- Sources/MacroToolkit/NormalizedType.swift | 103 ++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 Sources/MacroToolkit/NormalizedType.swift diff --git a/Sources/MacroToolkit/NormalizedType.swift b/Sources/MacroToolkit/NormalizedType.swift new file mode 100644 index 0000000..bbba9b1 --- /dev/null +++ b/Sources/MacroToolkit/NormalizedType.swift @@ -0,0 +1,103 @@ +import SwiftSyntax +import SwiftSyntaxBuilder + +public enum NormalizedType: TypeProtocol, SyntaxExpressibleByStringInterpolation { + /// A composition of two types (e.g. `Encodable & Decodable`). Used to + /// combine protocol requirements. + case composition(NormalizedCompositionType) + /// A some or any protocol type (e.g. `any T` or `some T`). + case someOrAny(NormalizedSomeOrAnyType) + /// A function type (e.g. `() -> ()`). + case function(NormalizedFunctionType) + /// An implicitly unwrapped optional type (e.g. `Int!`). + case implicitlyUnwrappedOptional(NormalizedImplicitlyUnwrappedOptionalType) + /// A member type (e.g. `Array.Element`). + case member(NormalizedMemberType) + /// A placeholder for invalid types that the resilient parser ignored. + case missing(NormalizedMissingType) + /// A pack expansion type (e.g. `repeat each V`). + case packExpansion(NormalizedPackExpansionType) + /// A pack reference type (e.g. `each V`). + case packReference(NormalizedPackReferenceType) + /// A simple type (e.g. `Int` or `Box`). + case simple(NormalizedSimpleType) + /// A suppressed type in a conformance position (e.g. `~Copyable`). + case suppressed(NormalizedSuppressedType) + //// A tuple type (e.g. `(Int, String)`). + case tuple(NormalizedTupleType) + + public var _baseSyntax: TypeSyntax { + let type: any TypeProtocol = switch self { + case .composition(let type as any TypeProtocol), + .someOrAny(let type as any TypeProtocol), + .function(let type as any TypeProtocol), + .implicitlyUnwrappedOptional(let type as any TypeProtocol), + .member(let type as any TypeProtocol), + .missing(let type as any TypeProtocol), + .packExpansion(let type as any TypeProtocol), + .packReference(let type as any TypeProtocol), + .simple(let type as any TypeProtocol), + .suppressed(let type as any TypeProtocol), + .tuple(let type as any TypeProtocol): + type + } + return TypeSyntax(type._baseSyntax) + } + + public var _attributedSyntax: AttributedTypeSyntax? { + let type: any TypeProtocol = switch self { + case .composition(let type as any TypeProtocol), + .someOrAny(let type as any TypeProtocol), + .function(let type as any TypeProtocol), + .implicitlyUnwrappedOptional(let type as any TypeProtocol), + .member(let type as any TypeProtocol), + .missing(let type as any TypeProtocol), + .packExpansion(let type as any TypeProtocol), + .packReference(let type as any TypeProtocol), + .simple(let type as any TypeProtocol), + .suppressed(let type as any TypeProtocol), + .tuple(let type as any TypeProtocol): + type + } + return type._attributedSyntax + } + + /// Wrap a `TypeSyntax` (e.g. `Int?` or `MyStruct<[String]>!`). + public init(_ syntax: TypeSyntax) { + self.init(syntax, attributedSyntax: nil) + } + + public init(_ syntax: TypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { + // TODO: Move this weird initializer to an internal protocol if possible + let syntax: TypeSyntaxProtocol = attributedSyntax ?? syntax + if let type = NormalizedCompositionType(syntax) { + self = .composition(type) + } else if let type = NormalizedSomeOrAnyType(syntax) { + self = .someOrAny(type) + } else if let type = NormalizedFunctionType(syntax) { + self = .function(type) + } else if let type = NormalizedImplicitlyUnwrappedOptionalType(syntax) { + self = .implicitlyUnwrappedOptional(type) + } else if let type = NormalizedMemberType(syntax) { + self = .member(type) + } else if let type = NormalizedPackExpansionType(syntax) { + self = .packExpansion(type) + } else if let type = NormalizedPackReferenceType(syntax) { + self = .packReference(type) + } else if let type = NormalizedSimpleType(syntax) { + self = .simple(type) + } else if let type = NormalizedSuppressedType(syntax) { + self = .suppressed(type) + } else if let type = NormalizedTupleType(syntax) { + self = .tuple(type) + } else { + fatalError("TODO: Implement wrappers for all types of type syntax") + } + } + + // TODO: add an optional version to all type syntax wrappers maybe? + /// Allows string interpolation syntax to be used to express type syntax. + public init(stringInterpolation: SyntaxStringInterpolation) { + self.init(TypeSyntax(stringInterpolation: stringInterpolation)) + } +} From 9302ec91bfe4db05b9c26d5de8efa3ede58914e4 Mon Sep 17 00:00:00 2001 From: Alessio Nossa Date: Sun, 7 Apr 2024 17:51:14 +0200 Subject: [PATCH 03/17] Implement normalized function for Type --- Sources/MacroToolkit/Type.swift | 224 ++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) diff --git a/Sources/MacroToolkit/Type.swift b/Sources/MacroToolkit/Type.swift index 3aee304..9e97b5d 100644 --- a/Sources/MacroToolkit/Type.swift +++ b/Sources/MacroToolkit/Type.swift @@ -174,6 +174,200 @@ public enum Type: TypeProtocol, SyntaxExpressibleByStringInterpolation { } // TODO: Implement rest of conversions + + public func normalized() -> NormalizedType { + switch self { + case .array(let type): + var arrayTypeSyntax: ArrayTypeSyntax = type._baseSyntax + var attributedTypeSyntax: AttributedTypeSyntax? = type._attributedSyntax + let normalizedElement = Type(arrayTypeSyntax.element).normalized() + arrayTypeSyntax.element = TypeSyntax(normalizedElement._syntax) + attributedTypeSyntax?.baseType = TypeSyntax(arrayTypeSyntax) + + var base = "Array<\(arrayTypeSyntax.element)>" + if let attributedTypeSyntax { + base = base.addingAttributes(from: attributedTypeSyntax) + } + return NormalizedType(stringLiteral: base) + case .classRestriction(let type): + // Not handling `_attributedSyntax` because `classRestriction` cannot have any attribute + + // let normalizedType: NormalizedType = "AnyObject" + let normalizedType: NormalizedType = .simple(.init(.init( + leadingTrivia: type._baseSyntax.leadingTrivia, + name: .identifier("AnyObject"), + trailingTrivia: type._baseSyntax.trailingTrivia + ))) + return normalizedType + + case .composition(let type): + // Looks like there can only be simple types in composition, with no generics, and therefore we + // don't ned to recursively normalize + + return .composition(.init(type._baseSyntax, attributedSyntax: type._attributedSyntax)) + case .someOrAny(let type): + var someOrAnyTypeSyntax: SomeOrAnyTypeSyntax = type._baseSyntax + var attributedTypeSyntax: AttributedTypeSyntax? = type._attributedSyntax + + let normalizedConstraint = Type(someOrAnyTypeSyntax.constraint).normalized() + someOrAnyTypeSyntax.constraint = TypeSyntax(normalizedConstraint._syntax) + attributedTypeSyntax?.baseType = TypeSyntax(someOrAnyTypeSyntax) + + return .someOrAny(.init(someOrAnyTypeSyntax, attributedSyntax: attributedTypeSyntax)) + case .dictionary(let type): + var dictionaryTypeSyntax: DictionaryTypeSyntax = type._baseSyntax + var attributedTypeSyntax: AttributedTypeSyntax? = type._attributedSyntax + let normalizedKey = Type(dictionaryTypeSyntax.key).normalized() + let normalizedValue = Type(dictionaryTypeSyntax.value).normalized() + dictionaryTypeSyntax.key = TypeSyntax(normalizedKey._syntax) + dictionaryTypeSyntax.value = TypeSyntax(normalizedValue._syntax) + attributedTypeSyntax?.baseType = TypeSyntax(dictionaryTypeSyntax) + + var base = "Dictionary<\(dictionaryTypeSyntax.key), \(dictionaryTypeSyntax.value)>" + if let attributedTypeSyntax { + base = base.addingAttributes(from: attributedTypeSyntax) + } + return NormalizedType(stringLiteral: base) + + case .function(let type): + var functionTypeSyntax: FunctionTypeSyntax + var attributedTypeSyntax: AttributedTypeSyntax? = nil + if let attributedSyntax = type._attributedSyntax { + functionTypeSyntax = attributedSyntax.baseType.cast(FunctionTypeSyntax.self) + attributedTypeSyntax = attributedSyntax + } else { + functionTypeSyntax = type._baseSyntax + } + let normalizedReturnClause = Type(functionTypeSyntax.returnClause.type).normalized() + let arrayOfTupleElements = functionTypeSyntax.parameters.map { tupleElement in + let normalizedType = Type(tupleElement.type).normalized() + let updatedElementType = TypeSyntax(normalizedType._syntax) + var newTupleElement = tupleElement + newTupleElement.type = updatedElementType + return newTupleElement + } + functionTypeSyntax.parameters = .init(arrayOfTupleElements) + + functionTypeSyntax.returnClause.type = TypeSyntax(normalizedReturnClause._syntax) + attributedTypeSyntax?.baseType = TypeSyntax(functionTypeSyntax) + + return .function(.init(functionTypeSyntax, attributedSyntax: attributedTypeSyntax)) + + case .implicitlyUnwrappedOptional(let type): + var implicitlyUnwrappedOptionalTypeSyntax: ImplicitlyUnwrappedOptionalTypeSyntax = type._baseSyntax + var attributedTypeSyntax: AttributedTypeSyntax? = type._attributedSyntax + + let normalizedConstraint = Type(implicitlyUnwrappedOptionalTypeSyntax.wrappedType).normalized() + implicitlyUnwrappedOptionalTypeSyntax.wrappedType = TypeSyntax(normalizedConstraint._syntax) + attributedTypeSyntax?.baseType = TypeSyntax(implicitlyUnwrappedOptionalTypeSyntax) + + return .implicitlyUnwrappedOptional(.init(implicitlyUnwrappedOptionalTypeSyntax, attributedSyntax: attributedTypeSyntax)) + + case .member(let type): + var memberTypeSyntax: MemberTypeSyntax = type._baseSyntax + var attributedTypeSyntax: AttributedTypeSyntax? = type._attributedSyntax + let normalizedBaseType = Type(type._baseSyntax.baseType).normalized() + + memberTypeSyntax.genericArgumentClause = memberTypeSyntax.genericArgumentClause?.normalized() + + memberTypeSyntax.baseType = TypeSyntax(normalizedBaseType._syntax) + attributedTypeSyntax?.baseType = TypeSyntax(memberTypeSyntax) + + return .member(.init(memberTypeSyntax, attributedSyntax: attributedTypeSyntax)) + + case .metatype(let type): + let baseType = type._baseSyntax + let memberTypeSyntax = MemberTypeSyntax.init( + leadingTrivia: baseType.leadingTrivia, + baseType: baseType.baseType, + name: baseType.metatypeSpecifier, + trailingTrivia: baseType.trailingTrivia + ) + if var attributedSyntax = type._attributedSyntax { + attributedSyntax.baseType = TypeSyntax(memberTypeSyntax) + return .member(.init(memberTypeSyntax, attributedSyntax: attributedSyntax)) + } else { + return .member(.init(memberTypeSyntax)) + } + case .missing(let type): + return .missing(.init(type._baseSyntax, attributedSyntax: type._attributedSyntax)) + case .optional(let type): + var optionalTypeSyntax: OptionalTypeSyntax = type._baseSyntax + var attributedTypeSyntax: AttributedTypeSyntax? = type._attributedSyntax + let normalizedElement = Type(optionalTypeSyntax.wrappedType).normalized() + optionalTypeSyntax.wrappedType = TypeSyntax(normalizedElement._syntax) + attributedTypeSyntax?.baseType = TypeSyntax(optionalTypeSyntax) + +// let identifierSyntax = IdentifierTypeSyntax( +// leadingTrivia: optionalTypeSyntax.leadingTrivia, +// name: .identifier("Optional"), +// genericArgumentClause: .init( +// arguments: .init(arrayLiteral: .init(argument: optionalTypeSyntax.wrappedType)) +// ), +// trailingTrivia: optionalTypeSyntax.leadingTrivia +// ) + + var base = "Optional<\(optionalTypeSyntax.wrappedType)>" + if let attributedTypeSyntax { + base = base.addingAttributes(from: attributedTypeSyntax) + } + return NormalizedType(stringLiteral: base) + case .packExpansion(let type): + // Looks like there can only be simple identifiers in pack expansions, with no generics, and therefore we + // don't ned to recursively normalize + + return .packExpansion(.init(type._baseSyntax, attributedSyntax: type._attributedSyntax)) + case .packReference(let type): + // Looks like there can only be simple identifiers in pack references, with no generics, and therefore we + // don't ned to recursively normalize + + return .packReference(.init(type._baseSyntax, attributedSyntax: type._attributedSyntax)) + case .simple(let type): + if type.name == "Void" { + // TODO: Add trivia + return .tuple(.init(.init(elements: []))) + } + var identifierTypeSyntax: IdentifierTypeSyntax = type._baseSyntax + var attributedTypeSyntax: AttributedTypeSyntax? = type._attributedSyntax + + identifierTypeSyntax.genericArgumentClause = identifierTypeSyntax.genericArgumentClause?.normalized() + + attributedTypeSyntax?.baseType = TypeSyntax(identifierTypeSyntax) + + return .simple(.init(identifierTypeSyntax, attributedSyntax: attributedTypeSyntax)) + case .suppressed(let type): + // Normalizing recursively because it may be needed when https://github.com/apple/swift/issues/62906 + // is fixed. Not handling `_attributedSyntax` because seems like `suppressed` cannot have any attribute + var suppressedTypeSyntax: SuppressedTypeSyntax = type._baseSyntax + + let normalizedConstraint = Type(suppressedTypeSyntax.type).normalized() + suppressedTypeSyntax.type = TypeSyntax(normalizedConstraint._syntax) + + return .suppressed(.init(suppressedTypeSyntax)) + case .tuple(let type): + if type.elements.count == 1 { + let child = type.elements[0] + if case .tuple(_) = child { + return child.normalized() + } + } + + var tupleTypeSyntax: TupleTypeSyntax = type._baseSyntax + var attributedTypeSyntax: AttributedTypeSyntax? = type._attributedSyntax + + let arrayOfTupleElements = tupleTypeSyntax.elements.map { tupleElement in + let normalizedType = Type(tupleElement.type).normalized() + let updatedElementType = TypeSyntax(normalizedType._syntax) + var newTupleElement = tupleElement + newTupleElement.type = updatedElementType + return newTupleElement + } + tupleTypeSyntax.elements = .init(arrayOfTupleElements) + + attributedTypeSyntax?.baseType = TypeSyntax(tupleTypeSyntax) + return .tuple(.init(tupleTypeSyntax, attributedSyntax: attributedTypeSyntax)) + } + } } extension Type? { @@ -186,3 +380,33 @@ extension Type? { } } } + +// MARK: Utilities for normalization + +fileprivate extension String { + func addingAttributes(from attributedType: AttributedTypeSyntax) -> String { + var updatedString = self + updatedString = "\(attributedType.attributes)\(self)" + + if let specifier = attributedType.specifier { + updatedString = "\(specifier)\(updatedString)" + } + + return updatedString + } +} + +fileprivate extension GenericArgumentClauseSyntax { + func normalized() -> Self { + var genericArgumentClause = self + let arrayOfGenericArgumentClauseArguments = genericArgumentClause.arguments.map { tupleElement in + let normalizedType = Type(tupleElement.argument).normalized() + let updatedElementType = TypeSyntax(normalizedType._syntax) + var newTupleElement = tupleElement + newTupleElement.argument = updatedElementType + return newTupleElement + } + genericArgumentClause.arguments = .init(arrayOfGenericArgumentClauseArguments) + return genericArgumentClause + } +} From 056ba4d2130c9d5007b67e85f6645d786c5d8b33 Mon Sep 17 00:00:00 2001 From: Alessio Nossa Date: Sun, 7 Apr 2024 17:51:46 +0200 Subject: [PATCH 04/17] Update normalizedDescription implementation for Type --- Sources/MacroToolkit/Type.swift | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/Sources/MacroToolkit/Type.swift b/Sources/MacroToolkit/Type.swift index 9e97b5d..058bcf7 100644 --- a/Sources/MacroToolkit/Type.swift +++ b/Sources/MacroToolkit/Type.swift @@ -135,20 +135,7 @@ public enum Type: TypeProtocol, SyntaxExpressibleByStringInterpolation { /// A normalized description of the type (e.g. for `()` this would be `Void`). public var normalizedDescription: String { - // TODO: Implement proper type normalization - // TODO: Normalize types nested within the type too (e.g. the parameter types of a function type) - if let tupleSyntax = _syntax.as(TupleTypeSyntax.self) { - if tupleSyntax.elements.count == 0 { - return "Void" - } else if tupleSyntax.elements.count == 1, let element = tupleSyntax.elements.first { - // TODO: Can we assume that we won't get a single-element tuple with a label (which would be invalid anyway)? - return element.type.withoutTrivia().description - } else { - return _syntax.withoutTrivia().description - } - } else { - return _syntax.withoutTrivia().description - } + self.normalized()._syntax.withoutTrivia().description } /// Gets whether the type is a void type (i.e. `Void`, `()`, `(Void)`, `((((()))))`, etc.). From 2640182095420b28c88cbdecd2c393ed85c0bd81 Mon Sep 17 00:00:00 2001 From: Alessio Nossa Date: Sun, 7 Apr 2024 17:52:06 +0200 Subject: [PATCH 05/17] Update isVoid implementation for Type --- Sources/MacroToolkit/Type.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/MacroToolkit/Type.swift b/Sources/MacroToolkit/Type.swift index 058bcf7..141a97d 100644 --- a/Sources/MacroToolkit/Type.swift +++ b/Sources/MacroToolkit/Type.swift @@ -140,7 +140,7 @@ public enum Type: TypeProtocol, SyntaxExpressibleByStringInterpolation { /// Gets whether the type is a void type (i.e. `Void`, `()`, `(Void)`, `((((()))))`, etc.). public var isVoid: Bool { - normalizedDescription == "Void" + normalizedDescription == "\(Void.self)" } // TODO: Generate type conversions with macro? From 0b789834eb118f76c62ee1a955bc5227602df787 Mon Sep 17 00:00:00 2001 From: Alessio Nossa Date: Sun, 7 Apr 2024 17:52:36 +0200 Subject: [PATCH 06/17] Implement tests for normalization --- .../MacroToolkitTests/MacroToolkitTests.swift | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) diff --git a/Tests/MacroToolkitTests/MacroToolkitTests.swift b/Tests/MacroToolkitTests/MacroToolkitTests.swift index 4b82a3e..3e50948 100644 --- a/Tests/MacroToolkitTests/MacroToolkitTests.swift +++ b/Tests/MacroToolkitTests/MacroToolkitTests.swift @@ -678,4 +678,195 @@ final class MacroToolkitTests: XCTestCase { macros: testMacros ) } + + func testSimpleDictionaryNormalizarion() { +// let decl: DeclSyntax = """ +// var items: [Int] { +// [1, 2].map { (_) in +// 3 +// } +// } +// """ + + let decl: DeclSyntax = """ + var items: [String: Int] + """ + + guard let variable = Decl(decl).asVariable else { + XCTFail("Expected decl to be variable") + return + } + + guard let type = variable.bindings[0].type else { + XCTFail("Expected type of variable") + return + } + + let dcs = type.normalized() + + XCTAssertEqual(dcs.description, "Dictionary") + } + + func testComplexDictionaryWithArrayNormalizarion() { + + let type: Type = "[String: [[Any]]]" + + let dcs = type.normalized() + + XCTAssertEqual(dcs.description, "Dictionary>>") + } + + func testNormalizedDecription() { + let type: Type = "(())" + + XCTAssertEqual(type.normalizedDescription, "()") + } + + func testIsVoid() { + let type: Type = "(())" + + XCTAssert(type.isVoid) + } + + func testNormalizedVoid() { + let type: Type = "((()))" + + let dcs = type.normalized() + + XCTAssertEqual(dcs.description, "()") + } + + func testNormalizedAttributedTuple() { + + let declSyntax: DeclSyntax = """ + let interestingType: (inout [Int], String) -> Float = { _,_ in + return 3 + } + """ + + guard let variable = Decl(declSyntax).asVariable else { + XCTFail("Expected decl to be variable") + return + } + + guard let type = variable.bindings[0].type else { + XCTFail("Expected type in bindings") + return + } + + let normalizedType = type.normalized() + + XCTAssertEqual(normalizedType.description, "(inout Array, String) -> Float") + } + + func testMemeberWithGenericNormalizarion() { + class TestClass { + struct TestMemberStruct { + var value: Value + } + } + + let type: `Type` = "(inout TestClass.TestMemberStruct<[any Hashable]>) -> ()" + + let dcs = type.normalized() + + XCTAssertEqual(dcs.description, "(inout TestClass.TestMemberStruct>) -> ()") + } + + func testMetatypeNormalizarion() { + let type: `Type` = "(inout TestClass.Type) -> Void" + + let dcs = type.normalized() + + XCTAssertEqual(dcs.description, "(inout TestClass.Type) -> ()") + } + + func testSuppressed() { + let type: `Type` = "~Copyable" + + let dcs = type.normalized() + + XCTAssertEqual(dcs.description, "~Copyable") + } + + func testNormalizedOptional() { + let type: `Type` = "((inout Int?) -> Void)?" + + let dcs = type.normalized() + + XCTAssertEqual(dcs.description, "Optional<((inout Optional) -> ())>") + } + + func testNormalizedSimple() { + let type: `Type` = "MyClass<[String]>" + + let dcs = type.normalized() + + XCTAssertEqual(dcs.description, "MyClass>") + } + + func testComposedNormalized() { + let type1: `Type` = "any Decodable & Identifiable" + let type2: `Type` = "(inout any Decodable & Identifiable) -> Void" + let type3: `Type` = "(inout Decodable & Codable) -> ([Int])" + + let dcs1 = type1.normalized() + let dcs2 = type2.normalized() + let dcs3 = type3.normalized() + + XCTAssertEqual(dcs1.description, "any Decodable & Identifiable") + XCTAssertEqual(dcs2.description, "(inout any Decodable & Identifiable) -> ()") + XCTAssertEqual(dcs3.description, "(inout Decodable & Codable) -> (Array)") + } + + func testSomeOrAnyNormalized() { + let typeComosed: `Type` = "any Decodable & Identifiable" + let typeGeneric: `Type` = "some Sequence<[String]>" + + let dcs = typeComosed.normalized() + let dcss = typeGeneric.normalized() + + XCTAssertEqual(dcs.description, "any Decodable & Identifiable") + XCTAssertEqual(dcss.description, "some Sequence>") + } + + func testImplicitylUnwrappedOptionalNormalized() { + let type1: `Type` = "MyClass<[String]>!" + let type2: `Type` = "((inout [String: Int]) -> Void)!" + + let dcs = type1.normalized() + let dcss = type2.normalized() + + XCTAssertEqual(dcs.description, "MyClass>!") + XCTAssertEqual(dcss.description, "((inout Dictionary) -> ())!") + } + + @available(macOS 14.0.0, *) + func testPackExpansionNormalized() { + + struct Tuple { + var elements: (repeat each Elements) + + subscript(keyPath: KeyPath<(repeat each Elements), Value>) -> Value { + return elements[keyPath: keyPath] + } + } + + let type1: `Type` = "(repeat each Elements)" + let type2: `Type` = "(keyPath: KeyPath<(repeat each Elements), Value>) -> Value" + + let dcs = type1.normalized() + let dcss = type2.normalized() + + XCTAssertEqual(dcs.description, "(repeat each Elements)") + XCTAssertEqual(dcss.description, "(keyPath: KeyPath<(repeat each Elements), Value>) -> Value") + } + + func testPackReferenceNormalized() { + let type: `Type` = "Tuple" + + let dcs = type.normalized() + + XCTAssertEqual(dcs.description, "Tuple") + } } From bb99f98d420903ca843ae1e2248610c5610d2f4f Mon Sep 17 00:00:00 2001 From: Alessio Nossa Date: Sun, 7 Apr 2024 18:11:08 +0200 Subject: [PATCH 07/17] Add isOptional to type --- Sources/MacroToolkit/Type.swift | 8 ++++++++ Tests/MacroToolkitTests/MacroToolkitTests.swift | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/Sources/MacroToolkit/Type.swift b/Sources/MacroToolkit/Type.swift index 0575464..5796a3f 100644 --- a/Sources/MacroToolkit/Type.swift +++ b/Sources/MacroToolkit/Type.swift @@ -142,6 +142,14 @@ public enum `Type`: TypeProtocol, SyntaxExpressibleByStringInterpolation { public var isVoid: Bool { normalizedDescription == "\(Void.self)" } + + /// Gets whether the type is optional + public var isOptional: Bool { + if case .simple(let normalizedSimpleType) = self.normalized() { + return normalizedSimpleType.name == "Optional" + } + return false + } // TODO: Generate type conversions with macro? /// Attempts to get the type as a simple type. diff --git a/Tests/MacroToolkitTests/MacroToolkitTests.swift b/Tests/MacroToolkitTests/MacroToolkitTests.swift index 9bbafd0..cbe2799 100644 --- a/Tests/MacroToolkitTests/MacroToolkitTests.swift +++ b/Tests/MacroToolkitTests/MacroToolkitTests.swift @@ -869,4 +869,14 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(dcs.description, "Tuple") } + + func testIsOptional() { + let type1: `Type` = "Int?" + let type2: `Type` = "Optional" + let type3: `Type` = "Array" + + XCTAssert(type1.isOptional) + XCTAssert(type2.isOptional) + XCTAssertFalse(type3.isOptional) + } } From b39e8e651df467b0fe3a55d4d15b555359034763 Mon Sep 17 00:00:00 2001 From: Alessio Nossa Date: Sun, 7 Apr 2024 18:31:59 +0200 Subject: [PATCH 08/17] Add convenience properties to NormalizedType --- Sources/MacroToolkit/NormalizedType.swift | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Sources/MacroToolkit/NormalizedType.swift b/Sources/MacroToolkit/NormalizedType.swift index bbba9b1..87c0114 100644 --- a/Sources/MacroToolkit/NormalizedType.swift +++ b/Sources/MacroToolkit/NormalizedType.swift @@ -100,4 +100,29 @@ public enum NormalizedType: TypeProtocol, SyntaxExpressibleByStringInterpolation public init(stringInterpolation: SyntaxStringInterpolation) { self.init(TypeSyntax(stringInterpolation: stringInterpolation)) } + + /// Gets whether the type is optional + public var isOptional: Bool { + if case .simple(let simpleType) = self { + return simpleType.name == "Optional" + } + return false + } + + // TODO: Generate type conversions with macro? + /// Attempts to get the type as a simple type. + public var asSimpleType: NormalizedSimpleType? { + switch self { + case .simple(let type): type + default: nil + } + } + + /// Attempts to get the type as a function type. + public var asFunctionType: NormalizedFunctionType? { + switch self { + case .function(let type): type + default: nil + } + } } From fa39ef05bf94fa733ac466ce280e34ea0913eb0c Mon Sep 17 00:00:00 2001 From: Alessio Nossa Date: Wed, 10 Apr 2024 19:13:10 +0200 Subject: [PATCH 09/17] Cleanup tests for normalization and add missing cases --- .../MacroToolkitTests/MacroToolkitTests.swift | 268 +++++++++--------- 1 file changed, 138 insertions(+), 130 deletions(-) diff --git a/Tests/MacroToolkitTests/MacroToolkitTests.swift b/Tests/MacroToolkitTests/MacroToolkitTests.swift index cbe2799..a077e1a 100644 --- a/Tests/MacroToolkitTests/MacroToolkitTests.swift +++ b/Tests/MacroToolkitTests/MacroToolkitTests.swift @@ -679,204 +679,212 @@ final class MacroToolkitTests: XCTestCase { ) } - func testSimpleDictionaryNormalizarion() { -// let decl: DeclSyntax = """ -// var items: [Int] { -// [1, 2].map { (_) in -// 3 -// } -// } -// """ + func testNormalizedDecriptionMultipleVoid() { + let type: `Type` = "((()))" - let decl: DeclSyntax = """ - var items: [String: Int] - """ - - guard let variable = Decl(decl).asVariable else { - XCTFail("Expected decl to be variable") - return - } + XCTAssertEqual(type.normalizedDescription, "()") + } + + func testIsVoid() { + let type: `Type` = "(())" - guard let type = variable.bindings[0].type else { - XCTFail("Expected type of variable") - return - } + XCTAssert(type.isVoid) + } + + func testIsOptional() { + let type1: `Type` = "Int?" + let type2: `Type` = "Optional" + let type3: `Type` = "Array" - let dcs = type.normalized() + XCTAssert(type1.isOptional) + XCTAssert(type2.isOptional) + XCTAssertFalse(type3.isOptional) + } + + func testNormalizedVoid() { + let type: `Type` = "((()))" - XCTAssertEqual(dcs.description, "Dictionary") + let normalizedType = type.normalized() + + XCTAssertEqual(normalizedType.description, "()") } - func testComplexDictionaryWithArrayNormalizarion() { - - let type: Type = "[String: [[Any]]]" + func testNestedArraysNormalizarion() { + let type: `Type` = "[[Int]]" - let dcs = type.normalized() + let normalizedType = type.normalized() - XCTAssertEqual(dcs.description, "Dictionary>>") + XCTAssertEqual(normalizedType.description, "Array>") } - func testNormalizedDecription() { - let type: Type = "(())" + func testClassRestrictionNormalized() { + let decl: DeclSyntax = """ + protocol TestProtocol: class { } + """ - XCTAssertEqual(type.normalizedDescription, "()") + guard let inheritedTypes = decl.as(ProtocolDeclSyntax.self)?.inheritanceClause?.inheritedTypes, + let classRestrictionTypeSyntax = inheritedTypes.first?.type.as(ClassRestrictionTypeSyntax.self) else { + XCTFail("Expected class restriction in inheritance clause") + return + } + + let type: `Type` = .classRestriction(.init(classRestrictionTypeSyntax)) + + let normalizedType = type.normalized() + + XCTAssertEqual(normalizedType.description, "AnyObject") } - func testIsVoid() { - let type: Type = "(())" + func testCompositionNormalized() { + let type1: `Type` = "any Decodable & Identifiable" + let type2: `Type` = "(inout any Decodable & Identifiable) -> Void" + let type3: `Type` = "(inout Decodable & Codable) -> ([Int])" - XCTAssert(type.isVoid) + let normalizedType1 = type1.normalized() + let normalizedType2 = type2.normalized() + let normalizedType3 = type3.normalized() + + XCTAssertEqual(normalizedType1.description, "any Decodable & Identifiable") + XCTAssertEqual(normalizedType2.description, "(inout any Decodable & Identifiable) -> ()") + XCTAssertEqual(normalizedType3.description, "(inout Decodable & Codable) -> (Array)") } - func testNormalizedVoid() { - let type: Type = "((()))" - - let dcs = type.normalized() + func testSomeOrAnyNormalized() { + let typeCompsed: `Type` = "any Decodable & Identifiable" + let typeGeneric: `Type` = "some Sequence<[String]>" + + let normalizedTypeComposed = typeCompsed.normalized() + let normalizedTypeGeneric = typeGeneric.normalized() - XCTAssertEqual(dcs.description, "()") + XCTAssertEqual(normalizedTypeComposed.description, "any Decodable & Identifiable") + XCTAssertEqual(normalizedTypeGeneric.description, "some Sequence>") } - func testNormalizedAttributedTuple() { + func testSimpleDictionaryNormalized() { + let decl: DeclSyntax = """ + var items: [String: Int] + """ - let declSyntax: DeclSyntax = """ - let interestingType: (inout [Int], String) -> Float = { _,_ in - return 3 - } - """ - - guard let variable = Decl(declSyntax).asVariable else { + guard let variable = Decl(decl).asVariable else { XCTFail("Expected decl to be variable") return } guard let type = variable.bindings[0].type else { - XCTFail("Expected type in bindings") + XCTFail("Expected type of variable") return } let normalizedType = type.normalized() - XCTAssertEqual(normalizedType.description, "(inout Array, String) -> Float") + XCTAssertEqual(normalizedType.description, "Dictionary") } - func testMemeberWithGenericNormalizarion() { - class TestClass { - struct TestMemberStruct { - var value: Value - } - } - - let type: `Type` = "(inout TestClass.TestMemberStruct<[any Hashable]>) -> ()" + func testDictionaryWithNestedArrayNormalized() { + let type: `Type` = "[String: [[Any]]]" - let dcs = type.normalized() + let normalizedType = type.normalized() - XCTAssertEqual(dcs.description, "(inout TestClass.TestMemberStruct>) -> ()") + XCTAssertEqual(normalizedType.description, "Dictionary>>") } - func testMetatypeNormalizarion() { - let type: `Type` = "(inout TestClass.Type) -> Void" + func testFunctionNormalized() { + let type: `Type` = "(testParameter: [Int]) -> Void" - let dcs = type.normalized() + let normalizedType = type.normalized() - XCTAssertEqual(dcs.description, "(inout TestClass.Type) -> ()") + XCTAssertEqual(normalizedType.description, "(testParameter: Array) -> ()") } - func testSuppressed() { - let type: `Type` = "~Copyable" - - let dcs = type.normalized() + func testImplicitylUnwrappedOptionalNormalized() { + let type1: `Type` = "MyClass<[String]>!" + let type2: `Type` = "((inout [String: Int]) -> Void)!" - XCTAssertEqual(dcs.description, "~Copyable") - } - - func testNormalizedOptional() { - let type: `Type` = "((inout Int?) -> Void)?" - - let dcs = type.normalized() + let normalizedType1 = type1.normalized() + let normalizedType2 = type2.normalized() - XCTAssertEqual(dcs.description, "Optional<((inout Optional) -> ())>") + XCTAssertEqual(normalizedType1.description, "MyClass>!") + XCTAssertEqual(normalizedType2.description, "((inout Dictionary) -> ())!") } - func testNormalizedSimple() { - let type: `Type` = "MyClass<[String]>" - - let dcs = type.normalized() - - XCTAssertEqual(dcs.description, "MyClass>") - } - - func testComposedNormalized() { - let type1: `Type` = "any Decodable & Identifiable" - let type2: `Type` = "(inout any Decodable & Identifiable) -> Void" - let type3: `Type` = "(inout Decodable & Codable) -> ([Int])" + func testMemeberWithGenericNormalized() { + let type: `Type` = "(inout TestClass.TestMemberStruct<[any Hashable]>) -> ()" - let dcs1 = type1.normalized() - let dcs2 = type2.normalized() - let dcs3 = type3.normalized() + let normalizedType = type.normalized() - XCTAssertEqual(dcs1.description, "any Decodable & Identifiable") - XCTAssertEqual(dcs2.description, "(inout any Decodable & Identifiable) -> ()") - XCTAssertEqual(dcs3.description, "(inout Decodable & Codable) -> (Array)") + XCTAssertEqual(normalizedType.description, "(inout TestClass.TestMemberStruct>) -> ()") } - func testSomeOrAnyNormalized() { - let typeComosed: `Type` = "any Decodable & Identifiable" - let typeGeneric: `Type` = "some Sequence<[String]>" + func testMetatypeNormalized() { + let type: `Type` = "(inout TestClass.Type) -> Void" - let dcs = typeComosed.normalized() - let dcss = typeGeneric.normalized() + let normalizedType = type.normalized() - XCTAssertEqual(dcs.description, "any Decodable & Identifiable") - XCTAssertEqual(dcss.description, "some Sequence>") + XCTAssertEqual(normalizedType.description, "(inout TestClass.Type) -> ()") } - func testImplicitylUnwrappedOptionalNormalized() { - let type1: `Type` = "MyClass<[String]>!" - let type2: `Type` = "((inout [String: Int]) -> Void)!" - - let dcs = type1.normalized() - let dcss = type2.normalized() + func testOptionalNormalized() { + let type: `Type` = "((inout Int?) -> Void)?" + + let normalizedType = type.normalized() - XCTAssertEqual(dcs.description, "MyClass>!") - XCTAssertEqual(dcss.description, "((inout Dictionary) -> ())!") + XCTAssertEqual(normalizedType.description, "Optional<((inout Optional) -> ())>") } - @available(macOS 14.0.0, *) func testPackExpansionNormalized() { - - struct Tuple { - var elements: (repeat each Elements) - - subscript(keyPath: KeyPath<(repeat each Elements), Value>) -> Value { - return elements[keyPath: keyPath] - } - } - let type1: `Type` = "(repeat each Elements)" let type2: `Type` = "(keyPath: KeyPath<(repeat each Elements), Value>) -> Value" - let dcs = type1.normalized() - let dcss = type2.normalized() + let normalizedType1 = type1.normalized() + let normalizedType2 = type2.normalized() - XCTAssertEqual(dcs.description, "(repeat each Elements)") - XCTAssertEqual(dcss.description, "(keyPath: KeyPath<(repeat each Elements), Value>) -> Value") + XCTAssertEqual(normalizedType1.description, "(repeat each Elements)") + XCTAssertEqual(normalizedType2.description, "(keyPath: KeyPath<(repeat each Elements), Value>) -> Value") } func testPackReferenceNormalized() { let type: `Type` = "Tuple" - let dcs = type.normalized() + let normalizedType = type.normalized() - XCTAssertEqual(dcs.description, "Tuple") + XCTAssertEqual(normalizedType.description, "Tuple") } - func testIsOptional() { - let type1: `Type` = "Int?" - let type2: `Type` = "Optional" - let type3: `Type` = "Array" + func testSimpleNormalized() { + let type: `Type` = "MyClass<[String]>" - XCTAssert(type1.isOptional) - XCTAssert(type2.isOptional) - XCTAssertFalse(type3.isOptional) + let normalizedType = type.normalized() + + XCTAssertEqual(normalizedType.description, "MyClass>") + } + + func testSuppressed() { + let type: `Type` = "~Copyable" + + let normalizedType = type.normalized() + + XCTAssertEqual(normalizedType.description, "~Copyable") + } + + func testNormalizedAttributedTuple() { + let declSyntax: DeclSyntax = """ + let interestingType: (inout [Int], String) -> Float = { _,_ in + return 3 + } + """ + + guard let variable = Decl(declSyntax).asVariable else { + XCTFail("Expected decl to be variable") + return + } + + guard let type = variable.bindings[0].type else { + XCTFail("Expected type in bindings") + return + } + + let normalizedType = type.normalized() + + XCTAssertEqual(normalizedType.description, "(inout Array, String) -> Float") } } From fc53c29ae87017274278b2791636809a61d94789 Mon Sep 17 00:00:00 2001 From: Alessio Nossa Date: Wed, 10 Apr 2024 19:20:21 +0200 Subject: [PATCH 10/17] Update comments for Type --- Sources/MacroToolkit/Type.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Sources/MacroToolkit/Type.swift b/Sources/MacroToolkit/Type.swift index 5796a3f..73d45b8 100644 --- a/Sources/MacroToolkit/Type.swift +++ b/Sources/MacroToolkit/Type.swift @@ -319,7 +319,6 @@ public enum `Type`: TypeProtocol, SyntaxExpressibleByStringInterpolation { return .packReference(.init(type._baseSyntax, attributedSyntax: type._attributedSyntax)) case .simple(let type): if type.name == "Void" { - // TODO: Add trivia return .tuple(.init(.init(elements: []))) } var identifierTypeSyntax: IdentifierTypeSyntax = type._baseSyntax @@ -379,6 +378,11 @@ extension Type? { // MARK: Utilities for normalization fileprivate extension String { + /// Builds a `String` that can then be used for interpolation attaching the attributes and + /// the specifiers of the attributed type syntax. + /// - Parameter attributedType: The `AttributedTypeSyntax` which `attributes` + /// and `specifier` should be used to prefix the string. + /// - Returns: A `String` with elements from the attributed type syntax attached to the original `String`. func addingAttributes(from attributedType: AttributedTypeSyntax) -> String { var updatedString = self updatedString = "\(attributedType.attributes)\(self)" @@ -392,6 +396,9 @@ fileprivate extension String { } fileprivate extension GenericArgumentClauseSyntax { + /// Normalize the arguments of the generic. + /// - Returns: An updated version of the generic argument clause with + /// the arguments types normalized. func normalized() -> Self { var genericArgumentClause = self let arrayOfGenericArgumentClauseArguments = genericArgumentClause.arguments.map { tupleElement in From c1de1a52d446babb31db064283798bdc0d3b1585 Mon Sep 17 00:00:00 2001 From: Alessio Nossa Date: Wed, 10 Apr 2024 23:58:18 +0200 Subject: [PATCH 11/17] Improve tests naming and add case from examples --- .../MacroToolkitTests/MacroToolkitTests.swift | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/Tests/MacroToolkitTests/MacroToolkitTests.swift b/Tests/MacroToolkitTests/MacroToolkitTests.swift index a077e1a..dc75e89 100644 --- a/Tests/MacroToolkitTests/MacroToolkitTests.swift +++ b/Tests/MacroToolkitTests/MacroToolkitTests.swift @@ -701,7 +701,7 @@ final class MacroToolkitTests: XCTestCase { XCTAssertFalse(type3.isOptional) } - func testNormalizedVoid() { + func testNormalizationVoid() { let type: `Type` = "((()))" let normalizedType = type.normalized() @@ -709,7 +709,15 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(normalizedType.description, "()") } - func testNestedArraysNormalizarion() { + func testNormalizationMultipleVoidWithOptional() { + let type: `Type` = "(((((()))?)))" + + let normalizedType = type.normalized() + + XCTAssertEqual(normalizedType.description, "(Optional<()>)") + } + + func testNormalizationNestedArrays() { let type: `Type` = "[[Int]]" let normalizedType = type.normalized() @@ -717,7 +725,7 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(normalizedType.description, "Array>") } - func testClassRestrictionNormalized() { + func testNormalizationClassRestriction() { let decl: DeclSyntax = """ protocol TestProtocol: class { } """ @@ -735,7 +743,7 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(normalizedType.description, "AnyObject") } - func testCompositionNormalized() { + func testNormalizationComposition() { let type1: `Type` = "any Decodable & Identifiable" let type2: `Type` = "(inout any Decodable & Identifiable) -> Void" let type3: `Type` = "(inout Decodable & Codable) -> ([Int])" @@ -749,7 +757,7 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(normalizedType3.description, "(inout Decodable & Codable) -> (Array)") } - func testSomeOrAnyNormalized() { + func testNormalizationSomeOrAny() { let typeCompsed: `Type` = "any Decodable & Identifiable" let typeGeneric: `Type` = "some Sequence<[String]>" @@ -760,7 +768,7 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(normalizedTypeGeneric.description, "some Sequence>") } - func testSimpleDictionaryNormalized() { + func testNormalizationSimpleDictionary() { let decl: DeclSyntax = """ var items: [String: Int] """ @@ -780,7 +788,7 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(normalizedType.description, "Dictionary") } - func testDictionaryWithNestedArrayNormalized() { + func testNormalizationDictionaryWithNestedArray() { let type: `Type` = "[String: [[Any]]]" let normalizedType = type.normalized() @@ -788,7 +796,7 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(normalizedType.description, "Dictionary>>") } - func testFunctionNormalized() { + func testNormalizationFunction() { let type: `Type` = "(testParameter: [Int]) -> Void" let normalizedType = type.normalized() @@ -796,7 +804,7 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(normalizedType.description, "(testParameter: Array) -> ()") } - func testImplicitylUnwrappedOptionalNormalized() { + func testNormalizationImplicitylUnwrappedOptional() { let type1: `Type` = "MyClass<[String]>!" let type2: `Type` = "((inout [String: Int]) -> Void)!" @@ -807,7 +815,7 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(normalizedType2.description, "((inout Dictionary) -> ())!") } - func testMemeberWithGenericNormalized() { + func testNormalizationMemeberWithGeneric() { let type: `Type` = "(inout TestClass.TestMemberStruct<[any Hashable]>) -> ()" let normalizedType = type.normalized() @@ -815,7 +823,7 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(normalizedType.description, "(inout TestClass.TestMemberStruct>) -> ()") } - func testMetatypeNormalized() { + func testNormalizationMetatype() { let type: `Type` = "(inout TestClass.Type) -> Void" let normalizedType = type.normalized() @@ -823,7 +831,7 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(normalizedType.description, "(inout TestClass.Type) -> ()") } - func testOptionalNormalized() { + func testNormalizationOptional() { let type: `Type` = "((inout Int?) -> Void)?" let normalizedType = type.normalized() @@ -831,7 +839,7 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(normalizedType.description, "Optional<((inout Optional) -> ())>") } - func testPackExpansionNormalized() { + func testNormalizationPackExpansion() { let type1: `Type` = "(repeat each Elements)" let type2: `Type` = "(keyPath: KeyPath<(repeat each Elements), Value>) -> Value" @@ -842,7 +850,7 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(normalizedType2.description, "(keyPath: KeyPath<(repeat each Elements), Value>) -> Value") } - func testPackReferenceNormalized() { + func testNormalizationPackReference() { let type: `Type` = "Tuple" let normalizedType = type.normalized() @@ -850,7 +858,7 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(normalizedType.description, "Tuple") } - func testSimpleNormalized() { + func testNormalizationSimple() { let type: `Type` = "MyClass<[String]>" let normalizedType = type.normalized() @@ -858,7 +866,7 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(normalizedType.description, "MyClass>") } - func testSuppressed() { + func testNormalizationSuppressed() { let type: `Type` = "~Copyable" let normalizedType = type.normalized() @@ -866,7 +874,7 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(normalizedType.description, "~Copyable") } - func testNormalizedAttributedTuple() { + func testNormalizationAttributedTuple() { let declSyntax: DeclSyntax = """ let interestingType: (inout [Int], String) -> Float = { _,_ in return 3 From 71aff30211ed934ad0f24d5287ad604b5bbd758e Mon Sep 17 00:00:00 2001 From: Alessio Nossa Date: Sat, 20 Jul 2024 11:32:20 +0200 Subject: [PATCH 12/17] Update code formatting --- Sources/MacroToolkit/NormalizedType.swift | 40 +++++++++++------------ Sources/MacroToolkit/Type.swift | 12 ++----- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/Sources/MacroToolkit/NormalizedType.swift b/Sources/MacroToolkit/NormalizedType.swift index 87c0114..81b8d70 100644 --- a/Sources/MacroToolkit/NormalizedType.swift +++ b/Sources/MacroToolkit/NormalizedType.swift @@ -29,16 +29,16 @@ public enum NormalizedType: TypeProtocol, SyntaxExpressibleByStringInterpolation public var _baseSyntax: TypeSyntax { let type: any TypeProtocol = switch self { case .composition(let type as any TypeProtocol), - .someOrAny(let type as any TypeProtocol), - .function(let type as any TypeProtocol), - .implicitlyUnwrappedOptional(let type as any TypeProtocol), - .member(let type as any TypeProtocol), - .missing(let type as any TypeProtocol), - .packExpansion(let type as any TypeProtocol), - .packReference(let type as any TypeProtocol), - .simple(let type as any TypeProtocol), - .suppressed(let type as any TypeProtocol), - .tuple(let type as any TypeProtocol): + .someOrAny(let type as any TypeProtocol), + .function(let type as any TypeProtocol), + .implicitlyUnwrappedOptional(let type as any TypeProtocol), + .member(let type as any TypeProtocol), + .missing(let type as any TypeProtocol), + .packExpansion(let type as any TypeProtocol), + .packReference(let type as any TypeProtocol), + .simple(let type as any TypeProtocol), + .suppressed(let type as any TypeProtocol), + .tuple(let type as any TypeProtocol): type } return TypeSyntax(type._baseSyntax) @@ -47,16 +47,16 @@ public enum NormalizedType: TypeProtocol, SyntaxExpressibleByStringInterpolation public var _attributedSyntax: AttributedTypeSyntax? { let type: any TypeProtocol = switch self { case .composition(let type as any TypeProtocol), - .someOrAny(let type as any TypeProtocol), - .function(let type as any TypeProtocol), - .implicitlyUnwrappedOptional(let type as any TypeProtocol), - .member(let type as any TypeProtocol), - .missing(let type as any TypeProtocol), - .packExpansion(let type as any TypeProtocol), - .packReference(let type as any TypeProtocol), - .simple(let type as any TypeProtocol), - .suppressed(let type as any TypeProtocol), - .tuple(let type as any TypeProtocol): + .someOrAny(let type as any TypeProtocol), + .function(let type as any TypeProtocol), + .implicitlyUnwrappedOptional(let type as any TypeProtocol), + .member(let type as any TypeProtocol), + .missing(let type as any TypeProtocol), + .packExpansion(let type as any TypeProtocol), + .packReference(let type as any TypeProtocol), + .simple(let type as any TypeProtocol), + .suppressed(let type as any TypeProtocol), + .tuple(let type as any TypeProtocol): type } return type._attributedSyntax diff --git a/Sources/MacroToolkit/Type.swift b/Sources/MacroToolkit/Type.swift index 73d45b8..139d7f7 100644 --- a/Sources/MacroToolkit/Type.swift +++ b/Sources/MacroToolkit/Type.swift @@ -135,7 +135,7 @@ public enum `Type`: TypeProtocol, SyntaxExpressibleByStringInterpolation { /// A normalized description of the type (e.g. for `()` this would be `Void`). public var normalizedDescription: String { - self.normalized()._syntax.withoutTrivia().description + normalized()._syntax.withoutTrivia().description } /// Gets whether the type is a void type (i.e. `Void`, `()`, `(Void)`, `((((()))))`, etc.). @@ -186,21 +186,19 @@ public enum `Type`: TypeProtocol, SyntaxExpressibleByStringInterpolation { return NormalizedType(stringLiteral: base) case .classRestriction(let type): // Not handling `_attributedSyntax` because `classRestriction` cannot have any attribute - - // let normalizedType: NormalizedType = "AnyObject" let normalizedType: NormalizedType = .simple(.init(.init( leadingTrivia: type._baseSyntax.leadingTrivia, name: .identifier("AnyObject"), trailingTrivia: type._baseSyntax.trailingTrivia ))) - return normalizedType + return normalizedType case .composition(let type): // Looks like there can only be simple types in composition, with no generics, and therefore we // don't ned to recursively normalize return .composition(.init(type._baseSyntax, attributedSyntax: type._attributedSyntax)) - case .someOrAny(let type): + case .someOrAny(let type): var someOrAnyTypeSyntax: SomeOrAnyTypeSyntax = type._baseSyntax var attributedTypeSyntax: AttributedTypeSyntax? = type._attributedSyntax @@ -223,7 +221,6 @@ public enum `Type`: TypeProtocol, SyntaxExpressibleByStringInterpolation { base = base.addingAttributes(from: attributedTypeSyntax) } return NormalizedType(stringLiteral: base) - case .function(let type): var functionTypeSyntax: FunctionTypeSyntax var attributedTypeSyntax: AttributedTypeSyntax? = nil @@ -247,7 +244,6 @@ public enum `Type`: TypeProtocol, SyntaxExpressibleByStringInterpolation { attributedTypeSyntax?.baseType = TypeSyntax(functionTypeSyntax) return .function(.init(functionTypeSyntax, attributedSyntax: attributedTypeSyntax)) - case .implicitlyUnwrappedOptional(let type): var implicitlyUnwrappedOptionalTypeSyntax: ImplicitlyUnwrappedOptionalTypeSyntax = type._baseSyntax var attributedTypeSyntax: AttributedTypeSyntax? = type._attributedSyntax @@ -257,7 +253,6 @@ public enum `Type`: TypeProtocol, SyntaxExpressibleByStringInterpolation { attributedTypeSyntax?.baseType = TypeSyntax(implicitlyUnwrappedOptionalTypeSyntax) return .implicitlyUnwrappedOptional(.init(implicitlyUnwrappedOptionalTypeSyntax, attributedSyntax: attributedTypeSyntax)) - case .member(let type): var memberTypeSyntax: MemberTypeSyntax = type._baseSyntax var attributedTypeSyntax: AttributedTypeSyntax? = type._attributedSyntax @@ -269,7 +264,6 @@ public enum `Type`: TypeProtocol, SyntaxExpressibleByStringInterpolation { attributedTypeSyntax?.baseType = TypeSyntax(memberTypeSyntax) return .member(.init(memberTypeSyntax, attributedSyntax: attributedTypeSyntax)) - case .metatype(let type): let baseType = type._baseSyntax let memberTypeSyntax = MemberTypeSyntax.init( From b1350990aa420b107eff4a35249732799a2089ec Mon Sep 17 00:00:00 2001 From: Alessio Nossa Date: Sat, 20 Jul 2024 12:16:54 +0200 Subject: [PATCH 13/17] Implement normalization of composition --- Sources/MacroToolkit/Type.swift | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Sources/MacroToolkit/Type.swift b/Sources/MacroToolkit/Type.swift index 139d7f7..0195bcb 100644 --- a/Sources/MacroToolkit/Type.swift +++ b/Sources/MacroToolkit/Type.swift @@ -194,10 +194,21 @@ public enum `Type`: TypeProtocol, SyntaxExpressibleByStringInterpolation { return normalizedType case .composition(let type): - // Looks like there can only be simple types in composition, with no generics, and therefore we - // don't ned to recursively normalize + var compositionTypeSyntax: CompositionTypeSyntax = type._baseSyntax + var attributedTypeSyntax: AttributedTypeSyntax? = type._attributedSyntax + + let arrayOfCompositionElements = compositionTypeSyntax.elements.map { compositionElement in + let normalizedType = Type(compositionElement.type).normalized() + let updatedElementType = TypeSyntax(normalizedType._syntax) + var newCompositionElement = compositionElement + newCompositionElement.type = updatedElementType + return newCompositionElement + } + + compositionTypeSyntax.elements = .init(arrayOfCompositionElements) + attributedTypeSyntax?.baseType = TypeSyntax(compositionTypeSyntax) - return .composition(.init(type._baseSyntax, attributedSyntax: type._attributedSyntax)) + return .composition(.init(compositionTypeSyntax, attributedSyntax: attributedTypeSyntax)) case .someOrAny(let type): var someOrAnyTypeSyntax: SomeOrAnyTypeSyntax = type._baseSyntax var attributedTypeSyntax: AttributedTypeSyntax? = type._attributedSyntax From 206fe0a7afe4e5241265196b31ae4df507400642 Mon Sep 17 00:00:00 2001 From: Alessio Nossa Date: Sat, 20 Jul 2024 12:25:59 +0200 Subject: [PATCH 14/17] Update Optional normalization implementation --- Sources/MacroToolkit/Type.swift | 46 ++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/Sources/MacroToolkit/Type.swift b/Sources/MacroToolkit/Type.swift index 0195bcb..3ea8178 100644 --- a/Sources/MacroToolkit/Type.swift +++ b/Sources/MacroToolkit/Type.swift @@ -292,26 +292,25 @@ public enum `Type`: TypeProtocol, SyntaxExpressibleByStringInterpolation { case .missing(let type): return .missing(.init(type._baseSyntax, attributedSyntax: type._attributedSyntax)) case .optional(let type): - var optionalTypeSyntax: OptionalTypeSyntax = type._baseSyntax + let optionalTypeSyntax: OptionalTypeSyntax = type._baseSyntax var attributedTypeSyntax: AttributedTypeSyntax? = type._attributedSyntax let normalizedElement = Type(optionalTypeSyntax.wrappedType).normalized() - optionalTypeSyntax.wrappedType = TypeSyntax(normalizedElement._syntax) - attributedTypeSyntax?.baseType = TypeSyntax(optionalTypeSyntax) -// let identifierSyntax = IdentifierTypeSyntax( -// leadingTrivia: optionalTypeSyntax.leadingTrivia, -// name: .identifier("Optional"), -// genericArgumentClause: .init( -// arguments: .init(arrayLiteral: .init(argument: optionalTypeSyntax.wrappedType)) -// ), -// trailingTrivia: optionalTypeSyntax.leadingTrivia -// ) - - var base = "Optional<\(optionalTypeSyntax.wrappedType)>" + let identifierSyntax = IdentifierTypeSyntax( + leadingTrivia: optionalTypeSyntax.leadingTrivia, + name: .identifier("Optional"), + genericArgumentClause: .init( + arguments: .init(arrayLiteral: .init(argument: TypeSyntax(normalizedElement._syntax))) + ), + trailingTrivia: optionalTypeSyntax.leadingTrivia + ) + if let attributedTypeSyntax { - base = base.addingAttributes(from: attributedTypeSyntax) + let normalizedAttributedTypeSyntax = identifierSyntax.addingAttributes(from: attributedTypeSyntax) + return .simple(.init(identifierSyntax, attributedSyntax: normalizedAttributedTypeSyntax)) + } else { + return .simple(.init(identifierSyntax)) } - return NormalizedType(stringLiteral: base) case .packExpansion(let type): // Looks like there can only be simple identifiers in pack expansions, with no generics, and therefore we // don't ned to recursively normalize @@ -400,6 +399,23 @@ fileprivate extension String { } } +fileprivate extension TypeSyntaxProtocol { + /// Builds a `AttributedTypeSyntax` attaching the attributes and + /// the specifiers of the attributed type syntax. + /// - Parameter attributedType: The `AttributedTypeSyntax` which `attributes` + /// and `specifier` should be attached to the `TypeSyntax`. + /// - Returns: A `AttributedTypeSyntax` with elements from the attributed type syntax attached to the original `TypeSyntax`. + func addingAttributes(from attributedType: AttributedTypeSyntax) -> AttributedTypeSyntax { + return AttributedTypeSyntax( + leadingTrivia: attributedType.leadingTrivia, + specifier: attributedType.specifier, + attributes: attributedType.attributes, + baseType: self, + trailingTrivia: attributedType.trailingTrivia + ) + } +} + fileprivate extension GenericArgumentClauseSyntax { /// Normalize the arguments of the generic. /// - Returns: An updated version of the generic argument clause with From d5b047a63632c1fc6256b59b4ecdce3c8ede6496 Mon Sep 17 00:00:00 2001 From: Alessio Nossa Date: Sat, 20 Jul 2024 12:26:22 +0200 Subject: [PATCH 15/17] Add test case for composition normalization --- Tests/MacroToolkitTests/MacroToolkitTests.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Tests/MacroToolkitTests/MacroToolkitTests.swift b/Tests/MacroToolkitTests/MacroToolkitTests.swift index dc75e89..2cdafdb 100644 --- a/Tests/MacroToolkitTests/MacroToolkitTests.swift +++ b/Tests/MacroToolkitTests/MacroToolkitTests.swift @@ -747,14 +747,17 @@ final class MacroToolkitTests: XCTestCase { let type1: `Type` = "any Decodable & Identifiable" let type2: `Type` = "(inout any Decodable & Identifiable) -> Void" let type3: `Type` = "(inout Decodable & Codable) -> ([Int])" + let type4: `Type` = "(Encodable & Sequence) & Equatable" let normalizedType1 = type1.normalized() let normalizedType2 = type2.normalized() let normalizedType3 = type3.normalized() + let normalizedType4 = type4.normalized() XCTAssertEqual(normalizedType1.description, "any Decodable & Identifiable") XCTAssertEqual(normalizedType2.description, "(inout any Decodable & Identifiable) -> ()") XCTAssertEqual(normalizedType3.description, "(inout Decodable & Codable) -> (Array)") + XCTAssertEqual(normalizedType4.description, "(Encodable & Sequence>) & Equatable") } func testNormalizationSomeOrAny() { From 84c2bd61cbdfa0534fb5e129b5f99aa85bc0623e Mon Sep 17 00:00:00 2001 From: Alessio Nossa Date: Sat, 20 Jul 2024 13:00:22 +0200 Subject: [PATCH 16/17] Update code formatting --- Sources/MacroToolkit/Type.swift | 2 +- Tests/MacroToolkitTests/MacroToolkitTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/MacroToolkit/Type.swift b/Sources/MacroToolkit/Type.swift index 3ea8178..bcd2ff6 100644 --- a/Sources/MacroToolkit/Type.swift +++ b/Sources/MacroToolkit/Type.swift @@ -293,7 +293,7 @@ public enum `Type`: TypeProtocol, SyntaxExpressibleByStringInterpolation { return .missing(.init(type._baseSyntax, attributedSyntax: type._attributedSyntax)) case .optional(let type): let optionalTypeSyntax: OptionalTypeSyntax = type._baseSyntax - var attributedTypeSyntax: AttributedTypeSyntax? = type._attributedSyntax + let attributedTypeSyntax: AttributedTypeSyntax? = type._attributedSyntax let normalizedElement = Type(optionalTypeSyntax.wrappedType).normalized() let identifierSyntax = IdentifierTypeSyntax( diff --git a/Tests/MacroToolkitTests/MacroToolkitTests.swift b/Tests/MacroToolkitTests/MacroToolkitTests.swift index 2cdafdb..fd47ff5 100644 --- a/Tests/MacroToolkitTests/MacroToolkitTests.swift +++ b/Tests/MacroToolkitTests/MacroToolkitTests.swift @@ -879,7 +879,7 @@ final class MacroToolkitTests: XCTestCase { func testNormalizationAttributedTuple() { let declSyntax: DeclSyntax = """ - let interestingType: (inout [Int], String) -> Float = { _,_ in + let interestingType: (inout [Int], String) -> Float = { _, _ in return 3 } """ From b4a470cef13821233613b3a6dfc0ffd7a566db15 Mon Sep 17 00:00:00 2001 From: Alessio Nossa Date: Sat, 20 Jul 2024 13:02:35 +0200 Subject: [PATCH 17/17] Update tuple normalization and tests --- Sources/MacroToolkit/Type.swift | 5 ++++- Tests/MacroToolkitTests/MacroToolkitTests.swift | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Sources/MacroToolkit/Type.swift b/Sources/MacroToolkit/Type.swift index bcd2ff6..6a2380b 100644 --- a/Sources/MacroToolkit/Type.swift +++ b/Sources/MacroToolkit/Type.swift @@ -345,8 +345,11 @@ public enum `Type`: TypeProtocol, SyntaxExpressibleByStringInterpolation { case .tuple(let type): if type.elements.count == 1 { let child = type.elements[0] - if case .tuple(_) = child { + switch child { + case .tuple, .simple: return child.normalized() + default: + break } } diff --git a/Tests/MacroToolkitTests/MacroToolkitTests.swift b/Tests/MacroToolkitTests/MacroToolkitTests.swift index fd47ff5..aacc080 100644 --- a/Tests/MacroToolkitTests/MacroToolkitTests.swift +++ b/Tests/MacroToolkitTests/MacroToolkitTests.swift @@ -717,6 +717,19 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(normalizedType.description, "(Optional<()>)") } + func testNormalizationTuple() { + let type1: `Type` = "(Optional<()>)" + let type2: `Type` = "((Int) -> (Int)) -> String" + + let normalizedType1 = type1.normalized() + let normalizedType2 = type2.normalized() + + XCTAssertEqual(normalizedType1.description, "Optional<()>") + XCTAssertEqual(normalizedType2.description, "((Int) -> Int) -> String") + XCTAssertEqual(normalizedType1.description, "\(((Optional<()>)).self)") + XCTAssertEqual(normalizedType2.description, "\((((Int) -> (Int)) -> String).self)") + } + func testNormalizationNestedArrays() { let type: `Type` = "[[Int]]"