Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
40f18bb
SwiftData: implement @Model and property macros
joshuaswarren Oct 3, 2026
7885c94
SwiftData: fix keypath escapes
joshuaswarren Oct 3, 2026
7c45087
SwiftData: add try to forwarded expansions
joshuaswarren Oct 3, 2026
5d750a6
SwiftData: fix compactMap return
joshuaswarren Oct 3, 2026
1fa80f7
SwiftData: fix contains(where:)
joshuaswarren Oct 3, 2026
bed8723
SwiftData: cast attribute list elements
joshuaswarren Oct 3, 2026
c69a9a9
SwiftData: diagnostics, let support, Query/ModelActor/Unique/Index im…
joshuaswarren Oct 3, 2026
f9e4e58
SwiftData: type-annotate accessor array for raw interpolation
joshuaswarren Oct 3, 2026
6dbedca
SwiftData: use classKeyword
joshuaswarren Oct 3, 2026
7afd698
SwiftData: fix syntax node member names
joshuaswarren Oct 3, 2026
698e483
SwiftData: fix property extraction, Unique/Index schema entries, Tran…
joshuaswarren Oct 3, 2026
352a4b2
SwiftData: plain interpolation in schema body string
joshuaswarren Oct 3, 2026
8fa42ca
SwiftData: diagnostic anchors, first-binding schema, spacing fixes
joshuaswarren Oct 3, 2026
59b27aa
SwiftData: actor detection, dedupe otherProperties declaration
joshuaswarren Oct 3, 2026
d5a0536
SwiftData: kind-specific blank line placement in schema metadata
joshuaswarren Oct 3, 2026
93307aa
SwiftData: add mixed Unique+Index fixture
joshuaswarren Oct 3, 2026
a14dd5f
SwiftData: index blocks before unique blocks in schema metadata
joshuaswarren Oct 4, 2026
5cf0b67
SwiftData: fix blank line assembly around schema blocks
joshuaswarren Oct 4, 2026
2857d8c
SwiftData: join schema blocks directly
joshuaswarren Oct 4, 2026
03dd511
SwiftData: mark macros as supported in README
joshuaswarren Oct 4, 2026
f3e2251
SwiftData: re-register macro target on current upstream main
joshuaswarren Nov 6, 2026
dc03bab
Fix SwiftData macro review findings
joshuaswarren Nov 6, 2026
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
10 changes: 10 additions & 0 deletions IntegrationTests/SwiftData/Attribute-options.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import SwiftData

@Model
final class Attributed {
@Attribute(.unique) var id: String = ""
@Attribute(.externalStorage) var blob: Data = Data()
@Attribute(originalName: "legacy_name") var renamed: String = ""
@Attribute(.unique, originalName: "old", hashModifier: "h1") var composite: Int = 0
@Attribute var bare: Int = 0
}
9 changes: 9 additions & 0 deletions IntegrationTests/SwiftData/Index-basic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import SwiftData

@Model
final class Indexed {
var name: String = ""
var age: Int = 0

#Index<Indexed>([\.name], [\.name, \.age])
}
6 changes: 6 additions & 0 deletions IntegrationTests/SwiftData/Model-actor-misuse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import SwiftData

@Model
actor ModelActor {
var name: String = ""
}
27 changes: 27 additions & 0 deletions IntegrationTests/SwiftData/Model-basic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Foundation
import SwiftData

@Model
final class Draft {
var content: String
var creationDate: Date
@Attribute(.unique) var id: UUID
@Transient var scratch: Int = 0
@Relationship(deleteRule: .cascade) var tags: [Tag] = []

init(content: String) {
self.content = content
self.creationDate = Date()
self.id = UUID()
}
}

@Model
final class Tag {
var title: String
var count: Int?

init(title: String) {
self.title = title
}
}
16 changes: 16 additions & 0 deletions IntegrationTests/SwiftData/Model-computed.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import SwiftData

