Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ObjC Tagging Fix #37

Merged
merged 1 commit into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,13 @@ extension ModelMacro: MemberAttributeMacro { // @attached(memberAttribute)
// property.declaredValueType is set
var lastname : String
*/
let addAtObjC = property.isKnownRelationshipPropertyType
let isRelationship: Bool
if case .relationship(_) = property.type {
isRelationship = true
} else {
isRelationship = false
}
let addAtObjC = isRelationship
|| (property.valueType?.canBeRepresentedInObjectiveC ?? false)

// We'd like @objc, but we don't know which ones to attach it to?
Expand Down
52 changes: 40 additions & 12 deletions Sources/ManagedModelMacros/Utilities/AttributeTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import SwiftSyntax
// This is a little fishy as the user might shadow those types,
// but I suppose an acceptable tradeoff.

private let attributeTypes : Set<String> = [
private let swiftTypes: Set<String> = [
// Swift
"String",
"Int", "Int8", "Int16", "Int32", "Int64",
Expand All @@ -20,7 +20,8 @@ private let attributeTypes : Set<String> = [
"Swift.UInt", "Swift.UInt8", "Swift.UInt16", "Swift.UInt32", "Swift.UInt64",
"Swift.Float", "Swift.Double",
"Swift.Bool",

]
private let foundationTypes: Set<String> = [
// Foundation
"Data", "Foundation.Data",
"Date", "Foundation.Date",
Expand All @@ -33,6 +34,7 @@ private let attributeTypes : Set<String> = [
"NSURL", "Foundation.NSURL",
"NSData", "Foundation.NSData"
]
private let attributeTypes : Set<String> = swiftTypes.union(foundationTypes)

private let toOneRelationshipTypes : Set<String> = [
// CoreData
Expand All @@ -42,6 +44,8 @@ private let toOneRelationshipTypes : Set<String> = [
]
private let toManyRelationshipTypes : Set<String> = [
// Foundation
"Array", "Foundation.Array",
"NSArray", "Foundation.NSArray",
"Set", "Foundation.Set",
"NSSet", "Foundation.NSSet",
"NSOrderedSet", "Foundation.NSOrderedSet"
Expand All @@ -52,28 +56,36 @@ extension TypeSyntax {
/// Whether the type can be represented in Objective-C.
/// A *very* basic implementation.
var canBeRepresentedInObjectiveC : Bool {
// TODO: Naive shortcut
if let id = self.as(IdentifierTypeSyntax.self) {
return id.isKnownAttributePropertyType
|| id.isKnownRelationshipPropertyType
}

if let opt = self.as(OptionalTypeSyntax.self) {
if let array = opt.wrappedType.as(ArrayTypeSyntax.self) {
return array.element.canBeRepresentedInObjectiveC
}
if let id = opt.wrappedType.as(IdentifierTypeSyntax.self) {
return id.isKnownAttributePropertyType
|| id.isKnownRelationshipPropertyType
if id.isKnownRelationshipPropertyType {
let element = id.genericArgumentClause?.arguments.first?.argument
return element?.isKnownAttributePropertyType ?? false
}
return id.isKnownFoundationPropertyType
}
// E.g. this is not representable: `String??`, this is `String?`.
// But Double? or Int? is not representable
// I.e. nesting of Optional's are not representable.
return false
}

if let array = self.as(ArrayTypeSyntax.self) {
// This *is* representable: `[String]`,
// even this `[ [ 10, 20 ], [ 30, 40 ] ]`
return array.element.canBeRepresentedInObjectiveC
}

return false

if let id = self.as(IdentifierTypeSyntax.self),
id.isKnownFoundationGenericPropertyType {
let arg = id.genericArgumentClause?.arguments.first?.argument
return arg?.isKnownAttributePropertyType ?? false
}

return self.isKnownAttributePropertyType
}

/**
Expand Down Expand Up @@ -137,7 +149,23 @@ extension IdentifierTypeSyntax {
return false
}
}

var isKnownFoundationPropertyType: Bool {
let name = name.trimmed.text
return foundationTypes.contains(name)
}

var isKnownFoundationGenericPropertyType: Bool {
let name = name.trimmed.text
guard toManyRelationshipTypes.contains(name) else {
return false
}
if let generic = genericArgumentClause {
return generic.arguments.count == 1
}
return false
}

var isKnownRelationshipPropertyType : Bool {
isKnownRelationshipPropertyType(checkOptional: true)
}
Expand Down
76 changes: 76 additions & 0 deletions Tests/ManagedModelTests/ObjCMarkedPropertiesTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//
// ObjCMarkedPropertiesTests.swift
// ManagedModels
//
// Created by Adam Kopeć on 12/02/2025.
//
import XCTest
import Foundation
import CoreData
@testable import ManagedModels

final class ObjCMarkedPropertiesTests: XCTestCase {
func getAllObjCPropertyNames() -> [String] {
let classType: AnyClass = Fixtures.AdvancedCodablePropertiesSchema.AdvancedStoredAccess.self

var count: UInt32 = 0
var properties = [String]()
class_copyPropertyList(classType, &count)?.withMemoryRebound(to: objc_property_t.self, capacity: Int(count), { pointer in
var ptr = pointer
for _ in 0..<count {
properties.append(String(cString: property_getName(ptr.pointee)))
ptr = ptr.successor()
}
pointer.deallocate()
})

return properties
}

func getObjCAttributes(propertyName: String) -> String {
let classType: AnyClass = Fixtures.AdvancedCodablePropertiesSchema.AdvancedStoredAccess.self

let property = class_getProperty(classType, propertyName)
XCTAssertNotNil(property, "Property \(propertyName) not found")
guard let property else { return "" }
let attributes = property_getAttributes(property)
let attributesString = String(cString: attributes!)

return attributesString
}

func testPropertiesMarkedObjC() {
let tokenAttributes = getObjCAttributes(propertyName: "token")
XCTAssertTrue(tokenAttributes.contains("T@\"NSString\""), "Property token is not marked as @objc (\(tokenAttributes))")

let expiresAttributes = getObjCAttributes(propertyName: "expires")
XCTAssertTrue(expiresAttributes.contains("T@\"NSDate\""), "Property expires is not marked as @objc (\(expiresAttributes))")

let integerAttributes = getObjCAttributes(propertyName: "integer")
XCTAssertTrue(!integerAttributes.isEmpty, "Property integer is not marked as @objc (\(integerAttributes))")

let arrayAttributes = getObjCAttributes(propertyName: "array")
XCTAssertTrue(arrayAttributes.contains("T@\"NSArray\""), "Property array is not marked as @objc (\(arrayAttributes))")

let array2Attributes = getObjCAttributes(propertyName: "array2")
XCTAssertTrue(arrayAttributes.contains("T@\"NSArray\""), "Property array2 is not marked as @objc (\(array2Attributes))")

let numArrayAttributes = getObjCAttributes(propertyName: "numArray")
XCTAssertTrue(numArrayAttributes.contains("T@\"NSArray\""), "Property numArray is not marked as @objc (\(numArrayAttributes))")

let optionalArrayAttributes = getObjCAttributes(propertyName: "optionalArray")
XCTAssertTrue(optionalArrayAttributes.contains("T@\"NSArray\""), "Property optionalArray is not marked as @objc (\(optionalArrayAttributes))")

let optionalArray2Attributes = getObjCAttributes(propertyName: "optionalArray2")
XCTAssertTrue(optionalArray2Attributes.contains("T@\"NSArray\""), "Property optionalArray2 is not marked as @objc (\(optionalArray2Attributes))")

let optionalNumArrayAttributes = getObjCAttributes(propertyName: "optionalNumArray")
XCTAssertTrue(optionalNumArrayAttributes.contains("T@\"NSArray\""), "Property optionalNumArray is not marked as @objc (\(optionalNumArrayAttributes))")

let optionalNumArray2Attributes = getObjCAttributes(propertyName: "optionalNumArray2")
XCTAssertTrue(optionalNumArray2Attributes.contains("T@\"NSArray\""), "Property optionalNumArray2 is not marked as @objc (\(optionalNumArray2Attributes))")

let objcSetAttributes = getObjCAttributes(propertyName: "objcSet")
XCTAssertTrue(objcSetAttributes.contains("T@\"NSSet\""), "Property objcSet is not marked as @objc (\(objcSetAttributes))")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// AdvancedCodablePropertiesSchema.swift
// ManagedModels
//
// Created by Adam Kopeć on 04/02/2025.
//

import ManagedModels

extension Fixtures {
// https://github.com/Data-swift/ManagedModels/issues/36

enum AdvancedCodablePropertiesSchema: VersionedSchema {
static var models : [ any PersistentModel.Type ] = [
AdvancedStoredAccess.self
]

public static let versionIdentifier = Schema.Version(0, 1, 0)

@Model
final class AdvancedStoredAccess: NSManagedObject {
var token : String
var expires : Date
var integer : Int
var distance: Int?
var avgSpeed: Double?
var sip : AccessSIP
var numArray: [Int]
var array : [String]
var array2 : Array<String>
var optionalNumArray : [Int]?
var optionalNumArray2: Array<Int>?
var optionalArray : [String]?
var optionalArray2 : Array<String>?
var optionalSip : AccessSIP?
var codableSet : Set<AccessSIP>
var objcSet : Set<String>
var objcNumSet : Set<Int>
var codableArray : [AccessSIP]
var optCodableSet : Set<AccessSIP>?
var optCodableArray : [AccessSIP]?
}

struct AccessSIP: Codable, Hashable {
var username : String
var password : String
var realm : String
}
}
}