0

I’m trying to use Predicate with generics but I keep getting the following compilation error :

Cannot convert value of type 'PredicateExpressions.Equal<PredicateExpressions.ConditionalCast<PredicateExpressions.KeyPath<PredicateExpressions.Variable<E.EntityType>, E.EntityType.PrimaryKey?>, Key>, PredicateExpressions.Value<Optional>>' to closure result type 'any StandardPredicateExpression'

I have the following :

public protocol Entity {
  associatedtype DomainModel
  associatedtype PrimaryKey: Equatable

  /// The entity's domain model
  var domainModel: DomainModel { get }
  /// The entity's primary key
  var primaryKey: PrimaryKey { get }
}

public protocol DomainEntity {
  associatedtype EntityType: PersistentModel & Entity

  var entity: EntityType? { get }
}

@ModelActor
actor DataSource<E: DomainEntity> {
  typealias Model = E.EntityType.DomainModel

  func get<Key: Equatable>(by key: Key) throws -> Model? {
    let predicate: Predicate<E.EntityType> = #Predicate {
      ($0.primaryKey as? Key) == key // I’m having the error here
    }
    let fetchDescriptor = FetchDescriptor<E.EntityType>(predicate: predicate)

    guard let entity: E.EntityType = try modelContext.fetch(fetchDescriptor).first else {
      return nil
    }

    guard let model = entity.domainModel as? Model else {
      throw DatabaseError.conversionFailed
    }
    return model
  }
}

I get that the error is kind of explaining that it can’t figure out the underlying types, but as both properties are Equatable, it shouldn’t be an issue.

Does anyone have an idea of how I could do such a comparison using predicates? Or is there another way?

1
  • 1
    No this is only part of my solution to illustrate the issue I had with generics. Commented Sep 4 at 13:07

1 Answer 1

1

#Predicate requires you to write a StandardPredicateExpression, which inherits from Codable.

For the predicate to be a StandardPredicateExpression, constant values such as the parameter key should also be Codable. Otherwise it would be no more than a regular PredicateExpression.

Try expanding the #Predicate macro and see how the parameter key becomes PredicateExpressions.build_Arg(key). This creates a PredicateExpressions.Value<Key>, which only conforms to StandardPredicateExpression when Key is Codable.

The generic type parameter Key should be constrained to Codable as well:

func get<Key: Equatable & Codable>(by key: Key) throws -> Model?

That said, wouldn't it make more sense to just use E.EntityType.PrimaryKey, instead of a separate type parameter? You don't need the as? Key cast at all.

func get(by key: E.EntityType.PrimaryKey) throws -> Model? {
    let predicate: Predicate<E.EntityType> = #Predicate {
        $0.primaryKey == key
    }
    ...
}

This means that PrimaryKey should be constrained to Codable too:

associatedtype PrimaryKey: Equatable, Codable
Sign up to request clarification or add additional context in comments.

1 Comment

I didn’t realise that PredicateExpressions.Value<Key> is only conform to StandardPredicateExpression when Key is Codable. The error message is kind of misleading ^^ I agree with you, now that I know that, the generic type parameter Key isn’t needed. Thanks very much for your explanation!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.