jamesfredley opened a new pull request, #15396:
URL: https://github.com/apache/grails-core/pull/15396

   ## Summary
   
   `@CompileStatic` (or `@GrailsCompileStatic`) on a `@Service` abstract class 
causes a compilation failure when the class has properties whose types are 
annotated with `@Service`. The error is:
   
   ```
   BUG! exception in phase 'instruction selection' in source unit '...'
   Unexpected return statement at -1:-1 return authorDataService
   ```
   
   This forces users to omit `@CompileStatic` on any `@Service` abstract class 
that injects other Data Services, losing static compilation benefits 
(compile-time type checking, better performance).
   
   ## Bug Description
   
   Given two Data Services where one injects the other:
   
   ```groovy
   @Service(Author)
   interface AuthorDataService {
       Author get(Serializable id)
       Author save(Author author)
   }
   
   @CompileStatic          // <-- causes compilation failure
   @Service(Book)
   @Transactional
   abstract class BookService implements BookDataService {
       AuthorDataService authorDataService  // <-- triggers the bug
   
       Book createBookWithAuthorCheck(String title, Serializable authorId) {
           Author author = authorDataService.get(authorId)
           // ...
       }
   }
   ```
   
   Compilation fails with:
   ```
   BUG! exception in phase 'instruction selection' in source unit 
'...BookService.groovy'
   Unexpected return statement at -1:-1 return authorDataService
   ```
   
   Without `@CompileStatic`, the code compiles and runs correctly.
   
   ## Root Cause
   
   `ServiceTransformation` (SEMANTIC_ANALYSIS phase) iterates over the abstract 
class's properties and, for each property whose type has `@Service`, sets a 
lazy getter block on the **abstract class's PropertyNode**:
   
   ```groovy
   pn.setGetterBlock(
       block(
           ifS(equalsNullX(fieldVar),
               assignX(fieldVar, callX(varX('datastore'), 'getService', 
classX(propertyType)))
           ),
           returnS(fieldVar)
       )
   )
   ```
   
   This causes two problems under `@CompileStatic`:
   
   1. **`varX('datastore')` is unresolvable** — the `datastore` field only 
exists on the generated `$BookServiceImplementation` class, not on the abstract 
`BookService` class where the getter block is being set.
   
   2. **`ReturnStatement` in property getter block** — 
`StaticTypeCheckingVisitor.visitProperty()` does not expect `ReturnStatement` 
nodes inside property getter blocks, causing the "Unexpected return statement" 
error during instruction selection.
   
   Under dynamic Groovy (no `@CompileStatic`), neither issue surfaces because 
variable references resolve at runtime and the static type checker is not 
invoked.
   
   ## Fix
   
   Removed the lazy getter block from the abstract class's `PropertyNode` 
entirely. The getter block was redundant because the generated `setDatastore()` 
method on the impl class already **eagerly populates** all `@Service`-typed 
properties during initialization:
   
   ```groovy
   // Generated on $BookServiceImplementation.setDatastore():
   this.authorDataService = datastore.getService(AuthorDataService)
   ```
   
   This eager initialization runs when the Spring context wires the `Datastore` 
bean, so by the time any user code accesses the property, it's already 
populated. The lazy fallback was unnecessary.
   
   **Changes**: 1 file modified — `ServiceTransformation.groovy` (8 insertions, 
9 deletions). Replaced the `pn.setGetterBlock(...)` block with a comment 
explaining why it was removed.
   
   ## Test Coverage
   
   Added `CompileStaticServiceInjectionSpec` with 6 Spock tests:
   
   1. **@CompileStatic + single @Service property compiles** — abstract class 
with `@CompileStatic` and one `@Service`-typed property compiles without error
   2. **Dynamic mode still works** — same class without `@CompileStatic` still 
compiles (regression check)
   3. **No @Service properties — no datastore infrastructure** — abstract class 
without `@Service`-typed properties doesn't get datastore field (regression 
check)
   4. **@CompileStatic + multiple @Service properties** — abstract class with 
two `@Service`-typed properties compiles without error
   5. **@CompileStatic + custom methods with complex return statements** — 
ensures return statements in custom methods are not confused with property 
getter returns
   6. **Impl has correct setDatastore/getDatastore/datastore field** — verifies 
the generated implementation class has the datastore infrastructure and 
setDatastore eagerly populates service properties
   
   All 6 new tests pass. All 30 existing `ServiceTransformSpec` tests pass (no 
regressions).
   
   ## Example Application
   
   https://github.com/jamesfredley/grails-compile-static-service-bug
   
   A minimal Grails 7 app demonstrating the bug. `BookService` is a 
`@Service(Book)` abstract class with an `AuthorDataService` property. 
`@CompileStatic` is commented out as a workaround.
   
   **To reproduce the bug:**
   1. Uncomment `@CompileStatic` in 
`grails-app/services/com/example/BookService.groovy`
   2. Run `./gradlew compileGroovy`
   3. Observe: `BUG! exception in phase 'instruction selection' ... Unexpected 
return statement at -1:-1 return authorDataService`
   
   **To run with workaround:**
   1. `./gradlew bootRun`
   2. Visit `http://localhost:8080/bugDemo/index`
   3. Returns: `{"bug_present": true, "workaround_applied": true, ...}`
   
   ## Environment Information
   
   - Grails 7.0.7
   - GORM 7.0.7
   - Spring Boot 3.5.10
   - Groovy 4.0.30
   - JDK 17+
   
   ## Version
   
   7.0.7


-- 
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