From 2d20983a10dd588e321b67d2ec10a3b74c04aeaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B8=D0=BB=D1=8F=D0=BD=20=D0=9F=D0=B0=D0=BB=D0=B0?= =?UTF-8?q?=D1=83=D0=B7=D0=BE=D0=B2?= Date: Thu, 5 Feb 2026 13:31:09 +0200 Subject: [PATCH] script.model.ScriptInterpreter: emit what cannot be casted to what after a single evaluation of the cast. Before this change internalEvaluate(castedExpression.getTarget(), context, indicator) was invoked twice. When on the first execution an error occured, the second execution was needed to create an error message, stating what cannot be casted to what. However at the time internalEvaluate was called for a second time, internal states have changed, so there might be no more errors with casting, thus producing incorrect error message. Except the line "Could not cast (" + result.class + ")" + result + " to " + typeName the implementation is the same as in XbaseInterpreter.java. --- .../interpreter/ScriptInterpreter.xtend | 53 ++++++++++++++----- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/interpreter/ScriptInterpreter.xtend b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/interpreter/ScriptInterpreter.xtend index 99a222f910c..def9ba252f8 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/interpreter/ScriptInterpreter.xtend +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/interpreter/ScriptInterpreter.xtend @@ -25,6 +25,7 @@ import org.openhab.core.model.script.scoping.StateAndCommandProvider import org.openhab.core.model.script.script.QuantityLiteral import org.eclipse.xtext.common.types.JvmField import org.eclipse.xtext.common.types.JvmIdentifiableElement +import org.eclipse.xtext.common.types.JvmPrimitiveType import org.eclipse.xtext.naming.QualifiedName import org.eclipse.xtext.util.CancelIndicator import org.eclipse.xtext.xbase.XAbstractFeatureCall @@ -33,9 +34,12 @@ import org.eclipse.xtext.xbase.XExpression import org.eclipse.xtext.xbase.XFeatureCall import org.eclipse.xtext.xbase.XMemberFeatureCall import org.eclipse.xtext.xbase.interpreter.IEvaluationContext +import org.eclipse.xtext.xbase.interpreter.impl.EvaluationException import org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter import org.eclipse.xtext.xbase.jvmmodel.IJvmModelAssociations import org.eclipse.xtext.xbase.typesystem.IBatchTypeResolver +import org.eclipse.xtext.xbase.typesystem.references.StandardTypeReferenceOwner +import org.eclipse.xtext.xbase.typesystem.util.CommonTypeComputationServices; /** * The script interpreter handles specific script components, which are not known @@ -60,6 +64,9 @@ class ScriptInterpreter extends XbaseInterpreter { @Inject extension IJvmModelAssociations + @Inject + CommonTypeComputationServices services + override protected _invokeFeature(JvmField jvmField, XAbstractFeatureCall featureCall, Object receiver, IEvaluationContext context, CancelIndicator indicator) { @@ -152,21 +159,43 @@ class ScriptInterpreter extends XbaseInterpreter { return QuantityType.valueOf(literal.value + " " + literal.unit.value); } - override Object _doEvaluate(XCastedExpression castedExpression, IEvaluationContext context, + /** + * This is the same implementation as in XbaseInterpreter.java with the + * {@code "Could not cast (" + result.class + ")" + result + " to " + typeName } + * line being the only difference. There is no other way to create an error message, + * stating what cannot be casted to what. See + * github.com/eclipse-xtext/xtext/issues/3595. + */ + override protected _doEvaluate(XCastedExpression castedExpression, IEvaluationContext context, CancelIndicator indicator) { - try { - return super._doEvaluate(castedExpression, context, indicator) - } catch (RuntimeException e) { - if (e.cause instanceof ClassCastException) { - val Object result = internalEvaluate(castedExpression.getTarget(), context, indicator); - throw new ScriptExecutionException(new ScriptError( - "Could not cast " + result + " to " + castedExpression.getType().getType().getQualifiedName(), - castedExpression)); - } else { - throw e; - } + var result = internalEvaluate(castedExpression.target, context, indicator) + val owner = new StandardTypeReferenceOwner(services, castedExpression) + val targetType = owner.toLightweightTypeReference(castedExpression.type) + result = wrapOrUnwrapArray(result, targetType) + result = coerceArgumentType(result, castedExpression.type) + val castType = castedExpression.type.type + if (castType instanceof JvmPrimitiveType) { + if (result === null) { + throwNullPointerException(castedExpression, "Cannot cast null to primitive " + castType.identifier) } + return castToPrimitiveType(result, services.primitives.primitiveKind(castType)) } + val typeName = castType.qualifiedName + var Class expectedType = null + try { + expectedType = getJavaType(castType) + } catch (ClassNotFoundException e) { + throw new EvaluationException(new NoClassDefFoundError(typeName)) + } + try { + expectedType.cast(result) + } catch (ClassCastException e) { + throw new EvaluationException(new ClassCastException( + "Could not cast (" + result.class + ")" + result + " to " + typeName + )) + } + return result + } }