"Understanding Swift Closure Capture Mechanism and Type Inference in the Context of SwiftUI Query Filters"

In SwiftUI, I have a class Notebook that contains an id property. I am encountering a compilation error when I attempt to initialize a Query filter using notebook.id. However, when I assign notebook.id to a local constant id and use that in the filter, the code compiles successfully.

Here is the code snippet:

  @Query var wordNotes: [WordNote]
  init(notebook: Notebook) {
    self.notebook = notebook
    let id = notebook.id
    _wordNotes = Query(filter: #Predicate<WordNote> { ** in
      **.notebookId == notebook.id // fails to compile
      **.notebookId == id          // compiles successfully
    }, sort: \.createTime)
  }

The compilation error I receive when using notebook.id directly is: Cannot convert value of type 'PredicateExpressions.Equal<PredicateExpressions.KeyPath<PredicateExpressions.Variable<WordNote>, String>, PredicateExpressions.KeyPath<PredicateExpressions.Value<Notebook>, String>>' to closure result type 'any StandardPredicateExpression<Bool>'.

It appears that the failing code is being converted into a PredicateExpressions.Equal<> object, while the successful code is converted into a StandardPredicateExpression<Bool>.

Github Copilot said:

In Swift, closures capture and store references to any constants and variables from the context in which they are defined. This is known as closing over those constants and variables, hence the name "closures". In your case, when you're using notebook.id directly inside the closure, Swift tries to capture the notebook object itself. However, because notebook is a complex object, Swift has trouble determining the exact type of the comparison expression, leading to the error you're seeing.

But, I have already define notebook: Notebook which means the notebook is a exact type of Notebook. Why the swift still can't determining the type?

I have also expand the macro in XCode shows:

Foundation.Predicate<WordNote>({ ** in // only <WordNote>
    PredicateExpressions.build_Equal(
        lhs: PredicateExpressions.build_KeyPath(
            root: PredicateExpressions.build_Arg(**),
            keyPath: \.notebookId
        ),
        rhs: PredicateExpressions.build_KeyPath(
            root: PredicateExpressions.build_Arg(notebook), // doesn't know what type is notebook
            keyPath: \.id
        )
    )
    })

Seems swift only declare that ** is type WordNote by <WordNote>, but why not declare notebook by another generic type? Is uncomplished work of swiftui? Or any obstacle that I don't know? Could you please explain the underlying reason for this behavior?

"Understanding Swift Closure Capture Mechanism and Type Inference in the Context of SwiftUI Query Filters"
 
 
Q