This is an automated email from the ASF dual-hosted git repository.
tkobayas pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-drools.git
The following commit(s) were added to refs/heads/main by this push:
new 11e8da64172 [6779] Exec model: include Java superclass @Position
fields in genera… (#6780)
11e8da64172 is described below
commit 11e8da64172c2a48a8782aa38ceb261e95124045
Author: Daniel Clark <[email protected]>
AuthorDate: Mon Jul 6 09:47:35 2026 +0200
[6779] Exec model: include Java superclass @Position fields in genera…
(#6780)
* [6779] Exec model: include Java superclass @Position fields in generated
constructor
When a DRL-declared type extends a Java (classpath) class, the executable
model
generated a constructor over only the type's own fields, omitting the
inherited
fields and the super(...) call. The classic compiler includes them, so rule
consequents such as `new Sub(inheritedField, ownField)` failed to compile
under
the executable model with "The constructor ... is undefined".
DescrTypeDefinition.findInheritedDeclaredFields() now pulls the resolved
Java
superclass's @Position fields (ordered by position) when the supertype is a
classpath class rather than a DRL-declared type, so the generated
full-argument
constructor includes them and forwards them to super(...). Falls back to
plain
declared-field order when the superclass carries no @Position.
Adds DeclaredTypesTest#testExtendPojoInheritedFieldsConstructor and a
minimal
PositionalParent POJO covering the scenario.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* Only inherit @Position superclass fields, else no-arg super()
---------
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
---
.../declaredtype/DescrTypeDefinition.java | 44 ++++++++++++-
.../model/codegen/execmodel/DeclaredTypesTest.java | 72 ++++++++++++++++++++++
.../codegen/execmodel/domain/PlainParent.java | 41 ++++++++++++
.../codegen/execmodel/domain/PositionalParent.java | 59 ++++++++++++++++++
4 files changed, 215 insertions(+), 1 deletion(-)
diff --git
a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/declaredtype/DescrTypeDefinition.java
b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/declaredtype/DescrTypeDefinition.java
index c07b8cf7d29..ef3ae10798a 100644
---
a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/declaredtype/DescrTypeDefinition.java
+++
b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/declaredtype/DescrTypeDefinition.java
@@ -18,10 +18,13 @@
*/
package org.drools.model.codegen.execmodel.generator.declaredtype;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -42,6 +45,7 @@ import
org.drools.model.codegen.execmodel.generator.declaredtype.api.FieldDefini
import
org.drools.model.codegen.execmodel.generator.declaredtype.api.MethodDefinition;
import
org.drools.model.codegen.execmodel.generator.declaredtype.api.TypeDefinition;
import
org.drools.model.codegen.execmodel.generator.declaredtype.api.TypeResolver;
+import org.kie.api.definition.type.Position;
import static java.util.Optional.of;
import static java.util.Optional.ofNullable;
@@ -161,7 +165,45 @@ public class DescrTypeDefinition implements TypeDefinition
{
@Override
public List<FieldDefinition> findInheritedDeclaredFields() {
- return findInheritedDeclaredFields(new ArrayList<>(),
getSuperType(typeDeclarationDescr, packageDescr));
+ List<FieldDefinition> fields = findInheritedDeclaredFields(new
ArrayList<>(), getSuperType(typeDeclarationDescr, packageDescr));
+ if (fields.isEmpty()) {
+ abstractClass.ifPresent(superClass ->
fields.addAll(inheritedFieldsFromSuperClass(superClass)));
+ }
+ return fields;
+ }
+
+ /**
+ * Collects the positional fields of a resolved Java superclass, walking
the class hierarchy.
+ * A field participates only when it carries {@link Position} (explicit
opt-in), ordered by its
+ * position value; this deterministically excludes non-positional members
and keeps the generated
+ * {@code super(...)} call aligned with a positional constructor on the
superclass. When the
+ * superclass declares no {@link Position} field, the result is empty so
the generated constructor
+ * uses a no-arg {@code super()} rather than guessing a signature from all
instance fields (which
+ * would not match any superclass constructor and would fail to compile).
+ */
+ private List<FieldDefinition> inheritedFieldsFromSuperClass(Class<?>
superClass) {
+ List<Class<?>> hierarchy = new ArrayList<>();
+ for (Class<?> c = superClass; c != null && c != Object.class; c =
c.getSuperclass()) {
+ hierarchy.add(0, c);
+ }
+
+ List<Field> instanceFields = new ArrayList<>();
+ for (Class<?> c : hierarchy) {
+ for (Field f : c.getDeclaredFields()) {
+ if (!Modifier.isStatic(f.getModifiers())) {
+ instanceFields.add(f);
+ }
+ }
+ }
+
+ // Only fields explicitly opted-in with @Position are inherited into
the generated
+ // constructor. When the superclass declares none, return empty (no
fallback to all instance
+ // fields) so the constructor uses a no-arg super() instead of an
unmatched super(...) call.
+ return instanceFields.stream()
+ .filter(f -> f.getAnnotation(Position.class) != null)
+ .sorted(Comparator.comparingInt(f ->
f.getAnnotation(Position.class).value()))
+ .map(f -> (FieldDefinition) new
DescrFieldDefinition(f.getName(), f.getType().getCanonicalName(), null))
+ .collect(Collectors.toList());
}
private List<FieldDefinition>
findInheritedDeclaredFields(List<FieldDefinition> fields,
Optional<TypeDeclarationDescr> superType) {
diff --git
a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/DeclaredTypesTest.java
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/DeclaredTypesTest.java
index 353caceb53f..0bc143692e7 100644
---
a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/DeclaredTypesTest.java
+++
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/DeclaredTypesTest.java
@@ -41,6 +41,8 @@ import org.drools.core.reteoo.EntryPointNode;
import org.drools.core.reteoo.ObjectTypeNode;
import org.drools.model.codegen.execmodel.domain.Address;
import org.drools.model.codegen.execmodel.domain.Person;
+import org.drools.model.codegen.execmodel.domain.PlainParent;
+import org.drools.model.codegen.execmodel.domain.PositionalParent;
import org.drools.model.codegen.execmodel.domain.Result;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -660,6 +662,76 @@ public class DeclaredTypesTest extends BaseModelTest {
assertThat(ksession.fireAllRules()).isEqualTo(1);
}
+ @ParameterizedTest
+ @MethodSource("parameters")
+ public void testExtendPojoInheritedFieldsConstructor(RUN_TYPE runType)
throws Exception {
+ // A declared type extending a Java (classpath) class must generate a
full-argument
+ // constructor that also includes the superclass @Position fields and
forwards them to
+ // super(...). PositionalParent has @Position(0) name and @Position(1)
age, so Child should
+ // expose Child(String name, int age, String dept). The executable
model previously omitted
+ // the inherited fields, generating only Child(String dept), which
failed to compile the
+ // 'new Child("Mario", 40, "Sales")' consequent.
+ String str =
+ "package org.test;\n" +
+ "import " + PositionalParent.class.getCanonicalName() + ";\n" +
+ "import " + Result.class.getCanonicalName() + ";\n" +
+ "declare PositionalParent end\n" +
+ "declare Child extends PositionalParent\n" +
+ " dept : String\n" +
+ "end\n" +
+ "rule Init when\n" +
+ "then\n" +
+ " insert( new Child(\"Mario\", 40, \"Sales\") );\n" +
+ "end\n" +
+ "rule Check when\n" +
+ " $c : Child( name == \"Mario\", age == 40, dept ==
\"Sales\" )\n" +
+ "then\n" +
+ " insert( new Result($c.getName()) );\n" +
+ "end";
+
+ KieSession ksession = getKieSession(runType, str);
+ assertThat(ksession.fireAllRules()).isEqualTo(2);
+
+ Collection<Result> results = getObjectsIntoList(ksession,
Result.class);
+ assertThat(results).hasSize(1);
+ assertThat(results.iterator().next().getValue()).isEqualTo("Mario");
+ }
+
+ @ParameterizedTest
+ @MethodSource("parametersPatternOnly")
+ public void testExtendPojoWithoutPositionUsesNoArgSuper(RUN_TYPE runType) {
+ // Executable-model regression for #6779: a declared type extending a
Java class that has NO
+ // @Position fields must not pull the superclass's instance fields
into the generated
+ // constructor - doing so would emit a super(...) call with no
matching superclass constructor
+ // and fail to compile ("The constructor Child(String) is undefined").
Only the declared type's
+ // own fields are constructor parameters, and a no-arg super() is
used. PlainParent has a single
+ // (unannotated) field and only a no-arg constructor. (Exec-model
codegen only; the classic
+ // compiler handles declared-extends-POJO via a different path.)
+ String str =
+ "package org.test;\n" +
+ "import " + PlainParent.class.getCanonicalName() + ";\n" +
+ "import " + Result.class.getCanonicalName() + ";\n" +
+ "declare Child extends PlainParent\n" +
+ " dept : String\n" +
+ "end\n" +
+ "rule Init when\n" +
+ "then\n" +
+ " insert( new Child(\"Sales\") );\n" +
+ "end\n" +
+ "rule Check when\n" +
+ " $c : Child( dept == \"Sales\" )\n" +
+ "then\n" +
+ " insert( new Result($c.getDept()) );\n" +
+ "end";
+
+ KieSession ksession = getKieSession(runType, str);
+ assertThat(ksession.fireAllRules()).isEqualTo(2);
+
+ Collection<Result> results = getObjectsIntoList(ksession,
Result.class);
+ assertThat(results).hasSize(1);
+ assertThat(results.iterator().next().getValue()).isEqualTo("Sales");
+ }
+
@ParameterizedTest
@MethodSource("parameters")
void testNonDefinedCustomAnnotation(RUN_TYPE runType) {
diff --git
a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/domain/PlainParent.java
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/domain/PlainParent.java
new file mode 100644
index 00000000000..9473559aef7
--- /dev/null
+++
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/domain/PlainParent.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.model.codegen.execmodel.domain;
+
+/**
+ * A plain classpath POJO with NO {@code @Position} fields and only a no-arg
constructor, used to
+ * verify that a DRL-declared type extending it does not pull the superclass's
instance fields into
+ * the generated constructor (which would emit an unmatched {@code super(...)}
call); instead a no-arg
+ * {@code super()} is used.
+ */
+public class PlainParent {
+
+ private String label;
+
+ public PlainParent() {
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+}
diff --git
a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/domain/PositionalParent.java
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/domain/PositionalParent.java
new file mode 100644
index 00000000000..70570848527
--- /dev/null
+++
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/domain/PositionalParent.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.model.codegen.execmodel.domain;
+
+import org.kie.api.definition.type.Position;
+
+/**
+ * Minimal classpath POJO with positional fields, used to verify that a
DRL-declared type
+ * extending a Java class generates a full-argument constructor that includes
the inherited
+ * {@code @Position} fields and forwards them to {@code super(...)}.
+ */
+public class PositionalParent {
+
+ @Position(0)
+ private String name;
+
+ @Position(1)
+ private int age;
+
+ public PositionalParent() {
+ }
+
+ public PositionalParent(String name, int age) {
+ this.name = name;
+ this.age = age;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]