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

porcelli 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 9b99105f42 Add configurable compile-time warning for self-referencing 
constraints (fieldA == fieldA) (#6618)
9b99105f42 is described below

commit 9b99105f42006a9d01d6e31eb7ab5eb57509e06c
Author: Alex Porcelli <[email protected]>
AuthorDate: Thu Mar 19 14:35:12 2026 -0400

    Add configurable compile-time warning for self-referencing constraints 
(fieldA == fieldA) (#6618)
---
 .../compiler/SelfReferencingConstraint.java        | 63 ++++++++++++++++
 .../generator/drlxparse/ConstraintParser.java      | 25 +++++++
 .../generator/drlxparse/ConstraintParserTest.java  | 85 ++++++++++++++++++++++
 .../builder/impl/KnowledgeBuilderTest.java         |  2 +
 4 files changed, 175 insertions(+)

diff --git 
a/drools-compiler/src/main/java/org/drools/compiler/compiler/SelfReferencingConstraint.java
 
b/drools-compiler/src/main/java/org/drools/compiler/compiler/SelfReferencingConstraint.java
new file mode 100644
index 0000000000..862deffcda
--- /dev/null
+++ 
b/drools-compiler/src/main/java/org/drools/compiler/compiler/SelfReferencingConstraint.java
@@ -0,0 +1,63 @@
+/*
+ * 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.compiler.compiler;
+
+import org.drools.drl.parser.BaseKnowledgeBuilderResultImpl;
+import org.kie.api.io.Resource;
+import org.kie.internal.builder.KnowledgeBuilderConfiguration;
+import org.kie.internal.builder.ResultSeverity;
+import org.kie.internal.builder.conf.KBuilderSeverityOption;
+
+public class SelfReferencingConstraint extends BaseKnowledgeBuilderResultImpl {
+
+    public static final String KEY = "selfReferencingConstraint";
+    private static final ResultSeverity DEFAULT_SEVERITY = 
ResultSeverity.WARNING;
+
+    private final ResultSeverity severity;
+    private final int[] lines;
+
+    public SelfReferencingConstraint(Resource resource, 
KnowledgeBuilderConfiguration config, String fieldName, String expression) {
+        super(resource, getMessage(fieldName, expression));
+        this.severity = resolveSeverity(config);
+        this.lines = new int[0];
+    }
+
+    private static ResultSeverity 
resolveSeverity(KnowledgeBuilderConfiguration config) {
+        if (config != null && 
config.getOptionSubKeys(KBuilderSeverityOption.KEY).contains(KEY)) {
+            return config.getOption(KBuilderSeverityOption.KEY, 
KEY).getSeverity();
+        }
+        return DEFAULT_SEVERITY;
+    }
+
+    @Override
+    public ResultSeverity getSeverity() {
+        return severity;
+    }
+
+    @Override
+    public int[] getLines() {
+        return lines;
+    }
+
+    private static String getMessage(String fieldName, String expression) {
+        return "Constraint '" + expression + "' compares field '" + fieldName +
+                "' to itself within the same pattern, which is always true and 
likely a bug. " +
+                "Consider using a bound variable (e.g. $" + fieldName + ") 
from another pattern instead.";
+    }
+}
diff --git 
a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/ConstraintParser.java
 
b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/ConstraintParser.java
index f8f50cd684..ac5369f6dc 100644
--- 
a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/ConstraintParser.java
+++ 
b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/ConstraintParser.java
@@ -53,6 +53,9 @@ import org.drools.util.DateUtils;
 import org.drools.model.Index;
 import org.drools.model.codegen.execmodel.PackageModel;
 import org.drools.model.codegen.execmodel.errors.ParseExpressionErrorResult;
+import org.drools.compiler.builder.impl.TypeDeclarationContext;
+import org.drools.compiler.compiler.SelfReferencingConstraint;
+import org.kie.internal.builder.KnowledgeBuilderConfiguration;
 import org.drools.model.codegen.execmodel.errors.VariableUsedInBindingError;
 import org.drools.model.codegen.execmodel.generator.TypedDeclarationSpec;
 import org.drools.model.codegen.execmodel.generator.DrlxParseUtil;
@@ -690,6 +693,19 @@ public class ConstraintParser {
 
         boolean equalityExpr = operator == EQUALS || operator == NOT_EQUALS;
 
+        try {
+            if (isBooleanOperator(operator) && isSelfComparison(left, right)) {
+                String fieldName = printNode(binaryExpr.getLeft());
+                TypeDeclarationContext typeDeclarationContext = 
context.getTypeDeclarationContext();
+                KnowledgeBuilderConfiguration config = typeDeclarationContext 
!= null ? typeDeclarationContext.getBuilderConfiguration() : null;
+                context.addCompilationWarning(new SelfReferencingConstraint(
+                        context.getRuleDescr() != null ? 
context.getRuleDescr().getResource() : null,
+                        config, fieldName, printNode(binaryExpr)));
+            }
+        } catch (Exception e) {
+            LOG.debug("Unable to check for self-comparison: {}", 
e.getMessage());
+        }
+
         CoercedExpression.CoercedExpressionResult coerced;
         try {
             coerced = new CoercedExpression(left, right, 
equalityExpr).coerce();
@@ -799,6 +815,15 @@ public class ConstraintParser {
         return op == AND || op == OR || op == EQUALS || op == NOT_EQUALS || op 
== LESS || op == GREATER || op == LESS_EQUALS || op == GREATER_EQUALS;
     }
 
+    private static boolean isSelfComparison(TypedExpression left, 
TypedExpression right) {
+        if (left.getExpression() == null || right.getExpression() == null) {
+            return false;
+        }
+        String leftStr = printNode(left.getExpression());
+        String rightStr = printNode(right.getExpression());
+        return leftStr.equals(rightStr) && leftStr.contains(THIS_PLACEHOLDER + 
".");
+    }
+
     public static TypedExpression getCoercedRightExpression( PackageModel 
packageModel, CoercedExpression.CoercedExpressionResult coerced ) {
         if ( coerced.isRightAsStaticField()) {
             TypedExpression expr = coerced.getCoercedRight();
diff --git 
a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/generator/drlxparse/ConstraintParserTest.java
 
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/generator/drlxparse/ConstraintParserTest.java
index 80fef32d67..21f1e24d10 100644
--- 
a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/generator/drlxparse/ConstraintParserTest.java
+++ 
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/generator/drlxparse/ConstraintParserTest.java
@@ -24,6 +24,10 @@ import java.util.Optional;
 import java.util.Set;
 
 import com.github.javaparser.ast.expr.Expression;
+import org.drools.compiler.builder.impl.BuildResultCollectorImpl;
+import org.drools.compiler.builder.impl.KnowledgeBuilderConfigurationImpl;
+import org.drools.compiler.compiler.SelfReferencingConstraint;
+import org.drools.drl.ast.descr.RuleDescr;
 import org.drools.model.codegen.execmodel.PackageModel;
 import org.drools.model.codegen.execmodel.domain.Person;
 import org.drools.model.codegen.execmodel.generator.DRLIdGenerator;
@@ -31,8 +35,11 @@ import 
org.drools.model.codegen.execmodel.generator.RuleContext;
 import org.drools.modelcompiler.util.EvaluationUtil;
 import org.drools.util.ClassTypeResolver;
 import org.drools.util.TypeResolver;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.kie.internal.builder.KnowledgeBuilderFactory;
+import org.kie.internal.builder.ResultSeverity;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -40,6 +47,11 @@ public class ConstraintParserTest {
 
     private ConstraintParser parser;
 
+    @AfterEach
+    public void tearDown() {
+        System.getProperties().remove("drools.kbuilder.severity." + 
SelfReferencingConstraint.KEY);
+    }
+
     @BeforeEach
     public void setup() {
         PackageModel packageModel = new 
PackageModel("org.kie.test:constraint-parser-test:1.0.0", "org.kie.test", null, 
null, new DRLIdGenerator());
@@ -248,4 +260,77 @@ public class ConstraintParserTest {
 
         
assertThat(result.getExpr().toString()).isEqualTo("D.eval(org.drools.model.operators.InOperator.INSTANCE,
 _this.getMoney(), new java.math.BigDecimal(\"100\"), new 
java.math.BigDecimal(\"200\"))");
     }
+
+    @Test
+    public void testSelfComparisonWarning() {
+        BuildResultCollectorImpl resultCollector = new 
BuildResultCollectorImpl();
+        PackageModel packageModel = new 
PackageModel("org.kie.test:constraint-parser-test:1.0.0", "org.kie.test", null, 
null, new DRLIdGenerator());
+        Set<String> imports = new HashSet<>();
+        imports.add(Person.class.getCanonicalName());
+        TypeResolver typeResolver = new ClassTypeResolver(imports, 
getClass().getClassLoader());
+        RuleContext context = new RuleContext(null, resultCollector, 
packageModel, typeResolver, new RuleDescr("testRule"), 0);
+        ConstraintParser selfCompParser = 
ConstraintParser.defaultConstraintParser(context, packageModel);
+
+        SingleDrlxParseSuccess result = (SingleDrlxParseSuccess) 
selfCompParser.drlxParse(Person.class, "$p", "name == name");
+
+        assertThat(result).isNotNull();
+        
assertThat(resultCollector.hasResults(ResultSeverity.WARNING)).isTrue();
+        assertThat(resultCollector.getAllResults()).anyMatch(r -> 
r.getSeverity() == ResultSeverity.WARNING
+                && r.getMessage().contains("compares field")
+                && r.getMessage().contains("to itself"));
+    }
+
+    @Test
+    public void testNoWarningForNormalConstraint() {
+        BuildResultCollectorImpl resultCollector = new 
BuildResultCollectorImpl();
+        PackageModel packageModel = new 
PackageModel("org.kie.test:constraint-parser-test:1.0.0", "org.kie.test", null, 
null, new DRLIdGenerator());
+        Set<String> imports = new HashSet<>();
+        imports.add(Person.class.getCanonicalName());
+        TypeResolver typeResolver = new ClassTypeResolver(imports, 
getClass().getClassLoader());
+        RuleContext context = new RuleContext(null, resultCollector, 
packageModel, typeResolver, new RuleDescr("testRule"), 0);
+        ConstraintParser selfCompParser = 
ConstraintParser.defaultConstraintParser(context, packageModel);
+
+        SingleDrlxParseSuccess result = (SingleDrlxParseSuccess) 
selfCompParser.drlxParse(Person.class, "$p", "name == \"John\"");
+
+        assertThat(result).isNotNull();
+        
assertThat(resultCollector.hasResults(ResultSeverity.WARNING)).isFalse();
+    }
+
+    @Test
+    public void testSelfReferencingConstraintDefaultSeverityIsWarning() {
+        SelfReferencingConstraint constraint = new 
SelfReferencingConstraint(null, null, "name", "name == name");
+        assertThat(constraint.getSeverity()).isEqualTo(ResultSeverity.WARNING);
+        assertThat(constraint.getMessage()).contains("compares field");
+        assertThat(constraint.getMessage()).contains("to itself");
+    }
+
+    @Test
+    public void testSelfReferencingConstraintConfiguredAsError() {
+        System.setProperty("drools.kbuilder.severity." + 
SelfReferencingConstraint.KEY, "ERROR");
+        KnowledgeBuilderConfigurationImpl config = 
KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration()
+                .as(KnowledgeBuilderConfigurationImpl.KEY);
+
+        SelfReferencingConstraint constraint = new 
SelfReferencingConstraint(null, config, "name", "name == name");
+        assertThat(constraint.getSeverity()).isEqualTo(ResultSeverity.ERROR);
+    }
+
+    @Test
+    public void testSelfReferencingConstraintConfiguredAsInfo() {
+        System.setProperty("drools.kbuilder.severity." + 
SelfReferencingConstraint.KEY, "INFO");
+        KnowledgeBuilderConfigurationImpl config = 
KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration()
+                .as(KnowledgeBuilderConfigurationImpl.KEY);
+
+        SelfReferencingConstraint constraint = new 
SelfReferencingConstraint(null, config, "name", "name == name");
+        assertThat(constraint.getSeverity()).isEqualTo(ResultSeverity.INFO);
+    }
+
+    @Test
+    public void testSelfReferencingConstraintDefaultsToWarningWithoutConfig() {
+        // When config exists but property is not set, severity defaults to 
WARNING
+        KnowledgeBuilderConfigurationImpl config = 
KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration()
+                .as(KnowledgeBuilderConfigurationImpl.KEY);
+
+        SelfReferencingConstraint constraint = new 
SelfReferencingConstraint(null, config, "age", "age == age");
+        assertThat(constraint.getSeverity()).isEqualTo(ResultSeverity.WARNING);
+    }
 }
diff --git 
a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/compiler/builder/impl/KnowledgeBuilderTest.java
 
b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/compiler/builder/impl/KnowledgeBuilderTest.java
index c8d7e24c52..2be671028d 100644
--- 
a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/compiler/builder/impl/KnowledgeBuilderTest.java
+++ 
b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/compiler/builder/impl/KnowledgeBuilderTest.java
@@ -33,6 +33,7 @@ import org.drools.compiler.compiler.Dialect;
 import org.drools.compiler.compiler.DialectCompiletimeRegistry;
 import org.drools.compiler.compiler.DuplicateFunction;
 import org.drools.compiler.compiler.DuplicateRule;
+import org.drools.compiler.compiler.SelfReferencingConstraint;
 import org.drools.base.base.ClassObjectType;
 import org.drools.core.common.ActivationGroupNode;
 import org.drools.core.common.ActivationNode;
@@ -113,6 +114,7 @@ public class KnowledgeBuilderTest {
         System.getProperties().remove( "drools.warning.filters" );
         System.getProperties().remove( "drools.kbuilder.severity." + 
DuplicateFunction.KEY);
         System.getProperties().remove( "drools.kbuilder.severity." + 
DuplicateRule.KEY);
+        System.getProperties().remove( "drools.kbuilder.severity." + 
SelfReferencingConstraint.KEY);
     }
 
     @Test


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to