@Model
final class WithComputed {
var width: Double = 0
var height: Double = 0

var area: Double {
width * height
}

var doubled: Double {
get { width * 2 }
set { width = newValue / 2 }
}
}
11 changes: 11 additions & 0 deletions IntegrationTests/SwiftData/Model-defaults.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation
import SwiftData

@Model
final class Defaults {
var name: String = "unnamed"
var flag: Bool = false
var rating: Double = 3.5
var createdAt = Date.now
var metadata: [String: Int] = [:]
}
6 changes: 6 additions & 0 deletions IntegrationTests/SwiftData/Model-empty.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import SwiftData

@Model
final class Empty {
init() {}
}
6 changes: 6 additions & 0 deletions IntegrationTests/SwiftData/Model-enum-misuse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import SwiftData

@Model
enum AlsoNotAClass {
case a
}
7 changes: 7 additions & 0 deletions IntegrationTests/SwiftData/Model-inferred.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import SwiftData

@Model
final class Inferred {
var count = 5
var label = "hi"
}
11 changes: 11 additions & 0 deletions IntegrationTests/SwiftData/Model-let.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import SwiftData

@Model
final class WithLet {
let identity: String
var name: String = ""

init(identity: String) {
self.identity = identity
}
}
6 changes: 6 additions & 0 deletions IntegrationTests/SwiftData/Model-multiple-bindings.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import SwiftData

@Model
final class Multi {
var first: String = "", second: String = ""
}
14 changes: 14 additions & 0 deletions IntegrationTests/SwiftData/Model-nested-types.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import SwiftData

@Model
final class Outer {
var title: String = ""

struct Point {
var x: Int = 0
}

@Model final class Inner {
var value: Int = 0
}
}
12 changes: 12 additions & 0 deletions IntegrationTests/SwiftData/Model-observers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import SwiftData

@Model
final class WithObservers {
var count: Int = 0 {
willSet {}
didSet {}
}
var note: String = ""

init() {}
}
8 changes: 8 additions & 0 deletions IntegrationTests/SwiftData/Model-optionals.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import SwiftData

@Model
final class Optionals {
var note: String?
var scores: [Int]?
var forced: String!
}
10 changes: 10 additions & 0 deletions IntegrationTests/SwiftData/Model-public.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import SwiftData

@Model
public class PublicModel {
public var name: String = ""
var internal_: Int = 0
private var secret: String = ""

public init() {}
}
6 changes: 6 additions & 0 deletions IntegrationTests/SwiftData/Model-qualified.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import SwiftData

@SwiftData.Model
final class Qualified {
var name: String = ""
}
8 changes: 8 additions & 0 deletions IntegrationTests/SwiftData/Model-static.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import SwiftData

@Model
final class WithStatic {
static var counter: Int = 0
class var kind: String { "generic" }
var title: String = ""
}
6 changes: 6 additions & 0 deletions IntegrationTests/SwiftData/Model-struct-misuse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import SwiftData

@Model
struct NotAClass {
var name: String = ""
}
6 changes: 6 additions & 0 deletions IntegrationTests/SwiftData/ModelActor-basic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import SwiftData

@ModelActor
actor DataStore {
func touch() {}
}
6 changes: 6 additions & 0 deletions IntegrationTests/SwiftData/ModelActor-misuse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import SwiftData

@ModelActor
struct NotAnActor {
var x: Int = 0
}
6 changes: 6 additions & 0 deletions IntegrationTests/SwiftData/ModelActor-public.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import SwiftData

@ModelActor
public actor PublicStore {
func touch() {}
}
15 changes: 15 additions & 0 deletions IntegrationTests/SwiftData/Query-basic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import SwiftUI
import SwiftData

@Model
final class Note {
var text: String = ""
}

struct NotesView: View {
@Query private var notes: [Note]

var body: some View {
Text("hi")
}
}
15 changes: 15 additions & 0 deletions IntegrationTests/SwiftData/Query-descriptor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import SwiftUI
import SwiftData

