From fa5b17aac826401c0850c55cdf7d4ae0f901b749 Mon Sep 17 00:00:00 2001 From: Anders Fugmann Date: Sun, 12 Jul 2026 18:38:34 +0200 Subject: [PATCH] [C15] Kotlin: emit `_` for source underscore locals under K2 A source underscore variable (an unused `_` in a `catch (_: E)` clause or a `val _ = ...` discard) is named differently by the two frontends: - K1 keeps the source spelling `_`. - K2 assigns the synthetic `SpecialNames.UNDERSCORE_FOR_UNUSED_VAR`, which renders as ``. Decision (D15): adopt the K1 behaviour. `_` is the actual source token, so it is the more intuitive and source-faithful name; it also keeps the local variable name consistent with the corresponding value-parameter case, where a prior fix already normalises the underscore setter parameter to `_`. `extractVariableExpr` now maps a variable whose IR name is `SpecialNames.UNDERSCORE_FOR_UNUSED_VAR` to `_` before writing `localvars`. The check is on the special name rather than a raw string so it is robust across compiler versions, and it only fires for the frontend-synthesised unused-variable name, leaving all other locals untouched. Full dual-suite relearn: all 3333 tests pass. The only changed expected row is in query-tests/UnderscoreIdentifier, where the K2 catch parameter row converges from `Exception ` to `Exception _`, matching K1. The remaining divergence in that file (the destructuring container `` vs `tmp0_container`) is a separate naming/location issue tracked under C14. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/main/kotlin/KotlinFileExtractor.kt | 9 ++++++++- .../query-tests/UnderscoreIdentifier/test.expected | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt index 679d04fc4dd..19fda5d0369 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt @@ -51,6 +51,7 @@ import org.jetbrains.kotlin.load.java.structure.JavaTypeParameter import org.jetbrains.kotlin.load.java.structure.JavaTypeParameterListOwner import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.SpecialNames import org.jetbrains.kotlin.types.Variance import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.psi.KtClass @@ -3489,7 +3490,13 @@ open class KotlinFileExtractor( val exprId = tw.getFreshIdLabel() val locId = getPsiBasedLocation(v) ?: tw.getLocation(getVariableLocationProvider(v)) val type = useType(v.type) - tw.writeLocalvars(varId, v.name.asString(), type.javaResult.id, exprId) + // K2 names a source `_` (unused) local/catch variable with the synthetic + // SpecialNames.UNDERSCORE_FOR_UNUSED_VAR (``), whereas K1 keeps the + // source spelling `_`. Emit `_` so both frontends produce the same, source-faithful + // name (D15). + val varName = + if (v.name == SpecialNames.UNDERSCORE_FOR_UNUSED_VAR) "_" else v.name.asString() + tw.writeLocalvars(varId, varName, type.javaResult.id, exprId) tw.writeLocalvarsKotlinType(varId, type.kotlinResult.id) tw.writeHasLocation(varId, locId) tw.writeExprs_localvariabledeclexpr(exprId, type.javaResult.id, parent, idx) diff --git a/java/ql/test-kotlin2/query-tests/UnderscoreIdentifier/test.expected b/java/ql/test-kotlin2/query-tests/UnderscoreIdentifier/test.expected index b5c3502a094..69b63cb3759 100644 --- a/java/ql/test-kotlin2/query-tests/UnderscoreIdentifier/test.expected +++ b/java/ql/test-kotlin2/query-tests/UnderscoreIdentifier/test.expected @@ -4,4 +4,4 @@ | Test.kt:6:9:6:26 | Pair p | | Test.kt:7:9:7:26 | Pair | | Test.kt:7:14:7:18 | int first | -| Test.kt:8:14:8:25 | Exception | +| Test.kt:8:14:8:25 | Exception _ |