From b4706a31521832169cd2fc259fdae33801820dfa Mon Sep 17 00:00:00 2001 From: Anders Fugmann Date: Tue, 7 Jul 2026 15:24:00 +0200 Subject: [PATCH] kotlin extractor: fold unaryMinus(IrConst) into a signed literal in K2 In K2 mode the frontend emits `-123L` as IrCall(unaryMinus, IrConst(123L)) rather than IrConst(-123L) as in K1. Queries that search for negative numeric literals therefore need to match both a UnaryMinusExpr wrapping a literal and a plain literal, depending on language mode. Fix: when extractCallExpression encounters an isNumericFunction(unaryMinus) call whose dispatchReceiver is already an IrConst, fold the negation into the constant before extracting. The resulting literal node is identical to what K1 emits. Location: extend the span one character to the left to cover the `-` sign. In K2 the IrCall's startOffset equals the receiver's startOffset, so we recover the minus by subtracting one from the receiver offset. K1 is unaffected: the K1 frontend folds the sign into the constant before IR generation, so this new branch never triggers when compiling with -language 1.9. Expected output changes: - test-kotlin2/library-tests/literals/literals.expected: negative long, float and double literals now appear as plain typed literals instead of as UnaryMinus nodes. The file is now byte-identical to test-kotlin1/library-tests/literals/literals.expected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/main/kotlin/KotlinFileExtractor.kt | 49 +++++++++++++++++++ .../library-tests/literals/literals.expected | 6 +-- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt index 0b975d9b829..4ff255a0fd1 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt @@ -4391,6 +4391,55 @@ open class KotlinFileExtractor( tw.writeExprsKotlinType(id, type.kotlinResult.id) unaryopDisp(id) } + // Fold unaryMinus applied to a numeric constant into a single negative literal. + // In K2 mode, `-123L` is emitted as IrCall(unaryMinus, IrConst(123L)) rather than + // IrConst(-123L) as in K1. Folding restores K1 behaviour and makes negative + // numeric literals directly queryable in both modes. + isNumericFunction(target, "unaryMinus") && c.dispatchReceiver is CodeQLIrConst<*> -> { + val receiver = c.dispatchReceiver as CodeQLIrConst<*> + val v = receiver.value + val type = useType(c.type) + // In K2 the IrCall's startOffset equals the receiver's startOffset, so the + // minus sign (one character before the literal) must be recovered from the source. + val locId = + if (receiver.startOffset > 0) + tw.getLocation(receiver.startOffset - 1, c.endOffset) + else + tw.getLocation(c) + when (v) { + is Int -> + extractConstantInteger(-v, locId, parent, idx, callable, enclosingStmt) + is Short -> + extractConstantInteger(-v.toInt(), locId, parent, idx, callable, enclosingStmt) + is Byte -> + extractConstantInteger(-v.toInt(), locId, parent, idx, callable, enclosingStmt) + is Long -> + exprIdOrFresh(null).also { id -> + tw.writeExprs_longliteral(id, type.javaResult.id, parent, idx) + tw.writeExprsKotlinType(id, type.kotlinResult.id) + extractExprContext(id, locId, callable, enclosingStmt) + tw.writeNamestrings((-v).toString(), (-v).toString(), id) + } + is Float -> + exprIdOrFresh(null).also { id -> + tw.writeExprs_floatingpointliteral(id, type.javaResult.id, parent, idx) + tw.writeExprsKotlinType(id, type.kotlinResult.id) + extractExprContext(id, locId, callable, enclosingStmt) + tw.writeNamestrings((-v).toString(), (-v).toString(), id) + } + is Double -> + exprIdOrFresh(null).also { id -> + tw.writeExprs_doubleliteral(id, type.javaResult.id, parent, idx) + tw.writeExprsKotlinType(id, type.kotlinResult.id) + extractExprContext(id, locId, callable, enclosingStmt) + tw.writeNamestrings((-v).toString(), (-v).toString(), id) + } + else -> + logger.errorElement( + "Unexpected constant type for unaryMinus folding: ${v?.javaClass}", c + ) + } + } isNumericFunction(target, "inv", "unaryMinus", "unaryPlus") -> { val type = useType(c.type) val id: Label = diff --git a/java/ql/test-kotlin2/library-tests/literals/literals.expected b/java/ql/test-kotlin2/library-tests/literals/literals.expected index bb6f6fd4739..3cea3c83ac5 100644 --- a/java/ql/test-kotlin2/library-tests/literals/literals.expected +++ b/java/ql/test-kotlin2/library-tests/literals/literals.expected @@ -11,16 +11,16 @@ | literals.kt:13:30:13:33 | -123 | LongLiteral | | literals.kt:14:30:14:31 | 0 | LongLiteral | | literals.kt:15:30:15:33 | 123 | LongLiteral | -| literals.kt:16:31:16:34 | 123 | LongLiteral | +| literals.kt:16:30:16:34 | -123 | LongLiteral | | literals.kt:17:30:17:33 | 15 | LongLiteral | | literals.kt:18:30:18:39 | 11 | LongLiteral | | literals.kt:19:30:19:38 | 1234567 | LongLiteral | | literals.kt:20:32:20:35 | 0.0 | FloatLiteral | | literals.kt:21:32:21:37 | 123.4 | FloatLiteral | -| literals.kt:22:33:22:38 | 123.4 | FloatLiteral | +| literals.kt:22:32:22:38 | -123.4 | FloatLiteral | | literals.kt:23:34:23:36 | 0.0 | DoubleLiteral | | literals.kt:24:34:24:38 | 123.4 | DoubleLiteral | -| literals.kt:25:35:25:39 | 123.4 | DoubleLiteral | +| literals.kt:25:34:25:39 | -123.4 | DoubleLiteral | | literals.kt:26:30:26:32 | c | CharacterLiteral | | literals.kt:27:30:27:33 | \n | CharacterLiteral | | literals.kt:28:34:28:35 | "" | StringLiteral |