Skip to content
Merged
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
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@

## [未リリース]

<!-- 次のリリースに含める変更をここに追加 -->
### 変更

- **@Statable マクロ**: `public init()` の自動生成を削除
- `@Observable` マクロとの相互作用問題を回避するため
- ユーザーが明示的に `public init() {}` を定義する必要あり
- 依存性注入などカスタム初期化が可能に

## [1.0.0] - 2025-01-01

Expand Down
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.0"),
.package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.4.0"),
],
targets: [
// MARK: - Macro Implementation
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import Statable
@Statable(MetabolicProfile.self)
@MainActor @Observable
final class ProfileStore {
// マクロが value, state, isLoading などを自動生成
public init() {}

// カスタムcomputed properties
var currentAge: Int { value?.age() ?? 0 }
Expand All @@ -41,7 +41,7 @@ enum WorkoutOperation: String, CaseIterable, Sendable {
@Statable([WorkoutActivity].self, operations: WorkoutOperation.self)
@MainActor @Observable
final class WorkoutStore {
// value, state, operations などが自動生成
public init() {}

var isRecording: Bool {
operations.isActive(.recordStrength) || operations.isActive(.recordCardio)
Expand Down
5 changes: 2 additions & 3 deletions README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ import Statable
@Statable(MetabolicProfile.self)
@MainActor @Observable
final class ProfileStore {
// Macro auto-generates value, state, isLoading, etc.
public init() {}

// Custom computed properties
var currentAge: Int { value?.age() ?? 0 }
}

Expand All @@ -41,7 +40,7 @@ enum WorkoutOperation: String, CaseIterable, Sendable {
@Statable([WorkoutActivity].self, operations: WorkoutOperation.self)
@MainActor @Observable
final class WorkoutStore {
// Auto-generates value, state, operations, etc.
public init() {}

var isRecording: Bool {
operations.isActive(.recordStrength) || operations.isActive(.recordCardio)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Statable/Macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,14 @@
/// try await api.record(workout)
/// }
/// ```
@attached(member, names: named(_asyncValue), named(_operations), named(init), named(value), named(state), named(isLoading), named(isIdle), named(isFailed), named(hasValue), named(error), named(operations), named(set), named(setError), named(startLoading), named(reset), named(load), named(loadIfNeeded), named(reload))
@attached(member, names: named(_asyncValue), named(_operations), named(value), named(state), named(isLoading), named(isIdle), named(isFailed), named(hasValue), named(error), named(operations), named(set), named(setError), named(startLoading), named(reset), named(load), named(loadIfNeeded), named(reload))
@attached(extension, conformances: Statable, Sendable)
public macro Statable<T: Sendable>(
_ valueType: T.Type
) = #externalMacro(module: "StatableMacros", type: "StatableMacro")

/// 操作トラッキング付きの@Statable
@attached(member, names: named(_asyncValue), named(_operations), named(init), named(value), named(state), named(isLoading), named(isIdle), named(isFailed), named(hasValue), named(error), named(operations), named(set), named(setError), named(startLoading), named(reset), named(load), named(loadIfNeeded), named(reload))
@attached(member, names: named(_asyncValue), named(_operations), named(value), named(state), named(isLoading), named(isIdle), named(isFailed), named(hasValue), named(error), named(operations), named(set), named(setError), named(startLoading), named(reset), named(load), named(loadIfNeeded), named(reload))
@attached(extension, conformances: Statable, Sendable)
public macro Statable<T: Sendable, Op: Hashable & Sendable>(
_ valueType: T.Type,
Expand Down
24 changes: 3 additions & 21 deletions Sources/StatableMacros/StatableMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,6 @@ public struct StatableMacro: MemberMacro, ExtensionMacro {
let valueType = args.valueType
let operationType = args.operationType

// 既存のinitがあるか確認
let hasExistingInit = declaration.memberBlock.members.contains { member in
if let initDecl = member.decl.as(InitializerDeclSyntax.self) {
return initDecl.signature.parameterClause.parameters.isEmpty
}
return false
}

var members: [DeclSyntax] = []

// 1. 内部 AsyncValue ストレージ
Expand All @@ -61,17 +53,7 @@ public struct StatableMacro: MemberMacro, ExtensionMacro {
)
}

// 3. init(既存がなければ生成)
if !hasExistingInit {
members.append(
"""
public init() {
}
"""
)
}

// 4. Computed Properties (パススルー)
// 3. Computed Properties (パススルー)
members.append(contentsOf: [
"""
/// 現在の値
Expand Down Expand Up @@ -117,7 +99,7 @@ public struct StatableMacro: MemberMacro, ExtensionMacro {
""",
])

// 5. Operations プロパティ(operationType指定時のみ)
// 4. Operations プロパティ(operationType指定時のみ)
if let opType = operationType {
members.append(
"""
Expand All @@ -129,7 +111,7 @@ public struct StatableMacro: MemberMacro, ExtensionMacro {
)
}

// 6. Methods (パススルー)
// 5. Methods (パススルー)
members.append(contentsOf: [
"""
/// 値を設定(loaded状態に遷移)
Expand Down
19 changes: 3 additions & 16 deletions Tests/StatableMacrosTests/StatableMacroTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ final class StatableMacroTests: XCTestCase {
@ObservationIgnored
private let _asyncValue = AsyncValue<Profile>()

public init() {
}

/// 現在の値
public var value: Profile? {
_asyncValue.value
Expand Down Expand Up @@ -107,22 +104,18 @@ final class StatableMacroTests: XCTestCase {
)
}

func testStatableDoesNotGenerateInitIfExists() throws {
func testStatablePreservesUserDefinedInit() throws {
assertMacroExpansion(
"""
@Statable(Profile.self)
final class ProfileStore {
init() {
print("custom init")
}
public init() {}
}
""",
expandedSource: """

final class ProfileStore {
init() {
print("custom init")
}
public init() {}

@ObservationIgnored
private let _asyncValue = AsyncValue<Profile>()
Expand Down Expand Up @@ -219,9 +212,6 @@ final class StatableMacroTests: XCTestCase {
@ObservationIgnored
private let _asyncValue = AsyncValue<Module.Profile>()

public init() {
}

/// 現在の値
public var value: Module.Profile? {
_asyncValue.value
Expand Down Expand Up @@ -317,9 +307,6 @@ final class StatableMacroTests: XCTestCase {
@ObservationIgnored
private let _operations = OperationTracker<Op>()

public init() {
}

/// 現在の値
public var value: [Activity]? {
_asyncValue.value
Expand Down
2 changes: 2 additions & 0 deletions Tests/StatableMacrosTests/TrackMacroTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ final class TrackMacroTests: XCTestCase {
}
}

@ObservationIgnored
private let _operations = OperationTracker<Operation>()
""",
macros: trackMacros
Expand All @@ -42,6 +43,7 @@ final class TrackMacroTests: XCTestCase {
}
}

@ObservationIgnored
private let _ops = OperationTracker<WorkoutStore.Operation>()
""",
macros: trackMacros
Expand Down