Skip to content

Commit 5dc4830

Browse files
Kotlin: exclude leading parameter modifiers from val/var parameter location
A primary-constructor property parameter written with leading modifiers, e.g. `public vararg val s: String`, is located differently by the two frontend paths: - K1 (-language-version 1.9) starts the parameter location at the first modifier token (`public`), giving test.kt:50:5:50:31. - K2 (default) starts it at the `val`/`var` keyword, giving test.kt:50:19:50:31. The K2 span is the more intuitive and consistent one: every other value parameter is already located from its `val`/`var` keyword (or its name), so including the leading modifier list here is an outlier that also makes the property parameter's span inconsistent with the property it backs. Converge K1 onto the K2 span. `getPsiBasedValueParameterLocation` finds the enclosing `KtParameter` via PSI back-mapping (available under K1, where `getKtFile` is non-null; it returns null under K2, which already emits the desired offsets) and, only when modifiers precede the keyword, re-anchors the location start at the `val`/`var` keyword while preserving the parameter's own end offset. The guard `keyword.startOffset <= vp.startOffset` is essential: when the `val`/`var` keyword already is the parameter start (no leading modifiers), the helper must not fire, otherwise it would rewrite the end offset to `vp.endOffset` and diverge from the raw location for ordinary property parameters (observed as orphaned `[Parameter]` rows in generics/ reflection/classes PrintAst during development). After this change test-kotlin1 and test-kotlin2 vararg/args.expected are byte-identical. All 3333 tests pass in both suites (K1 2.3.20 / lang 1.9 and K2 2.4.0 / lang 2.0). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 32d00a0 commit 5dc4830

2 files changed

Lines changed: 34 additions & 1 deletion

File tree

java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import org.jetbrains.kotlin.psi.KtClass
5858
import org.jetbrains.kotlin.psi.KtClassOrObject
5959
import org.jetbrains.kotlin.psi.KtConstructor
6060
import org.jetbrains.kotlin.psi.KtDestructuringDeclaration
61+
import org.jetbrains.kotlin.psi.KtParameter
6162
import org.jetbrains.kotlin.psi.KtProperty
6263
import org.jetbrains.kotlin.psi.KtPropertyAccessor
6364
import org.jetbrains.kotlin.psi.KtVariableDeclaration
@@ -1382,6 +1383,7 @@ open class KotlinFileExtractor(
13821383
val location =
13831384
locOverride
13841385
?: getGeneratedDataClassCopyParamLocation(vp, idx)
1386+
?: getPsiBasedValueParameterLocation(vp)
13851387
?: getLocation(vp, classTypeArgsIncludingOuterClasses)
13861388
val maybeAlteredType =
13871389
(vp.parent as? IrFunction)?.let {
@@ -3132,6 +3134,37 @@ open class KotlinFileExtractor(
31323134
return tw.getLocation(destructuring.startOffset, destructuring.endOffset)
31333135
}
31343136

3137+
/**
3138+
* Location for a primary-constructor `val`/`var` value parameter that excludes any
3139+
* leading modifiers, spanning from the `val`/`var` keyword to the parameter's end.
3140+
*
3141+
* For a property parameter declared with modifiers (for example
3142+
* `public vararg val s: String`), the K1 frontend's [IrValueParameter] offsets start at
3143+
* the first modifier (`public`), whereas K2 starts at the `val`/`var` keyword, consistent
3144+
* with how declarations exclude leading modifiers from their own span (annotations and
3145+
* modifiers carry their own locations). We converge K1 onto the K2 form.
3146+
*
3147+
* Returns null under K2 (where [getKtFile] is unavailable and the raw offsets already
3148+
* exclude the modifiers), and for any parameter without a `val`/`var` keyword (an ordinary
3149+
* value parameter), where the start already coincides with the parameter and there is no
3150+
* modifier span to strip.
3151+
*/
3152+
private fun getPsiBasedValueParameterLocation(vp: IrValueParameter): Label<DbLocation>? {
3153+
if (vp.startOffset < 0 || vp.endOffset < 0) return null
3154+
val file = currentIrFile ?: return null
3155+
val ktFile = getPsi2Ir()?.getKtFile(file) ?: return null
3156+
val ktParam = ktFile.findElementAt(vp.startOffset)
3157+
?.let { leaf -> generateSequence(leaf) { it.parent }.filterIsInstance<KtParameter>().firstOrNull() }
3158+
?: return null
3159+
val keyword = ktParam.valOrVarKeyword ?: return null
3160+
// Only converge when leading modifiers (e.g. `vararg`, visibility) precede the
3161+
// `val`/`var` keyword: there the raw K1 offset starts at the first modifier while
3162+
// K2 starts at the keyword. When the keyword is already the parameter start there
3163+
// is nothing to exclude, and rewriting the end offset would diverge from K2.
3164+
if (keyword.startOffset <= vp.startOffset) return null
3165+
return tw.getLocation(keyword.startOffset, vp.endOffset)
3166+
}
3167+
31353168
/**
31363169
* Returns the PSI-based location for a synthesised (`DEFAULT_PROPERTY_ACCESSOR`)
31373170
* getter or setter, matching the span the K2 frontend emits natively.

java/ql/test-kotlin1/library-tests/vararg/args.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ varargsParams
55
| test.kt:20:24:20:37 | xs | file://:0:0:0:0 | int[] |
66
| test.kt:24:50:24:63 | xs | file://:0:0:0:0 | int[] |
77
| test.kt:28:37:28:50 | xs | file://:0:0:0:0 | int[] |
8-
| test.kt:50:5:50:31 | s | file://:0:0:0:0 | String[] |
8+
| test.kt:50:19:50:31 | s | file://:0:0:0:0 | String[] |
99
explicitVarargsArguments
1010
| test.kt:12:50:12:51 | xs | test.kt:12:44:12:52 | this(...) |
1111
| test.kt:38:25:38:29 | array | test.kt:38:5:38:30 | funWithOnlyVarArgs(...) |

0 commit comments

Comments
 (0)