diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt index 864acf81931..679d04fc4dd 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt @@ -55,6 +55,7 @@ import org.jetbrains.kotlin.types.Variance import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtConstructor import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.psi.KtPropertyAccessor import org.jetbrains.kotlin.psi.KtVariableDeclaration @@ -2937,9 +2938,37 @@ open class KotlinFileExtractor( tw.writeHasLocation(it, locId) } + /** + * Returns a PSI-based location for a constructor body block that starts at the + * `constructor` keyword rather than at any leading modifiers. + * + * For a constructor declared with a visibility modifier (for example + * `internal constructor(...) { }`), the K1 frontend's [IrBlockBody] offsets start + * at the modifier (`3:3`); the K2 frontend starts at the `constructor` keyword + * (`3:12`), consistent with how declarations exclude leading modifiers from their + * own span. We converge K1 onto the K2 form. + * + * The end offset is left as the block body's own [IrBlockBody.endOffset]. For a + * constructor without modifiers the keyword already coincides with the block start, + * so this is a no-op there. Returns null under K2 (where [getKtFile] is unavailable + * and the raw offsets already exclude the modifier), for non-constructor bodies, and + * when the constructor has no `constructor` keyword (an implicit primary + * constructor). + */ + private fun getPsiBasedConstructorBodyLocation(b: IrBlockBody): Label? { + if (b.startOffset < 0 || b.endOffset < 0) return null + val file = currentIrFile ?: return null + val ktCtor = getPsi2Ir()?.getKtFile(file) + ?.findElementAt(b.startOffset) + ?.let { leaf -> generateSequence(leaf) { it.parent }.filterIsInstance>().firstOrNull() } + ?: return null + val keyword = ktCtor.getConstructorKeyword() ?: return null + return tw.getLocation(keyword.startOffset, b.endOffset) + } + private fun extractBlockBody(b: IrBlockBody, callable: Label) { with("block body", b) { - extractBlockBody(callable, tw.getLocation(b)).also { + extractBlockBody(callable, getPsiBasedConstructorBodyLocation(b) ?: tw.getLocation(b)).also { for ((sIdx, stmt) in b.statements.withIndex()) { extractStatement(stmt, callable, it, sIdx) } diff --git a/java/ql/test-kotlin1/library-tests/annotation_classes/PrintAst.expected b/java/ql/test-kotlin1/library-tests/annotation_classes/PrintAst.expected index 1f4b1f2011e..286c0cb18ce 100644 --- a/java/ql/test-kotlin1/library-tests/annotation_classes/PrintAst.expected +++ b/java/ql/test-kotlin1/library-tests/annotation_classes/PrintAst.expected @@ -223,7 +223,7 @@ def.kt: #-----| 1: (Annotations) # 41| 1: [Annotation] Annot0k # 0| 1: [IntegerLiteral] 0 -# 41| 5: [BlockStmt] { ... } +# 42| 5: [BlockStmt] { ... } # 42| 0: [SuperConstructorInvocationStmt] super(...) # 42| 1: [BlockStmt] { ... } use.java: diff --git a/java/ql/test-kotlin1/library-tests/internal-constructor-called-from-java/test.expected b/java/ql/test-kotlin1/library-tests/internal-constructor-called-from-java/test.expected index 44dbca7993e..c3eb1d9284f 100644 --- a/java/ql/test-kotlin1/library-tests/internal-constructor-called-from-java/test.expected +++ b/java/ql/test-kotlin1/library-tests/internal-constructor-called-from-java/test.expected @@ -1 +1 @@ -| User.java:3:31:3:44 | new Test(...) | test.kt:3:3:3:51 | { ... } | +| User.java:3:31:3:44 | new Test(...) | test.kt:3:12:3:51 | { ... } |