Skip to content

Commit bfecc4a

Browse files
committed
Macro-based schema discovery proof of concept
1 parent 45ea4a0 commit bfecc4a

File tree

11 files changed

+306
-169
lines changed

11 files changed

+306
-169
lines changed

.jenkins.yml

Lines changed: 1 addition & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -4,145 +4,12 @@
44
# This is a generated file produced by scripts/pr-ci-matrix.rb.
55

66
xcode_version:
7-
- 14.1
8-
- 14.2
9-
- 14.3.1
7+
- 15.0
108
target:
119
- docs
1210
- swiftlint
13-
- osx
14-
- osx-encryption
15-
- osx-object-server
16-
- swiftpm
17-
- swiftpm-debug
18-
- swiftpm-address
19-
- swiftpm-thread
20-
- swiftpm-ios
21-
- ios-static
22-
- ios-dynamic
23-
- watchos
24-
- tvos
2511
- osx-swift
26-
- ios-swift
27-
- tvos-swift
28-
- osx-swift-evolution
29-
- ios-swift-evolution
30-
- tvos-swift-evolution
31-
- catalyst
32-
- catalyst-swift
33-
- xcframework
34-
- cocoapods-osx
35-
- cocoapods-ios
36-
- cocoapods-ios-dynamic
37-
- cocoapods-watchos
38-
- swiftui-ios
39-
- swiftui-server-osx
4012
configuration:
4113
- N/A
4214

4315
exclude:
44-
45-
- xcode_version: 14.1
46-
target: docs
47-
48-
- xcode_version: 14.2
49-
target: docs
50-
51-
- xcode_version: 14.1
52-
target: swiftlint
53-
54-
- xcode_version: 14.2
55-
target: swiftlint
56-
57-
- xcode_version: 14.1
58-
target: osx-encryption
59-
60-
- xcode_version: 14.2
61-
target: osx-encryption
62-
63-
- xcode_version: 14.2
64-
target: osx-object-server
65-
66-
- xcode_version: 14.2
67-
target: swiftpm
68-
69-
- xcode_version: 14.1
70-
target: swiftpm-address
71-
72-
- xcode_version: 14.2
73-
target: swiftpm-address
74-
75-
- xcode_version: 14.1
76-
target: swiftpm-thread
77-
78-
- xcode_version: 14.2
79-
target: swiftpm-thread
80-
81-
- xcode_version: 14.2
82-
target: ios-static
83-
84-
- xcode_version: 14.2
85-
target: ios-dynamic
86-
87-
- xcode_version: 14.2
88-
target: watchos
89-
90-
- xcode_version: 14.2
91-
target: tvos
92-
93-
- xcode_version: 14.2
94-
target: ios-swift
95-
96-
- xcode_version: 14.2
97-
target: tvos-swift
98-
99-
- xcode_version: 14.1
100-
target: osx-swift-evolution
101-
102-
- xcode_version: 14.2
103-
target: osx-swift-evolution
104-
105-
- xcode_version: 14.1
106-
target: ios-swift-evolution
107-
108-
- xcode_version: 14.2
109-
target: ios-swift-evolution
110-
111-
- xcode_version: 14.1
112-
target: tvos-swift-evolution
113-
114-
- xcode_version: 14.2
115-
target: tvos-swift-evolution
116-
117-
- xcode_version: 14.2
118-
target: catalyst
119-
120-
- xcode_version: 14.2
121-
target: catalyst-swift
122-
123-
- xcode_version: 14.1
124-
target: xcframework
125-
126-
- xcode_version: 14.2
127-
target: xcframework
128-
129-
- xcode_version: 14.2
130-
target: cocoapods-ios
131-
132-
- xcode_version: 14.2
133-
target: cocoapods-ios-dynamic
134-
135-
- xcode_version: 14.2
136-
target: cocoapods-watchos
137-
138-
- xcode_version: 14.1
139-
target: swiftui-ios
140-
141-
- xcode_version: 14.2
142-
target: swiftui-ios
143-
144-
- xcode_version: 14.1
145-
target: swiftui-server-osx
146-
147-
- xcode_version: 14.2
148-
target: swiftui-server-osx

