Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/DependenciesMacros/Macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ public macro DependencyClient() =
/// ```
@attached(accessor, names: named(init), named(get), named(set))
@attached(peer, names: arbitrary)
public macro DependencyEndpoint(method: String = "") =
public macro DependencyEndpoint(method: String = "", initAccessor: Bool = false) =
#externalMacro(
module: "DependenciesMacrosPlugin", type: "DependencyEndpointMacro"
)
Expand Down
25 changes: 23 additions & 2 deletions Sources/DependenciesMacrosPlugin/DependencyEndpointMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public enum DependencyEndpointMacro: AccessorMacro, PeerMacro {
return []
}

return [
var syntax: [AccessorDeclSyntax] = [
"""
get {
_\(raw: identifier)
Expand All @@ -42,6 +42,17 @@ public enum DependencyEndpointMacro: AccessorMacro, PeerMacro {
}
""",
]

if node.initAccessor {
syntax.insert("""
@storageRestrictions(initializes: _\(raw: identifier))
init(initialValue) {
_\(raw: identifier) = initialValue
}
""", at: 0)
}

return syntax
}

public static func expansion<D: DeclSyntaxProtocol, C: MacroExpansionContext>(
Expand Down Expand Up @@ -290,7 +301,6 @@ extension AttributeSyntax {
get throws {
guard
let arguments = self.arguments?.as(LabeledExprListSyntax.self),
arguments.count == 1,
let argument = arguments.first,
argument.label?.text == "method"
else { return nil }
Expand Down Expand Up @@ -337,6 +347,17 @@ extension AttributeSyntax {
return .identifier(name)
}
}

var initAccessor: Bool {
arguments?
.as(LabeledExprListSyntax.self)?
.first { expr in
expr.label?.tokenKind == .identifier("initAccessor")
}?
.expression.as(BooleanLiteralExprSyntax.self)?
.literal
.tokenKind == .keyword(.true)
}
}

extension String {
Expand Down
177 changes: 177 additions & 0 deletions Tests/DependenciesMacrosPluginTests/DependencyEndpointMacroTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -969,4 +969,181 @@ final class DependencyEndpointMacroTests: BaseTestCase {
"""#
}
}

func testInitAccessor() {
assertMacro {
"""
struct Setting<ValueType: Codable & Equatable & Sendable> {
public var value: () -> ValueType
@DependencyEndpoint(initAccessor: true)
public var set: (_ value: ValueType?) -> Void
}
"""
} expansion: {
#"""
struct Setting<ValueType: Codable & Equatable & Sendable> {
public var value: () -> ValueType
public var set: (_ value: ValueType?) -> Void {
@storageRestrictions(initializes: _set)
init(initialValue) {
_set = initialValue
}
get {
_set
}
set {
_set = newValue
}
}

public func set(value p0: ValueType?) -> Void {
self.set(p0)
}

private var _set: (_ value: ValueType?) -> Void = { _ in
IssueReporting.reportIssue("Unimplemented: '\(Self.self).set'")
}
}
"""#
}
}

func testInitAccessorMissingDefaultValue() {
assertMacro {
"""
struct Setting<ValueType: Codable & Equatable & Sendable> {
public var value: () -> ValueType
@DependencyEndpoint(initAccessor: true)
public var set: (_ value: ValueType?) -> Int
}
"""
} diagnostics: {
"""
struct Setting<ValueType: Codable & Equatable & Sendable> {
public var value: () -> ValueType
@DependencyEndpoint(initAccessor: true)
public var set: (_ value: ValueType?) -> Int
┬────────────────────────────────
╰─ 🛑 Default value required for non-throwing closure 'set'

Defaults are required so that the macro can generate a default, "unimplemented" version of the dependency. The default value can be anything and does not need to signify a real value. For example, if the endpoint returns a boolean, you can return 'false', or if it returns an array, you can return '[]'.

See the documentation for @DependencyClient for more information: https://swiftpackageindex.com/pointfreeco/swift-dependencies/main/documentation/dependenciesmacros/dependencyclient()#Restrictions
✏️ Insert '= { _ in <#Int#> }'
}
"""
} fixes: {
"""
struct Setting<ValueType: Codable & Equatable & Sendable> {
public var value: () -> ValueType
@DependencyEndpoint(initAccessor: true)
public var set: (_ value: ValueType?) -> Int = { _ in <#Int#> }
}
"""
} expansion: {
#"""
struct Setting<ValueType: Codable & Equatable & Sendable> {
public var value: () -> ValueType
public var set: (_ value: ValueType?) -> Int {
@storageRestrictions(initializes: _set)
init(initialValue) {
_set = initialValue
}
get {
_set
}
set {
_set = newValue
}
}

public func set(value p0: ValueType?) -> Int {
self.set(p0)
}

private var _set: (_ value: ValueType?) -> Int = { _ in
IssueReporting.reportIssue("Unimplemented: '\(Self.self).set'")
return <#Int#>
}
}
"""#
}
}

func testInitAccessorWithDefaultValue() {
assertMacro {
"""
struct Setting<ValueType: Codable & Equatable & Sendable> {
public var value: () -> ValueType
@DependencyEndpoint(initAccessor: true)
public var set: (_ value: ValueType?) -> Int = { _ in 42 }
}
"""
} expansion: {
#"""
struct Setting<ValueType: Codable & Equatable & Sendable> {
public var value: () -> ValueType
public var set: (_ value: ValueType?) -> Int {
@storageRestrictions(initializes: _set)
init(initialValue) {
_set = initialValue
}
get {
_set
}
set {
_set = newValue
}
}

public func set(value p0: ValueType?) -> Int {
self.set(p0)
}

private var _set: (_ value: ValueType?) -> Int = { _ in
IssueReporting.reportIssue("Unimplemented: '\(Self.self).set'")
return 42
}
}
"""#
}
}

func testMethodAndInitAccessor() {
assertMacro {
"""
struct Setting<ValueType: Codable & Equatable & Sendable> {
public var value: () -> ValueType
@DependencyEndpoint(method: "setValue", initAccessor: true)
public var set: (_ value: ValueType?) -> Void
}
"""
} expansion: {
#"""
struct Setting<ValueType: Codable & Equatable & Sendable> {
public var value: () -> ValueType
public var set: (_ value: ValueType?) -> Void {
@storageRestrictions(initializes: _set)
init(initialValue) {
_set = initialValue
}
get {
_set
}
set {
_set = newValue
}
}

public func setValue(value p0: ValueType?) -> Void {
self.set(p0)
}

private var _set: (_ value: ValueType?) -> Void = { _ in
IssueReporting.reportIssue("Unimplemented: '\(Self.self).set'")
}
}
"""#
}
}
}