Skip to content

Commit

Permalink
More Swift 6
Browse files Browse the repository at this point in the history
  • Loading branch information
dfed committed Sep 13, 2024
1 parent 96b65d5 commit 63ff4b7
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 57 deletions.
3 changes: 1 addition & 2 deletions Package@swift-6.0.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
],
Expand Down
12 changes: 10 additions & 2 deletions Sources/SafeDICore/Models/Initializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
110 changes: 77 additions & 33 deletions Sources/SafeDICore/Models/Property.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
55 changes: 45 additions & 10 deletions Sources/SafeDICore/Models/TypeDescription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -92,26 +92,26 @@ 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,
// however code that puts an attribute first does not parse as AttributedTypeSyntax.
// 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
Expand Down Expand Up @@ -331,9 +331,14 @@ 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 = [typeIdentifier.specifier?.text].compactMap { $0 }
#endif
return .attributed(
typeIdentifier.baseType.typeDescription,
specifier: typeIdentifier.specifier?.text,
specifiers: specifiers,
attributes: attributes.isEmpty ? nil : attributes
)

Expand Down Expand Up @@ -369,10 +374,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
)

Expand Down Expand Up @@ -476,10 +486,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
)
}
Expand Down Expand Up @@ -516,3 +531,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
4 changes: 2 additions & 2 deletions Sources/SafeDIMacros/Macros/InstantiableMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
)
)
Expand Down
15 changes: 15 additions & 0 deletions Tests/SafeDICoreTests/TypeDescriptionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]()
Expand Down
28 changes: 20 additions & 8 deletions Tests/SafeDIMacrosTests/InstantiableMacroTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down

0 comments on commit 63ff4b7

Please sign in to comment.