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]