This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 8.0.x-hibernate7
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 0da476b728aa1bf33039d867eebd7447c457aaf0
Author: Walter Duque de Estrada <[email protected]>
AuthorDate: Mon Feb 23 19:17:11 2026 -0600

    reduced Mapping CompileDynamic
---
 grails-data-hibernate7/COMPILE-STATIC-AUDIT.md     | 37 ++++---------
 .../org/grails/orm/hibernate/cfg/Mapping.groovy    | 24 ++++-----
 .../grails/orm/hibernate/cfg/MappingSpec.groovy    | 63 ++++++++++++++++++++++
 3 files changed, 85 insertions(+), 39 deletions(-)

diff --git a/grails-data-hibernate7/COMPILE-STATIC-AUDIT.md 
b/grails-data-hibernate7/COMPILE-STATIC-AUDIT.md
index b61d845715..5d8ecd1bc8 100644
--- a/grails-data-hibernate7/COMPILE-STATIC-AUDIT.md
+++ b/grails-data-hibernate7/COMPILE-STATIC-AUDIT.md
@@ -27,7 +27,7 @@ Legend:
 | `cfg/PropertyDefinitionDelegate.groovy` | ✅ Done | |
 | `cfg/SortConfig.groovy` | ✅ Done | |
 | `cfg/Table.groovy` | ✅ Done | |
-| `cfg/Mapping.groovy` | ⚠️ Partial | `methodMissing` intentionally 
`@CompileDynamic` — DSL property dispatch hook. No change needed. |
+| `cfg/Mapping.groovy` | ✅ Done | `methodMissing` is required for DSL 
property-name dispatch (domain field names are unknown at compile time). 
`@CompileDynamic` on method body removed — replaced `args[-1]` (Groovy negative 
array index) with `argsArray[argsArray.length - 1]`; cast `args` to `Object[]` 
explicitly. `@CompileDynamic` import removed. Verified by `MappingSpec` (5 new 
`methodMissing` tests + 8 existing = 13/13 pass). |
 
 ### DSL / Mapping builder
 
@@ -45,8 +45,8 @@ Legend:
 | `HibernateGormEnhancer.groovy` | ✅ Done | |
 | `HibernateGormValidationApi.groovy` | ✅ Done | |
 | `AbstractHibernateGormValidationApi.groovy` | ✅ Done | |
-| `HibernateGormInstanceApi.groovy` | ✅ Done | Stale `@CompileDynamic` removed 
from `isDirty`, `findDirty`, `getDirtyPropertyNames`; typed with `EntityEntry`, 
`NonIdentifierAttribute[]`, explicit for-loops |
-| `HibernateGormStaticApi.groovy` | ⚠️ Partial | 
`findWithSql`/`findAllWithSql` fixed. `getAllInternal` still has untyped locals 
— see detail below |
+| `HibernateGormInstanceApi.groovy` | ⚠️ Partial | 
`isDirty`/`findDirty`/`getDirtyPropertyNames` correctly typed. `nextId()` 
removed (dead code — no callers). Three remaining `@CompileDynamic` methods: 
`runDeferredBinding` (nullable dynamic dispatch on `DEFERRED_BINDING`), 
`setErrorsOnInstance` (dynamic property write 
`target."$GormProperties.ERRORS"`), `incrementVersion` (dynamic read/write of 
`VERSION` property). |
+| `HibernateGormStaticApi.groovy` | ✅ Done | `findWithSql`/`findAllWithSql` 
deprecated in favour of `findWithNativeSql`/`findAllWithNativeSql`. 
`getAllInternal` fully typed. |
 
 ### Infrastructure
 
@@ -54,7 +54,7 @@ Legend:
 |------|--------|-------|
 | `GrailsHibernateTransactionManager.groovy` | ✅ Done | |
 | `MetadataIntegrator.groovy` | ✅ Done | |