Realm.xcodeproj/project.pbxproj

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@
218218
3FA5E94D266064C4008F1345 /* ModernObjectCreationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA5E94C266064C4008F1345 /* ModernObjectCreationTests.swift */; };
219219
3FAF2D4129577100002EAC93 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FAF2D4029577100002EAC93 /* TestUtils.swift */; };
220220
3FAF2D4229577100002EAC93 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FAF2D4029577100002EAC93 /* TestUtils.swift */; };
221+
3FB0FF102A31019800E13BF3 /* RealmMacro in Frameworks */ = {isa = PBXBuildFile; productRef = 3FB0FF0F2A31019800E13BF3 /* RealmMacro */; };
221222
3FB19069265ECF0C00DA7C76 /* ModernObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB19068265ECF0C00DA7C76 /* ModernObjectTests.swift */; };
222223
3FB1906B265ED23300DA7C76 /* ModernTestObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB1906A265ED23300DA7C76 /* ModernTestObjects.swift */; };
223224
3FB4FA1719F5D2740020D53B /* SwiftTestObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F8D90B196CB8DD00475368 /* SwiftTestObjects.swift */; };
@@ -941,6 +942,7 @@
941942
3F9F53D42718E8E6000EEB4A /* CustomPersistableTestObjects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPersistableTestObjects.swift; sourceTree = "<group>"; };
942943
3FA5E94C266064C4008F1345 /* ModernObjectCreationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModernObjectCreationTests.swift; sourceTree = "<group>"; };
943944
3FAF2D4029577100002EAC93 /* TestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = "<group>"; };
945+
3FB0FF0E2A2FE6A200E13BF3 /* RealmMacro */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = RealmMacro; sourceTree = "<group>"; };
944946
3FB19068265ECF0C00DA7C76 /* ModernObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModernObjectTests.swift; sourceTree = "<group>"; };
945947
3FB1906A265ED23300DA7C76 /* ModernTestObjects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModernTestObjects.swift; sourceTree = "<group>"; };
946948
3FB56E7E250D457A00A6216B /* ObjectServerTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ObjectServerTests.xcconfig; sourceTree = "<group>"; };
@@ -1306,6 +1308,7 @@
13061308
buildActionMask = 2147483647;
13071309
files = (
13081310
3F98162A2317763000C3543D /* libc++.tbd in Frameworks */,
1311+
3FB0FF102A31019800E13BF3 /* RealmMacro in Frameworks */,
13091312
3F98162B2317763600C3543D /* libz.tbd in Frameworks */,
13101313
5D66102A1BE98DD00021E04F /* Realm.framework in Frameworks */,
13111314
);
@@ -1840,6 +1843,7 @@
18401843
E8D89B8E1955FC6D00CF2B9A = {
18411844
isa = PBXGroup;
18421845
children = (
1846+
3FB0FF0E2A2FE6A200E13BF3 /* RealmMacro */,
18431847
5D660FB71BE98B770021E04F /* Configuration */,
18441848
1A7B82361D51254600750296 /* Frameworks */,
18451849
E81A1FB41955FCE000FDED82 /* LICENSE */,
@@ -2338,6 +2342,9 @@
23382342
5D66102C1BE98DF60021E04F /* PBXTargetDependency */,
23392343
);
23402344
name = RealmSwift;
2345+
packageProductDependencies = (
2346+
3FB0FF0F2A31019800E13BF3 /* RealmMacro */,
2347+
);
23412348
productName = RealmSwift;
23422349
productReference = 5D660FCC1BE98C560021E04F /* RealmSwift.framework */;
23432350
productType = "com.apple.product-type.framework";
@@ -3850,6 +3857,13 @@
38503857
defaultConfigurationName = Release;
38513858
};
38523859
/* End XCConfigurationList section */
3860+
3861+
/* Begin XCSwiftPackageProductDependency section */
3862+
3FB0FF0F2A31019800E13BF3 /* RealmMacro */ = {
3863+
isa = XCSwiftPackageProductDependency;
3864+
productName = RealmMacro;
3865+
};
3866+
/* End XCSwiftPackageProductDependency section */
38533867
};
38543868
rootObject = E8D89B8F1955FC6D00CF2B9A /* Project object */;
38553869
}

RealmMacro/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc

