From dc299b9b82553892d08eeba2c107f69536ce21fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dinis=20Ferreira?= Date: Thu, 18 Jun 2026 15:12:19 +0200 Subject: [PATCH 1/3] fix: restore scope and export expression code generation Recognize nested arithmetic calls recursively as numeric expressions. Without this, an outer arithmetic call can fall through to method-call rendering and produce invalid Java such as *(4 + 2, 3). Leave == and != untranslated because Xbase equality is value-based while the legacy Java generator emits identity equality. Current production generation already uses the string compiler for these expressions, so this is a defensive translator-contract correction rather than a live equality regression. Apply both changes symmetrically to the duplicated Scope and Export expression pipelines. --- .../xtext/export/jvmmodel/ExportExpressionCompiler.xtend | 4 ++++ .../xtext/export/jvmmodel/ExportExpressionTranslator.xtend | 6 +++--- .../ddk/xtext/scope/jvmmodel/ScopeExpressionCompiler.xtend | 4 ++++ .../xtext/scope/jvmmodel/ScopeExpressionTranslator.xtend | 6 +++--- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/jvmmodel/ExportExpressionCompiler.xtend b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/jvmmodel/ExportExpressionCompiler.xtend index c831834a0..0ce3b5664 100644 --- a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/jvmmodel/ExportExpressionCompiler.xtend +++ b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/jvmmodel/ExportExpressionCompiler.xtend @@ -385,6 +385,10 @@ class ExportExpressionCompiler { } def private boolean isNumber(Expression it, ExportTranslationContext ctx) { + if (isArithmeticOperatorCall(ctx)) { + // Arithmetic calls have no resolvable JVM method but still produce numeric values. + return true + } val type = translator.resolveType(it, ctx) type !== null && type.isNumeric } diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/jvmmodel/ExportExpressionTranslator.xtend b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/jvmmodel/ExportExpressionTranslator.xtend index b47720fb5..896376b3b 100644 --- a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/jvmmodel/ExportExpressionTranslator.xtend +++ b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/jvmmodel/ExportExpressionTranslator.xtend @@ -43,7 +43,6 @@ import org.eclipse.xtext.common.types.util.TypeReferences import org.eclipse.xtext.xbase.XExpression import org.eclipse.xtext.xbase.XbaseFactory import org.eclipse.xtext.xbase.lib.BooleanExtensions -import org.eclipse.xtext.xbase.lib.ObjectExtensions /** * Translates the custom {@link Expression} AST of the export expression DSL into equivalent Xbase @@ -436,8 +435,9 @@ class ExportExpressionTranslator { switch operator { case '||': toBinaryOperation(xLeft, xRight, BooleanExtensions, 'operator_or', sourceElement) case '&&': toBinaryOperation(xLeft, xRight, BooleanExtensions, 'operator_and', sourceElement) - case '==': toBinaryOperation(xLeft, xRight, ObjectExtensions, 'operator_equals', sourceElement) - case '!=': toBinaryOperation(xLeft, xRight, ObjectExtensions, 'operator_notEquals', sourceElement) + // Xbase equality is value-based, unlike the identity equality emitted by the legacy Java generator. + case '==': null + case '!=': null default: null } } diff --git a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/jvmmodel/ScopeExpressionCompiler.xtend b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/jvmmodel/ScopeExpressionCompiler.xtend index 9343d6c10..044da8dfc 100644 --- a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/jvmmodel/ScopeExpressionCompiler.xtend +++ b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/jvmmodel/ScopeExpressionCompiler.xtend @@ -387,6 +387,10 @@ class ScopeExpressionCompiler { } def private boolean isNumber(Expression it, ScopeTranslationContext ctx) { + if (isArithmeticOperatorCall(ctx)) { + // Arithmetic calls have no resolvable JVM method but still produce numeric values. + return true + } val type = translator.resolveType(it, ctx) type !== null && type.isNumeric } diff --git a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/jvmmodel/ScopeExpressionTranslator.xtend b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/jvmmodel/ScopeExpressionTranslator.xtend index 6ebcaed4f..b911e52c3 100644 --- a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/jvmmodel/ScopeExpressionTranslator.xtend +++ b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/jvmmodel/ScopeExpressionTranslator.xtend @@ -44,7 +44,6 @@ import org.eclipse.xtext.common.types.util.TypeReferences import org.eclipse.xtext.xbase.XExpression import org.eclipse.xtext.xbase.XbaseFactory import org.eclipse.xtext.xbase.lib.BooleanExtensions -import org.eclipse.xtext.xbase.lib.ObjectExtensions /** * Translates the custom {@link Expression} AST of the scope/export expression DSL into equivalent Xbase @@ -531,8 +530,9 @@ class ScopeExpressionTranslator { switch operator { case '||': toBinaryOperation(xLeft, xRight, BooleanExtensions, 'operator_or', sourceElement) case '&&': toBinaryOperation(xLeft, xRight, BooleanExtensions, 'operator_and', sourceElement) - case '==': toBinaryOperation(xLeft, xRight, ObjectExtensions, 'operator_equals', sourceElement) - case '!=': toBinaryOperation(xLeft, xRight, ObjectExtensions, 'operator_notEquals', sourceElement) + // Xbase equality is value-based, unlike the identity equality emitted by the legacy Java generator. + case '==': null + case '!=': null default: null } } From 7f82b08b1c83a64244460f41912cb60721cec457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dinis=20Ferreira?= Date: Wed, 26 Aug 2026 10:10:49 +0100 Subject: [PATCH 2/3] test: restore scope and export expression compiler coverage Add focused generator tests for recursive arithmetic, arithmetic/string-concatenation controls, and the unsupported equality translator contract in both duplicated DSL pipelines. Register them in the existing aggregate test suite. --- .../META-INF/MANIFEST.MF | 2 + .../AbstractExpressionCodeGenerationTest.java | 85 +++++++++++++++++++ .../ExportExpressionCodeGenerationTest.java | 51 +++++++++++ .../ScopeExpressionCodeGenerationTest.java | 51 +++++++++++ .../test/generator/GeneratorTestSuite.java | 4 + 5 files changed, 193 insertions(+) create mode 100644 com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/AbstractExpressionCodeGenerationTest.java create mode 100644 com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/ExportExpressionCodeGenerationTest.java create mode 100644 com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/ScopeExpressionCodeGenerationTest.java diff --git a/com.avaloq.tools.ddk.xtext.generator.test/META-INF/MANIFEST.MF b/com.avaloq.tools.ddk.xtext.generator.test/META-INF/MANIFEST.MF index ea494375f..6a1bd1d8f 100644 --- a/com.avaloq.tools.ddk.xtext.generator.test/META-INF/MANIFEST.MF +++ b/com.avaloq.tools.ddk.xtext.generator.test/META-INF/MANIFEST.MF @@ -8,7 +8,9 @@ Bundle-RequiredExecutionEnvironment: JavaSE-21 Bundle-ActivationPolicy: lazy Fragment-Host: com.avaloq.tools.ddk.xtext.generator Require-Bundle: com.avaloq.tools.ddk.test.core, + com.avaloq.tools.ddk.xtext.export, com.avaloq.tools.ddk.xtext.expression, + com.avaloq.tools.ddk.xtext.scope, com.avaloq.tools.ddk.xtext.test.core, org.eclipse.xtext, org.mockito.mockito-core, diff --git a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/AbstractExpressionCodeGenerationTest.java b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/AbstractExpressionCodeGenerationTest.java new file mode 100644 index 000000000..bd455094c --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/AbstractExpressionCodeGenerationTest.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2026 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.generator.expression; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.io.IOException; + +import org.eclipse.xtext.xbase.XExpression; +import org.junit.jupiter.api.Test; + +import com.avaloq.tools.ddk.xtext.expression.expression.Expression; +import com.avaloq.tools.ddk.xtext.generator.test.util.GeneratorTestUtil; +import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractXtextTest; + + +/** + * Shared expression code-generation contract for DSL-specific compiler implementations. + */ +@SuppressWarnings("nls") +abstract class AbstractExpressionCodeGenerationTest extends AbstractXtextTest { + + @Override + protected GeneratorTestUtil getXtextTestUtil() { + return GeneratorTestUtil.getInstance(); + } + + /** + * This test class does not have a test source file. {@inheritDoc} + */ + @Override + protected String getTestSourceFileName() { + return null; + } + + @Test + @SuppressWarnings("PMD.SignatureDeclareThrowsException") + void testNestedArithmetic() throws IOException { + assertCompilesUnchanged("(4 + 2) * 3"); + assertCompilesUnchanged("(4 + 2) * 3 * 4"); + } + + @Test + @SuppressWarnings("PMD.SignatureDeclareThrowsException") + void testArithmeticControls() throws IOException { + assertCompilesUnchanged("4 + 2"); + assertEquals("\"x\" + 2 + 3 + 4", compile("('x' + 2) + 3 + 4")); + } + + @Test + @SuppressWarnings("PMD.SignatureDeclareThrowsException") + void testEqualityIsNotTranslated() throws IOException { + assertNull(translate("1 == 2")); + assertNull(translate("1 != 2")); + } + + protected abstract String compile(Expression expression); + + protected abstract XExpression translate(Expression expression); + + private String compile(final String source) throws IOException { + return compile(parse(source)); + } + + private void assertCompilesUnchanged(final String source) throws IOException { + assertEquals(source, compile(source)); + } + + private XExpression translate(final String source) throws IOException { + return translate(parse(source)); + } + + private Expression parse(final String source) throws IOException { + return (Expression) getXtextTestUtil().getModel("test.expression", source); + } +} diff --git a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/ExportExpressionCodeGenerationTest.java b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/ExportExpressionCodeGenerationTest.java new file mode 100644 index 000000000..29de222cc --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/ExportExpressionCodeGenerationTest.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2026 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.generator.expression; + +import org.eclipse.xtext.xbase.XExpression; + +import com.avaloq.tools.ddk.xtext.export.ExportStandaloneSetup; +import com.avaloq.tools.ddk.xtext.export.jvmmodel.ExportExpressionCompiler; +import com.avaloq.tools.ddk.xtext.export.jvmmodel.ExportExpressionTranslator; +import com.avaloq.tools.ddk.xtext.export.jvmmodel.ExportTranslationContext; +import com.avaloq.tools.ddk.xtext.expression.expression.Expression; +import com.google.inject.Injector; + + +/** + * Tests Java source generation for expressions used by the Export DSL. + */ +public class ExportExpressionCodeGenerationTest extends AbstractExpressionCodeGenerationTest { + + @Override + protected void beforeAllTests() { + super.beforeAllTests(); + final Injector injector = new ExportStandaloneSetup().createInjector(); + getTestInformation().putTestObject(ExportExpressionCompiler.class, injector.getInstance(ExportExpressionCompiler.class)); + getTestInformation().putTestObject(ExportExpressionTranslator.class, injector.getInstance(ExportExpressionTranslator.class)); + } + + @Override + protected String compile(final Expression expression) { + final ExportTranslationContext context = new ExportTranslationContext(); + context.setSourceElement(expression); + final ExportExpressionCompiler compiler = (ExportExpressionCompiler) getTestInformation().getTestObject(ExportExpressionCompiler.class); + return compiler.javaExpression(expression, context); + } + + @Override + protected XExpression translate(final Expression expression) { + final ExportTranslationContext context = new ExportTranslationContext(); + context.setSourceElement(expression); + final ExportExpressionTranslator translator = (ExportExpressionTranslator) getTestInformation().getTestObject(ExportExpressionTranslator.class); + return translator.translate(expression, context); + } +} diff --git a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/ScopeExpressionCodeGenerationTest.java b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/ScopeExpressionCodeGenerationTest.java new file mode 100644 index 000000000..b28ded203 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/ScopeExpressionCodeGenerationTest.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2026 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.generator.expression; + +import org.eclipse.xtext.xbase.XExpression; + +import com.avaloq.tools.ddk.xtext.expression.expression.Expression; +import com.avaloq.tools.ddk.xtext.scope.ScopeStandaloneSetup; +import com.avaloq.tools.ddk.xtext.scope.jvmmodel.ScopeExpressionCompiler; +import com.avaloq.tools.ddk.xtext.scope.jvmmodel.ScopeExpressionTranslator; +import com.avaloq.tools.ddk.xtext.scope.jvmmodel.ScopeTranslationContext; +import com.google.inject.Injector; + + +/** + * Tests Java source generation for expressions used by the Scope DSL. + */ +public class ScopeExpressionCodeGenerationTest extends AbstractExpressionCodeGenerationTest { + + @Override + protected void beforeAllTests() { + super.beforeAllTests(); + final Injector injector = new ScopeStandaloneSetup().createInjector(); + getTestInformation().putTestObject(ScopeExpressionCompiler.class, injector.getInstance(ScopeExpressionCompiler.class)); + getTestInformation().putTestObject(ScopeExpressionTranslator.class, injector.getInstance(ScopeExpressionTranslator.class)); + } + + @Override + protected String compile(final Expression expression) { + final ScopeTranslationContext context = new ScopeTranslationContext(); + context.setSourceElement(expression); + final ScopeExpressionCompiler compiler = (ScopeExpressionCompiler) getTestInformation().getTestObject(ScopeExpressionCompiler.class); + return compiler.javaExpression(expression, context); + } + + @Override + protected XExpression translate(final Expression expression) { + final ScopeTranslationContext context = new ScopeTranslationContext(); + context.setSourceElement(expression); + final ScopeExpressionTranslator translator = (ScopeExpressionTranslator) getTestInformation().getTestObject(ScopeExpressionTranslator.class); + return translator.translate(expression, context); + } +} diff --git a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/test/generator/GeneratorTestSuite.java b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/test/generator/GeneratorTestSuite.java index 9e2644a0a..faf395b6e 100644 --- a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/test/generator/GeneratorTestSuite.java +++ b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/test/generator/GeneratorTestSuite.java @@ -13,7 +13,9 @@ import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; +import com.avaloq.tools.ddk.xtext.generator.expression.ExportExpressionCodeGenerationTest; import com.avaloq.tools.ddk.xtext.generator.expression.ExpressionsExtentionsTest; +import com.avaloq.tools.ddk.xtext.generator.expression.ScopeExpressionCodeGenerationTest; import com.avaloq.tools.ddk.xtext.generator.test.util.EClassComparatorTest; import com.avaloq.tools.ddk.xtext.generator.test.util.GraphTest; import com.avaloq.tools.ddk.xtext.generator.xbase.test.XbaseGeneratorFragmentTest; @@ -25,7 +27,9 @@ @Suite @SelectClasses({ // @Format-Off + ExportExpressionCodeGenerationTest.class, ExpressionsExtentionsTest.class, + ScopeExpressionCodeGenerationTest.class, EClassComparatorTest.class, GraphTest.class, XbaseGeneratorFragmentTest.class From 7154a3131b7dd7fa395e795210287c8a2571d1cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Dinis=20Ferreira?= Date: Wed, 26 Aug 2026 10:10:50 +0100 Subject: [PATCH 3/3] build: bump scope and export bundles to 17.3.3 Both bundles change production code relative to their 17.3.2 baselines. --- com.avaloq.tools.ddk.xtext.export/META-INF/MANIFEST.MF | 2 +- com.avaloq.tools.ddk.xtext.export/pom.xml | 4 ++-- com.avaloq.tools.ddk.xtext.scope/META-INF/MANIFEST.MF | 2 +- com.avaloq.tools.ddk.xtext.scope/pom.xml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/com.avaloq.tools.ddk.xtext.export/META-INF/MANIFEST.MF b/com.avaloq.tools.ddk.xtext.export/META-INF/MANIFEST.MF index 0b34131a9..4fcb354f0 100644 --- a/com.avaloq.tools.ddk.xtext.export/META-INF/MANIFEST.MF +++ b/com.avaloq.tools.ddk.xtext.export/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: com.avaloq.tools.ddk.xtext.export Bundle-SymbolicName: com.avaloq.tools.ddk.xtext.export;singleton:=true -Bundle-Version: 17.3.2.qualifier +Bundle-Version: 17.3.3.qualifier Bundle-Vendor: Avaloq Group AG Bundle-RequiredExecutionEnvironment: JavaSE-21 Bundle-ActivationPolicy: lazy diff --git a/com.avaloq.tools.ddk.xtext.export/pom.xml b/com.avaloq.tools.ddk.xtext.export/pom.xml index 3e6e99b71..65b107c1a 100644 --- a/com.avaloq.tools.ddk.xtext.export/pom.xml +++ b/com.avaloq.tools.ddk.xtext.export/pom.xml @@ -6,8 +6,8 @@ 18.0.1-SNAPSHOT ../ddk-parent - 17.3.2-SNAPSHOT + 17.3.3-SNAPSHOT com.avaloq.tools.ddk com.avaloq.tools.ddk.xtext.export eclipse-plugin - \ No newline at end of file + diff --git a/com.avaloq.tools.ddk.xtext.scope/META-INF/MANIFEST.MF b/com.avaloq.tools.ddk.xtext.scope/META-INF/MANIFEST.MF index 7c3c6b168..eb79e677a 100644 --- a/com.avaloq.tools.ddk.xtext.scope/META-INF/MANIFEST.MF +++ b/com.avaloq.tools.ddk.xtext.scope/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: com.avaloq.tools.ddk.xtext.scope Bundle-SymbolicName: com.avaloq.tools.ddk.xtext.scope;singleton:=true -Bundle-Version: 17.3.2.qualifier +Bundle-Version: 17.3.3.qualifier Bundle-Vendor: Avaloq Group AG Bundle-RequiredExecutionEnvironment: JavaSE-21 Require-Bundle: org.eclipse.xtext, diff --git a/com.avaloq.tools.ddk.xtext.scope/pom.xml b/com.avaloq.tools.ddk.xtext.scope/pom.xml index 542bfb0ea..c5745c9e6 100644 --- a/com.avaloq.tools.ddk.xtext.scope/pom.xml +++ b/com.avaloq.tools.ddk.xtext.scope/pom.xml @@ -6,8 +6,8 @@ 18.0.1-SNAPSHOT ../ddk-parent - 17.3.2-SNAPSHOT + 17.3.3-SNAPSHOT com.avaloq.tools.ddk com.avaloq.tools.ddk.xtext.scope eclipse-plugin - \ No newline at end of file +