Skip to content

Commit

Permalink
Macros work
Browse files Browse the repository at this point in the history
  • Loading branch information
dfed committed Nov 26, 2023
1 parent 29c8032 commit 0696aa9
Show file tree
Hide file tree
Showing 27 changed files with 2,129 additions and 203 deletions.
18 changes: 18 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
{
"pins" : [
{
"identity" : "swift-macro-testing",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-macro-testing",
"state" : {
"revision" : "10dcef36314ddfea6f60442169b0b320204cbd35",
"version" : "0.2.2"
}
},
{
"identity" : "swift-snapshot-testing",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-snapshot-testing",
"state" : {
"revision" : "4862d48562483d274a2ac7522d905c9237a31a48",
"version" : "1.15.0"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
Expand Down
17 changes: 3 additions & 14 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0"),
.package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.2.0"),
],
targets: [
.macro(
Expand All @@ -32,23 +33,11 @@ let package = Package(
),
.target(name: "SafeDI", dependencies: ["SafeDIMacros"]),
.testTarget(
name: "SafeDITests",
name: "SafeDIMacrosTests",
dependencies: [
"SafeDIMacros",
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
]
),
.target(
name: "SafeDIVisitors",
dependencies: [
.product(name: "SwiftSyntax", package: "swift-syntax"),
]
),
.testTarget(
name: "SafeDIVisitorsTests",
dependencies: [
"SafeDIVisitors",
.product(name: "SwiftParser", package: "swift-syntax"),
.product(name: "MacroTesting", package: "swift-macro-testing"), // TODO: write tests that use this!
]
),
]
Expand Down
48 changes: 36 additions & 12 deletions Sources/SafeDI/SafeDI.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
// The Swift Programming Language
// https://docs.swift.org/swift-book

// TODO: Define macros here. Sample below.
///// A macro that produces both a value and a string containing the
///// source code that generated the value. For example,
/////
///// #stringify(x + y)
/////
///// produces a tuple `(x + y, "x + y")`.
//@freestanding(expression)
//public macro stringify<T>(_ value: T) -> (T, String) = #externalMacro(module: "SafeDIMacros", type: "StringifyMacro")
// Distributed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

// TODO: Document macro.
@attached(member, names: named(`init`), named(build), named(getDependencies), arbitrary)
public macro builder(_ propertyName: StaticString) = #externalMacro(module: "SafeDIMacros", type: "BuilderMacro")

// TODO: Document macro.
@attached(member, names: named(`init`))
public macro dependencies() = #externalMacro(module: "SafeDIMacros", type: "DependenciesMacro")

// TODO: Document macro.
@attached(member)
public macro constructed() = #externalMacro(module: "SafeDIMacros", type: "ConstructedMacro")

// TODO: Document macro.
@attached(member)
public macro singleton() = #externalMacro(module: "SafeDIMacros", type: "SingletonMacro")

81 changes: 81 additions & 0 deletions Sources/SafeDIMacros/Extensions/ArrayExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Distributed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import SwiftSyntax
import SwiftSyntaxBuilder

extension Array where Element == Dependency {

var variantParameterList: FunctionParameterListSyntax {
FunctionParameterListSyntax(
filter { $0.source == .variant }
.map { "\(raw: $0.variableName): \(raw: $0.type)" }
.transformUntilLast {
var functionPamameterSyntax = $0
functionPamameterSyntax.trailingComma = TokenSyntax(.comma, presence: .present)
functionPamameterSyntax.trailingTrivia = .space
return functionPamameterSyntax
}
)
}

var variantLabeledExpressionList: String {
filter { $0.isVariant }
.map { "\($0.variableName): \($0.variableName)" }
.joined(separator: ", ")
}

var invariantParameterList: FunctionParameterListSyntax {
FunctionParameterListSyntax(
filter { $0.isInvariant }
.map { "\(raw: $0.variableName): \(raw: $0.type)" }
.transformUntilLast {
var functionPamameterSyntax = $0
functionPamameterSyntax.trailingComma = TokenSyntax(.comma, presence: .present)
functionPamameterSyntax.trailingTrivia = .space
return functionPamameterSyntax
}
)
}

var invariantAssignmentExpressionList: String {
"""
\(filter(\.isInvariant)
.map { "self.\($0.variableName) = \($0.variableName)" }
.joined(separator: "\n"))
"""
}

}

extension Array {

/// Returns an array with all of the items in the array except for the last transformed.
/// - Parameter transform: A transforming closure. `transform` accepts an element of this sequence as its parameter and returns a transformed value of the same type.
/// - Returns: An array containing the transformed elements of this sequence, plus the untransfomred last element.
func transformUntilLast(_ transform: (Element) throws -> Element) rethrows -> [Element] {
var arrayToTransform = self
guard let lastItem = arrayToTransform.popLast() else {
// Array is empty.
return self
}
return try arrayToTransform.map { try transform($0) } + [lastItem]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Distributed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import SwiftSyntax

extension AttributeListSyntax {

var isDecoratedWithDependenciesMacro: Bool {
contains(where: { element in
switch element {
case let .attribute(attribute):
return attribute.attributeName.as(IdentifierTypeSyntax.self)?.name.text == DependenciesMacro.name
case .ifConfigDecl:
return false
}
})
}

var attributedNodes: [(attribute: String, node: AttributeListSyntax.Element)] {
compactMap { element in
switch element {
case let .attribute(attribute):
guard let identifierText = attribute.attributeName.as(IdentifierTypeSyntax.self)?.name.text else {
return nil
}
return (attribute: identifierText, node: element)
case .ifConfigDecl:
return nil
}
}
}

var dependencySources: [(source: Dependency.Source, node: AttributeListSyntax.Element)] {
attributedNodes.compactMap {
guard let source = Dependency.Source.init($0.attribute) else {
return nil
}
return (source: source, node: $0.node)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Distributed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import SwiftSyntax

extension AttributeSyntax.Arguments {
var string: String? {
switch self {
case let .argumentList(labeledExprListSyntax):
return labeledExprListSyntax
.map(\.expression)
.compactMap(StringLiteralExprSyntax.init)
.map(\.segments)
.flatMap { $0 }
.compactMap(StringSegmentSyntax.init)
.map(\.content)
.map(\.text)
.first
default:
return nil
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Distributed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import SwiftSyntax

extension DeclModifierListSyntax {

var containsPublic: Bool {
contains(where: { modifier in
modifier.name.text == "public"
})
}

var staticModifier: Element? {
first(where: { modifier in
modifier.name.text == "static"
})
}
}
15 changes: 15 additions & 0 deletions Sources/SafeDIMacros/Extensions/FunctionDeclSyntaxExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import SwiftSyntax
import SwiftSyntaxBuilder

extension FunctionDeclSyntax {

static var buildTemplate: Self {
try! FunctionDeclSyntax("public func build(<#T##parameter#>: <#T##ParameterType#>) \(returnClauseTemplate)")
}

static var returnClauseTemplate: ReturnClauseSyntax {
ReturnClauseSyntax(
type: TypeSyntax(" <#T##BuiltProductType#>")
)
}
}
38 changes: 38 additions & 0 deletions Sources/SafeDIMacros/Extensions/StructDeclSyntaxExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Distributed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import SwiftSyntax
import SwiftSyntaxBuilder

extension StructDeclSyntax {

static var dependenciesTemplate: Self {
try! StructDeclSyntax("""
@dependencies public struct Dependencies {
\(FunctionDeclSyntax.buildTemplate) {
<#T##ConcreteBuiltProductType#>(<#T##parameter#>: <#T##ParameterType#>)
}
private let <#T##dependency#>: <#T##DependencyType#>
}
""")
}

}
Loading

0 comments on commit 0696aa9

Please sign in to comment.