Method calls are dispatched incorrectly if entity type is referenced as supertype #91

Closed
opened 2025-12-29 15:24:20 +01:00 by adam · 4 comments
Owner

Originally created by @andriichernenko on GitHub (Oct 7, 2016).

Consider the following class hierarchy:

// abstract entity class
open class StorableEntity: NSManagedObject, ImportableUniqueObject
    // ...
    open class var uniqueIDKeyPath: String {
        fatalError("this has to be overridden")
    }
}

// concrete entity
@objc(Thing)
class Thing: StorableEntity {
    class var uniqueIDKeyPath: String {
        return "id"
    }

    @NSManaged var id: NSString
    @NSManaged var content: String?
}

Let's try to import a Thing now. The following code will work:

let entityType = Thing.self
let object = try transaction.importUniqueObject(Into(entityType), source: ["id": "1", "content": "some content"])

but the following will not:

let entityType: StorableEntity.Type = Thing.self
let object = try transaction.importUniqueObject(Into(entityType), source: ["id": "1", "content": "some content"])

entityType is now StorableEntity.Type. Since importUniqueObject method relies on generic parameter to determine which type to use for static method calls this means that all invocations will be dispatched to StorableEntity instead of Thing. Which in this concrete example means that the app will crash because StorableEntity.uniqueIDKeyPath is abstract.

Here's the project which reproduces this issue:
CoreStoreImportCrasher.zip

Originally created by @andriichernenko on GitHub (Oct 7, 2016). Consider the following class hierarchy: ``` swift // abstract entity class open class StorableEntity: NSManagedObject, ImportableUniqueObject // ... open class var uniqueIDKeyPath: String { fatalError("this has to be overridden") } } // concrete entity @objc(Thing) class Thing: StorableEntity { class var uniqueIDKeyPath: String { return "id" } @NSManaged var id: NSString @NSManaged var content: String? } ``` Let's try to import a `Thing` now. The following code will work: ``` swift let entityType = Thing.self let object = try transaction.importUniqueObject(Into(entityType), source: ["id": "1", "content": "some content"]) ``` but the following will not: ``` swift let entityType: StorableEntity.Type = Thing.self let object = try transaction.importUniqueObject(Into(entityType), source: ["id": "1", "content": "some content"]) ``` `entityType` is now `StorableEntity.Type`. Since `importUniqueObject` method relies on generic parameter to determine which type to use for static method calls this means that all invocations will be dispatched to `StorableEntity` instead of `Thing`. Which in this concrete example means that the app will crash because `StorableEntity.uniqueIDKeyPath` is abstract. Here's the project which reproduces this issue: [CoreStoreImportCrasher.zip](https://github.com/JohnEstropia/CoreStore/files/516105/CoreStoreImportCrasher.zip)
adam added the fixedcorestore bug labels 2025-12-29 15:24:20 +01:00
adam closed this issue 2025-12-29 15:24:20 +01:00
Author
Owner

@JohnEstropia commented on GitHub (Oct 7, 2016):

Hi, thank you for the feedback.

I'm not sure if this is something to "fix". This is just how Swift generics works. If you really need dynamic behavior, Into clauses actually support that:

let object = try transaction.importUniqueObject(
    Into<StorableEntity>(Thing.self as AnyClass), 
    source: ["id": "1", "content": "some content"]
)

Use the initializer that accepts AnyClass argument. From clauses also support this.

@JohnEstropia commented on GitHub (Oct 7, 2016): Hi, thank you for the feedback. I'm not sure if this is something to "fix". This is just how Swift generics works. If you really need dynamic behavior, `Into` clauses actually support that: ``` swift let object = try transaction.importUniqueObject( Into<StorableEntity>(Thing.self as AnyClass), source: ["id": "1", "content": "some content"] ) ``` Use the initializer that accepts `AnyClass` argument. `From` clauses also support this.
Author
Owner

@andriichernenko commented on GitHub (Oct 7, 2016):

@JohnEstropia well, your example actually crashes as well. Have you tried running the test project?

This is just how Swift generics works.

yes, that's why generics cannot be relied on for correct method dispatch. I created pull request #108 which fixes this issue by calling type methods on entityClass from the Into object instead of calling them on the generic type. I'm not sure, however, if similar fixes are needed elsewhere.

@andriichernenko commented on GitHub (Oct 7, 2016): @JohnEstropia well, your example actually crashes as well. Have you tried running the test project? > This is just how Swift generics works. yes, that's why generics cannot be relied on for correct method dispatch. I created pull request #108 which fixes this issue by calling type methods on `entityClass` from the `Into` object instead of calling them on the generic type. I'm not sure, however, if similar fixes are needed elsewhere.
Author
Owner

@JohnEstropia commented on GitHub (Oct 7, 2016):

Thanks for the PR! I'll check your project when I get access to Xcode later. Your PR looks good though, I'll look for other places similar.

@JohnEstropia commented on GitHub (Oct 7, 2016): Thanks for the PR! I'll check your project when I get access to Xcode later. Your PR looks good though, I'll look for other places similar.
Author
Owner

@JohnEstropia commented on GitHub (Oct 8, 2016):

Merged your PR. Thanks again!

Will update the Swift 2.3 branch with the same fixes as well

@JohnEstropia commented on GitHub (Oct 8, 2016): Merged your PR. Thanks again! Will update the Swift 2.3 branch with the same fixes as well
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/CoreStore#91