-| `HibernateEntity.groovy` | ✅ Done | |
+| `HibernateEntity.groovy` | ✅ Done | `findWithSql`/`findAllWithSql` 
`@Deprecated` and delegating to `findWithNativeSql`/`findAllWithNativeSql`; all 
new methods `@Generated` |
 | `mapping/MappingBuilder.groovy` | ✅ Done | |
 | `compiler/HibernateEntityTransformation.groovy` | ✅ Done | |
 | `connections/HibernateConnectionSourceSettings.groovy` | ✅ Done | |
@@ -120,26 +120,9 @@ Legend:
 
 ## Detailed Analysis of Remaining Partial Files
 
-### `HibernateGormStaticApi.groovy` — `getAllInternal`
-
-```groovy
-@CompileDynamic
-private getAllInternal(List ids) {
-    def identityType = ...
-    def identityName = ...
-    def idsMap = [:]
-    def root = cq.from(...)
-    ...
-}
-```
-
-Can be fully typed: `Class identityType`, `String identityName`, 
`Map<Object,Object> idsMap`, `Root<?> root`. Medium effort.
-
----
-
 ### `cfg/Mapping.groovy`
 
-`methodMissing` dispatches property-name calls (e.g. `firstName column: 
'f_name'`) to `property(name, closure)`. Inherently dynamic — `@CompileDynamic` 
on this method alone is correct. No change needed.
+`methodMissing` is required for the GORM mapping DSL — domain property names 
(`firstName`, `lastName`, etc.) are user-defined and unknown to `Mapping` at 
compile time. The method itself must remain, but the `@CompileDynamic` 
annotation on its body has been removed. The only dynamic construct 
(`args[-1]`) was replaced with `((Object[])args)[args.length - 1]`.
 
 ---
 
