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>
This commit is contained in:
Anders Fugmann
2026-07-07 15:24:00 +02:00
parent 6465078bfe
commit b4706a3152
2 changed files with 52 additions and 3 deletions

View File

@@ -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<DbLongliteral>(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<DbFloatingpointliteral>(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<DbDoubleliteral>(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<out DbExpr> =

View File

@@ -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 |