From 6a0479e92add476c9f7f9d99c76fe600650fd0b1 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Fri, 13 Sep 2024 12:28:17 -0700 Subject: [PATCH] Use Swift Syntax v600 in Package@swift-6.0 --- Package@swift-6.0.swift | 3 +- Sources/SafeDICore/Models/Initializer.swift | 20 +++- Sources/SafeDICore/Models/Property.swift | 110 ++++++++++++------ .../SafeDICore/Models/TypeDescription.swift | 59 ++++++++-- .../Macros/InstantiableMacro.swift | 4 +- .../TypeDescriptionTests.swift | 15 +++ .../InstantiableMacroTests.swift | 28 +++-- 7 files changed, 178 insertions(+), 61 deletions(-) diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index a1353e5..0157ddf 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -33,8 +33,7 @@ let package = Package( dependencies: [ .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0"), .package(url: "https://github.com/apple/swift-collections.git", from: "1.0.0"), - // TODO: Bump to 600.0.0 once it's available. - .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "510.0.0"), + .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.0"), .package(url: "https://github.com/michaeleisel/ZippyJSON.git", from: "1.2.0"), .package(url: "https://github.com/pointfreeco/swift-macro-testing.git", from: "0.5.0"), ], diff --git a/Sources/SafeDICore/Models/Initializer.swift b/Sources/SafeDICore/Models/Initializer.swift index b451810..b212897 100644 --- a/Sources/SafeDICore/Models/Initializer.swift +++ b/Sources/SafeDICore/Models/Initializer.swift @@ -32,7 +32,11 @@ public struct Initializer: Codable, Hashable, Sendable { isPublicOrOpen = node.modifiers.containsPublicOrOpen isOptional = node.optionalMark != nil isAsync = node.signature.effectSpecifiers?.asyncSpecifier != nil - doesThrow = node.signature.effectSpecifiers?.throwsSpecifier != nil + #if compiler(>=6.0) + doesThrow = node.signature.effectSpecifiers?.throwsClause?.throwsSpecifier != nil + #else + doesThrow = node.signature.effectSpecifiers?.throwsSpecifier != nil + #endif hasGenericParameter = node.genericParameterClause != nil hasGenericWhereClause = node.genericWhereClause != nil arguments = node @@ -46,7 +50,11 @@ public struct Initializer: Codable, Hashable, Sendable { isPublicOrOpen = node.modifiers.containsPublicOrOpen isOptional = false isAsync = node.signature.effectSpecifiers?.asyncSpecifier != nil - doesThrow = node.signature.effectSpecifiers?.throwsSpecifier != nil + #if compiler(>=6.0) + doesThrow = node.signature.effectSpecifiers?.throwsClause?.throwsSpecifier != nil + #else + doesThrow = node.signature.effectSpecifiers?.throwsSpecifier != nil + #endif hasGenericParameter = node.genericParameterClause != nil hasGenericWhereClause = node.genericWhereClause != nil arguments = node @@ -294,7 +302,7 @@ extension ConcreteDeclType { extension TypeDescription { fileprivate func isEqualToFunctionArgument(_ argument: TypeDescription) -> Bool { switch argument { - case let .attributed(argumentTypeDescription, argumentSpecifier, argumentAttributes): + case let .attributed(argumentTypeDescription, argumentSpecifiers, argumentAttributes): switch self { case .simple, .nested, @@ -311,11 +319,11 @@ extension TypeDescription { .unknown, .void: self == argumentTypeDescription - && argumentSpecifier == nil + && argumentSpecifiers?.isEmpty ?? true && (argumentAttributes ?? []).contains("escaping") - case let .attributed(parameterTypeDescription, parameterSpecifier, parameterAttributes): + case let .attributed(parameterTypeDescription, parameterSpecifiers, parameterAttributes): parameterTypeDescription == argumentTypeDescription - && parameterSpecifier == argumentSpecifier + && Set(parameterSpecifiers ?? []) == Set(argumentSpecifiers ?? []) && Set(argumentAttributes ?? []).subtracting(parameterAttributes ?? []) == ["escaping"] } case .simple, diff --git a/Sources/SafeDICore/Models/Property.swift b/Sources/SafeDICore/Models/Property.swift index adda115..5a13510 100644 --- a/Sources/SafeDICore/Models/Property.swift +++ b/Sources/SafeDICore/Models/Property.swift @@ -56,43 +56,87 @@ public struct Property: Codable, Hashable, Comparable, Sendable { var asFunctionParamter: FunctionParameterSyntax { switch typeDescription { case .closure: - FunctionParameterSyntax( - firstName: .identifier(label), - colon: .colonToken(trailingTrivia: .space), - type: AttributedTypeSyntax( - attributes: AttributeListSyntax { - AttributeSyntax(attributeName: IdentifierTypeSyntax( - name: "escaping", - trailingTrivia: .space - )) - }, - baseType: IdentifierTypeSyntax(name: .identifier(typeDescription.asSource)) + #if compiler(>=6.0) + FunctionParameterSyntax( + firstName: .identifier(label), + colon: .colonToken(trailingTrivia: .space), + type: AttributedTypeSyntax( + specifiers: [], + attributes: AttributeListSyntax { + AttributeSyntax(attributeName: IdentifierTypeSyntax( + name: "escaping", + trailingTrivia: .space + )) + }, + baseType: IdentifierTypeSyntax(name: .identifier(typeDescription.asSource)) + ) ) - ) + #else + FunctionParameterSyntax( + firstName: .identifier(label), + colon: .colonToken(trailingTrivia: .space), + type: AttributedTypeSyntax( + specifier: nil, + attributes: AttributeListSyntax { + AttributeSyntax(attributeName: IdentifierTypeSyntax( + name: "escaping", + trailingTrivia: .space + )) + }, + baseType: IdentifierTypeSyntax(name: .identifier(typeDescription.asSource)) + ) + ) + #endif case let .attributed(typeDescription, _, attributes): - FunctionParameterSyntax( - firstName: .identifier(label), - colon: .colonToken(trailingTrivia: .space), - type: AttributedTypeSyntax( - // It is not possible for a property declaration to have specifiers today. - specifier: nil, - attributes: AttributeListSyntax { - AttributeSyntax(attributeName: IdentifierTypeSyntax( - name: "escaping", - trailingTrivia: .space - )) - if let attributes { - for attribute in attributes { - AttributeSyntax( - attributeName: IdentifierTypeSyntax(name: .identifier(attribute)), - trailingTrivia: .space - ) + #if compiler(>=6.0) + FunctionParameterSyntax( + firstName: .identifier(label), + colon: .colonToken(trailingTrivia: .space), + type: AttributedTypeSyntax( + // It is not possible for a property declaration to have specifiers today. + specifiers: [], + attributes: AttributeListSyntax { + AttributeSyntax(attributeName: IdentifierTypeSyntax( + name: "escaping", + trailingTrivia: .space + )) + if let attributes { + for attribute in attributes { + AttributeSyntax( + attributeName: IdentifierTypeSyntax(name: .identifier(attribute)), + trailingTrivia: .space + ) + } } - } - }, - baseType: IdentifierTypeSyntax(name: .identifier(typeDescription.asSource)) + }, + baseType: IdentifierTypeSyntax(name: .identifier(typeDescription.asSource)) + ) ) - ) + #else + FunctionParameterSyntax( + firstName: .identifier(label), + colon: .colonToken(trailingTrivia: .space), + type: AttributedTypeSyntax( + // It is not possible for a property declaration to have specifiers today. + specifier: nil, + attributes: AttributeListSyntax { + AttributeSyntax(attributeName: IdentifierTypeSyntax( + name: "escaping", + trailingTrivia: .space + )) + if let attributes { + for attribute in attributes { + AttributeSyntax( + attributeName: IdentifierTypeSyntax(name: .identifier(attribute)), + trailingTrivia: .space + ) + } + } + }, + baseType: IdentifierTypeSyntax(name: .identifier(typeDescription.asSource)) + ) + ) + #endif case .simple, .nested, .composition, diff --git a/Sources/SafeDICore/Models/TypeDescription.swift b/Sources/SafeDICore/Models/TypeDescription.swift index 1af8443..23e846e 100644 --- a/Sources/SafeDICore/Models/TypeDescription.swift +++ b/Sources/SafeDICore/Models/TypeDescription.swift @@ -41,7 +41,7 @@ public enum TypeDescription: Codable, Hashable, Comparable, Sendable { /// A meta type. e.g. `Int.Type` or `Equatable.Protocol` indirect case metatype(TypeDescription, isType: Bool) /// A type identifier with a specifier or attributes. e.g. `inout Int` or `@autoclosure () -> Void` - indirect case attributed(TypeDescription, specifier: String?, attributes: [String]?) + indirect case attributed(TypeDescription, specifiers: [String]?, attributes: [String]?) /// An array. e.g. [Int] indirect case array(element: TypeDescription) /// A dictionary. e.g. [Int: String] @@ -92,18 +92,18 @@ public enum TypeDescription: Codable, Hashable, Comparable, Sendable { return "some \(type.wrappedIfAmbiguous.asSource)" case let .any(type): return "any \(type.wrappedIfAmbiguous.asSource)" - case let .attributed(type, specifier, attributes): + case let .attributed(type, specifiers, attributes): func attributesFromList(_ attributes: [String]) -> String { attributes .map { "@\($0)" } .joined(separator: " ") } - switch (specifier, attributes) { - case let (.some(specifier), .none): - return "\(specifier) \(type.asSource)" + switch (specifiers, attributes) { + case let (.some(specifiers), .none): + return "\(specifiers.joined(separator: " ")) \(type.asSource)" case let (.none, .some(attributes)): return "\(attributesFromList(attributes)) \(type.asSource)" - case let (.some(specifier), .some(attributes)): + case let (.some(specifiers), .some(attributes)): // This case likely represents an error. // We are unaware of type reference that compiles with both a specifier and attributes. // The Swift reference manual specifies that attributes come before the specifier, @@ -111,7 +111,7 @@ public enum TypeDescription: Codable, Hashable, Comparable, Sendable { // Only code where the specifier comes before the attribute parses as an AttributedTypeSyntax. // As a result, we construct this source with the specifier first. // Reference manual: https://docs.swift.org/swift-book/ReferenceManual/Types.html#grammar_type - return "\(specifier) \(attributesFromList(attributes)) \(type.asSource)" + return "\(specifiers.joined(separator: " ")) \(attributesFromList(attributes)) \(type.asSource)" case (.none, .none): // This case represents an error. return type.asSource @@ -331,9 +331,18 @@ extension TypeSyntax { let attributes: [String] = typeIdentifier.attributes.compactMap { AttributeSyntax($0)?.attributeName.as(IdentifierTypeSyntax.self)?.name.text } + #if compiler(>=6.0) + let specifiers = typeIdentifier.specifiers.textRepresentation + #else + let specifiers: [String]? = if let specifier = typeIdentifier.specifier?.text { + [specifier] + } else { + nil + } + #endif return .attributed( typeIdentifier.baseType.typeDescription, - specifier: typeIdentifier.specifier?.text, + specifiers: specifiers, attributes: attributes.isEmpty ? nil : attributes ) @@ -369,10 +378,15 @@ extension TypeSyntax { return .simple(name: "AnyObject") } else if let typeIdentifier = FunctionTypeSyntax(self) { + #if compiler(>=6.0) + let doesThrow = typeIdentifier.effectSpecifiers?.throwsClause?.throwsSpecifier != nil + #else + let doesThrow = typeIdentifier.effectSpecifiers?.throwsSpecifier != nil + #endif return .closure( arguments: typeIdentifier.parameters.map(\.type.typeDescription), isAsync: typeIdentifier.effectSpecifiers?.asyncSpecifier != nil, - doesThrow: typeIdentifier.effectSpecifiers?.throwsSpecifier != nil, + doesThrow: doesThrow, returnType: typeIdentifier.returnClause.type.typeDescription ) @@ -476,10 +490,15 @@ extension ExprSyntax { ]), let returnType = sequenceExpr.elements.last { + #if compiler(>=6.0) + let doesThrow = arrow.effectSpecifiers?.throwsClause?.throwsSpecifier != nil + #else + let doesThrow = arrow.effectSpecifiers?.throwsSpecifier != nil + #endif return .closure( arguments: arguments.elements.map(\.expression.typeDescription), isAsync: arrow.effectSpecifiers?.asyncSpecifier != nil, - doesThrow: arrow.effectSpecifiers?.throwsSpecifier != nil, + doesThrow: doesThrow, returnType: returnType.typeDescription ) } @@ -516,3 +535,23 @@ private final class GenericArgumentVisitor: SyntaxVisitor { return .skipChildren } } + +#if compiler(>=6.0) + extension TypeSpecifierListSyntax { + fileprivate var textRepresentation: [String]? { + let specifiers = compactMap { specifier in + if case let .simpleTypeSpecifier(simpleTypeSpecifierSyntax) = specifier { + simpleTypeSpecifierSyntax.specifier.text + } else { + // lifetimeTypeSpecifier is SPI, so we ignore it. + nil + } + } + if specifiers.isEmpty { + return nil + } else { + return specifiers + } + } + } +#endif diff --git a/Sources/SafeDIMacros/Macros/InstantiableMacro.swift b/Sources/SafeDIMacros/Macros/InstantiableMacro.swift index d6fba79..8eb8756 100644 --- a/Sources/SafeDIMacros/Macros/InstantiableMacro.swift +++ b/Sources/SafeDIMacros/Macros/InstantiableMacro.swift @@ -383,14 +383,14 @@ extension TypeDescription { ) || self == .attributed( .simple(name: "Instantiable"), - specifier: nil, + specifiers: nil, attributes: ["retroactive"] ) || self == .nested( name: "Instantiable", parentType: .attributed( .simple(name: "SafeDI"), - specifier: nil, + specifiers: nil, attributes: ["retroactive"] ) ) diff --git a/Tests/SafeDICoreTests/TypeDescriptionTests.swift b/Tests/SafeDICoreTests/TypeDescriptionTests.swift index 0acb333..8b535fc 100644 --- a/Tests/SafeDICoreTests/TypeDescriptionTests.swift +++ b/Tests/SafeDICoreTests/TypeDescriptionTests.swift @@ -247,6 +247,21 @@ final class TypeDescriptionTests: XCTestCase { XCTAssertEqual(typeDescription.asSource, "inout @autoclosure () -> Void") } + #if compiler(>=6.0) + func test_typeDescription_whenCalledOnATypeSyntaxNodeRepresentingAnAttributedTypeSyntax_withMultipleSpecifiers_findsTheType() throws { + let content = """ + func test(parameter: sending @autoclosure () -> Void) {} + """ + + let visitor = AttributedTypeSyntaxVisitor(viewMode: .sourceAccurate) + visitor.walk(Parser.parse(source: content)) + + let typeDescription = try XCTUnwrap(visitor.attributedTypeIdentifier) + XCTAssertFalse(typeDescription.isUnknown, "Type description is not of known type!") + XCTAssertEqual(typeDescription.asSource, "sending @autoclosure () -> Void") + } + #endif + func test_typeDescription_whenCalledOnATypeSyntaxNodeRepresentingAnArrayTypeSyntax_findsTheType() throws { let content = """ var intArray: [Int] = [Int]() diff --git a/Tests/SafeDIMacrosTests/InstantiableMacroTests.swift b/Tests/SafeDIMacrosTests/InstantiableMacroTests.swift index d81b97b..aa9d18c 100644 --- a/Tests/SafeDIMacrosTests/InstantiableMacroTests.swift +++ b/Tests/SafeDIMacrosTests/InstantiableMacroTests.swift @@ -429,15 +429,27 @@ import SafeDICore } """ } expansion: { - """ - public struct ExampleService: Instantiable { - public init(instantiatedA: InstantiatedA) { - self.instantiatedA = instantiatedA - }@Unknown + #if compiler(>=6.0) + """ + public struct ExampleService: Instantiable { + public init(instantiatedA: InstantiatedA) { + self.instantiatedA = instantiatedA + } - let instantiatedA: InstantiatedA - } - """ + @Unknown let instantiatedA: InstantiatedA + } + """ + #else + """ + public struct ExampleService: Instantiable { + public init(instantiatedA: InstantiatedA) { + self.instantiatedA = instantiatedA + }@Unknown + + let instantiatedA: InstantiatedA + } + """ + #endif } }