RealmMacro/Package.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// swift-tools-version: 5.9
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
import CompilerPluginSupport
6+
7+
let package = Package(
8+
name: "RealmMacro",
9+
platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)],
10+
products: [
11+
.library(
12+
name: "RealmMacro",
13+
targets: ["RealmMacro"]
14+
),
15+
.executable(
16+
name: "RealmMacroClient",
17+
targets: ["RealmMacroClient"]
18+
),
19+
],
20+
dependencies: [
21+
.package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0-swift-5.9-DEVELOPMENT-SNAPSHOT-2023-04-25-b"),
22+
],
23+
targets: [
24+
.macro(
25+
name: "RealmMacroMacros",
26+
dependencies: [
27+
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
28+
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
29+
]
30+
),
31+
.target(name: "RealmMacro", dependencies: ["RealmMacroMacros"]),
32+
.executableTarget(name: "RealmMacroClient", dependencies: ["RealmMacro"]),
33+
.testTarget(
34+
name: "RealmMacroTests",
35+
dependencies: [
36+
"RealmMacroMacros",
37+
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
38+
]
39+
),
40+
]
41+
)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@attached(conformance)
2+
@attached(member, names: named(_rlmProperties))
3+
//@attached(memberAttribute)
4+
public macro RealmModel() -> () = #externalMacro(module: "RealmMacroMacros", type: "RealmObjectMacro")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import RealmMacro
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import SwiftCompilerPlugin
2+
import SwiftSyntax
3+
import SwiftSyntaxBuilder
4+
import SwiftSyntaxMacros
5+
6+
public func expansion(
7+
of node: AttributeSyntax,
8+
attachedTo declaration: some DeclGroupSyntax,
9+
providingAttributesFor member: some DeclSyntaxProtocol,
10+
in context: some MacroExpansionContext
11+
) throws -> [AttributeSyntax] {
12+
return []
13+
guard let property = member.as(VariableDeclSyntax.self), property.bindings.count == 1 else {
14+
return []
15+
}
16+
17+
if let attributes = property.attributes {
18+
for attr in attributes {
19+
if case let .attribute(attr) = attr {
20+
if attr.attributeName.as(SimpleTypeIdentifierSyntax.self)?.name.text == "Ignored" {
21+
return []
22+
}
23+
}
24+
}
25+
}
26+
27+
let binding = property.bindings.first!
28+
switch binding.accessor {
29+
case .none:
30+
break
31+
case .accessors(let node):
32+
for accessor in node.accessors {
33+
switch accessor.accessorKind.tokenKind {
34+
case .keyword(.get), .keyword(.set):
35+
return []
36+
default:
37+
break
38+
}
39+
}
40+
break
41+
case .getter:
42+
return []
43+
}
44+
45+
return [
46+
AttributeSyntax(
47+
attributeName: SimpleTypeIdentifierSyntax(name: .identifier("Persisted"))
48+
)
49+
.with(\.leadingTrivia, [.newlines(1), .spaces(2)])
50+
]
51+
}
52+
53+
public struct RealmObjectMacro: MemberMacro, ConformanceMacro {
54+
public static func expansion(
55+
of node: AttributeSyntax,
56+
providingConformancesOf declaration: some DeclGroupSyntax,
57+
in context: some MacroExpansionContext
58+
) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] {
59+
return [(TypeSyntax("RealmSwift._RealmObjectSchemaDiscoverable"), nil)]
60+
}
61+
62+
public static func expansion(
63+
of node: AttributeSyntax,
64+
providingMembersOf declaration: some DeclGroupSyntax,
65+
in context: some MacroExpansionContext
66+
) throws -> [DeclSyntax] {
67+
guard let declaration = declaration.as(ClassDeclSyntax.self) else { fatalError() }
68+
let className = declaration.identifier
69+
let properties = declaration.memberBlock.members.compactMap { (decl) -> (String, String, AttributeSyntax)? in
70+
guard let property = decl.decl.as(VariableDeclSyntax.self), property.bindings.count == 1 else {
71+
return nil
72+
}
73+
guard let attributes = property.attributes else { return nil }
74+
let persistedAttr = attributes.compactMap { attr in
75+
if case let .attribute(attr) = attr {
76+
if attr.attributeName.as(SimpleTypeIdentifierSyntax.self)?.name.text == "Persisted" {
77+
return attr
78+
}
79+
}
80+
return nil
81+
}.first
82+
guard let persistedAttr else { return nil }
83+
84+
let binding = property.bindings.first!
85+
guard let identifier = binding.pattern.as(IdentifierPatternSyntax.self) else { return nil }
86+
guard let typeAnnotation = binding.typeAnnotation else { return nil }
87+
let name = identifier.identifier.text
88+
let type = typeAnnotation.type.trimmedDescription
89+
return (name, type, persistedAttr)
90+
}
91+
92+
let rlmProperties = properties.map { (name, type, persistedAttr) in
93+
let expr = ExprSyntax("RLMProperty(name: \(literal: name), type: \(raw: type).self, keyPath: \\\(className).\(raw: name))")
94+
var functionCall = expr.as(FunctionCallExprSyntax.self)!
95+
96+
if let argument = persistedAttr.argument, case let .argumentList(argList) = argument {
97+
var argumentList = Array(functionCall.argumentList)
98+
argumentList[argumentList.count - 1].trailingComma = ", "
99+
argumentList.append(contentsOf: argList)
100+
functionCall.argumentList = TupleExprElementListSyntax(argumentList)
101+
}
102+
return functionCall.as(ExprSyntax.self)!
103+
}
104+
return ["""
105+
106+
static var _rlmProperties: [RLMProperty] = \(ArrayExprSyntax {
107+
for property in rlmProperties {
108+
ArrayElementSyntax(expression: property)
109+
}
110+
})
111+
"""]
112+
}
113+
}
114+
115+
@main
116+
struct RealmMacroPlugin: CompilerPlugin {
117+
let providingMacros: [Macro.Type] = [
118+
RealmObjectMacro.self,
119+
]
120+
}

0 commit comments

Comments
 (0)