matrei commented on issue #16425:
URL: https://github.com/apache/grails-core/issues/16425#issuecomment-5887230278

   Thanks for the detailed report. I can confirm it on `7.2.x` (Groovy 4) and 
on `8.0.x` (Groovy 5.1.3). The `@DelegatesTo` declarations are identical on 
`7.0.x`. Without `@CompileStatic` the same code builds the correct criteria. 
With it, a restriction inside the nested closure is added to the enclosing 
criteria and the subquery is left empty:
   
   ```groovy
   @CompileStatic
   static DetachedCriteria<Author> query() {
       new DetachedCriteria<Author>(Author).where {
           exists new DetachedCriteria<Book>(Book).where { eq 'title', 'X' 
}.id()
       }
   }
   // Author criteria: [Equals(title, X), Exists(Book criteria: [])]
   ```
   
   One note on the snippet in the description: `where { title == 'X' }` called 
on a `new DetachedCriteria<>(Book)` receiver is not rewritten by the 
where-query transformation, so as written it compiles to a boolean comparison 
instead of a restriction. The bug does reproduce with the where-query syntax 
when the receivers are typed variables, as well as with explicit criteria 
methods:
   
   ```groovy
   DetachedCriteria<Author> authors = new DetachedCriteria<Author>(Author)
   DetachedCriteria<Book> books = new DetachedCriteria<Book>(Book)
   authors.where { exists books.where { title == 'X' }.id() }
   // Author criteria: [Equals(title, X), Exists(Book criteria: [])]
   ```
   
   Other forms that end up on the outer criteria:
   
   - `eqProperty` inside the nested `where`
   - a nested `where` inside an `or { }` junction
   - nested `build { }`
   - closure subqueries such as `inList('id') { eq 'name', 'X'; projections { 
id() } }` and `gtAll('id') { ... }`
   
   In each case the inner restriction is added to the outer criteria. The 
`inList`/`gtAll` cases need the subquery helpers in the fix as well, not only 
`where`/`build`.
   
   Your analysis matches GROOVY-9283, and `strategy = Closure.DELEGATE_FIRST` 
is the right direction. I prototyped it on `DetachedCriteria` and 
`AbstractDetachedCriteria` (`where`, `whereLazy`, `build`, `buildLazy`) and all 
of the nested forms above then produce the correct criteria. The change does 
have two effects on statically compiled application code, and we need to 
account for them before deciding where it lands:
   
   1. **Forwarding a closure parameter no longer compiles.** The same error you 
hit on the internal forwarders also hits user code. This compiles today and 
fails after the change:
   
      ```groovy
      @CompileStatic
      class BookQueries {
          static DetachedCriteria<Book> 
withExtra(@DelegatesTo(DetachedCriteria) Closure extra) {
              new DetachedCriteria<Book>(Book).where(extra)
              // [Static type checking] - Closure parameter with resolve 
strategy OWNER_FIRST
              // passed to method with resolve strategy DELEGATE_FIRST
          }
      }
      ```
   
      The same happens with a plain `Closure extra` parameter. Callers have to 
add `strategy = Closure.DELEGATE_FIRST` to their own parameter, or cast.
   
   2. **Name resolution changes silently.** A property or method that exists on 
both the enclosing class and the criteria resolves to the criteria after the 
change. It already does that in dynamic code, so this brings static code in 
line with it. Still, it is a behaviour change. For example, in a class with a 
`getOrders()` method, `where { inList 'name', orders }` currently uses the 
class's `orders`. After the change it uses the criteria's (empty) order list.
   
   Because of these, I think this is better suited to `8.0.x`, with an entry in 
the upgrade notes, than to a `7.x` patch release. Other maintainers may see 
that differently.
   
   Some suggestions for the PR:
   
   - **Keep the public signature changes small.** For internal forwarders like 
`GormStaticApi.where/find/findAll`, `buildQueryableCriteria` and 
`withPopulatedQuery`, casting the argument (`build((Closure) callable)`) is 
enough to satisfy the type checker. That way the forwarding break in (1) 
doesn't also spread to `GormEntity`, `GormStaticOperations` and 
`TenantDelegatingGormOperations`.
   - **Treat `GormEntity.where` as a separate change.** 
`GormEntity.where(Closure)` and related methods currently have no 
`@DelegatesTo` at all, so `Author.where { exists Book.where { ... }.id() }` 
doesn't type-check under `@CompileStatic` today. Adding it would be a separate 
improvement. It needs its own verification against the where-query 
transformation and the datastore TCKs.
   - **Cover each affected form in the regression test.** Under 
`@CompileStatic`, cover explicit criteria methods, the where-query syntax with 
typed variables, junctions, and the closure subquery helpers. Also cover a 
hoisted subquery as a control.
   - **Document the change.** Add a note to the upgrade guide in `grails-doc` 
about the stricter closure typing and the forwarding error, and how to fix it.
   
   The workarounds in the description (`@CompileDynamic` on the method, or 
hoisting the subquery into a local variable) work as described.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to