From 10afb27251c49727d3cdde7fc8c1dba97c3535d2 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Wed, 7 Jun 2023 11:04:39 -0700 Subject: [PATCH] Macro-based schema discovery proof of concept --- .jenkins.yml | 135 +----- Realm.xcodeproj/project.pbxproj | 14 + Realm/RLMCollection.mm | 4 +- RealmMacro/.gitignore | 8 + RealmMacro/Package.swift | 41 ++ .../Sources/RealmMacro/RealmMacro.swift | 26 ++ .../Sources/RealmMacroClient/main.swift | 1 + .../RealmMacroMacros/RealmMacroMacro.swift | 415 ++++++++++++++++++ .../RealmMacroTests/RealmMacroTests.swift | 248 +++++++++++ RealmSwift/Impl/PropertyAccessors.swift | 8 +- RealmSwift/Impl/SchemaDiscovery.swift | 56 +++ RealmSwift/PersistedProperty.swift | 206 ++++----- RealmSwift/Tests/ModernTestObjects.swift | 88 +++- scripts/pr-ci-matrix.rb | 36 +- 14 files changed, 993 insertions(+), 293 deletions(-) create mode 100644 RealmMacro/.gitignore create mode 100644 RealmMacro/Package.swift create mode 100644 RealmMacro/Sources/RealmMacro/RealmMacro.swift create mode 100644 RealmMacro/Sources/RealmMacroClient/main.swift create mode 100644 RealmMacro/Sources/RealmMacroMacros/RealmMacroMacro.swift create mode 100644 RealmMacro/Tests/RealmMacroTests/RealmMacroTests.swift diff --git a/.jenkins.yml b/.jenkins.yml index 96a102c093..16c43369bf 100644 --- a/.jenkins.yml +++ b/.jenkins.yml @@ -4,145 +4,12 @@ # This is a generated file produced by scripts/pr-ci-matrix.rb. xcode_version: - - 14.1 - - 14.2 - - 14.3.1 + - 15.0 target: - docs - swiftlint - - osx - - osx-encryption - - osx-object-server - - swiftpm - - swiftpm-debug - - swiftpm-address - - swiftpm-thread - - swiftpm-ios - - ios-static - - ios-dynamic - - watchos - - tvos - osx-swift - - ios-swift - - tvos-swift - - osx-swift-evolution - - ios-swift-evolution - - tvos-swift-evolution - - catalyst - - catalyst-swift - - xcframework - - cocoapods-osx - - cocoapods-ios - - cocoapods-ios-dynamic - - cocoapods-watchos - - swiftui-ios - - swiftui-server-osx configuration: - N/A exclude: - - - xcode_version: 14.1 - target: docs - - - xcode_version: 14.2 - target: docs - - - xcode_version: 14.1 - target: swiftlint - - - xcode_version: 14.2 - target: swiftlint - - - xcode_version: 14.1 - target: osx-encryption - - - xcode_version: 14.2 - target: osx-encryption - - - xcode_version: 14.2 - target: osx-object-server - - - xcode_version: 14.2 - target: swiftpm - - - xcode_version: 14.1 - target: swiftpm-address - - - xcode_version: 14.2 - target: swiftpm-address - - - xcode_version: 14.1 - target: swiftpm-thread - - - xcode_version: 14.2 - target: swiftpm-thread - - - xcode_version: 14.2 - target: ios-static - - - xcode_version: 14.2 - target: ios-dynamic - - - xcode_version: 14.2 - target: watchos - - - xcode_version: 14.2 - target: tvos - - - xcode_version: 14.2 - target: ios-swift - - - xcode_version: 14.2 - target: tvos-swift - - - xcode_version: 14.1 - target: osx-swift-evolution - - - xcode_version: 14.2 - target: osx-swift-evolution - - - xcode_version: 14.1 - target: ios-swift-evolution - - - xcode_version: 14.2 - target: ios-swift-evolution - - - xcode_version: 14.1 - target: tvos-swift-evolution - - - xcode_version: 14.2 - target: tvos-swift-evolution - - - xcode_version: 14.2 - target: catalyst - - - xcode_version: 14.2 - target: catalyst-swift - - - xcode_version: 14.1 - target: xcframework - - - xcode_version: 14.2 - target: xcframework - - - xcode_version: 14.2 - target: cocoapods-ios - - - xcode_version: 14.2 - target: cocoapods-ios-dynamic - - - xcode_version: 14.2 - target: cocoapods-watchos - - - xcode_version: 14.1 - target: swiftui-ios - - - xcode_version: 14.2 - target: swiftui-ios - - - xcode_version: 14.1 - target: swiftui-server-osx - - - xcode_version: 14.2 - target: swiftui-server-osx diff --git a/Realm.xcodeproj/project.pbxproj b/Realm.xcodeproj/project.pbxproj index 506a77279e..5cbb148e41 100644 --- a/Realm.xcodeproj/project.pbxproj +++ b/Realm.xcodeproj/project.pbxproj @@ -218,6 +218,7 @@ 3FA5E94D266064C4008F1345 /* ModernObjectCreationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA5E94C266064C4008F1345 /* ModernObjectCreationTests.swift */; }; 3FAF2D4129577100002EAC93 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FAF2D4029577100002EAC93 /* TestUtils.swift */; }; 3FAF2D4229577100002EAC93 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FAF2D4029577100002EAC93 /* TestUtils.swift */; }; + 3FB0FF102A31019800E13BF3 /* RealmMacro in Frameworks */ = {isa = PBXBuildFile; productRef = 3FB0FF0F2A31019800E13BF3 /* RealmMacro */; }; 3FB19069265ECF0C00DA7C76 /* ModernObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB19068265ECF0C00DA7C76 /* ModernObjectTests.swift */; }; 3FB1906B265ED23300DA7C76 /* ModernTestObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB1906A265ED23300DA7C76 /* ModernTestObjects.swift */; }; 3FB4FA1719F5D2740020D53B /* SwiftTestObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F8D90B196CB8DD00475368 /* SwiftTestObjects.swift */; }; @@ -941,6 +942,7 @@ 3F9F53D42718E8E6000EEB4A /* CustomPersistableTestObjects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPersistableTestObjects.swift; sourceTree = ""; }; 3FA5E94C266064C4008F1345 /* ModernObjectCreationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModernObjectCreationTests.swift; sourceTree = ""; }; 3FAF2D4029577100002EAC93 /* TestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = ""; }; + 3FB0FF0E2A2FE6A200E13BF3 /* RealmMacro */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = RealmMacro; sourceTree = ""; }; 3FB19068265ECF0C00DA7C76 /* ModernObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModernObjectTests.swift; sourceTree = ""; }; 3FB1906A265ED23300DA7C76 /* ModernTestObjects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModernTestObjects.swift; sourceTree = ""; }; 3FB56E7E250D457A00A6216B /* ObjectServerTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ObjectServerTests.xcconfig; sourceTree = ""; }; @@ -1306,6 +1308,7 @@ buildActionMask = 2147483647; files = ( 3F98162A2317763000C3543D /* libc++.tbd in Frameworks */, + 3FB0FF102A31019800E13BF3 /* RealmMacro in Frameworks */, 3F98162B2317763600C3543D /* libz.tbd in Frameworks */, 5D66102A1BE98DD00021E04F /* Realm.framework in Frameworks */, ); @@ -1840,6 +1843,7 @@ E8D89B8E1955FC6D00CF2B9A = { isa = PBXGroup; children = ( + 3FB0FF0E2A2FE6A200E13BF3 /* RealmMacro */, 5D660FB71BE98B770021E04F /* Configuration */, 1A7B82361D51254600750296 /* Frameworks */, E81A1FB41955FCE000FDED82 /* LICENSE */, @@ -2338,6 +2342,9 @@ 5D66102C1BE98DF60021E04F /* PBXTargetDependency */, ); name = RealmSwift; + packageProductDependencies = ( + 3FB0FF0F2A31019800E13BF3 /* RealmMacro */, + ); productName = RealmSwift; productReference = 5D660FCC1BE98C560021E04F /* RealmSwift.framework */; productType = "com.apple.product-type.framework"; @@ -3850,6 +3857,13 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + 3FB0FF0F2A31019800E13BF3 /* RealmMacro */ = { + isa = XCSwiftPackageProductDependency; + productName = RealmMacro; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = E8D89B8F1955FC6D00CF2B9A /* Project object */; } diff --git a/Realm/RLMCollection.mm b/Realm/RLMCollection.mm index aebaf1eb63..6bb79e0fe2 100644 --- a/Realm/RLMCollection.mm +++ b/Realm/RLMCollection.mm @@ -276,9 +276,7 @@ NSUInteger RLMUnmanagedFastEnumerate(id collection, NSFastEnumerationState *stat auto swiftAccessor = prop.swiftAccessor; for (size_t i = 0; i < count; i++) { accessor->_row = collection.get(i); - if (swiftAccessor) { - [swiftAccessor initialize:prop on:accessor]; - } + [swiftAccessor initialize:prop on:accessor]; [array addObject:[accessor valueForKey:key] ?: NSNull.null]; } return array; diff --git a/RealmMacro/.gitignore b/RealmMacro/.gitignore new file mode 100644 index 0000000000..0023a53406 --- /dev/null +++ b/RealmMacro/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/RealmMacro/Package.swift b/RealmMacro/Package.swift new file mode 100644 index 0000000000..a1215f8b88 --- /dev/null +++ b/RealmMacro/Package.swift @@ -0,0 +1,41 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription +import CompilerPluginSupport + +let package = Package( + name: "RealmMacro", + platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)], + products: [ + .library( + name: "RealmMacro", + targets: ["RealmMacro"] + ), + .executable( + name: "RealmMacroClient", + targets: ["RealmMacroClient"] + ), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-syntax.git", branch: "release/5.9") + ], + targets: [ + .macro( + name: "RealmMacroMacros", + dependencies: [ + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + .product(name: "SwiftCompilerPlugin", package: "swift-syntax") + ] + ), + .target(name: "RealmMacro", dependencies: ["RealmMacroMacros"]), + .executableTarget(name: "RealmMacroClient", dependencies: ["RealmMacro"]), + .testTarget( + name: "RealmMacroTests", + dependencies: [ + "RealmMacroMacros", + .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), + ] + ), + ] +) diff --git a/RealmMacro/Sources/RealmMacro/RealmMacro.swift b/RealmMacro/Sources/RealmMacro/RealmMacro.swift new file mode 100644 index 0000000000..f183989881 --- /dev/null +++ b/RealmMacro/Sources/RealmMacro/RealmMacro.swift @@ -0,0 +1,26 @@ +@attached(conformance) +@attached(member, names: named(_realmProperties)) +//@attached(memberAttribute) +public macro RealmSchemaDiscovery() -> Void = #externalMacro(module: "RealmMacroMacros", type: "RealmSchemaDiscovery") + +@attached(conformance) +@attached(member, names: named(_realmProperties), named(_realmUnmanagedStorage)) +@attached(memberAttribute) +public macro RealmModel() -> () = #externalMacro(module: "RealmMacroMacros", type: "RealmObjectMacro2") + +@attached(accessor) +@attached(peer, names: arbitrary) +public macro _PersistedProperty(index: Int) -> () = #externalMacro(module: "RealmMacroMacros", type: "PersistedProperty2") + +@attached(peer, names: arbitrary) +public macro PersistedProperty(index: Int) -> () = #externalMacro(module: "RealmMacroMacros", type: "PersistedProperty2") + +@attached(accessor) +public macro OriginProperty(name: String) -> () = #externalMacro(module: "RealmMacroMacros", type: "Marker") + +@attached(accessor) +public macro PrimaryKey() -> () = #externalMacro(module: "RealmMacroMacros", type: "Marker") +@attached(accessor) +public macro Indexed() -> () = #externalMacro(module: "RealmMacroMacros", type: "Marker") +@attached(accessor) +public macro Ignored() -> () = #externalMacro(module: "RealmMacroMacros", type: "Marker") diff --git a/RealmMacro/Sources/RealmMacroClient/main.swift b/RealmMacro/Sources/RealmMacroClient/main.swift new file mode 100644 index 0000000000..9e288d16f5 --- /dev/null +++ b/RealmMacro/Sources/RealmMacroClient/main.swift @@ -0,0 +1 @@ +import RealmMacro diff --git a/RealmMacro/Sources/RealmMacroMacros/RealmMacroMacro.swift b/RealmMacro/Sources/RealmMacroMacros/RealmMacroMacro.swift new file mode 100644 index 0000000000..f120439bb7 --- /dev/null +++ b/RealmMacro/Sources/RealmMacroMacros/RealmMacroMacro.swift @@ -0,0 +1,415 @@ +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros + +public func expansion( + of node: AttributeSyntax, + attachedTo declaration: some DeclGroupSyntax, + providingAttributesFor member: some DeclSyntaxProtocol, + in context: some MacroExpansionContext +) throws -> [AttributeSyntax] { + return [] + guard let property = member.as(VariableDeclSyntax.self), property.bindings.count == 1 else { + return [] + } + + if let attributes = property.attributes { + for attr in attributes { + if case let .attribute(attr) = attr { + if attr.attributeName.as(SimpleTypeIdentifierSyntax.self)?.name.text == "Ignored" { + return [] + } + } + } + } + + let binding = property.bindings.first! + switch binding.accessor { + case .none: + break + case .accessors(let node): + for accessor in node.accessors { + switch accessor.accessorKind.tokenKind { + case .keyword(.get), .keyword(.set): + return [] + default: + break + } + } + break + case .getter: + return [] + } + + return [ + AttributeSyntax( + attributeName: SimpleTypeIdentifierSyntax(name: .identifier("Persisted")) + ) + .with(\.leadingTrivia, [.newlines(1), .spaces(2)]) + ] +} + +enum RealmSchemaDiscoveryError: CustomStringConvertible, Error { + case missingTypeAnnotation + case noProperties + + var description: String { + switch self { + case .missingTypeAnnotation: + return "@RealmSchemaDiscovery requires an explicit type annotation for all @Persisted properties and cannot infer the type from the default value" + case .noProperties: + return "No properties found in @RealmModel class. All object types must have at least one persisted property." + } + } +} + +public struct RealmSchemaDiscovery: MemberMacro, ConformanceMacro { + public static func expansion( + of node: AttributeSyntax, + providingConformancesOf declaration: some DeclGroupSyntax, + in context: some MacroExpansionContext + ) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] { + return [(TypeSyntax("RealmSwift._RealmObjectSchemaDiscoverable"), nil)] + } + + public static func expansion( + of node: AttributeSyntax, + providingMembersOf declaration: some DeclGroupSyntax, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + guard let declaration = declaration.as(ClassDeclSyntax.self) else { fatalError() } + let className = declaration.identifier + let properties = try declaration.memberBlock.members.compactMap { (decl) -> (String, String, AttributeSyntax)? in + guard let property = decl.decl.as(VariableDeclSyntax.self), property.bindings.count == 1 else { + return nil + } + guard let attributes = property.attributes else { return nil } + let persistedAttr = attributes.compactMap { attr in + if case let .attribute(attr) = attr { + if attr.attributeName.as(SimpleTypeIdentifierSyntax.self)?.name.text == "Persisted" { + return attr + } + } + return nil + }.first + guard let persistedAttr else { return nil } + + let binding = property.bindings.first! + guard let identifier = binding.pattern.as(IdentifierPatternSyntax.self) else { return nil } + guard let typeAnnotation = binding.typeAnnotation else { + throw RealmSchemaDiscoveryError.missingTypeAnnotation + } + let name = identifier.identifier.text + let type = typeAnnotation.type.trimmedDescription + return (name, type, persistedAttr) + } + + let rlmProperties = properties.map { (name, type, persistedAttr) in + let expr = ExprSyntax("RLMProperty(name: \(literal: name), type: \(raw: type).self, keyPath: \\\(className).\(raw: name))") + var functionCall = expr.as(FunctionCallExprSyntax.self)! + + if let argument = persistedAttr.argument, case let .argumentList(argList) = argument { + var argumentList = Array(functionCall.argumentList) + argumentList[argumentList.count - 1].trailingComma = ", " + argumentList.append(contentsOf: argList) + functionCall.argumentList = TupleExprElementListSyntax(argumentList) + } + return functionCall.as(ExprSyntax.self)! + } + return [""" + + static var _realmProperties: [RLMProperty] = \(ArrayExprSyntax { + for property in rlmProperties { + ArrayElementSyntax(expression: property) + } + }) + """] + } +} + +func validatedProperty(_ property: some DeclSyntaxProtocol) -> VariableDeclSyntax? { + guard let property = property.as(VariableDeclSyntax.self), property.bindings.count == 1 else { + return nil + } + + if property.modifiers != nil { + return nil + } + + if let attributes = property.attributes { + for attr in attributes { + if case let .attribute(attr) = attr { + if attr.attributeName.as(SimpleTypeIdentifierSyntax.self)?.name.text == "Ignored" { + return nil + } + } + } + } + + let binding = property.bindings.first! + switch binding.accessor { + case .none: + break + case .accessors(let node): + for accessor in node.accessors { + switch accessor.accessorKind.tokenKind { + case .keyword(.get), .keyword(.set): + return nil + default: + break + } + } + break + case .getter: + return nil + } + return property +} + +func appendArgument(_ call: inout FunctionCallExprSyntax, label: TokenSyntax, expression: some ExprSyntaxProtocol) { + let trivia: Trivia? = call.argumentList.isEmpty ? nil : ", " + call = call.addArgument(TupleExprElementSyntax(leadingTrivia: trivia, label: label, colon: ": ", expression: expression)) +} + +struct Property { + var parentName: TokenSyntax + var name: TokenSyntax + var index: Int + var indexed: Bool = false + var primaryKey: Bool = false + var originProperty: ExprSyntax? = nil + + var rlmProperty: FunctionCallExprSyntax { + var call = ExprSyntax("RLMProperty(name: \(literal: name.text), index: \(literal: index), keyPath: \(keyPath))").as(FunctionCallExprSyntax.self)! + if primaryKey { + appendArgument(&call, label: "primaryKey", expression: BooleanLiteralExprSyntax(booleanLiteral: true)) + } + else if indexed { + appendArgument(&call, label: "indexed", expression: BooleanLiteralExprSyntax(booleanLiteral: true)) + } + if let originProperty { + appendArgument(&call, label: "originProperty", expression: originProperty) + } + return call + } + + var rlmProperty2: FunctionCallExprSyntax { + var call = ExprSyntax("RLMProperty(name: \(literal: name.text), keyPath: \(keyPath))").as(FunctionCallExprSyntax.self)! + if primaryKey { + appendArgument(&call, label: "primaryKey", expression: BooleanLiteralExprSyntax(booleanLiteral: true)) + } + else if indexed { + appendArgument(&call, label: "indexed", expression: BooleanLiteralExprSyntax(booleanLiteral: true)) + } + if let originProperty { + appendArgument(&call, label: "originProperty", expression: originProperty) + } + return call + } + + var keyPath: ExprSyntax { + "\\\(parentName).\(name)" + } +} + +func getProperties(ofClass cls: ClassDeclSyntax) -> [Property] { + var index = 0 + return cls.memberBlock.members.compactMap { (member) -> Property? in + guard let property = validatedProperty(member.decl), + let binding = property.bindings.first, + let identifier = binding.pattern.as(IdentifierPatternSyntax.self) + else { + return nil + } + var p = Property(parentName: cls.identifier, name: identifier.identifier, index: index) + index += 1 + + if let attributes = property.attributes { + for attribute in attributes.compactMap({ $0.as(AttributeSyntax.self) }) { + switch attribute.attributeName.trimmedDescription { + case "PrimaryKey", "RealmSwift.PrimaryKey": + p.primaryKey = true + case "Indexed", "RealmSwift.Indexed": + p.indexed = true + case "OriginProperty", "RealmSwift.OriginProperty": + p.originProperty = attribute.argument?.as(TupleExprElementListSyntax.self)?.first?.expression + default: + print("Unrecognized attribute: \(attribute.attributeName.trimmedDescription)") + break + } + } + } + + return p + } +} + +public struct RealmObjectMacro: MemberMacro, ConformanceMacro, MemberAttributeMacro { + static public func expansion( + of node: AttributeSyntax, + attachedTo declaration: some DeclGroupSyntax, + providingAttributesFor member: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [AttributeSyntax] { + guard let property = validatedProperty(member) else { return [] } + guard let cls = declaration.as(ClassDeclSyntax.self) else { return [] } + var index = 0 + let description = property.trimmedDescription + for decl in cls.memberBlock.members { + if decl.decl.trimmedDescription == description { + break; + } + if validatedProperty(decl.decl) != nil { + index += 1 + } + } + + return ["@_PersistedProperty(index: \(raw: index))"] + } + + public static func expansion( + of node: AttributeSyntax, + providingConformancesOf declaration: some DeclGroupSyntax, + in context: some MacroExpansionContext + ) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] { + return [(TypeSyntax("RealmSwift._RealmObjectSchemaDiscoverable"), nil)] + } + + + public static func expansion( + of node: AttributeSyntax, + providingMembersOf declaration: some DeclGroupSyntax, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + guard let declaration = declaration.as(ClassDeclSyntax.self) else { fatalError() } + let properties = getProperties(ofClass: declaration) + if properties.isEmpty { + throw RealmSchemaDiscoveryError.noProperties + } + return [ + "@Ignored private var _realmUnmanagedStorage = _unmanagedStorage(\(raw: properties.map(\.keyPath).map(\.trimmedDescription).joined(separator: ", ")))", + "public static var _realmProperties: [RLMProperty] = [\n\(raw: properties.map(\.rlmProperty).map(\.trimmedDescription).joined(separator: ",\n"))\n]" + ] + } +} + +public struct RealmObjectMacro2: MemberMacro, ConformanceMacro, MemberAttributeMacro { + static public func expansion( + of node: AttributeSyntax, + attachedTo declaration: some DeclGroupSyntax, + providingAttributesFor member: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [AttributeSyntax] { + guard let property = validatedProperty(member) else { return [] } + guard let cls = declaration.as(ClassDeclSyntax.self) else { return [] } + var index = 0 + let description = property.trimmedDescription + for decl in cls.memberBlock.members { + if decl.decl.trimmedDescription == description { + break; + } + if validatedProperty(decl.decl) != nil { + index += 1 + } + } + + return ["@_PersistedProperty(index: \(raw: index))"] + } + + public static func expansion( + of node: AttributeSyntax, + providingConformancesOf declaration: some DeclGroupSyntax, + in context: some MacroExpansionContext + ) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] { + return [(TypeSyntax("RealmSwift._RealmObjectSchemaDiscoverable"), nil)] + } + + + public static func expansion( + of node: AttributeSyntax, + providingMembersOf declaration: some DeclGroupSyntax, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + guard let declaration = declaration.as(ClassDeclSyntax.self) else { fatalError() } + let properties = getProperties(ofClass: declaration) + if properties.isEmpty { + throw RealmSchemaDiscoveryError.noProperties + } + return [ + "public static var _realmProperties: [RLMProperty] = [\n\(raw: properties.map(\.rlmProperty2).map(\.trimmedDescription).joined(separator: ",\n"))\n]" + ] + } +} + +public struct Marker: AccessorMacro { + public static func expansion(of node: AttributeSyntax, providingAccessorsOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext) throws -> [AccessorDeclSyntax] { + return [] + } +} + +public struct PersistedProperty: AccessorMacro { + public static func expansion(of node: AttributeSyntax, providingAccessorsOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext) throws -> [AccessorDeclSyntax] { +// let prop = validatedProperty(declaration) + let index = node.argument!.as(TupleExprElementListSyntax.self)!.first!.expression + return [ + """ + get { + if let unmanaged = _realmUnmanagedStorage { + return unmanaged.\(index) + } else { + return RealmSwift._getProperty(self, \(index)) + } + } + """, + """ + set { + if _realmUnmanagedStorage != nil { + _realmUnmanagedStorage!.\(index) = newValue + } else { + RealmSwift._setProperty(self, \(index), newValue) + } + } + """, + ] + } +} + +public struct PersistedProperty2: AccessorMacro, PeerMacro { + public static func expansion(of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext) throws -> [DeclSyntax] { + let prop = validatedProperty(declaration)!.bindings.first! + let name = prop.pattern.as(IdentifierPatternSyntax.self)!.identifier + let type = prop.typeAnnotation!.type.trimmed + let ret: [DeclSyntax] = ["@Ignored var _$\(name) = PropertyStorage<\(type)>.unmanagedNoDefault()"] + print("\(ret[0].description)") + return ret + } + + public static func expansion(of node: AttributeSyntax, providingAccessorsOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext) throws -> [AccessorDeclSyntax] { + let name = validatedProperty(declaration)!.bindings.first!.pattern.as(IdentifierPatternSyntax.self)!.identifier + return [ + """ + get { + _$\(name).get(self) + } + """, + """ + set { + _$\(name).set(self, value: newValue) + } + """, + ] + } +} + +@main +struct RealmMacroPlugin: CompilerPlugin { + let providingMacros: [Macro.Type] = [ + RealmSchemaDiscovery.self, + RealmObjectMacro.self, + RealmObjectMacro2.self, + Marker.self, + PersistedProperty.self, + PersistedProperty2.self, + ] +} diff --git a/RealmMacro/Tests/RealmMacroTests/RealmMacroTests.swift b/RealmMacro/Tests/RealmMacroTests/RealmMacroTests.swift new file mode 100644 index 0000000000..46e734a3be --- /dev/null +++ b/RealmMacro/Tests/RealmMacroTests/RealmMacroTests.swift @@ -0,0 +1,248 @@ +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest +import RealmMacroMacros +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros +import SwiftParser +import SwiftBasicFormat + +final class RealmMacroTests: XCTestCase { + func _testStuff() { + assertMacroExpansion( + #""" + @RealmModel class MacroObject: Object { + @PrimaryKey var pk: ObjectId + @Ignored var ignored: Int = 1 + var intCol: Int = 2 + @Indexed var indexed: Int + var obj: MacroObject? + @OriginProperty(name: "obj") + var linkingObjects: LinkingObjects + } + """#, + expandedSource: #""" + class MacroObject: Object { + var pk: ObjectId { + get { + if let unmanaged = _realmUnmanagedStorage { + return unmanaged.0 + } else { + return ObjectId._rlmGetProperty(self, 0) + } + } + set { + if _realmUnmanagedStorage != nil { + _realmUnmanagedStorage!.0 = newValue + } else { + ObjectId._rlmSetProperty(self, 0, newValue) + } + } + } + var ignored: Int = 1 + var intCol: Int { + get { + if let unmanaged = _realmUnmanagedStorage { + return unmanaged.1 + } else { + return Int._rlmGetProperty(self, 1) + } + } + set { + if _realmUnmanagedStorage != nil { + _realmUnmanagedStorage!.1 = newValue + } else { + Int._rlmSetProperty(self, 1, newValue) + } + } + } + var indexed: Int { + get { + if let unmanaged = _realmUnmanagedStorage { + return unmanaged.2 + } else { + return Int._rlmGetProperty(self, 2) + } + } + set { + if _realmUnmanagedStorage != nil { + _realmUnmanagedStorage!.2 = newValue + } else { + Int._rlmSetProperty(self, 2, newValue) + } + } + } + var obj: MacroObject? { + get { + if let unmanaged = _realmUnmanagedStorage { + return unmanaged.3 + } else { + return MacroObject?._rlmGetProperty(self, 3) + } + } + set { + if _realmUnmanagedStorage != nil { + _realmUnmanagedStorage!.3 = newValue + } else { + MacroObject?._rlmSetProperty(self, 3, newValue) + } + } + } + var linkingObjects: LinkingObjects { + get { + if let unmanaged = _realmUnmanagedStorage { + return unmanaged.4 + } else { + return LinkingObjects ._rlmGetProperty(self, 4) + } + } + set { + if _realmUnmanagedStorage != nil { + _realmUnmanagedStorage!.4 = newValue + } else { + LinkingObjects ._rlmSetProperty(self, 4, newValue) + } + } + } + private var _realmUnmanagedStorage: (ObjectId, Int, Int, MacroObject?, LinkingObjects)? = nil + public static var _realmProperties: [RLMProperty] = [ + RLMProperty(name: "pk", index: 0, keyPath: \MacroObject.pk), + RLMProperty(name: "intCol", index: 1, keyPath: \MacroObject.intCol), + RLMProperty(name: "indexed", index: 2, keyPath: \MacroObject.indexed), + RLMProperty(name: "obj", index: 3, keyPath: \MacroObject.obj), + RLMProperty(name: "linkingObjects", index: 4, keyPath: \MacroObject.linkingObjects) + ] + } + extension MacroObject: RealmSwift._RealmObjectSchemaDiscoverable { + } + """#, + macros: [ + "RealmModel": RealmObjectMacro.self, + "PrimaryKey": Marker.self, + "Indexed": Marker.self, + "Ignored": Marker.self, + "OriginProperty": Marker.self, + "_PersistedProperty": PersistedProperty.self + ]) + } + + func testStuff2() { + assertMacroExpansion( + #""" + @RealmModel class MacroObject: Object { + @PrimaryKey var pk: ObjectId + @Ignored var ignored: Int = 1 + var intCol: Int = 2 + @Indexed var indexed: Int + var obj: MacroObject? + @OriginProperty(name: "obj") + var linkingObjects: LinkingObjects + } + """#, + expandedSource: #""" + class MacroObject: Object { + var pk: ObjectId { + get { + _$pk.get(self) + } + set { + _$pk.set(self, newValue) + } + } + var _$pk: PropertyStorage = .unmanagedNoDefault + var ignored: Int = 1 + var intCol: Int { + get { + _$intCol.get(self) + } + set { + _$intCol.set(self, newValue) + } + } + var _$intCol: PropertyStorage = .unmanagedNoDefault + var indexed: Int { + get { + _$indexed.get(self) + } + set { + _$indexed.set(self, newValue) + } + } + var _$indexed: PropertyStorage = .unmanagedNoDefault + var obj: MacroObject? { + get { + _$obj.get(self) + } + set { + _$obj.set(self, newValue) + } + } + var _$obj: PropertyStorage = .unmanagedNoDefault + var linkingObjects: LinkingObjects { + get { + _$linkingObjects.get(self) + } + set { + _$linkingObjects.set(self, newValue) + } + } + var _$linkingObject: PropertyStorage> = .unmanagedNoDefault + public static var _realmProperties: [RLMProperty] = [ + RLMProperty(name: "pk", keyPath: \MacroObject.pk, primaryKey: true), + RLMProperty(name: "intCol", keyPath: \MacroObject.intCol), + RLMProperty(name: "indexed", keyPath: \MacroObject.indexed, indexed: true), + RLMProperty(name: "obj", keyPath: \MacroObject.obj), + RLMProperty(name: "linkingObjects", keyPath: \MacroObject.linkingObjects, originProperty: "obj") + ] + } + extension MacroObject: RealmSwift._RealmObjectSchemaDiscoverable { + } + """#, + macros: [ + "RealmModel": RealmObjectMacro2.self, + "PrimaryKey": Marker.self, + "Indexed": Marker.self, + "Ignored": Marker.self, + "OriginProperty": Marker.self, + "_PersistedProperty": PersistedProperty2.self + ]) + } + + func testStuff3() { + assertMacroExpansion( + #""" + class MacroObject: Object { + @_PersistedProperty var pk: ObjectId + @_PersistedProperty var intCol: Int = 2 + } + """#, + expandedSource: #""" + class MacroObject: Object { + var pk: ObjectId { + get { + _$pk.get(self) + } + set { + _$pk.set(self, value: newValue) + } + } + @Ignored var _$pk: PropertyStorage = .unmanagedNoDefault + var intCol: Int = 2 { + get { + _$intCol.get(self) + } + set { + _$intCol.set(self, value: newValue) + } + } + @Ignored var _$intCol: PropertyStorage = .unmanagedNoDefault + } + """#, + macros: [ + "_PersistedProperty": PersistedProperty2.self + ]) + + } +} diff --git a/RealmSwift/Impl/PropertyAccessors.swift b/RealmSwift/Impl/PropertyAccessors.swift index 3ae25a0226..abdb88024d 100644 --- a/RealmSwift/Impl/PropertyAccessors.swift +++ b/RealmSwift/Impl/PropertyAccessors.swift @@ -163,8 +163,8 @@ internal class RealmPropertyAccessor: RLMManagedProper // MARK: - Modern Property Accessors internal class PersistedPropertyAccessor: RLMManagedPropertyAccessor { - fileprivate static func bound(_ property: RLMProperty, _ obj: RLMObjectBase) -> UnsafeMutablePointer> { - return ptr(property, obj).assumingMemoryBound(to: Persisted.self) + fileprivate static func bound(_ property: RLMProperty, _ obj: RLMObjectBase) -> UnsafeMutablePointer> { + return ptr(property, obj).assumingMemoryBound(to: PropertyStorage.self) } @objc override class func initialize(_ property: RLMProperty, on parent: RLMObjectBase) { @@ -227,8 +227,8 @@ internal class PersistedMapAccessor: RLMManagedPropertyAccessor { - private static func bound(_ property: RLMProperty, _ obj: RLMObjectBase) -> UnsafeMutablePointer>> { - return ptr(property, obj).assumingMemoryBound(to: Persisted>.self) + private static func bound(_ property: RLMProperty, _ obj: RLMObjectBase) -> UnsafeMutablePointer>> { + return ptr(property, obj).assumingMemoryBound(to: PropertyStorage>.self) } @objc override class func initialize(_ property: RLMProperty, on parent: RLMObjectBase) { diff --git a/RealmSwift/Impl/SchemaDiscovery.swift b/RealmSwift/Impl/SchemaDiscovery.swift index a2c5df936a..e5d26a7aa8 100644 --- a/RealmSwift/Impl/SchemaDiscovery.swift +++ b/RealmSwift/Impl/SchemaDiscovery.swift @@ -20,6 +20,10 @@ import Foundation import Realm import Realm.Private +public protocol _RealmObjectSchemaDiscoverable { + static var _realmProperties: [RLMProperty] { get } +} + // A type which we can get the runtime schema information from public protocol _RealmSchemaDiscoverable { // The Realm property type associated with this type @@ -62,6 +66,54 @@ internal extension RLMProperty { } } +public extension RLMProperty { + convenience init(name: String, type: _RealmSchemaDiscoverable.Type, keyPath: KeyPath, indexed: Bool = false, primaryKey: Bool = false, originProperty: String? = nil) { + self.init() + self.name = name + self.type = type._rlmType + self.optional = type._rlmOptional + self.indexed = indexed + self.isPrimary = primaryKey + self.linkOriginPropertyName = originProperty + type._rlmPopulateProperty(self) + U._rlmSetAccessor(self) + self.swiftIvar = ivar_getOffset(class_getInstanceVariable(T.self, "_" + name)!) + } + + convenience init(name: String, keyPath: KeyPath, indexed: Bool = false, primaryKey: Bool = false, originProperty: String? = nil) { + self.init() + self.name = name + self.type = U._rlmType + self.optional = U._rlmOptional + self.indexed = indexed + self.isPrimary = primaryKey + self.linkOriginPropertyName = originProperty + U._rlmPopulateProperty(self) + U._rlmSetAccessor(self) + self.swiftIvar = ivar_getOffset(class_getInstanceVariable(T.self, "_$" + name)!) + } + + convenience init(name: String, index: Int, keyPath: KeyPath, indexed: Bool = false, primaryKey: Bool = false, originProperty: String? = nil) { + self.init() + self.name = name + self.type = U._rlmType + self.optional = U._rlmOptional + self.indexed = indexed + self.isPrimary = primaryKey + self.linkOriginPropertyName = originProperty + U._rlmPopulateProperty(self) + U._rlmSetAccessor(self) + self.swiftIvar = -index + } +} + +public func _getProperty(_ obj: ObjectBase, _ index: Int) -> T { + T._rlmGetProperty(obj, PropertyKey(index)) +} +public func _setProperty(_ obj: ObjectBase, _ index: Int, _ value: T) { + T._rlmSetProperty(obj, PropertyKey(index), value) +} + private func getModernProperties(_ object: ObjectBase) -> [RLMProperty] { let columnNames: [String: String] = type(of: object).propertiesMapping() return Mirror(reflecting: object).children.compactMap { prop in @@ -179,6 +231,10 @@ private func getLegacyProperties(_ object: ObjectBase, _ cls: ObjectBase.Type) - } private func getProperties(_ cls: RLMObjectBase.Type) -> [RLMProperty] { + if let cls = cls as? _RealmObjectSchemaDiscoverable.Type { + return cls._realmProperties + } + // Check for any modern properties and only scan for legacy properties if // none are found. let object = cls.init() diff --git a/RealmSwift/PersistedProperty.swift b/RealmSwift/PersistedProperty.swift index d094fff055..a88b9a0f22 100644 --- a/RealmSwift/PersistedProperty.swift +++ b/RealmSwift/PersistedProperty.swift @@ -95,7 +95,7 @@ import Realm.Private /// runtime errors. @propertyWrapper public struct Persisted { - private var storage: PropertyStorage + internal var storage: PropertyStorage /// :nodoc: @available(*, unavailable, message: "@Persisted can only be used as a property on a Realm object") @@ -123,111 +123,12 @@ public struct Persisted { storage storageKeyPath: ReferenceWritableKeyPath ) -> Value { get { - return observed[keyPath: storageKeyPath].get(observed) + return observed[keyPath: storageKeyPath].storage.get(observed) } set { - observed[keyPath: storageKeyPath].set(observed, value: newValue) + observed[keyPath: storageKeyPath].storage.set(observed, value: newValue) } } - - // Called via RLMInitializeSwiftAccessor() to initialize the wrapper on a - // newly created managed accessor object. - internal mutating func initialize(_ object: ObjectBase, key: PropertyKey) { - storage = .managed(key: key) - } - - // Collection types use this instead of the above because when promoting a - // unmanaged object to a managed object we want to reuse the existing collection - // object if it exists. Currently it always will exist because we read the - // value of the property first, but there's a potential optimization to - // skip initializing it on that read. - internal mutating func initializeCollection(_ object: ObjectBase, key: PropertyKey) -> Value? { - if case let .unmanaged(value, _, _) = storage { - storage = .managedCached(value: value, key: key) - return value - } - if case let .unmanagedObserved(value, _) = storage { - storage = .managedCached(value: value, key: key) - return value - } - storage = .managed(key: key) - return nil - } - - internal mutating func get(_ object: ObjectBase) -> Value { - switch storage { - case let .unmanaged(value, _, _): - return value - case .unmanagedNoDefault: - let value = Value._rlmDefaultValue() - storage = .unmanaged(value: value) - return value - case let .unmanagedObserved(value, key): - if let lastAccessedNames = object.lastAccessedNames { - let name: String - if Value._rlmType == .linkingObjects { - name = RLMObjectBaseObjectSchema(object)!.computedProperties[Int(key)].name - } else { - name = RLMObjectBaseObjectSchema(object)!.properties[Int(key)].name - } - lastAccessedNames.add(name) - if let type = Value.self as? KeypathRecorder.Type { - return type.keyPathRecorder(with: lastAccessedNames) as! Value - } - return Value._rlmDefaultValue() - } - return value - case let .managed(key): - let v = Value._rlmGetProperty(object, key) - if Value._rlmRequiresCaching { - // Collection types are initialized once and stored on the - // object rather than on every access. Non-collection types - // cannot be cached without some mechanism for knowing when to - // reread them which we don't currently have. - storage = .managedCached(value: v, key: key) - } - return v - case let .managedCached(value, _): - return value - } - } - - internal mutating func set(_ object: ObjectBase, value: Value) { - if value is MutableRealmCollection { - (get(object) as! MutableRealmCollection).assign(value) - return - } - switch storage { - case let .unmanagedObserved(_, key): - let name = RLMObjectBaseObjectSchema(object)!.properties[Int(key)].name - object.willChangeValue(forKey: name) - storage = .unmanagedObserved(value: value, key: key) - object.didChangeValue(forKey: name) - case .managed(let key), .managedCached(_, let key): - Value._rlmSetProperty(object, key, value) - case .unmanaged, .unmanagedNoDefault: - storage = .unmanaged(value: value, indexed: false, primary: false) - } - } - - // Initialize an unmanaged property for observation - internal mutating func observe(_ object: ObjectBase, property: RLMProperty) { - let value: Value - switch storage { - case let .unmanaged(v, _, _): - value = v - case .unmanagedNoDefault: - value = Value._rlmDefaultValue() - case .unmanagedObserved, .managed, .managedCached: - return - } - // Mutating a collection triggers a KVO notification on the parent, so - // we need to ensure that the collection has a pointer to its parent. - if let value = value as? MutableRealmCollection { - value.setParent(object, property) - } - storage = .unmanagedObserved(value: value, key: PropertyKey(property.index)) - } } extension Persisted: Decodable where Value: Decodable { @@ -411,7 +312,7 @@ extension Persisted: DiscoverablePersistedProperty where Value: _Persistable { // The indexed and primary members of the unmanaged cases are used only for // schema discovery and are not always preserved once the Persisted is actually // used for anything. -private enum PropertyStorage { +public enum PropertyStorage { // An unmanaged value. This is used as the initial state if the user did // supply a default value, or if an unmanaged property is read or written // (but not observed). @@ -440,4 +341,103 @@ private enum PropertyStorage { // performance optimization (creating them involves a few memory allocations) // and is required for KVO to work correctly. case managedCached(value: T, key: PropertyKey) + + public mutating func get(_ object: ObjectBase) -> T { + switch self { + case let .unmanaged(value, _, _): + return value + case .unmanagedNoDefault: + let value = T._rlmDefaultValue() + self = .unmanaged(value: value) + return value + case let .unmanagedObserved(value, key): + if let lastAccessedNames = object.lastAccessedNames { + let name: String + if T._rlmType == .linkingObjects { + name = RLMObjectBaseObjectSchema(object)!.computedProperties[Int(key)].name + } else { + name = RLMObjectBaseObjectSchema(object)!.properties[Int(key)].name + } + lastAccessedNames.add(name) + if let type = T.self as? KeypathRecorder.Type { + return type.keyPathRecorder(with: lastAccessedNames) as! T + } + return T._rlmDefaultValue() + } + return value + case let .managed(key): + let v = T._rlmGetProperty(object, key) + if T._rlmRequiresCaching { + // Collection types are initialized once and stored on the + // object rather than on every access. Non-collection types + // cannot be cached without some mechanism for knowing when to + // reread them which we don't currently have. + self = .managedCached(value: v, key: key) + } + return v + case let .managedCached(value, _): + return value + } + } + + public mutating func set(_ object: ObjectBase, value: T) { + if value is MutableRealmCollection { + (get(object) as! MutableRealmCollection).assign(value) + return + } + switch self { + case let .unmanagedObserved(_, key): + let name = RLMObjectBaseObjectSchema(object)!.properties[Int(key)].name + object.willChangeValue(forKey: name) + self = .unmanagedObserved(value: value, key: key) + object.didChangeValue(forKey: name) + case .managed(let key), .managedCached(_, let key): + T._rlmSetProperty(object, key, value) + case .unmanaged, .unmanagedNoDefault: + self = .unmanaged(value: value, indexed: false, primary: false) + } + } + + // Called via RLMInitializeSwiftAccessor() to initialize the wrapper on a + // newly created managed accessor object. + public mutating func initialize(_ object: ObjectBase, key: PropertyKey) { + self = .managed(key: key) + } + + // Collection types use this instead of the above because when promoting a + // unmanaged object to a managed object we want to reuse the existing collection + // object if it exists. Currently it always will exist because we read the + // value of the property first, but there's a potential optimization to + // skip initializing it on that read. + public mutating func initializeCollection(_ object: ObjectBase, key: PropertyKey) -> T? { + if case let .unmanaged(value, _, _) = self { + self = .managedCached(value: value, key: key) + return value + } + if case let .unmanagedObserved(value, _) = self { + self = .managedCached(value: value, key: key) + return value + } + self = .managed(key: key) + return nil + } + + // Initialize an unmanaged property for observation + public mutating func observe(_ object: ObjectBase, property: RLMProperty) { + let value: T + switch self { + case let .unmanaged(v, _, _): + value = v + case .unmanagedNoDefault: + value = T._rlmDefaultValue() + case .unmanagedObserved, .managed, .managedCached: + return + } + // Mutating a collection triggers a KVO notification on the parent, so + // we need to ensure that the collection has a pointer to its parent. + if let value = value as? MutableRealmCollection { + value.setParent(object, property) + } + self = .unmanagedObserved(value: value, key: PropertyKey(property.index)) + } } diff --git a/RealmSwift/Tests/ModernTestObjects.swift b/RealmSwift/Tests/ModernTestObjects.swift index f80cb93228..e79fc9314a 100644 --- a/RealmSwift/Tests/ModernTestObjects.swift +++ b/RealmSwift/Tests/ModernTestObjects.swift @@ -19,7 +19,28 @@ import Foundation import RealmSwift import Realm +import RealmMacro +func _unmanagedStorage(_ fields: repeat KeyPath) -> (repeat each U)? { + return nil +} + +class MacroObject2: Object { + @PersistedProperty(index: 0) var value: Int = 1 +} + +@RealmModel class MacroObject: Object { + @PrimaryKey var pk: ObjectId + @Ignored var ignored: Int = 1 + var intCol: Int = 2 + @Indexed var indexed: Int + + var obj: MacroObject? + @OriginProperty(name: "obj") + var linkingObjects: LinkingObjects +} + +@RealmSchemaDiscovery class ModernAllTypesObject: Object { @Persisted(primaryKey: true) var pk: ObjectId var ignored: Int = 1 @@ -160,6 +181,7 @@ class ModernAllTypesObject: Object { var linkingObjects: LinkingObjects } +@RealmSchemaDiscovery class LinkToModernAllTypesObject: Object { @Persisted var object: ModernAllTypesObject? @Persisted var list: List @@ -188,19 +210,17 @@ class ModernImplicitlyUnwrappedOptionalObject: Object { @Persisted var optUuidCol: UUID! } +@RealmSchemaDiscovery class ModernLinkToPrimaryStringObject: Object { - @Persisted var pk = "" + @Persisted(primaryKey: true) var pk: String = "" @Persisted var object: ModernPrimaryStringObject? @Persisted var objects: List - - override class func primaryKey() -> String? { - return "pk" - } } +@RealmSchemaDiscovery class ModernUTF8Object: Object { // swiftlint:disable:next identifier_name - @Persisted var 柱колоéнǢкƱаم👍 = "值значен™👍☞⎠‱௹♣︎☐▼❒∑⨌⧭иеمرحبا" + @Persisted var 柱колоéнǢкƱаم👍: String = "值значен™👍☞⎠‱௹♣︎☐▼❒∑⨌⧭иеمرحبا" } protocol ModernPrimaryKeyObject: Object { @@ -208,86 +228,107 @@ protocol ModernPrimaryKeyObject: Object { var pk: PrimaryKey { get set } } +@RealmSchemaDiscovery class ModernPrimaryStringObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: String } +@RealmSchemaDiscovery class ModernPrimaryOptionalStringObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: String? } +@RealmSchemaDiscovery class ModernPrimaryIntObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int } +@RealmSchemaDiscovery class ModernPrimaryOptionalIntObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int? } +@RealmSchemaDiscovery class ModernPrimaryInt8Object: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int8 } +@RealmSchemaDiscovery class ModernPrimaryOptionalInt8Object: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int8? } +@RealmSchemaDiscovery class ModernPrimaryInt16Object: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int16 } +@RealmSchemaDiscovery class ModernPrimaryOptionalInt16Object: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int16? } +@RealmSchemaDiscovery class ModernPrimaryInt32Object: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int32 } +@RealmSchemaDiscovery class ModernPrimaryOptionalInt32Object: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int32? } +@RealmSchemaDiscovery class ModernPrimaryInt64Object: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int64 } +@RealmSchemaDiscovery class ModernPrimaryOptionalInt64Object: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: Int64? } +@RealmSchemaDiscovery class ModernPrimaryUUIDObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: UUID } +@RealmSchemaDiscovery class ModernPrimaryOptionalUUIDObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: UUID? } +@RealmSchemaDiscovery class ModernPrimaryObjectIdObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: ObjectId } +@RealmSchemaDiscovery class ModernPrimaryOptionalObjectIdObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: ObjectId? } +@RealmSchemaDiscovery class ModernPrimaryIntEnumObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: ModernIntEnum } +@RealmSchemaDiscovery class ModernPrimaryOptionalIntEnumObject: Object, ModernPrimaryKeyObject { @Persisted(primaryKey: true) var pk: ModernIntEnum? } +@RealmSchemaDiscovery class ModernIndexedIntEnumObject: Object { @Persisted(indexed: true) var value: ModernIntEnum } +@RealmSchemaDiscovery class ModernIndexedOptionalIntEnumObject: Object { @Persisted(indexed: true) var value: ModernIntEnum? } +@RealmSchemaDiscovery class ModernCustomInitializerObject: Object { @Persisted var stringCol: String @@ -302,8 +343,9 @@ class ModernCustomInitializerObject: Object { } } +@RealmSchemaDiscovery class ModernConvenienceInitializerObject: Object { - @Persisted var stringCol = "" + @Persisted var stringCol: String = "" convenience init(stringCol: String) { self.init() @@ -311,16 +353,19 @@ class ModernConvenienceInitializerObject: Object { } } +@RealmSchemaDiscovery @objc(ModernObjcRenamedObject) class ModernObjcRenamedObject: Object { - @Persisted var stringCol = "" + @Persisted var stringCol: String = "" } +@RealmSchemaDiscovery @objc(ModernObjcRenamedObjectWithTotallyDifferentName) class ModernObjcArbitrarilyRenamedObject: Object { - @Persisted var boolCol = false + @Persisted var boolCol: Bool = false } +@RealmSchemaDiscovery class ModernIntAndStringObject: Object { @Persisted var intCol: Int @Persisted var optIntCol: Int? @@ -328,22 +373,26 @@ class ModernIntAndStringObject: Object { @Persisted var optStringCol: String? } +@RealmSchemaDiscovery class ModernCollectionObject: Object { @Persisted var list: List @Persisted var set: MutableSet @Persisted var map: Map } +@RealmSchemaDiscovery class ModernCircleObject: Object { @Persisted var obj: ModernCircleObject? @Persisted var array: List } +@RealmSchemaDiscovery class ModernEmbeddedParentObject: Object { @Persisted var object: ModernEmbeddedTreeObject1? @Persisted var array: List } +@RealmSchemaDiscovery class ModernEmbeddedPrimaryParentObject: Object { @Persisted(primaryKey: true) var pk: Int = 0 @Persisted var object: ModernEmbeddedTreeObject1? @@ -354,9 +403,10 @@ protocol ModernEmbeddedTreeObject: EmbeddedObject { var value: Int { get set } } +@RealmSchemaDiscovery class ModernEmbeddedTreeObject1: EmbeddedObject, ModernEmbeddedTreeObject { - @Persisted var value = 0 - @Persisted var bool = false + @Persisted var value: Int = 0 + @Persisted var bool: Bool = false @Persisted var child: ModernEmbeddedTreeObject2? @Persisted var children: List @@ -366,8 +416,9 @@ class ModernEmbeddedTreeObject1: EmbeddedObject, ModernEmbeddedTreeObject { var parent2: LinkingObjects } +@RealmSchemaDiscovery class ModernEmbeddedTreeObject2: EmbeddedObject, ModernEmbeddedTreeObject { - @Persisted var value = 0 + @Persisted var value: Int = 0 @Persisted var child: ModernEmbeddedTreeObject3? @Persisted var children: List @@ -377,8 +428,9 @@ class ModernEmbeddedTreeObject2: EmbeddedObject, ModernEmbeddedTreeObject { var parent4: LinkingObjects } +@RealmSchemaDiscovery class ModernEmbeddedTreeObject3: EmbeddedObject, ModernEmbeddedTreeObject { - @Persisted var value = 0 + @Persisted var value: Int = 0 @Persisted(originProperty: "child") var parent3: LinkingObjects @@ -386,10 +438,12 @@ class ModernEmbeddedTreeObject3: EmbeddedObject, ModernEmbeddedTreeObject { var parent4: LinkingObjects } +@RealmSchemaDiscovery class ModernEmbeddedObject: EmbeddedObject { - @Persisted var value = 0 + @Persisted var value: Int = 0 } +@RealmSchemaDiscovery class SetterObservers: Object { @Persisted var value: Int { willSet { @@ -404,6 +458,7 @@ class SetterObservers: Object { var didSetCallback: (() -> Void)? } +@RealmSchemaDiscovery class ObjectWithArcMethodCategoryNames: Object { // @objc properties with these names would crash with asan (and unreliably // without it) because they would not have the correct behavior for the @@ -415,6 +470,7 @@ class ObjectWithArcMethodCategoryNames: Object { @Persisted var initValue: String } +@RealmSchemaDiscovery class ModernAllIndexableTypesObject: Object { @Persisted(indexed: true) var boolCol: Bool @Persisted(indexed: true) var intCol: Int @@ -443,6 +499,7 @@ class ModernAllIndexableTypesObject: Object { @Persisted(indexed: true) var optStringEnumCol: ModernStringEnum? } +@RealmSchemaDiscovery class ModernAllIndexableButNotIndexedObject: Object { @Persisted(indexed: false) var boolCol: Bool @Persisted(indexed: false) var intCol: Int @@ -471,6 +528,7 @@ class ModernAllIndexableButNotIndexedObject: Object { @Persisted(indexed: false) var optStringEnumCol: ModernStringEnum? } +@RealmSchemaDiscovery class ModernCollectionsOfEnums: Object { @Persisted var listInt: List @Persisted var listInt8: List @@ -527,6 +585,7 @@ class ModernCollectionsOfEnums: Object { @Persisted var mapStringOpt: Map } +@RealmSchemaDiscovery class LinkToModernCollectionsOfEnums: Object { @Persisted var object: ModernCollectionsOfEnums? @Persisted var list: List @@ -534,6 +593,7 @@ class LinkToModernCollectionsOfEnums: Object { @Persisted var map: Map } +@RealmSchemaDiscovery class ModernListAnyRealmValueObject: Object { @Persisted var value: List } diff --git a/scripts/pr-ci-matrix.rb b/scripts/pr-ci-matrix.rb index 932cfb24ef..2650e3b054 100755 --- a/scripts/pr-ci-matrix.rb +++ b/scripts/pr-ci-matrix.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby # A script to generate the .jenkins.yml file for the CI pull request job -XCODE_VERSIONS = %w(14.1 14.2 14.3.1) +XCODE_VERSIONS = %w(15.0) all = ->(v) { true } latest_only = ->(v) { v == XCODE_VERSIONS.last } @@ -14,41 +14,7 @@ def minimum_version(major) 'docs' => latest_only, 'swiftlint' => latest_only, - 'osx' => all, - 'osx-encryption' => latest_only, - 'osx-object-server' => oldest_and_latest, - - 'swiftpm' => oldest_and_latest, - 'swiftpm-debug' => all, - 'swiftpm-address' => latest_only, - 'swiftpm-thread' => latest_only, - 'swiftpm-ios' => all, - - 'ios-static' => oldest_and_latest, - 'ios-dynamic' => oldest_and_latest, - 'watchos' => oldest_and_latest, - 'tvos' => oldest_and_latest, - 'osx-swift' => all, - 'ios-swift' => oldest_and_latest, - 'tvos-swift' => oldest_and_latest, - - 'osx-swift-evolution' => latest_only, - 'ios-swift-evolution' => latest_only, - 'tvos-swift-evolution' => latest_only, - - 'catalyst' => oldest_and_latest, - 'catalyst-swift' => oldest_and_latest, - - 'xcframework' => latest_only, - - 'cocoapods-osx' => all, - 'cocoapods-ios' => oldest_and_latest, - 'cocoapods-ios-dynamic' => oldest_and_latest, - 'cocoapods-watchos' => oldest_and_latest, - # 'cocoapods-catalyst' => oldest_and_latest, - 'swiftui-ios' => latest_only, - 'swiftui-server-osx' => latest_only, } output_file = """