Skip to content

Commit 4350861

Browse files
committed
Merge branch 'develop'
2 parents 4c4769f + a44e2a5 commit 4350861

File tree

7 files changed

+212
-24
lines changed

7 files changed

+212
-24
lines changed

.github/workflows/swift.yml

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ jobs:
88
xcode15:
99
runs-on: macos-13
1010
steps:
11-
- name: Select latest available Xcode
12-
uses: maxim-lobanov/setup-xcode@v1.5.1
11+
- name: Select Xcode 15.2
12+
uses: maxim-lobanov/setup-xcode@v1.6.0
1313
with:
1414
xcode-version: '15.2.0'
1515
- name: Checkout Repository
@@ -24,7 +24,7 @@ jobs:
2424
runs-on: macos-latest
2525
steps:
2626
- name: Select latest available Xcode
27-
uses: maxim-lobanov/setup-xcode@v1.5.1
27+
uses: maxim-lobanov/setup-xcode@v1.6.0
2828
with:
2929
xcode-version: latest
3030
- name: Checkout Repository
@@ -35,3 +35,18 @@ jobs:
3535
run: swift build -c release
3636
- name: Run Tests
3737
run: swift test
38+
xcode26:
39+
runs-on: macos-15
40+
steps:
41+
- name: Select Xcode 26-beta
42+
uses: maxim-lobanov/[email protected]
43+
with:
44+
xcode-version: 26-beta
45+
- name: Checkout Repository
46+
uses: actions/checkout@v4
47+
- name: Build Swift Debug Package
48+
run: swift build --enable-experimental-prebuilts -c debug
49+
- name: Build Swift Release Package
50+
run: swift build --enable-experimental-prebuilts -c release
51+
- name: Run Tests
52+
run: swift test

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ let package = Package(
1717
],
1818
dependencies: [
1919
.package(url: "https://github.com/swiftlang/swift-syntax.git",
20-
"509.0.0"..<"600.0.1")
20+
from: "601.0.1")
2121
],
2222
targets: [
2323
.target(

Sources/ManagedModelMacros/ModelMacro/ModelMemberAttributes.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,13 @@ extension ModelMacro: MemberAttributeMacro { // @attached(memberAttribute)
5151
// property.declaredValueType is set
5252
var lastname : String
5353
*/
54-
let addAtObjC = property.isKnownRelationshipPropertyType
54+
let isRelationship: Bool
55+
if case .relationship(_) = property.type {
56+
isRelationship = true
57+
} else {
58+
isRelationship = false
59+
}
60+
let addAtObjC = isRelationship
5561
|| (property.valueType?.canBeRepresentedInObjectiveC ?? false)
5662

5763
// We'd like @objc, but we don't know which ones to attach it to?

Sources/ManagedModelMacros/ModelMacro/ModelMembers.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//
22
// Created by Helge Heß.
3-
// Copyright © 2023 ZeeZide GmbH.
3+
// Copyright © 2023-2025 ZeeZide GmbH.
44
//
55

66
import SwiftCompilerPlugin
@@ -24,6 +24,7 @@ extension ModelMacro: MemberMacro { // @attached(member, names:...)
2424
public static func expansion(
2525
of macroNode : AttributeSyntax,
2626
providingMembersOf declaration : some DeclGroupSyntax,
27+
conformingTo protocols : [ TypeSyntax ],
2728
in context : some MacroExpansionContext
2829
) throws -> [ DeclSyntax ]
2930
{

Sources/ManagedModelMacros/Utilities/AttributeTypes.swift

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
//
22
// Created by Helge Heß.
3-
// Copyright © 2023 ZeeZide GmbH.
3+
// Copyright © 2023-2025 ZeeZide GmbH.
44
//
55

66
import SwiftSyntax
77

88
// This is a little fishy as the user might shadow those types,
99
// but I suppose an acceptable tradeoff.
1010

11-
private let attributeTypes : Set<String> = [
11+
private let swiftTypes: Set<String> = [
1212
// Swift
1313
"String",
1414
"Int", "Int8", "Int16", "Int32", "Int64",
@@ -20,7 +20,8 @@ private let attributeTypes : Set<String> = [
2020
"Swift.UInt", "Swift.UInt8", "Swift.UInt16", "Swift.UInt32", "Swift.UInt64",
2121
"Swift.Float", "Swift.Double",
2222
"Swift.Bool",
23-
23+
]
24+
private let foundationTypes: Set<String> = [
2425
// Foundation
2526
"Data", "Foundation.Data",
2627
"Date", "Foundation.Date",
@@ -33,6 +34,7 @@ private let attributeTypes : Set<String> = [
3334
"NSURL", "Foundation.NSURL",
3435
"NSData", "Foundation.NSData"
3536
]
37+
private let attributeTypes : Set<String> = swiftTypes.union(foundationTypes)
3638

3739
private let toOneRelationshipTypes : Set<String> = [
3840
// CoreData
@@ -42,6 +44,8 @@ private let toOneRelationshipTypes : Set<String> = [
4244
]
4345
private let toManyRelationshipTypes : Set<String> = [
4446
// Foundation
47+
"Array", "Foundation.Array",
48+
"NSArray", "Foundation.NSArray",
4549
"Set", "Foundation.Set",
4650
"NSSet", "Foundation.NSSet",
4751
"NSOrderedSet", "Foundation.NSOrderedSet"
@@ -52,28 +56,45 @@ extension TypeSyntax {
5256
/// Whether the type can be represented in Objective-C.
5357
/// A *very* basic implementation.
5458
var canBeRepresentedInObjectiveC : Bool {
55-
// TODO: Naive shortcut
56-
if let id = self.as(IdentifierTypeSyntax.self) {
57-
return id.isKnownAttributePropertyType
58-
|| id.isKnownRelationshipPropertyType
59-
}
60-
6159
if let opt = self.as(OptionalTypeSyntax.self) {
60+
if let array = opt.wrappedType.as(ArrayTypeSyntax.self) {
61+
return array.element.canBeRepresentedInObjectiveC
62+
}
6263
if let id = opt.wrappedType.as(IdentifierTypeSyntax.self) {
63-
return id.isKnownAttributePropertyType
64-
|| id.isKnownRelationshipPropertyType
64+
if id.isKnownRelationshipPropertyType {
65+
guard let argument = id.genericArgumentClause?.arguments.first,
66+
case .type(let typeSyntax) = argument.argument else
67+
{
68+
return false
69+
}
70+
return typeSyntax.isKnownAttributePropertyType
71+
}
72+
return id.isKnownFoundationPropertyType
6573
}
6674
// E.g. this is not representable: `String??`, this is `String?`.
67-
// I.e. nesting of Optional's are not representable.
75+
// But Double? or Int? is not representable
76+
// I.e. nestings of Optional's are not representable.
6877
return false
6978
}
79+
7080
if let array = self.as(ArrayTypeSyntax.self) {
7181
// This *is* representable: `[String]`,
7282
// even this `[ [ 10, 20 ], [ 30, 40 ] ]`
7383
return array.element.canBeRepresentedInObjectiveC
7484
}
75-
76-
return false
85+
86+
if let id = self.as(IdentifierTypeSyntax.self),
87+
id.isKnownFoundationGenericPropertyType
88+
{
89+
guard let arg = id.genericArgumentClause?.arguments.first,
90+
case .type(let typeSyntax) = arg.argument else
91+
{
92+
return false
93+
}
94+
return typeSyntax.isKnownAttributePropertyType
95+
}
96+
97+
return self.isKnownAttributePropertyType
7798
}
7899

79100
/**
@@ -132,12 +153,31 @@ extension IdentifierTypeSyntax {
132153

133154
switch name {
134155
case "Array", "Optional", "Set":
135-
return genericArgument.argument.isKnownAttributePropertyType
156+
guard case .type(let typeSyntax) = genericArgument.argument else {
157+
return false
158+
}
159+
return typeSyntax.isKnownAttributePropertyType
136160
default:
137161
return false
138162
}
139163
}
164+
165+
var isKnownFoundationPropertyType: Bool {
166+
let name = name.trimmed.text
167+
return foundationTypes.contains(name)
168+
}
140169

170+
var isKnownFoundationGenericPropertyType: Bool {
171+
let name = name.trimmed.text
172+
guard toManyRelationshipTypes.contains(name) else {
173+
return false
174+
}
175+
if let generic = genericArgumentClause {
176+
return generic.arguments.count == 1
177+
}
178+
return false
179+
}
180+
141181
var isKnownRelationshipPropertyType : Bool {
142182
isKnownRelationshipPropertyType(checkOptional: true)
143183
}
@@ -148,12 +188,12 @@ extension IdentifierTypeSyntax {
148188
if name == "Optional" { // recurse
149189
guard let generic = genericArgumentClause,
150190
let genericArgument = generic.arguments.first,
151-
generic.arguments.count != 1 else
191+
generic.arguments.count != 1,
192+
case .type(let typeSyntax) = genericArgument.argument else
152193
{
153194
return false
154195
}
155-
return genericArgument.argument
156-
.isKnownRelationshipPropertyType(checkOptional: false)
196+
return typeSyntax.isKnownRelationshipPropertyType(checkOptional: false)
157197
}
158198

159199
if toOneRelationshipTypes.contains(name) {
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//
2+
// ObjCMarkedPropertiesTests.swift
3+
// ManagedModels
4+
//
5+
// Created by Adam Kopeć on 12/02/2025.
6+
//
7+
import XCTest
8+
import Foundation
9+
import CoreData
10+
@testable import ManagedModels
11+
12+
final class ObjCMarkedPropertiesTests: XCTestCase {
13+
func getAllObjCPropertyNames() -> [String] {
14+
let classType: AnyClass = Fixtures.AdvancedCodablePropertiesSchema.AdvancedStoredAccess.self
15+
16+
var count: UInt32 = 0
17+
var properties = [String]()
18+
class_copyPropertyList(classType, &count)?.withMemoryRebound(to: objc_property_t.self, capacity: Int(count), { pointer in
19+
var ptr = pointer
20+
for _ in 0..<count {
21+
properties.append(String(cString: property_getName(ptr.pointee)))
22+
ptr = ptr.successor()
23+
}
24+
pointer.deallocate()
25+
})
26+
27+
return properties
28+
}
29+
30+
func getObjCAttributes(propertyName: String) -> String {
31+
let classType: AnyClass = Fixtures.AdvancedCodablePropertiesSchema.AdvancedStoredAccess.self
32+
33+
let property = class_getProperty(classType, propertyName)
34+
XCTAssertNotNil(property, "Property \(propertyName) not found")
35+
guard let property else { return "" }
36+
let attributes = property_getAttributes(property)
37+
let attributesString = String(cString: attributes!)
38+
39+
return attributesString
40+
}
41+
42+
func testPropertiesMarkedObjC() {
43+
let tokenAttributes = getObjCAttributes(propertyName: "token")
44+
XCTAssertTrue(tokenAttributes.contains("T@\"NSString\""), "Property token is not marked as @objc (\(tokenAttributes))")
45+
46+
let expiresAttributes = getObjCAttributes(propertyName: "expires")
47+
XCTAssertTrue(expiresAttributes.contains("T@\"NSDate\""), "Property expires is not marked as @objc (\(expiresAttributes))")
48+
49+
let integerAttributes = getObjCAttributes(propertyName: "integer")
50+
XCTAssertTrue(!integerAttributes.isEmpty, "Property integer is not marked as @objc (\(integerAttributes))")
51+
52+
let arrayAttributes = getObjCAttributes(propertyName: "array")
53+
XCTAssertTrue(arrayAttributes.contains("T@\"NSArray\""), "Property array is not marked as @objc (\(arrayAttributes))")
54+
55+
let array2Attributes = getObjCAttributes(propertyName: "array2")
56+
XCTAssertTrue(arrayAttributes.contains("T@\"NSArray\""), "Property array2 is not marked as @objc (\(array2Attributes))")
57+
58+
let numArrayAttributes = getObjCAttributes(propertyName: "numArray")
59+
XCTAssertTrue(numArrayAttributes.contains("T@\"NSArray\""), "Property numArray is not marked as @objc (\(numArrayAttributes))")
60+
61+
let optionalArrayAttributes = getObjCAttributes(propertyName: "optionalArray")
62+
XCTAssertTrue(optionalArrayAttributes.contains("T@\"NSArray\""), "Property optionalArray is not marked as @objc (\(optionalArrayAttributes))")
63+
64+
let optionalArray2Attributes = getObjCAttributes(propertyName: "optionalArray2")
65+
XCTAssertTrue(optionalArray2Attributes.contains("T@\"NSArray\""), "Property optionalArray2 is not marked as @objc (\(optionalArray2Attributes))")
66+
67+
let optionalNumArrayAttributes = getObjCAttributes(propertyName: "optionalNumArray")
68+
XCTAssertTrue(optionalNumArrayAttributes.contains("T@\"NSArray\""), "Property optionalNumArray is not marked as @objc (\(optionalNumArrayAttributes))")
69+
70+
let optionalNumArray2Attributes = getObjCAttributes(propertyName: "optionalNumArray2")
71+
XCTAssertTrue(optionalNumArray2Attributes.contains("T@\"NSArray\""), "Property optionalNumArray2 is not marked as @objc (\(optionalNumArray2Attributes))")
72+
73+
let objcSetAttributes = getObjCAttributes(propertyName: "objcSet")
74+
XCTAssertTrue(objcSetAttributes.contains("T@\"NSSet\""), "Property objcSet is not marked as @objc (\(objcSetAttributes))")
75+
}
76+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// AdvancedCodablePropertiesSchema.swift
3+
// ManagedModels
4+
//
5+
// Created by Adam Kopeć on 04/02/2025.
6+
//
7+
8+
import ManagedModels
9+
10+
extension Fixtures {
11+
// https://github.com/Data-swift/ManagedModels/issues/36
12+
13+
enum AdvancedCodablePropertiesSchema: VersionedSchema {
14+
static var models : [ any PersistentModel.Type ] = [
15+
AdvancedStoredAccess.self
16+
]
17+
18+
public static let versionIdentifier = Schema.Version(0, 1, 0)
19+
20+
@Model
21+
final class AdvancedStoredAccess: NSManagedObject {
22+
var token : String
23+
var expires : Date
24+
var integer : Int
25+
var distance: Int?
26+
var avgSpeed: Double?
27+
var sip : AccessSIP
28+
var numArray: [Int]
29+
var array : [String]
30+
var array2 : Array<String>
31+
var optionalNumArray : [Int]?
32+
var optionalNumArray2: Array<Int>?
33+
var optionalArray : [String]?
34+
var optionalArray2 : Array<String>?
35+
var optionalSip : AccessSIP?
36+
var codableSet : Set<AccessSIP>
37+
var objcSet : Set<String>
38+
var objcNumSet : Set<Int>
39+
var codableArray : [AccessSIP]
40+
var optCodableSet : Set<AccessSIP>?
41+
var optCodableArray : [AccessSIP]?
42+
}
43+
44+
struct AccessSIP: Codable, Hashable {
45+
var username : String
46+
var password : String
47+
var realm : String
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)