{"record":{"id":"938ad7f5685e7dd0","repo":"apache/skywalking","slug":"operator-opsymbol-requires-numeric-operands-g","errorCode":null,"errorMessage":"Operator '{opSymbol}' requires numeric operands; got non-numeric expression. Cast operands with 'as Integer/Long/Float/Double' to enable arithmetic.","messagePattern":"Operator '(.+?)' requires numeric operands; got non-numeric expression\\. Cast operands with 'as Integer/Long/Float/Double' to enable arithmetic\\.","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALValueCodegen.java","lineNumber":1561,"sourceCode":"        String accExpr = accBuf.toString();\n\n        for (int i = 1; i < parts.size(); i++) {\n            final LALScriptModel.BinaryOp op = ops.get(i - 1);\n            final ExprType rhsType = inferType(parts.get(i), genCtx);\n            final StringBuilder rhsBuf = new StringBuilder();\n            appendOperandRaw(rhsBuf, parts.get(i), rhsType, genCtx);\n            final String rhsExpr = rhsBuf.toString();\n\n            if (accType.isNumeric() && rhsType.isNumeric()) {\n                final ExprType promoted = promote(accType, rhsType);\n                final String widenedAcc = widenPrimitiveExpr(accExpr, accType, promoted);\n                final String widenedRhs = widenPrimitiveExpr(rhsExpr, rhsType, promoted);\n                accExpr = \"(\" + widenedAcc + \" \" + op.symbol() + \" \" + widenedRhs + \")\";\n                accType = promoted;\n                continue;\n            }\n            if (op != LALScriptModel.BinaryOp.PLUS) {\n                throw new IllegalArgumentException(\n                    \"Operator '\" + op.symbol() + \"' requires numeric operands; \"\n                        + \"got non-numeric expression. Cast operands with \"\n                        + \"'as Integer/Long/Float/Double' to enable arithmetic.\");\n            }\n            // String concat path. Java's `+` only accepts the chain when\n            // at least one operand is statically a String; otherwise (e.g.\n            // `int + Object` or `Object + Object`) the source won't even\n            // compile. Prepend a leading `\"\"` whenever neither side is\n            // statically String at the transition point. The numeric\n            // arithmetic prefix is already in parens, so `(\"\" + (1 + 2))`\n            // still computes the sum first (\"3\"), preserving the\n            // semantics of `1 + 2 + parsed.x` (= \"3<obj>\").\n            if (accType != ExprType.STRING && rhsType != ExprType.STRING) {\n                accExpr = \"\\\"\\\" + \" + accExpr;\n            }\n            accExpr = accExpr + \" + \" + rhsExpr;\n            accType = ExprType.STRING;\n        }","sourceCodeStart":1543,"sourceCodeEnd":1579,"githubUrl":"https://github.com/apache/skywalking/blob/102af09b4a56064e22050dded10e2c52e490d040/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALValueCodegen.java#L1543-L1579","documentation":"Thrown at LAL compile time when a binary arithmetic operator other than + is applied to an operand the type inference does not consider numeric. Only + has a string-concatenation fallback; -, *, /, % require both sides to be statically numeric. Since parsed values from json{}/yaml{} are untyped Objects, the codegen cannot prove numericness and rejects the expression, suggesting an explicit 'as' cast.","triggerScenarios":"parsed.latency - 100 where parsed.* is map-typed (Object); parsed.count * parsed.factor without casts; arithmetic on safe-navigated chains (parsed?.x / 2) whose static type is Object.","commonSituations":"Computing latency/duration deltas from parsed JSON fields; porting Groovy v1 rules that did dynamic numeric coercion at runtime; metric expressions in the extractor that mix strings and numbers.","solutions":["Cast every operand with an explicit 'as' cast, e.g. (parsed.latency as Long) - (parsed.base as Long)","Cast at least the accumulator-side of the chain so inference promotes correctly: parsed.latency as Long - 100","For string concatenation use + (it falls back to concat), not other operators"],"exampleFix":"# before\ntimestamp parsed.ts - parsed.offset\n\n# after\ntimestamp (parsed.ts as Long) - (parsed.offset as Long)","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Add 'as Long/Integer/Float/Double' casts to every parsed operand in arithmetic","Reserve + for concatenation when any side may be a string","Test metric arithmetic rules against sample log data in dsl-scripts-test"],"tags":["lal","log-analyzer","compiler","typing","arithmetic"],"backgroundTag":null,"analyzedSha":"102af09b4a56064e22050dded10e2c52e490d040","analyzedAt":"2026-08-14T10:47:52.647Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}