Skip to content

Commit e380454

Browse files
authored
Merge pull request #37 from admkopec/bugs/fix-optional-objc
ObjC Tagging Fix
2 parents eaeec80 + 5040c28 commit e380454

File tree

4 files changed

+173
-13
lines changed

4 files changed

+173
-13
lines changed

Sources/ManagedModelMacros/ModelMacro/ModelMemberAttributes.swift

+7-1
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/Utilities/AttributeTypes.swift

+40-12
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import SwiftSyntax
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,36 @@ 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+
let element = id.genericArgumentClause?.arguments.first?.argument
66+
return element?.isKnownAttributePropertyType ?? false
67+
}
68+
return id.isKnownFoundationPropertyType
6569
}
6670
// E.g. this is not representable: `String??`, this is `String?`.
71+
// But Double? or Int? is not representable
6772
// I.e. nesting of Optional's are not representable.
6873
return false
6974
}
75+
7076
if let array = self.as(ArrayTypeSyntax.self) {
7177
// This *is* representable: `[String]`,
7278
// even this `[ [ 10, 20 ], [ 30, 40 ] ]`
7379
return array.element.canBeRepresentedInObjectiveC
7480
}
75-
76-
return false
81+
82+
if let id = self.as(IdentifierTypeSyntax.self),
83+
id.isKnownFoundationGenericPropertyType {
84+
let arg = id.genericArgumentClause?.arguments.first?.argument
85+
return arg?.isKnownAttributePropertyType ?? false
86+
}
87+
88+
return self.isKnownAttributePropertyType
7789
}
7890

7991
/**
@@ -137,7 +149,23 @@ extension IdentifierTypeSyntax {
137149
return false
138150
}
139151
}
152+
153+
var isKnownFoundationPropertyType: Bool {
154+
let name = name.trimmed.text
155+
return foundationTypes.contains(name)
156+
}
140157

158+
var isKnownFoundationGenericPropertyType: Bool {
159+
let name = name.trimmed.text
160+
guard toManyRelationshipTypes.contains(name) else {
161+
return false
162+
}
163+
if let generic = genericArgumentClause {
164+
return generic.arguments.count == 1
165+
}
166+
return false
167+
}
168+
141169
var isKnownRelationshipPropertyType : Bool {
142170
isKnownRelationshipPropertyType(checkOptional: true)
143171
}
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+
}
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)