@Model
final class Item {
var name: String = ""
}

struct ItemView: View {
@Query(FetchDescriptor<Item>()) private var items: [Item]

var body: some View {
Text("hi")
}
}
15 changes: 15 additions & 0 deletions IntegrationTests/SwiftData/Query-filter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import SwiftUI
import SwiftData

@Model
final class Task {
var done: Bool = false
}

struct TaskView: View {
@Query(filter: #Predicate<Task> { !$0.done }, sort: \Task.done) private var tasks: [Task]

var body: some View {
Text("hi")
}
}
15 changes: 15 additions & 0 deletions IntegrationTests/SwiftData/Query-public.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import SwiftUI
import SwiftData

@Model
final class Entry {
var title: String = ""
}

struct EntryView: View {
@Query(sort: \Entry.title) var entries: [Entry]

var body: some View {
Text("hi")
}
}
16 changes: 16 additions & 0 deletions IntegrationTests/SwiftData/Query-sort-animation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import SwiftUI
import SwiftData

@Model
final class Note {
var text: String = ""
var date: Date = Date.now
}

struct SortedView: View {
@Query(sort: \Note.date, order: .reverse, animation: .default) private var notes: [Note]

var body: some View {
Text("hi")
}
}
21 changes: 21 additions & 0 deletions IntegrationTests/SwiftData/Relationship-options.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import SwiftData

@Model
final class Parent {
var name: String = ""
@Relationship(deleteRule: .cascade) var cascading: [Child] = []
@Relationship(deleteRule: .nullify) var nullifying: [Child] = []
@Relationship(deleteRule: .noAction) var untouched: [Child] = []
@Relationship(deleteRule: .deny) var denied: [Child] = []
@Relationship(inverse: \Child.parent) var withInverse: [Child] = []
@Relationship(minimumModelCount: 1, maximumModelCount: 10) var bounded: [Child] = []
@Relationship(originalName: "old_rel", hashModifier: "h") var renamed: [Child] = []
@Relationship var plain: [Child] = []
@Relationship(deleteRule: .nullify) var optionalChild: Child?
}

@Model
final class Child {
var name: String = ""
var parent: Parent?
}
6 changes: 6 additions & 0 deletions IntegrationTests/SwiftData/Transformable-basic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import SwiftData

@Model
final class WithTransformable {
@_TransformablePersistedProperty var payload: Data = Data()
}
12 changes: 12 additions & 0 deletions IntegrationTests/SwiftData/Transient-qualified.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import SwiftData

@Model
final class QualifiedTransient {
@SwiftData.Transient var cache: Int = 0
@Transient var genericCache: Optional<String>
var name: String = ""

init() {
genericCache = nil
}
}
13 changes: 13 additions & 0 deletions IntegrationTests/SwiftData/Transient-usage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import SwiftData

@Model
final class WithTransient {
var name: String = ""
@Transient var cache: Int = 0
@Transient var cacheNoDefault: Int
@Transient var weakref: String?

init() {
cacheNoDefault = 0
}
}
9 changes: 9 additions & 0 deletions IntegrationTests/SwiftData/Unique-basic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import SwiftData

@Model
final class Constrained {
var email: String = ""
var handle: String = ""

#Unique<Constrained>([\.email], [\.handle])
}
10 changes: 10 additions & 0 deletions IntegrationTests/SwiftData/Unique-index-mixed.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import SwiftData

@Model
final class Mixed {
var email: String = ""
var city: String = ""

#Unique<Mixed>([\.email])
#Index<Mixed>([\.city])
}
7 changes: 7 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ let macroTargets: [Target] = [
name: "PreviewsMacros",
dependencies: ["OpenAppleMacrosBase"],
),
.target(
name: "SwiftDataMacros",
dependencies: [
"OpenAppleMacrosBase",
.product(name: "SwiftDiagnostics", package: "swift-syntax"),
],
),
.target(
name: "SwiftUIMacros",
dependencies: [
Expand Down
Loading