Search code examples
iosswiftpredicateswiftdataxcode16

Predicate too complex in Xcode 16


On Xcode 16, when trying to compile, getting error "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions" (In expansion of macro 'Predicate' here)

Same code was compiling and working fine on Xcode 15. Removing any two of six parts divided by || compiles fine.

Code:

class FeaturesPredicate {
    class func predicate(_ enabledFeaturesName: String) -> Predicate<Caliber> {
        #Predicate {
            caliber in
            caliber.caliberData.featuresABCD.contains {
                feature in
                enabledFeaturesName == feature.name
            } || caliber.caliberData.featuresA.contains {
                feature in
                enabledFeaturesName == feature.name
            } || caliber.caliberData.featuresB.contains {
                feature in
                enabledFeaturesName == feature.name
            } || caliber.caliberData.featuresC.contains {
                feature in
                enabledFeaturesName == feature.name
            } || caliber.caliberData.featuresD.contains {
               feature in
               enabledFeaturesName == feature.name
            } || caliber.caliberData.featuresN.contains {
                feature in
                enabledFeaturesName == feature.name
            }
        }

Expanded predicate:

        Foundation.Predicate({
                    caliber in
            PredicateExpressions.build_Disjunction(
                lhs: PredicateExpressions.build_Disjunction(
                    lhs: PredicateExpressions.build_Disjunction(
                        lhs: PredicateExpressions.build_Disjunction(
                            lhs: PredicateExpressions.build_Disjunction(
                                lhs: PredicateExpressions.build_contains(
                                    PredicateExpressions.build_KeyPath(
                                        root: PredicateExpressions.build_KeyPath(
                                            root: PredicateExpressions.build_Arg(caliber),
                                            keyPath: \.caliberData
                                        ),
                                        keyPath: \.featuresABCD
                                    )
                                ) {
                        feature in
                                    PredicateExpressions.build_Equal(
                                        lhs: PredicateExpressions.build_Arg(enabledFeaturesName),
                                        rhs: PredicateExpressions.build_KeyPath(
                                            root: PredicateExpressions.build_Arg(feature),
                                            keyPath: \.name
                                        )
                                    )
                                },
                                rhs: PredicateExpressions.build_contains(
                                    PredicateExpressions.build_KeyPath(
                                        root: PredicateExpressions.build_KeyPath(
                                            root: PredicateExpressions.build_Arg(caliber),
                                            keyPath: \.caliberData
                                        ),
                                        keyPath: \.featuresA
                                    )
                                ) {
                        feature in
                                    PredicateExpressions.build_Equal(
                                        lhs: PredicateExpressions.build_Arg(enabledFeaturesName),
                                        rhs: PredicateExpressions.build_KeyPath(
                                            root: PredicateExpressions.build_Arg(feature),
                                            keyPath: \.name
                                        )
                                    )
                                }
                            ),
                            rhs: PredicateExpressions.build_contains(
                                PredicateExpressions.build_KeyPath(
                                    root: PredicateExpressions.build_KeyPath(
                                        root: PredicateExpressions.build_Arg(caliber),
                                        keyPath: \.caliberData
                                    ),
                                    keyPath: \.featuresB
                                )
                            ) {
                        feature in
                                PredicateExpressions.build_Equal(
                                    lhs: PredicateExpressions.build_Arg(enabledFeaturesName),
                                    rhs: PredicateExpressions.build_KeyPath(
                                        root: PredicateExpressions.build_Arg(feature),
                                        keyPath: \.name
                                    )
                                )
                            }
                        ),
                        rhs: PredicateExpressions.build_contains(
                            PredicateExpressions.build_KeyPath(
                                root: PredicateExpressions.build_KeyPath(
                                    root: PredicateExpressions.build_Arg(caliber),
                                    keyPath: \.caliberData
                                ),
                                keyPath: \.featuresC
                            )
                        ) {
                        feature in
                            PredicateExpressions.build_Equal(
                                lhs: PredicateExpressions.build_Arg(enabledFeaturesName),
                                rhs: PredicateExpressions.build_KeyPath(
                                    root: PredicateExpressions.build_Arg(feature),
                                    keyPath: \.name
                                )
                            )
                        }
                    ),
                    rhs: PredicateExpressions.build_contains(
                        PredicateExpressions.build_KeyPath(
                            root: PredicateExpressions.build_KeyPath(
                                root: PredicateExpressions.build_Arg(caliber),
                                keyPath: \.caliberData
                            ),
                            keyPath: \.featuresD
                        )
                    ) {
                       feature in
                        PredicateExpressions.build_Equal(
                            lhs: PredicateExpressions.build_Arg(enabledFeaturesName),
                            rhs: PredicateExpressions.build_KeyPath(
                                root: PredicateExpressions.build_Arg(feature),
                                keyPath: \.name
                            )
                        )
                    }
                ),
                rhs: PredicateExpressions.build_contains(
                    PredicateExpressions.build_KeyPath(
                        root: PredicateExpressions.build_KeyPath(
                            root: PredicateExpressions.build_Arg(caliber),
                            keyPath: \.caliberData
                        ),
                        keyPath: \.featuresN
                    )
                ) {
                        feature in
                    PredicateExpressions.build_Equal(
                        lhs: PredicateExpressions.build_Arg(enabledFeaturesName),
                        rhs: PredicateExpressions.build_KeyPath(
                            root: PredicateExpressions.build_Arg(feature),
                            keyPath: \.name
                        )
                    )
                }
            )
        })

Solution

  • As the error message says, you should break it down into smaller pieces and then compose them.

    Here I have made each of the contains calls its own separate Predicate, and I have also broken down the big disjunction into two halves.

    class func predicate(_ enabledFeaturesName: String) -> Predicate<Caliber> {
        let featuresABCD = #Predicate<Caliber> { caliber in
            caliber.caliberData.featuresABCD.contains {
                feature in
                enabledFeaturesName == feature.name
            }
        }
        let featuresA = #Predicate<Caliber> { caliber in
            caliber.caliberData.featuresA.contains {
                feature in
                enabledFeaturesName == feature.name
            }
        }
        let featuresB = #Predicate<Caliber> { caliber in
            caliber.caliberData.featuresB.contains {
                feature in
                enabledFeaturesName == feature.name
            }
        }
        let featuresC = #Predicate<Caliber> { caliber in
            caliber.caliberData.featuresC.contains {
                feature in
                enabledFeaturesName == feature.name
            }
        }
        let featuresD = #Predicate<Caliber> { caliber in
            caliber.caliberData.featuresD.contains {
                feature in
                enabledFeaturesName == feature.name
            }
        }
        let featuresN = #Predicate<Caliber> { caliber in
            caliber.caliberData.featuresN.contains {
                feature in
                enabledFeaturesName == feature.name
            }
        }
        
        let firstHalf = #Predicate<Caliber> { caliber in
            featuresABCD.evaluate(caliber) ||
            featuresA.evaluate(caliber) ||
            featuresB.evaluate(caliber)
        }
        
        let secondHalf = #Predicate<Caliber> { caliber in
            featuresC.evaluate(caliber) ||
            featuresD.evaluate(caliber) ||
            featuresN.evaluate(caliber)
        }
        
        return #Predicate {
            caliber in
            firstHalf.evaluate(caliber) ||
            secondHalf.evaluate(caliber)
        }
    }
    

    This compiles very fast on my computer. You don't have to break it down this much if you just want it to successfully compile. You can combine firstHalf and secondHalf into a single #Predicate, but it takes considerably longer to compile on my computer.

    Note that this uses build_evaluate which requires iOS 17.4.