@@ -156,7 +139,9 @@ Can be fully typed: `Class identityType`, `String 
identityName`, `Map<Object,Obj
 
 | Action | File | Effort |
 |--------|------|--------|
-| Type untyped locals | `getAllInternal` in `HibernateGormStaticApi` | Medium |
-| Remove metaClass guard + type locals | `parseToNode` in 
`GroovyChangeLogParser` | Medium |
-| Add `instanceof` guards for dynamic map values | `setChangeLogProperties` in 
`GroovyChangeLogParser` | Medium |
-| Accept as permanently dynamic | `methodMissing` in `Mapping`, 
`DatabaseMigrationTransactionManager`, `DatabaseMigrationGrailsPlugin`, 
`createDatabase` in `DatabaseMigrationCommand`, `mergeEnvironmentConfig` in 
`EnvironmentAwareCodeGenConfig` | No action |
+| Fix `runDeferredBinding` nullable dynamic dispatch | 
`HibernateGormInstanceApi.groovy:195` | Low |
+| Fix `setErrorsOnInstance` dynamic property write | 
`HibernateGormInstanceApi.groovy:394` | Low — use `GormValidateable` cast |
+| Fix `incrementVersion` dynamic read/write of VERSION | 
`HibernateGormInstanceApi.groovy:412` | Low — use 
`GroovyObject.getProperty`/`setProperty` with cast |
+| Remove metaClass guard + type locals in `parseToNode` | 
`GroovyChangeLogParser.groovy:48` | Medium |
+| Add `instanceof` guards for dynamic map values in `setChangeLogProperties` | 
`GroovyChangeLogParser.groovy:91` | Medium |
+| Accept as permanently dynamic |  `DatabaseMigrationTransactionManager`, 
`DatabaseMigrationGrailsPlugin`, `createDatabase` in 
`DatabaseMigrationCommand`, `mergeEnvironmentConfig` in 
`EnvironmentAwareCodeGenConfig` | No action |
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/Mapping.groovy
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/Mapping.groovy
index 91acb8ceb6..897a358724 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/Mapping.groovy
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/Mapping.groovy
@@ -15,7 +15,6 @@
  */
 package org.grails.orm.hibernate.cfg
 
-import groovy.transform.CompileDynamic
 import groovy.transform.CompileStatic
 import groovy.transform.builder.Builder
 import groovy.transform.builder.SimpleStrategy
@@ -522,34 +521,33 @@ class Mapping extends Entity<PropertyConfig> {
         }
     }
 
-    @CompileDynamic
     @Override
     def methodMissing(String name, Object args) {
         if(args && args.getClass().isArray()) {
-            if(args[0] instanceof Closure) {
-                property(name, (Closure)args[0])
+            Object[] argsArray = (Object[]) args
+            if(argsArray[0] instanceof Closure) {
+                property(name, (Closure)argsArray[0])
             }
-            else if(args[0] instanceof PropertyConfig) {
-                columns[name] = (PropertyConfig)args[0]
+            else if(argsArray[0] instanceof PropertyConfig) {
+                columns[name] = (PropertyConfig)argsArray[0]
             }
-            else if(args[0] instanceof Map) {
+            else if(argsArray[0] instanceof Map) {
                 PropertyConfig property = getOrInitializePropertyConfig(name)
-                Map namedArgs = (Map) args[0]
-                if(args[-1] instanceof Closure) {
+                Map namedArgs = (Map) argsArray[0]
+                if(argsArray[argsArray.length - 1] instanceof Closure) {
                     PropertyConfig.configureExisting(
                             property,
-                            ((Closure)args[-1])
+                            ((Closure)argsArray[argsArray.length - 1])
                     )
-
                 }
                 PropertyConfig.configureExisting(property, namedArgs)
             }
             else {
-                throw new MissingMethodException(name, getClass(), args)
+                throw new MissingMethodException(name, getClass(), argsArray)
             }
         }
         else {
-            throw new MissingMethodException(name, getClass(), args)
+            throw new MissingMethodException(name, getClass(), (Object[]) args)
         }
     }
 
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/MappingSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/MappingSpec.groovy
index 13cbb3d873..3e1cbc07b6 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/MappingSpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/MappingSpec.groovy
@@ -54,6 +54,69 @@ class MappingSpec extends HibernateGormDatastoreSpec {
         "a property in composite identity" | CompositeIdBook | 'title'      | 
false
     }
 
+    // --- methodMissing dispatch tests (pure unit, no datastore) ---
+
+    void "methodMissing dispatches Closure arg to property(name, closure)"() {
+        given:
+        Mapping mapping = new Mapping()
+
+        when:
+        mapping.firstName { column 'first_name' }
+
+        then:
+        mapping.columns['firstName'] != null
+        mapping.columns['firstName'].column == 'first_name'
+    }
+
+    void "methodMissing dispatches PropertyConfig arg directly into columns 
map"() {
+        given:
+        Mapping mapping = new Mapping()
+        PropertyConfig pc = new PropertyConfig()
+        pc.column('first_name')
+
+        when:
+        mapping.firstName(pc)
+
+        then:
+        mapping.columns['firstName'].is(pc)
+        mapping.columns['firstName'].column == 'first_name'
+    }
+
+    void "methodMissing dispatches Map arg to 
PropertyConfig.configureExisting"() {
+        given:
+        Mapping mapping = new Mapping()
+
+        when:
+        mapping.firstName(column: 'first_name')
+
+        then:
+        mapping.columns['firstName'] != null
+        mapping.columns['firstName'].column == 'first_name'
+    }
+
+    void "methodMissing dispatches Map + Closure args — Map configures, 
Closure also applied"() {
+        given:
+        Mapping mapping = new Mapping()
+
+        when: "Map is first arg, Closure is last arg"
+        mapping.firstName([column: 'first_name'], { formula = 
'UPPER(first_name)' })
+
+        then:
+        mapping.columns['firstName'] != null
+        mapping.columns['firstName'].formula == 'UPPER(first_name)'
+    }
+
+    void "methodMissing throws MissingMethodException for unknown arg type"() {
+        given:
+        Mapping mapping = new Mapping()
+
+        when:
+        mapping.firstName(42)
+
+        then:
+        thrown(MissingMethodException)
+    }
+
 }
 
 // --- Test Domain Classes ---

Reply via email to