mirror of
https://github.com/github/codeql.git
synced 2026-07-15 00:08:14 +02:00
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>
This commit is contained in:
@@ -58,6 +58,7 @@ import org.jetbrains.kotlin.psi.KtClass
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtConstructor
|
||||
import org.jetbrains.kotlin.psi.KtDestructuringDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import org.jetbrains.kotlin.psi.KtPropertyAccessor
|
||||
import org.jetbrains.kotlin.psi.KtVariableDeclaration
|
||||
@@ -1382,6 +1383,7 @@ open class KotlinFileExtractor(
|
||||
val location =
|
||||
locOverride
|
||||
?: getGeneratedDataClassCopyParamLocation(vp, idx)
|
||||
?: getPsiBasedValueParameterLocation(vp)
|
||||
?: getLocation(vp, classTypeArgsIncludingOuterClasses)
|
||||
val maybeAlteredType =
|
||||
(vp.parent as? IrFunction)?.let {
|
||||
@@ -3132,6 +3134,37 @@ open class KotlinFileExtractor(
|
||||
return tw.getLocation(destructuring.startOffset, destructuring.endOffset)
|
||||
}
|
||||
|
||||
/**
|
||||
* Location for a primary-constructor `val`/`var` value parameter that excludes any
|
||||
* leading modifiers, spanning from the `val`/`var` keyword to the parameter's end.
|
||||
*
|
||||
* For a property parameter declared with modifiers (for example
|
||||
* `public vararg val s: String`), the K1 frontend's [IrValueParameter] offsets start at
|
||||
* the first modifier (`public`), whereas K2 starts at the `val`/`var` keyword, consistent
|
||||
* with how declarations exclude leading modifiers from their own span (annotations and
|
||||
* modifiers carry their own locations). We converge K1 onto the K2 form.
|
||||
*
|
||||
* Returns null under K2 (where [getKtFile] is unavailable and the raw offsets already
|
||||
* exclude the modifiers), and for any parameter without a `val`/`var` keyword (an ordinary
|
||||
* value parameter), where the start already coincides with the parameter and there is no
|
||||
* modifier span to strip.
|
||||
*/
|
||||
private fun getPsiBasedValueParameterLocation(vp: IrValueParameter): Label<DbLocation>? {
|
||||
if (vp.startOffset < 0 || vp.endOffset < 0) return null
|
||||
val file = currentIrFile ?: return null
|
||||
val ktFile = getPsi2Ir()?.getKtFile(file) ?: return null
|
||||
val ktParam = ktFile.findElementAt(vp.startOffset)
|
||||
?.let { leaf -> generateSequence(leaf) { it.parent }.filterIsInstance<KtParameter>().firstOrNull() }
|
||||
?: return null
|
||||
val keyword = ktParam.valOrVarKeyword ?: return null
|
||||
// Only converge when leading modifiers (e.g. `vararg`, visibility) precede the
|
||||
// `val`/`var` keyword: there the raw K1 offset starts at the first modifier while
|
||||
// K2 starts at the keyword. When the keyword is already the parameter start there
|
||||
// is nothing to exclude, and rewriting the end offset would diverge from K2.
|
||||
if (keyword.startOffset <= vp.startOffset) return null
|
||||
return tw.getLocation(keyword.startOffset, vp.endOffset)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the PSI-based location for a synthesised (`DEFAULT_PROPERTY_ACCESSOR`)
|
||||
* getter or setter, matching the span the K2 frontend emits natively.
|
||||
|
||||
@@ -5,7 +5,7 @@ varargsParams
|
||||
| test.kt:20:24:20:37 | xs | file://:0:0:0:0 | int[] |
|
||||
| test.kt:24:50:24:63 | xs | file://:0:0:0:0 | int[] |
|
||||
| test.kt:28:37:28:50 | xs | file://:0:0:0:0 | int[] |
|
||||
| test.kt:50:5:50:31 | s | file://:0:0:0:0 | String[] |
|
||||
| test.kt:50:19:50:31 | s | file://:0:0:0:0 | String[] |
|
||||
explicitVarargsArguments
|
||||
| test.kt:12:50:12:51 | xs | test.kt:12:44:12:52 | this(...) |
|
||||
| test.kt:38:25:38:29 | array | test.kt:38:5:38:30 | funWithOnlyVarArgs(...) |
|
||||
|
||||
Reference in New Issue
Block a user