From 73b65f87c2de18c5cc80501fb57ce52e4737ce54 Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Fri, 3 Jul 2026 14:52:12 +0200 Subject: [PATCH 01/18] Update to Kotlin 2.2.20 For scip-kotlinc: * CheckerContext.containingFile was renamed to containingFileSymbol. * FirCallableSymbol<*>.directOverriddenSymbolsSafe now takes context by context parameter. * FirCallableSymbol.callableId was made nullable and is replaced by FirCallableSymbol.callableIdForRendering for rendering purpose. * Anonymous objects are now considered as locals. Ported from https://github.com/mozsearch/semanticdb-kotlinc/commit/3f4756486010cce6163b8b409441cd1e51278008 --- gradle/libs.versions.toml | 2 +- .../gradle/kotlin-jvm-toolchains/build.gradle | 2 +- .../fixtures/gradle/kotlin2/build.gradle | 2 +- .../scip_java/kotlinc/AnalyzerCheckers.kt | 78 ++++++------------- .../kotlinc/ScipTextDocumentBuilder.kt | 13 ++-- .../scip_java/kotlinc/ScipVisitor.kt | 66 +++++++--------- .../scip_java/kotlinc/test/AnalyzerTest.kt | 20 ++--- .../common/src/main/kotlin/snapshots/Class.kt | 18 ++--- 8 files changed, 83 insertions(+), 118 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 93d930c20..54c8d6f36 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ gradle-api = "8.11.1" junit-jupiter = "5.11.4" kctfork = "0.7.1" kotest = "6.2.1" -kotlin = "2.2.0" +kotlin = "2.2.20" kotlinx-serialization = "1.11.0" lombok = "1.18.46" maven-plugin-annotations = "3.15.2" diff --git a/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle b/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle index 21b68107f..712fe1a60 100644 --- a/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle +++ b/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.jetbrains.kotlin.jvm' version '2.2.0' + id 'org.jetbrains.kotlin.jvm' version '2.2.20' } java { toolchain { diff --git a/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle b/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle index d0a96fa86..43952f0bc 100644 --- a/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle +++ b/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'org.jetbrains.kotlin.jvm' version '2.2.0' + id 'org.jetbrains.kotlin.jvm' version '2.2.20' } kotlin { jvmToolchain(17) diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt index 37ece4b57..3258bc0c6 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt @@ -134,7 +134,7 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio if (names != null) { eachFqNameElement(fqName, source.treeStructure, names) { fqName, name -> - visitor?.visitPackage(fqName, name, context) + visitor?.visitPackage(fqName, name) } } } @@ -161,13 +161,13 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio ) if (klass != null) { - visitor?.visitClassReference(klass, name, context) + visitor?.visitClassReference(klass, name) } else if (callables.isNotEmpty()) { for (callable in callables) { - visitor?.visitCallableReference(callable, name, context) + visitor?.visitCallableReference(callable, name) } } else { - visitor?.visitPackage(fqName, name, context) + visitor?.visitPackage(fqName, name) } } } @@ -179,7 +179,7 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio context(context: CheckerContext, reporter: DiagnosticReporter) override fun check(declaration: FirClassLikeDeclaration) { val source = declaration.source ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] val objectKeyword = if (declaration is FirAnonymousObject) { @@ -192,7 +192,6 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio visitor?.visitClassOrObject( declaration, objectKeyword ?: getIdentifier(source), - context, enclosingSource = source, ) @@ -201,7 +200,7 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio val superSymbol = superType.toClassLikeSymbol(context.session) val superSource = superType.source if (superSymbol != null && superSource != null) { - visitor?.visitClassReference(superSymbol, superSource, context) + visitor?.visitClassReference(superSymbol, superSource) } } } @@ -212,7 +211,7 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio context(context: CheckerContext, reporter: DiagnosticReporter) override fun check(declaration: FirConstructor) { val source = declaration.source ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] if (declaration.isPrimary) { @@ -238,14 +237,12 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio visitor?.visitPrimaryConstructor( declaration, constructorKeyboard ?: objectKeyword ?: getIdentifier(klassSource), - context, enclosingSource = source, ) } else { visitor?.visitSecondaryConstructor( declaration, getIdentifier(source), - context, enclosingSource = source, ) } @@ -256,12 +253,11 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio context(context: CheckerContext, reporter: DiagnosticReporter) override fun check(declaration: FirSimpleFunction) { val source = declaration.source ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] visitor?.visitNamedFunction( declaration, getIdentifier(source), - context, enclosingSource = source, ) @@ -270,7 +266,7 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio if ( klass != null && klassSource != null && klassSource.kind !is KtFakeSourceElementKind ) { - visitor?.visitClassReference(klass, getIdentifier(klassSource), context) + visitor?.visitClassReference(klass, getIdentifier(klassSource)) } } } @@ -280,9 +276,9 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio context(context: CheckerContext, reporter: DiagnosticReporter) override fun check(declaration: FirAnonymousFunction) { val source = declaration.source ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] - visitor?.visitNamedFunction(declaration, source, context, enclosingSource = source) + visitor?.visitNamedFunction(declaration, source, enclosingSource = source) } } @@ -290,21 +286,16 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio context(context: CheckerContext, reporter: DiagnosticReporter) override fun check(declaration: FirProperty) { val source = declaration.source ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] - visitor?.visitProperty( - declaration, - getIdentifier(source), - context, - enclosingSource = source, - ) + visitor?.visitProperty(declaration, getIdentifier(source), enclosingSource = source) val klass = declaration.returnTypeRef.toClassLikeSymbol(context.session) val klassSource = declaration.returnTypeRef.source if ( klass != null && klassSource != null && klassSource.kind !is KtFakeSourceElementKind ) { - visitor?.visitClassReference(klass, getIdentifier(klassSource), context) + visitor?.visitClassReference(klass, getIdentifier(klassSource)) } } } @@ -313,21 +304,16 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio context(context: CheckerContext, reporter: DiagnosticReporter) override fun check(declaration: FirValueParameter) { val source = declaration.source ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] - visitor?.visitParameter( - declaration, - getIdentifier(source), - context, - enclosingSource = source, - ) + visitor?.visitParameter(declaration, getIdentifier(source), enclosingSource = source) val klass = declaration.returnTypeRef.toClassLikeSymbol(context.session) val klassSource = declaration.returnTypeRef.source if ( klass != null && klassSource != null && klassSource.kind !is KtFakeSourceElementKind ) { - visitor?.visitClassReference(klass, getIdentifier(klassSource), context) + visitor?.visitClassReference(klass, getIdentifier(klassSource)) } } } @@ -336,12 +322,11 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio context(context: CheckerContext, reporter: DiagnosticReporter) override fun check(declaration: FirTypeParameter) { val source = declaration.source ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] visitor?.visitTypeParameter( declaration, getIdentifier(source), - context, enclosingSource = source, ) } @@ -351,14 +336,9 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio context(context: CheckerContext, reporter: DiagnosticReporter) override fun check(declaration: FirTypeAlias) { val source = declaration.source ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] - visitor?.visitTypeAlias( - declaration, - getIdentifier(source), - context, - enclosingSource = source, - ) + visitor?.visitTypeAlias(declaration, getIdentifier(source), enclosingSource = source) } } @@ -367,7 +347,7 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio context(context: CheckerContext, reporter: DiagnosticReporter) override fun check(declaration: FirPropertyAccessor) { val source = declaration.source ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] val identifierSource = if (declaration.isGetter) { @@ -382,12 +362,7 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio getIdentifier(source) } - visitor?.visitPropertyAccessor( - declaration, - identifierSource, - context, - enclosingSource = source, - ) + visitor?.visitPropertyAccessor(declaration, identifierSource, enclosingSource = source) } } @@ -401,12 +376,11 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio return } - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] visitor?.visitSimpleNameExpression( calleeReference, getIdentifier(calleeReference.source ?: source), - context, ) val resolvedSymbol = calleeReference.resolvedSymbol @@ -420,7 +394,6 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio visitor?.visitClassReference( referencedKlass, getIdentifier(calleeReference.source ?: source), - context, ) } } @@ -432,14 +405,12 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio visitor?.visitCallableReference( it, getIdentifier(calleeReference.source ?: source), - context, ) } resolvedSymbol.setterSymbol?.let { visitor?.visitCallableReference( it, getIdentifier(calleeReference.source ?: source), - context, ) } } @@ -454,13 +425,12 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio val source = typeRef.source ?: return val classSymbol = expression.conversionTypeRef.toClassLikeSymbol(context.session) ?: return - val ktFile = context.containingFile?.sourceFile ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] visitor?.visitClassReference( classSymbol, getIdentifier(expression.conversionTypeRef.source ?: source), - context, ) } } diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt index f767dda2b..55f353fab 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt @@ -39,26 +39,26 @@ class ScipTextDocumentBuilder( fun build(): Document = documentBuilder.build("kotlin", relativePath(), fileText) + context(context: CheckerContext) fun emitScipData( firBasedSymbol: FirBasedSymbol<*>?, symbol: Symbol, element: KtSourceElement, isDefinition: Boolean, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { documentBuilder.addOccurrence(occurrence(symbol, element, isDefinition, enclosingSource)) if (isDefinition) { - documentBuilder.addSymbol(symbolInformation(firBasedSymbol, symbol, element, context)) + documentBuilder.addSymbol(symbolInformation(firBasedSymbol, symbol, element)) } } @OptIn(SymbolInternals::class) + context(context: CheckerContext) private fun symbolInformation( firBasedSymbol: FirBasedSymbol<*>?, symbol: Symbol, element: KtSourceElement, - context: CheckerContext, ): SymbolInformation { val supers = when (firBasedSymbol) { @@ -68,7 +68,7 @@ class ScipTextDocumentBuilder( .mapNotNull { it.toClassLikeSymbol(firBasedSymbol.moduleData.session) } .flatMap { cache[it] } is FirFunctionSymbol<*> -> - firBasedSymbol.directOverriddenSymbolsSafe(context).flatMap { cache[it] } + firBasedSymbol.directOverriddenSymbolsSafe().flatMap { cache[it] } else -> emptyList() } return symbolInformation { @@ -184,13 +184,14 @@ class ScipTextDocumentBuilder( } companion object { - @OptIn(SymbolInternals::class) + @OptIn(SymbolInternals::class, RenderingInternals::class) private fun displayName(firBasedSymbol: FirBasedSymbol<*>): String = when (firBasedSymbol) { is FirClassSymbol -> firBasedSymbol.classId.shortClassName.asString() is FirPropertyAccessorSymbol -> firBasedSymbol.fir.propertySymbol.name.asString() is FirFunctionSymbol -> firBasedSymbol.callableId.callableName.asString() - is FirPropertySymbol -> firBasedSymbol.callableId.callableName.asString() + is FirPropertySymbol -> + firBasedSymbol.callableIdForRendering.callableName.asString() is FirVariableSymbol -> firBasedSymbol.name.asString() else -> firBasedSymbol.toString() } diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipVisitor.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipVisitor.kt index 80b9866e5..d5d668864 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipVisitor.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipVisitor.kt @@ -33,10 +33,10 @@ class ScipVisitor( fun build(): Document = documentBuilder.build() + context(context: CheckerContext) private fun Sequence?.emitAll( element: KtSourceElement, isDefinition: Boolean, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ): List? = this?.onEach { (firBasedSymbol, symbol) -> @@ -45,7 +45,6 @@ class ScipVisitor( symbol, element, isDefinition, - context, enclosingSource, ) } @@ -55,132 +54,127 @@ class ScipVisitor( private fun Sequence.with(firBasedSymbol: FirBasedSymbol<*>?) = this.map { SymbolDescriptorPair(firBasedSymbol, it) } - fun visitPackage(pkg: FqName, element: KtSourceElement, context: CheckerContext) { - cache[pkg].with(null).emitAll(element, isDefinition = false, context) + context(context: CheckerContext) + fun visitPackage(pkg: FqName, element: KtSourceElement) { + cache[pkg].with(null).emitAll(element, isDefinition = false) } - fun visitClassReference( - firClassSymbol: FirClassLikeSymbol<*>, - element: KtSourceElement, - context: CheckerContext, - ) { - cache[firClassSymbol].with(firClassSymbol).emitAll(element, isDefinition = false, context) + context(context: CheckerContext) + fun visitClassReference(firClassSymbol: FirClassLikeSymbol<*>, element: KtSourceElement) { + cache[firClassSymbol].with(firClassSymbol).emitAll(element, isDefinition = false) } - fun visitCallableReference( - firClassSymbol: FirCallableSymbol<*>, - element: KtSourceElement, - context: CheckerContext, - ) { - cache[firClassSymbol].with(firClassSymbol).emitAll(element, isDefinition = false, context) + context(context: CheckerContext) + fun visitCallableReference(firClassSymbol: FirCallableSymbol<*>, element: KtSourceElement) { + cache[firClassSymbol].with(firClassSymbol).emitAll(element, isDefinition = false) } + context(context: CheckerContext) fun visitClassOrObject( firClass: FirClassLikeDeclaration, element: KtSourceElement, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { cache[firClass.symbol] .with(firClass.symbol) - .emitAll(element, isDefinition = true, context, enclosingSource) + .emitAll(element, isDefinition = true, enclosingSource) } + context(context: CheckerContext) fun visitPrimaryConstructor( firConstructor: FirConstructor, source: KtSourceElement, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { cache[firConstructor.symbol] .with(firConstructor.symbol) - .emitAll(source, isDefinition = true, context, enclosingSource) + .emitAll(source, isDefinition = true, enclosingSource) } + context(context: CheckerContext) fun visitSecondaryConstructor( firConstructor: FirConstructor, source: KtSourceElement, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { cache[firConstructor.symbol] .with(firConstructor.symbol) - .emitAll(source, isDefinition = true, context, enclosingSource) + .emitAll(source, isDefinition = true, enclosingSource) } + context(context: CheckerContext) fun visitNamedFunction( firFunction: FirFunction, source: KtSourceElement, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { cache[firFunction.symbol] .with(firFunction.symbol) - .emitAll(source, isDefinition = true, context, enclosingSource) + .emitAll(source, isDefinition = true, enclosingSource) } + context(context: CheckerContext) fun visitProperty( firProperty: FirProperty, source: KtSourceElement, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { cache[firProperty.symbol] .with(firProperty.symbol) - .emitAll(source, isDefinition = true, context, enclosingSource) + .emitAll(source, isDefinition = true, enclosingSource) } + context(context: CheckerContext) fun visitParameter( firParameter: FirValueParameter, source: KtSourceElement, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { cache[firParameter.symbol] .with(firParameter.symbol) - .emitAll(source, isDefinition = true, context, enclosingSource) + .emitAll(source, isDefinition = true, enclosingSource) } + context(context: CheckerContext) fun visitTypeParameter( firTypeParameter: FirTypeParameter, source: KtSourceElement, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { cache[firTypeParameter.symbol] .with(firTypeParameter.symbol) - .emitAll(source, isDefinition = true, context, enclosingSource) + .emitAll(source, isDefinition = true, enclosingSource) } + context(context: CheckerContext) fun visitTypeAlias( firTypeAlias: FirTypeAlias, source: KtSourceElement, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { cache[firTypeAlias.symbol] .with(firTypeAlias.symbol) - .emitAll(source, isDefinition = true, context, enclosingSource) + .emitAll(source, isDefinition = true, enclosingSource) } + context(context: CheckerContext) fun visitPropertyAccessor( firPropertyAccessor: FirPropertyAccessor, source: KtSourceElement, - context: CheckerContext, enclosingSource: KtSourceElement? = null, ) { cache[firPropertyAccessor.symbol] .with(firPropertyAccessor.symbol) - .emitAll(source, isDefinition = true, context, enclosingSource) + .emitAll(source, isDefinition = true, enclosingSource) } + context(context: CheckerContext) fun visitSimpleNameExpression( firResolvedNamedReference: FirResolvedNamedReference, source: KtSourceElement, - context: CheckerContext, ) { cache[firResolvedNamedReference.resolvedSymbol] .with(firResolvedNamedReference.resolvedSymbol) - .emitAll(source, isDefinition = false, context) + .emitAll(source, isDefinition = false) } } diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt index 36e94ad56..55b422c31 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt @@ -502,7 +502,7 @@ class AnalyzerTest { }, scipOccurrence { role = DEFINITION - symbol = "sample/``#" + symbol = "local 1" range { startLine = 7 startCharacter = 12 @@ -518,7 +518,7 @@ class AnalyzerTest { }, scipOccurrence { role = DEFINITION - symbol = "sample/``#``()." + symbol = "local 2" range { startLine = 7 startCharacter = 12 @@ -544,7 +544,7 @@ class AnalyzerTest { }, scipOccurrence { role = DEFINITION - symbol = "sample/``#foo()." + symbol = "local 3" range { startLine = 8 startCharacter = 21 @@ -560,7 +560,7 @@ class AnalyzerTest { }, scipOccurrence { role = DEFINITION - symbol = "sample/``#" + symbol = "local 5" range { startLine = 10 startCharacter = 12 @@ -576,7 +576,7 @@ class AnalyzerTest { }, scipOccurrence { role = DEFINITION - symbol = "sample/``#``()." + symbol = "local 6" range { startLine = 10 startCharacter = 12 @@ -602,7 +602,7 @@ class AnalyzerTest { }, scipOccurrence { role = DEFINITION - symbol = "sample/``#foo()." + symbol = "local 7" range { startLine = 11 startCharacter = 21 @@ -627,25 +627,25 @@ class AnalyzerTest { signatureText = "public abstract interface Interface : Any" }, scipSymbol { - symbol = "sample/``#" + symbol = "local 1" displayName = "" signatureText = "object : Interface" addOverriddenSymbols("sample/Interface#") }, scipSymbol { - symbol = "sample/``#foo()." + symbol = "local 3" displayName = "foo" signatureText = "public open override fun foo(): Unit" addOverriddenSymbols("sample/Interface#foo().") }, scipSymbol { - symbol = "sample/``#" + symbol = "local 5" displayName = "" signatureText = "object : Interface" addOverriddenSymbols("sample/Interface#") }, scipSymbol { - symbol = "sample/``#foo()." + symbol = "local 7" displayName = "foo" signatureText = "public open override fun foo(): Unit" addOverriddenSymbols("sample/Interface#foo().") diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt index 7ee742752..3ec43a153 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt @@ -75,29 +75,29 @@ // display_name asdf // signature_documentation // > public get(): Any -// ⌄ enclosing_range_start scip-java maven . . snapshots/``# -// ⌄ enclosing_range_start scip-java maven . . snapshots/``#``(). +// ⌄ enclosing_range_start local 0 +// ⌄ enclosing_range_start local 1 object { -// ^^^^^^ definition scip-java maven . . snapshots/``# +// ^^^^^^ definition local 0 // display_name // signature_documentation // > object : Any -// ^^^^^^ definition scip-java maven . . snapshots/``#``(). +// ^^^^^^ definition local 1 // display_name // signature_documentation // > private constructor(): -// ⌄ enclosing_range_start scip-java maven . . snapshots/``#doStuff(). +// ⌄ enclosing_range_start local 2 fun doStuff() = Unit -// ^^^^^^^ definition scip-java maven . . snapshots/``#doStuff(). +// ^^^^^^^ definition local 2 // display_name doStuff // signature_documentation // > public final fun doStuff(): Unit -// ⌃ enclosing_range_end scip-java maven . . snapshots/``#doStuff(). +// ⌃ enclosing_range_end local 2 } // ⌃ enclosing_range_end scip-java maven . . snapshots/Class#asdf. // ⌃ enclosing_range_end scip-java maven . . snapshots/Class#getAsdf(). -// ⌃ enclosing_range_end scip-java maven . . snapshots/``# -// ⌃ enclosing_range_end scip-java maven . . snapshots/``#``(). +// ⌃ enclosing_range_end local 0 +// ⌃ enclosing_range_end local 1 // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#``(+1). constructor() : this(1, "") From 46ae31c39ac272a062c0772dca4cdbf4f3009bde Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Fri, 3 Jul 2026 15:14:32 +0200 Subject: [PATCH 02/18] scip-kotlinc: Populate SymbolInformation.Kind This will allow us to distinguish between method and constructors, and get better kind information for locals. Ported from https://github.com/mozsearch/semanticdb-kotlinc/commit/6affaf7a525cea82a018cccf8d58bbedcef8a8e1 --- .../kotlinc/ScipTextDocumentBuilder.kt | 20 +++++++++++++++++++ .../scip_java/kotlinc/test/AnalyzerTest.kt | 19 ++++++++++++++++++ .../scip_java/kotlinc/test/ScipBuilders.kt | 2 ++ .../scip_java/kotlinc/test/ScipSymbolsTest.kt | 3 +++ .../common/src/main/kotlin/snapshots/Class.kt | 17 ++++++++++++++++ .../main/kotlin/snapshots/CompanionOwner.kt | 6 ++++++ .../src/main/kotlin/snapshots/Docstrings.kt | 5 +++++ .../src/main/kotlin/snapshots/Functions.kt | 2 ++ .../main/kotlin/snapshots/Implementations.kt | 17 ++++++++++++++++ .../src/main/kotlin/snapshots/Lambdas.kt | 9 +++++++++ .../src/main/kotlin/snapshots/ObjectKt.kt | 4 ++++ 11 files changed, 104 insertions(+) diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt index 55f353fab..dab8f500d 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt @@ -5,10 +5,13 @@ import java.nio.file.Paths import org.jetbrains.kotlin.KtSourceElement import org.jetbrains.kotlin.KtSourceFile import org.jetbrains.kotlin.fir.FirElement +import org.jetbrains.kotlin.fir.FirPackageDirective import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.directOverriddenSymbolsSafe import org.jetbrains.kotlin.fir.analysis.checkers.toClassLikeSymbol import org.jetbrains.kotlin.fir.analysis.getChild +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.declarations.utils.isInterface import org.jetbrains.kotlin.fir.renderer.* import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.SymbolInternals @@ -19,6 +22,7 @@ import org.jetbrains.kotlin.text import org.scip_code.scip.Document import org.scip_code.scip.Occurrence import org.scip_code.scip.SymbolInformation +import org.scip_code.scip.SymbolInformation.Kind import org.scip_code.scip.SymbolRole import org.scip_code.scip.relationship import org.scip_code.scip.signature @@ -84,6 +88,7 @@ class ScipTextDocumentBuilder( } docComment(firBasedSymbol.fir)?.let { documentation += it } } + this.kind = scipKind(firBasedSymbol?.fir) for (parent in supers) { relationships += relationship { this.symbol = parent.toString() @@ -159,6 +164,21 @@ class ScipTextDocumentBuilder( return stripKdoc(kdoc).ifEmpty { null } } + private fun scipKind(element: FirElement?): Kind = + when (element) { + is FirClass if element.isInterface -> Kind.Interface + is FirClassLikeDeclaration -> Kind.Class + is FirConstructor -> Kind.Constructor + is FirTypeParameter -> Kind.TypeParameter + is FirValueParameter -> Kind.Parameter + is FirField -> Kind.Field + is FirProperty -> Kind.Property + is FirVariable -> Kind.Variable + is FirCallableDeclaration -> Kind.Method + is FirPackageDirective -> Kind.Package + else -> Kind.UNRECOGNIZED + } + /** Strips the `/**`, leading `*`s, and `*/` from a kdoc block, returning just the body text. */ private fun stripKdoc(kdoc: String): String { if (kdoc.isEmpty()) return kdoc diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt index 55b422c31..338b41b2d 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt @@ -15,6 +15,7 @@ import org.intellij.lang.annotations.Language import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi import org.junit.jupiter.api.io.TempDir import org.scip_code.scip.Document +import org.scip_code.scip.SymbolInformation.Kind import org.scip_code.scip_java.kotlinc.* @OptIn(ExperimentalCompilerApi::class) @@ -107,11 +108,13 @@ class AnalyzerTest { arrayOf( scipSymbol { symbol = "sample/Banana#" + kind = Kind.Class displayName = "Banana" signatureText = "public final class Banana : Any" }, scipSymbol { symbol = "sample/Banana#foo()." + kind = Kind.Method displayName = "foo" signatureText = "public final fun foo(): Unit" }, @@ -279,21 +282,25 @@ class AnalyzerTest { arrayOf( scipSymbol { symbol = "sample/foo()." + kind = Kind.Method displayName = "foo" signatureText = "public final fun foo(): Unit" }, scipSymbol { symbol = "local 0" + kind = Kind.Class displayName = "LocalClass" signatureText = "local final class LocalClass : Any" }, scipSymbol { symbol = "local 1" + kind = Kind.Constructor displayName = "LocalClass" signatureText = "public constructor(): LocalClass" }, scipSymbol { symbol = "local 2" + kind = Kind.Method displayName = "localClassMethod" signatureText = "public final fun localClassMethod(): Unit" }, @@ -410,22 +417,26 @@ class AnalyzerTest { arrayOf( scipSymbol { symbol = "sample/Interface#" + kind = Kind.Interface displayName = "Interface" signatureText = "public abstract interface Interface : Any" }, scipSymbol { symbol = "sample/Interface#foo()." + kind = Kind.Method displayName = "foo" signatureText = "public abstract fun foo(): Unit\n" }, scipSymbol { symbol = "sample/Class#" + kind = Kind.Class displayName = "Class" signatureText = "public final class Class : Interface" addOverriddenSymbols("sample/Interface#") }, scipSymbol { symbol = "sample/Class#foo()." + kind = Kind.Method displayName = "foo" signatureText = "public open override fun foo(): Unit" addOverriddenSymbols("sample/Interface#foo().") @@ -623,29 +634,34 @@ class AnalyzerTest { arrayOf( scipSymbol { symbol = "sample/Interface#" + kind = Kind.Interface displayName = "Interface" signatureText = "public abstract interface Interface : Any" }, scipSymbol { symbol = "local 1" + kind = Kind.Class displayName = "" signatureText = "object : Interface" addOverriddenSymbols("sample/Interface#") }, scipSymbol { symbol = "local 3" + kind = Kind.Method displayName = "foo" signatureText = "public open override fun foo(): Unit" addOverriddenSymbols("sample/Interface#foo().") }, scipSymbol { symbol = "local 5" + kind = Kind.Class displayName = "" signatureText = "object : Interface" addOverriddenSymbols("sample/Interface#") }, scipSymbol { symbol = "local 7" + kind = Kind.Method displayName = "foo" signatureText = "public open override fun foo(): Unit" addOverriddenSymbols("sample/Interface#foo().") @@ -1300,6 +1316,7 @@ class AnalyzerTest { arrayOf( scipSymbol { symbol = "hello/sample/Apple#" + kind = Kind.Class displayName = "Apple" signatureText = "public final class Apple : Any" } @@ -1385,11 +1402,13 @@ class AnalyzerTest { arrayOf( scipSymbol { symbol = "sample/Banana#" + kind = Kind.Class displayName = "Banana" signatureText = "public final class Banana : Any" }, scipSymbol { symbol = "sample/Banana#foo()." + kind = Kind.Method displayName = "foo" signatureText = "public final fun foo(): Unit" }, diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipBuilders.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipBuilders.kt index 1e8ee95a7..a7d549dd7 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipBuilders.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipBuilders.kt @@ -76,6 +76,7 @@ class ScipOccurrenceBuilder { @ScipBuilderDsl class ScipSymbolInformationBuilder { var symbol: String = "" + var kind: SymbolInformation.Kind = SymbolInformation.Kind.UnspecifiedKind var displayName: String = "" var signatureText: String? = null private val docs = mutableListOf() @@ -95,6 +96,7 @@ class ScipSymbolInformationBuilder { internal fun build(): SymbolInformation = symbolInformation { symbol = this@ScipSymbolInformationBuilder.symbol + kind = this@ScipSymbolInformationBuilder.kind if (this@ScipSymbolInformationBuilder.displayName.isNotEmpty()) { displayName = this@ScipSymbolInformationBuilder.displayName } diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipSymbolsTest.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipSymbolsTest.kt index 048d33cee..e0865cd2f 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipSymbolsTest.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipSymbolsTest.kt @@ -3,6 +3,7 @@ package org.scip_code.scip_java.kotlinc.test import com.tschuchort.compiletesting.SourceFile import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi import org.junit.jupiter.api.TestFactory +import org.scip_code.scip.SymbolInformation.Kind import org.scip_code.scip_java.kotlinc.* import org.scip_code.scip_java.kotlinc.test.ExpectedSymbols.ScipData import org.scip_code.scip_java.kotlinc.test.ExpectedSymbols.SymbolCacheData @@ -775,12 +776,14 @@ class ScipSymbolsTest { listOf( scipSymbol { symbol = "x." + kind = Kind.Property displayName = "x" signatureText = "public final val x: String" documentation("hello world\n test content") }, scipSymbol { symbol = "getX()." + kind = Kind.Method displayName = "x" signatureText = "public get(): String" documentation("hello world\n test content") diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt index 3ec43a153..e24088f4b 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt @@ -11,37 +11,45 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#``().(apple) class Class constructor(private var banana: Int, apple: String) : // ^^^^^ definition scip-java maven . . snapshots/Class# +// kind Class // display_name Class // signature_documentation // > public final class Class : Throwable // relationship scip-java maven . . kotlin/Throwable# implementation // ^^^^^^^^^^^ definition scip-java maven . . snapshots/Class#``(). +// kind Constructor // display_name Class // signature_documentation // > public constructor(banana: Int, apple: String): Class // ^^^^^^ definition scip-java maven . . snapshots/Class#``().(banana) +// kind Parameter // display_name banana // signature_documentation // > banana: Int // ^^^^^^ definition scip-java maven . . snapshots/Class#banana. +// kind Property // display_name banana // signature_documentation // > private final var banana: Int // ^^^^^^ reference scip-java maven . . snapshots/Class#``().(banana) // ^^^^^^ definition scip-java maven . . snapshots/Class#getBanana(). +// kind Method // display_name banana // signature_documentation // > private get(): Int // ^^^^^^ definition scip-java maven . . snapshots/Class#setBanana(). +// kind Method // display_name banana // signature_documentation // > private set(value: Int): Unit // ^^^^^^ definition scip-java maven . . snapshots/Class#setBanana().(value) +// kind Parameter // display_name value // signature_documentation // > value: Int // ^^^ reference scip-java maven . . kotlin/Int# // ^^^^^ definition scip-java maven . . snapshots/Class#``().(apple) +// kind Parameter // display_name apple // signature_documentation // > apple: String @@ -68,10 +76,12 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#getAsdf(). val asdf = // ^^^^ definition scip-java maven . . snapshots/Class#asdf. +// kind Property // display_name asdf // signature_documentation // > public final val asdf: Any // ^^^^ definition scip-java maven . . snapshots/Class#getAsdf(). +// kind Method // display_name asdf // signature_documentation // > public get(): Any @@ -79,16 +89,19 @@ // ⌄ enclosing_range_start local 1 object { // ^^^^^^ definition local 0 +// kind Class // display_name // signature_documentation // > object : Any // ^^^^^^ definition local 1 +// kind Constructor // display_name // signature_documentation // > private constructor(): // ⌄ enclosing_range_start local 2 fun doStuff() = Unit // ^^^^^^^ definition local 2 +// kind Method // display_name doStuff // signature_documentation // > public final fun doStuff(): Unit @@ -102,6 +115,7 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#``(+1). constructor() : this(1, "") // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Class#``(+1). +// kind Constructor // display_name Class // signature_documentation // > public constructor(): Class @@ -111,10 +125,12 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#``(+2).(banana) constructor(banana: Int) : this(banana, "") // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Class#``(+2). +// kind Constructor // display_name Class // signature_documentation // > public constructor(banana: Int): Class // ^^^^^^ definition scip-java maven . . snapshots/Class#``(+2).(banana) +// kind Parameter // display_name banana // signature_documentation // > banana: Int @@ -126,6 +142,7 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#run(). fun run() { // ^^^ definition scip-java maven . . snapshots/Class#run(). +// kind Method // display_name run // signature_documentation // > public final fun run(): Unit diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/CompanionOwner.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/CompanionOwner.kt index d81175cdb..f16b420f1 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/CompanionOwner.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/CompanionOwner.kt @@ -5,10 +5,12 @@ //⌄ enclosing_range_start scip-java maven . . snapshots/CompanionOwner#``(). class CompanionOwner { // ^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/CompanionOwner# +// kind Class // display_name CompanionOwner // signature_documentation // > public final class CompanionOwner : Any // ^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/CompanionOwner#``(). +// kind Constructor // display_name CompanionOwner // signature_documentation // > public constructor(): CompanionOwner @@ -16,16 +18,19 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/CompanionOwner#Companion#``(). companion object { // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/CompanionOwner#Companion# +// kind Class // display_name Companion // signature_documentation // > public final companion object Companion : Any // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/CompanionOwner#Companion#``(). +// kind Constructor // display_name Companion // signature_documentation // > private constructor(): CompanionOwner.Companion // ⌄ enclosing_range_start scip-java maven . . snapshots/CompanionOwner#Companion#create(). fun create(): CompanionOwner = CompanionOwner() // ^^^^^^ definition scip-java maven . . snapshots/CompanionOwner#Companion#create(). +// kind Method // display_name create // signature_documentation // > public final fun create(): CompanionOwner @@ -38,6 +43,7 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/CompanionOwner#create(). fun create(): Int = CompanionOwner.create().hashCode() // ^^^^^^ definition scip-java maven . . snapshots/CompanionOwner#create(). +// kind Method // display_name create // signature_documentation // > public final fun create(): Int diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Docstrings.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Docstrings.kt index b7a60c8a5..69c6333f2 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Docstrings.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Docstrings.kt @@ -10,10 +10,12 @@ //⌄ enclosing_range_start scip-java maven . . snapshots/DocstringSuperclass#``(). abstract class DocstringSuperclass // ^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/DocstringSuperclass# +// kind Class // display_name DocstringSuperclass // signature_documentation // > public abstract class DocstringSuperclass : Any // ^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/DocstringSuperclass#``(). +// kind Constructor // display_name DocstringSuperclass // signature_documentation // > public constructor(): DocstringSuperclass @@ -24,6 +26,7 @@ /** Example class docstring. */ class Docstrings : DocstringSuperclass(), Serializable { // ^^^^^^^^^^ definition scip-java maven . . snapshots/Docstrings# +// kind Class // display_name Docstrings // signature_documentation // > public final class Docstrings : DocstringSuperclass, Serializable @@ -32,6 +35,7 @@ // relationship scip-java maven . . snapshots/DocstringSuperclass# implementation // relationship scip-java maven jdk 17 java/io/Serializable# implementation // ^^^^^^^^^^ definition scip-java maven . . snapshots/Docstrings#``(). +// kind Constructor // display_name Docstrings // signature_documentation // > public constructor(): Docstrings @@ -47,6 +51,7 @@ /** Example method docstring. */ fun docstrings() { } // ^^^^^^^^^^ definition scip-java maven . . snapshots/docstrings(). +// kind Method // display_name docstrings // signature_documentation // > public final fun docstrings(): Unit diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Functions.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Functions.kt index 2b9224e80..0ee9ed764 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Functions.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Functions.kt @@ -5,10 +5,12 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/sampleText().(x) fun sampleText(x: String = "") { // ^^^^^^^^^^ definition scip-java maven . . snapshots/sampleText(). +// kind Method // display_name sampleText // signature_documentation // > public final fun sampleText(x: String = ...): Unit // ^ definition scip-java maven . . snapshots/sampleText().(x) +// kind Parameter // display_name x // signature_documentation // > x: String = ... diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Implementations.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Implementations.kt index c9c88d892..5a6005427 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Implementations.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Implementations.kt @@ -5,11 +5,13 @@ //⌄ enclosing_range_start scip-java maven . . snapshots/Overrides#``(). class Overrides : AutoCloseable { // ^^^^^^^^^ definition scip-java maven . . snapshots/Overrides# +// kind Class // display_name Overrides // signature_documentation // > public final class Overrides : {kotlin/AutoCloseable=} AutoCloseable // relationship scip-java maven jdk 17 java/lang/AutoCloseable# implementation // ^^^^^^^^^ definition scip-java maven . . snapshots/Overrides#``(). +// kind Constructor // display_name Overrides // signature_documentation // > public constructor(): Overrides @@ -17,6 +19,7 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Overrides#close(). override fun close() { // ^^^^^ definition scip-java maven . . snapshots/Overrides#close(). +// kind Method // display_name close // signature_documentation // > public open override fun close(): Unit @@ -32,6 +35,7 @@ //⌄ enclosing_range_start scip-java maven . . snapshots/Animal# interface Animal { // ^^^^^^ definition scip-java maven . . snapshots/Animal# +// kind Interface // display_name Animal // signature_documentation // > public abstract interface Animal : Any @@ -39,10 +43,12 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Animal#getFavoriteNumber(). val favoriteNumber: Int // ^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Animal#favoriteNumber. +// kind Property // display_name favoriteNumber // signature_documentation // > public abstract val favoriteNumber: Int // ^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Animal#getFavoriteNumber(). +// kind Method // display_name favoriteNumber // signature_documentation // > public get(): Int @@ -52,6 +58,7 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Animal#sound(). fun sound(): String // ^^^^^ definition scip-java maven . . snapshots/Animal#sound(). +// kind Method // display_name sound // signature_documentation // > public abstract fun sound(): String @@ -64,11 +71,13 @@ //⌄ enclosing_range_start scip-java maven . . snapshots/Bird#``(). open class Bird : Animal { // ^^^^ definition scip-java maven . . snapshots/Bird# +// kind Class // display_name Bird // signature_documentation // > public open class Bird : Animal // relationship scip-java maven . . snapshots/Animal# implementation // ^^^^ definition scip-java maven . . snapshots/Bird#``(). +// kind Constructor // display_name Bird // signature_documentation // > public constructor(): Bird @@ -76,6 +85,7 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Bird#favoriteNumber. override val favoriteNumber: Int // ^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Bird#favoriteNumber. +// kind Property // display_name favoriteNumber // signature_documentation // > public open override val favoriteNumber: Int @@ -83,6 +93,7 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Bird#getFavoriteNumber(). get() = 42 // ^^^ definition scip-java maven . . snapshots/Bird#getFavoriteNumber(). +// kind Method // display_name favoriteNumber // signature_documentation // > public get(): Int @@ -92,6 +103,7 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Bird#sound(). override fun sound(): String { // ^^^^^ definition scip-java maven . . snapshots/Bird#sound(). +// kind Method // display_name sound // signature_documentation // > public open override fun sound(): String @@ -107,11 +119,13 @@ //⌄ enclosing_range_start scip-java maven . . snapshots/Seagull#``(). class Seagull : Bird() { // ^^^^^^^ definition scip-java maven . . snapshots/Seagull# +// kind Class // display_name Seagull // signature_documentation // > public final class Seagull : Bird // relationship scip-java maven . . snapshots/Bird# implementation // ^^^^^^^ definition scip-java maven . . snapshots/Seagull#``(). +// kind Constructor // display_name Seagull // signature_documentation // > public constructor(): Seagull @@ -119,6 +133,7 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Seagull#favoriteNumber. override val favoriteNumber: Int // ^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Seagull#favoriteNumber. +// kind Property // display_name favoriteNumber // signature_documentation // > public open override val favoriteNumber: Int @@ -126,6 +141,7 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Seagull#getFavoriteNumber(). get() = 1337 // ^^^ definition scip-java maven . . snapshots/Seagull#getFavoriteNumber(). +// kind Method // display_name favoriteNumber // signature_documentation // > public get(): Int @@ -134,6 +150,7 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Seagull#sound(). override fun sound(): String { // ^^^^^ definition scip-java maven . . snapshots/Seagull#sound(). +// kind Method // display_name sound // signature_documentation // > public open override fun sound(): String diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt index 0d80eb5bc..af772e260 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt @@ -7,20 +7,24 @@ // ⌄ enclosing_range_start local 1 val x = arrayListOf().forEachIndexed { i, s -> println("$i $s") } // ^ definition scip-java maven . . snapshots/x. +// kind Property // display_name x // signature_documentation // > public final val x: Unit // ^ definition scip-java maven . . snapshots/getX(). +// kind Method // display_name x // signature_documentation // > public get(): Unit // ^^^^^^^^^^^ reference scip-java maven . . kotlin/collections/arrayListOf(). // ^^^^^^^^^^^^^^ reference scip-java maven . . kotlin/collections/forEachIndexed(+9). // ^ definition local 0 +// kind Parameter // display_name i // signature_documentation // > i: Int // ^ definition local 1 +// kind Parameter // display_name s // signature_documentation // > s: String @@ -36,10 +40,12 @@ //⌄ enclosing_range_start scip-java maven . . snapshots/getY(). val y = "fdsa".run { this.toByteArray() } // ^ definition scip-java maven . . snapshots/y. +// kind Property // display_name y // signature_documentation // > public final val y: ByteArray // ^ definition scip-java maven . . snapshots/getY(). +// kind Method // display_name y // signature_documentation // > public get(): ByteArray @@ -53,10 +59,12 @@ // ⌄ enclosing_range_start local 2 val z = y.let { it.size } // ^ definition scip-java maven . . snapshots/z. +// kind Property // display_name z // signature_documentation // > public final val z: Int // ^ definition scip-java maven . . snapshots/getZ(). +// kind Method // display_name z // signature_documentation // > public get(): Int @@ -64,6 +72,7 @@ // ^ reference scip-java maven . . snapshots/getY(). // ^^^ reference scip-java maven . . kotlin/let(). // ^^^^^^^^^^^ definition local 2 +// kind Parameter // display_name it // signature_documentation // > it: ByteArray diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/ObjectKt.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/ObjectKt.kt index 3b803c7c3..6211a42e2 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/ObjectKt.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/ObjectKt.kt @@ -10,10 +10,12 @@ //⌄ enclosing_range_start scip-java maven . . snapshots/ObjectKt#``(). object ObjectKt { // ^^^^^^^^ definition scip-java maven . . snapshots/ObjectKt# +// kind Class // display_name ObjectKt // signature_documentation // > public final object ObjectKt : Any // ^^^^^^^^ definition scip-java maven . . snapshots/ObjectKt#``(). +// kind Constructor // display_name ObjectKt // signature_documentation // > private constructor(): ObjectKt @@ -21,10 +23,12 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/ObjectKt#fail().(message) fun fail(message: String?): Nothing { // ^^^^ definition scip-java maven . . snapshots/ObjectKt#fail(). +// kind Method // display_name fail // signature_documentation // > public final fun fail(message: String?): Nothing // ^^^^^^^ definition scip-java maven . . snapshots/ObjectKt#fail().(message) +// kind Parameter // display_name message // signature_documentation // > message: String? From 03c9dadfedda5a57a78e075fde886368804b7bd5 Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Fri, 3 Jul 2026 15:24:09 +0200 Subject: [PATCH 03/18] scip-kotlinc: Rework getters and setters to be property children This changes the symbols of getters and setters to be `x.get().` and `x.set().` instead of `getX().` and `setX().`. Ported from https://github.com/mozsearch/semanticdb-kotlinc/commit/7b4bd701b8cfc83855f0e336fb81588d04a19741 --- .../scip_java/kotlinc/SymbolsCache.kt | 12 ++----- .../scip_java/kotlinc/test/ScipSymbolsTest.kt | 24 +++++++------- .../common/src/main/kotlin/snapshots/Class.kt | 32 +++++++++---------- .../main/kotlin/snapshots/Implementations.kt | 18 +++++------ .../src/main/kotlin/snapshots/Lambdas.kt | 22 ++++++------- 5 files changed, 51 insertions(+), 57 deletions(-) diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt index 57f5b1b69..c7eb0f9a6 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt @@ -14,7 +14,6 @@ import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.SymbolInternals import org.jetbrains.kotlin.fir.symbols.impl.* import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly import org.scip_code.scip_java.kotlinc.ScipSymbolDescriptor.Kind import org.scip_code.scip_java.shared.LocalSymbolsCache as SharedLocalSymbolsCache @@ -115,6 +114,7 @@ class GlobalSymbolsCache(testing: Boolean = false) : Iterable { return getSymbol(symbol.containingDeclarationSymbol, locals) is FirValueParameterSymbol -> return getSymbol(symbol.containingDeclarationSymbol, locals) + is FirPropertyAccessorSymbol -> return getSymbol(symbol.propertySymbol, locals) is FirCallableSymbol -> { val session = symbol.fir.moduleData.session return symbol.getContainingSymbol(session)?.let { getSymbol(it, locals) } @@ -142,15 +142,9 @@ class GlobalSymbolsCache(testing: Boolean = false) : Iterable { symbol is FirClassLikeSymbol -> ScipSymbolDescriptor(Kind.TYPE, symbol.classId.shortClassName.asString()) symbol is FirPropertyAccessorSymbol && symbol.isSetter -> - ScipSymbolDescriptor( - Kind.METHOD, - "set" + symbol.propertySymbol.fir.name.toString().capitalizeAsciiOnly(), - ) + ScipSymbolDescriptor(Kind.METHOD, "set") symbol is FirPropertyAccessorSymbol && symbol.isGetter -> - ScipSymbolDescriptor( - Kind.METHOD, - "get" + symbol.propertySymbol.fir.name.toString().capitalizeAsciiOnly(), - ) + ScipSymbolDescriptor(Kind.METHOD, "get") symbol is FirConstructorSymbol -> ScipSymbolDescriptor(Kind.METHOD, "", methodDisambiguator(symbol)) symbol is FirFunctionSymbol -> diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipSymbolsTest.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipSymbolsTest.kt index e0865cd2f..b4c7035b8 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipSymbolsTest.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipSymbolsTest.kt @@ -276,7 +276,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "getX()." + symbol = "x.get()." range { startLine = 0 startCharacter = 4 @@ -287,7 +287,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "setX()." + symbol = "x.set()." range { startLine = 0 startCharacter = 4 @@ -328,7 +328,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "setX()." + symbol = "x.set()." range { startLine = 0 startCharacter = 4 @@ -342,7 +342,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "getX()." + symbol = "x.get()." range { startLine = 1 startCharacter = 4 @@ -388,7 +388,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "getX()." + symbol = "x.get()." range { startLine = 0 startCharacter = 4 @@ -402,7 +402,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "setX()." + symbol = "x.set()." range { startLine = 1 startCharacter = 4 @@ -449,7 +449,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "getX()." + symbol = "x.get()." range { startLine = 1 startCharacter = 4 @@ -465,7 +465,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "setX()." + symbol = "x.set()." range { startLine = 2 startCharacter = 4 @@ -528,7 +528,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "Test#getSample()." + symbol = "Test#sample.get()." range { startLine = 0 startCharacter = 15 @@ -542,7 +542,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = DEFINITION - symbol = "Test#setSample()." + symbol = "Test#sample.set()." range { startLine = 0 startCharacter = 15 @@ -576,7 +576,7 @@ class ScipSymbolsTest { }, scipOccurrence { role = REFERENCE - symbol = "Test#getSample()." + symbol = "Test#sample.get()." range { startLine = 2 startCharacter = 16 @@ -782,7 +782,7 @@ class ScipSymbolsTest { documentation("hello world\n test content") }, scipSymbol { - symbol = "getX()." + symbol = "x.get()." kind = Kind.Method displayName = "x" signatureText = "public get(): String" diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt index e24088f4b..b5da6c141 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt @@ -5,9 +5,9 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#``(). // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#``().(banana) // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#banana. -// ⌄ enclosing_range_start scip-java maven . . snapshots/Class#getBanana(). -// ⌄ enclosing_range_start scip-java maven . . snapshots/Class#setBanana(). -// ⌄ enclosing_range_start scip-java maven . . snapshots/Class#setBanana().(value) +// ⌄ enclosing_range_start scip-java maven . . snapshots/Class#banana.get(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Class#banana.set(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Class#banana.set().(value) // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#``().(apple) class Class constructor(private var banana: Int, apple: String) : // ^^^^^ definition scip-java maven . . snapshots/Class# @@ -32,17 +32,17 @@ // signature_documentation // > private final var banana: Int // ^^^^^^ reference scip-java maven . . snapshots/Class#``().(banana) -// ^^^^^^ definition scip-java maven . . snapshots/Class#getBanana(). +// ^^^^^^ definition scip-java maven . . snapshots/Class#banana.get(). // kind Method // display_name banana // signature_documentation // > private get(): Int -// ^^^^^^ definition scip-java maven . . snapshots/Class#setBanana(). +// ^^^^^^ definition scip-java maven . . snapshots/Class#banana.set(). // kind Method // display_name banana // signature_documentation // > private set(value: Int): Unit -// ^^^^^^ definition scip-java maven . . snapshots/Class#setBanana().(value) +// ^^^^^^ definition scip-java maven . . snapshots/Class#banana.set().(value) // kind Parameter // display_name value // signature_documentation @@ -56,9 +56,9 @@ // ^^^^^^ reference scip-java maven . . kotlin/String# // ⌃ enclosing_range_end scip-java maven . . snapshots/Class#``().(banana) // ⌃ enclosing_range_end scip-java maven . . snapshots/Class#banana. -// ⌃ enclosing_range_end scip-java maven . . snapshots/Class#getBanana(). -// ⌃ enclosing_range_end scip-java maven . . snapshots/Class#setBanana(). -// ⌃ enclosing_range_end scip-java maven . . snapshots/Class#setBanana().(value) +// ⌃ enclosing_range_end scip-java maven . . snapshots/Class#banana.get(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Class#banana.set(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Class#banana.set().(value) // ⌃ enclosing_range_end scip-java maven . . snapshots/Class#``().(apple) // ⌃ enclosing_range_end scip-java maven . . snapshots/Class#``(). Throwable(banana.toString() + apple) { @@ -73,14 +73,14 @@ } // ⌄ enclosing_range_start scip-java maven . . snapshots/Class#asdf. -// ⌄ enclosing_range_start scip-java maven . . snapshots/Class#getAsdf(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Class#asdf.get(). val asdf = // ^^^^ definition scip-java maven . . snapshots/Class#asdf. // kind Property // display_name asdf // signature_documentation // > public final val asdf: Any -// ^^^^ definition scip-java maven . . snapshots/Class#getAsdf(). +// ^^^^ definition scip-java maven . . snapshots/Class#asdf.get(). // kind Method // display_name asdf // signature_documentation @@ -108,7 +108,7 @@ // ⌃ enclosing_range_end local 2 } // ⌃ enclosing_range_end scip-java maven . . snapshots/Class#asdf. -// ⌃ enclosing_range_end scip-java maven . . snapshots/Class#getAsdf(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Class#asdf.get(). // ⌃ enclosing_range_end local 0 // ⌃ enclosing_range_end local 1 @@ -151,12 +151,12 @@ println("I eat $banana for lunch") // ^^^^^^^ reference scip-java maven . . kotlin/io/println(). // ^^^^^^ reference scip-java maven . . snapshots/Class#banana. -// ^^^^^^ reference scip-java maven . . snapshots/Class#getBanana(). -// ^^^^^^ reference scip-java maven . . snapshots/Class#setBanana(). +// ^^^^^^ reference scip-java maven . . snapshots/Class#banana.get(). +// ^^^^^^ reference scip-java maven . . snapshots/Class#banana.set(). banana = 42 // ^^^^^^ reference scip-java maven . . snapshots/Class#banana. -// ^^^^^^ reference scip-java maven . . snapshots/Class#getBanana(). -// ^^^^^^ reference scip-java maven . . snapshots/Class#setBanana(). +// ^^^^^^ reference scip-java maven . . snapshots/Class#banana.get(). +// ^^^^^^ reference scip-java maven . . snapshots/Class#banana.set(). } // ⌃ enclosing_range_end scip-java maven . . snapshots/Class#run(). } diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Implementations.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Implementations.kt index 5a6005427..0041a9f5a 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Implementations.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Implementations.kt @@ -40,21 +40,21 @@ // signature_documentation // > public abstract interface Animal : Any // ⌄ enclosing_range_start scip-java maven . . snapshots/Animal#favoriteNumber. -// ⌄ enclosing_range_start scip-java maven . . snapshots/Animal#getFavoriteNumber(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Animal#favoriteNumber.get(). val favoriteNumber: Int // ^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Animal#favoriteNumber. // kind Property // display_name favoriteNumber // signature_documentation // > public abstract val favoriteNumber: Int -// ^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Animal#getFavoriteNumber(). +// ^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/Animal#favoriteNumber.get(). // kind Method // display_name favoriteNumber // signature_documentation // > public get(): Int // ^^^ reference scip-java maven . . kotlin/Int# // ⌃ enclosing_range_end scip-java maven . . snapshots/Animal#favoriteNumber. -// ⌃ enclosing_range_end scip-java maven . . snapshots/Animal#getFavoriteNumber(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Animal#favoriteNumber.get(). // ⌄ enclosing_range_start scip-java maven . . snapshots/Animal#sound(). fun sound(): String // ^^^^^ definition scip-java maven . . snapshots/Animal#sound(). @@ -90,15 +90,15 @@ // signature_documentation // > public open override val favoriteNumber: Int // ^^^ reference scip-java maven . . kotlin/Int# -// ⌄ enclosing_range_start scip-java maven . . snapshots/Bird#getFavoriteNumber(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Bird#favoriteNumber.get(). get() = 42 -// ^^^ definition scip-java maven . . snapshots/Bird#getFavoriteNumber(). +// ^^^ definition scip-java maven . . snapshots/Bird#favoriteNumber.get(). // kind Method // display_name favoriteNumber // signature_documentation // > public get(): Int // ⌃ enclosing_range_end scip-java maven . . snapshots/Bird#favoriteNumber. -// ⌃ enclosing_range_end scip-java maven . . snapshots/Bird#getFavoriteNumber(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Bird#favoriteNumber.get(). // ⌄ enclosing_range_start scip-java maven . . snapshots/Bird#sound(). override fun sound(): String { @@ -138,15 +138,15 @@ // signature_documentation // > public open override val favoriteNumber: Int // ^^^ reference scip-java maven . . kotlin/Int# -// ⌄ enclosing_range_start scip-java maven . . snapshots/Seagull#getFavoriteNumber(). +// ⌄ enclosing_range_start scip-java maven . . snapshots/Seagull#favoriteNumber.get(). get() = 1337 -// ^^^ definition scip-java maven . . snapshots/Seagull#getFavoriteNumber(). +// ^^^ definition scip-java maven . . snapshots/Seagull#favoriteNumber.get(). // kind Method // display_name favoriteNumber // signature_documentation // > public get(): Int // ⌃ enclosing_range_end scip-java maven . . snapshots/Seagull#favoriteNumber. -// ⌃ enclosing_range_end scip-java maven . . snapshots/Seagull#getFavoriteNumber(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/Seagull#favoriteNumber.get(). // ⌄ enclosing_range_start scip-java maven . . snapshots/Seagull#sound(). override fun sound(): String { // ^^^^^ definition scip-java maven . . snapshots/Seagull#sound(). diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt index af772e260..ffcfb7682 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt @@ -2,7 +2,7 @@ // ^^^^^^^^^ reference scip-java maven . . snapshots/ //⌄ enclosing_range_start scip-java maven . . snapshots/x. -//⌄ enclosing_range_start scip-java maven . . snapshots/getX(). +//⌄ enclosing_range_start scip-java maven . . snapshots/x.get(). // ⌄ enclosing_range_start local 0 // ⌄ enclosing_range_start local 1 val x = arrayListOf().forEachIndexed { i, s -> println("$i $s") } @@ -11,7 +11,7 @@ // display_name x // signature_documentation // > public final val x: Unit -// ^ definition scip-java maven . . snapshots/getX(). +// ^ definition scip-java maven . . snapshots/x.get(). // kind Method // display_name x // signature_documentation @@ -34,17 +34,17 @@ // ⌃ enclosing_range_end local 0 // ⌃ enclosing_range_end local 1 // ⌃ enclosing_range_end scip-java maven . . snapshots/x. -// ⌃ enclosing_range_end scip-java maven . . snapshots/getX(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/x.get(). //⌄ enclosing_range_start scip-java maven . . snapshots/y. -//⌄ enclosing_range_start scip-java maven . . snapshots/getY(). +//⌄ enclosing_range_start scip-java maven . . snapshots/y.get(). val y = "fdsa".run { this.toByteArray() } // ^ definition scip-java maven . . snapshots/y. // kind Property // display_name y // signature_documentation // > public final val y: ByteArray -// ^ definition scip-java maven . . snapshots/getY(). +// ^ definition scip-java maven . . snapshots/y.get(). // kind Method // display_name y // signature_documentation @@ -52,10 +52,10 @@ // ^^^ reference scip-java maven . . kotlin/run(+1). // ^^^^^^^^^^^ reference scip-java maven . . kotlin/text/toByteArray(). // ⌃ enclosing_range_end scip-java maven . . snapshots/y. -// ⌃ enclosing_range_end scip-java maven . . snapshots/getY(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/y.get(). //⌄ enclosing_range_start scip-java maven . . snapshots/z. -//⌄ enclosing_range_start scip-java maven . . snapshots/getZ(). +//⌄ enclosing_range_start scip-java maven . . snapshots/z.get(). // ⌄ enclosing_range_start local 2 val z = y.let { it.size } // ^ definition scip-java maven . . snapshots/z. @@ -63,13 +63,13 @@ // display_name z // signature_documentation // > public final val z: Int -// ^ definition scip-java maven . . snapshots/getZ(). +// ^ definition scip-java maven . . snapshots/z.get(). // kind Method // display_name z // signature_documentation // > public get(): Int // ^ reference scip-java maven . . snapshots/y. -// ^ reference scip-java maven . . snapshots/getY(). +// ^ reference scip-java maven . . snapshots/y.get(). // ^^^ reference scip-java maven . . kotlin/let(). // ^^^^^^^^^^^ definition local 2 // kind Parameter @@ -78,8 +78,8 @@ // > it: ByteArray // ^^ reference local 2 // ^^^^ reference scip-java maven . . kotlin/ByteArray#size. -// ^^^^ reference scip-java maven . . kotlin/ByteArray#getSize(). +// ^^^^ reference scip-java maven . . kotlin/ByteArray#size.get(). // ⌃ enclosing_range_end scip-java maven . . snapshots/z. -// ⌃ enclosing_range_end scip-java maven . . snapshots/getZ(). +// ⌃ enclosing_range_end scip-java maven . . snapshots/z.get(). // ⌃ enclosing_range_end local 2 From 9d26915a8a74641d249cead9779b76cf2d19d0f0 Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Fri, 3 Jul 2026 15:30:47 +0200 Subject: [PATCH 04/18] scip-kotlinc: Add enclosing_symbol field This will allow us to get the parent symbol for locals as well. For non-locals, it should be equal to the current symbol minus the last segment. Ported from https://github.com/mozsearch/semanticdb-kotlinc/commit/63c9b6582e081f62b2cc736c6c640b75b334185a --- .../kotlinc/ScipTextDocumentBuilder.kt | 3 +++ .../scip_java/kotlinc/test/AnalyzerTest.kt | 18 ++++++++++++++++++ .../scip_java/kotlinc/test/ScipBuilders.kt | 2 ++ .../scip_java/kotlinc/test/ScipSymbolsTest.kt | 2 ++ 4 files changed, 25 insertions(+) diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt index dab8f500d..3ed4578a5 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt @@ -89,6 +89,9 @@ class ScipTextDocumentBuilder( docComment(firBasedSymbol.fir)?.let { documentation += it } } this.kind = scipKind(firBasedSymbol?.fir) + this.enclosingSymbol = + context.containingDeclarations.lastOrNull()?.let { cache[it].last().toString() } + ?: "" for (parent in supers) { relationships += relationship { this.symbol = parent.toString() diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt index 338b41b2d..ade938cfe 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt @@ -109,12 +109,14 @@ class AnalyzerTest { scipSymbol { symbol = "sample/Banana#" kind = Kind.Class + enclosingSymbol = "sample/" displayName = "Banana" signatureText = "public final class Banana : Any" }, scipSymbol { symbol = "sample/Banana#foo()." kind = Kind.Method + enclosingSymbol = "sample/Banana#" displayName = "foo" signatureText = "public final fun foo(): Unit" }, @@ -283,24 +285,28 @@ class AnalyzerTest { scipSymbol { symbol = "sample/foo()." kind = Kind.Method + enclosingSymbol = "sample/" displayName = "foo" signatureText = "public final fun foo(): Unit" }, scipSymbol { symbol = "local 0" kind = Kind.Class + enclosingSymbol = "sample/foo()." displayName = "LocalClass" signatureText = "local final class LocalClass : Any" }, scipSymbol { symbol = "local 1" kind = Kind.Constructor + enclosingSymbol = "local 0" displayName = "LocalClass" signatureText = "public constructor(): LocalClass" }, scipSymbol { symbol = "local 2" kind = Kind.Method + enclosingSymbol = "local 0" displayName = "localClassMethod" signatureText = "public final fun localClassMethod(): Unit" }, @@ -418,18 +424,21 @@ class AnalyzerTest { scipSymbol { symbol = "sample/Interface#" kind = Kind.Interface + enclosingSymbol = "sample/" displayName = "Interface" signatureText = "public abstract interface Interface : Any" }, scipSymbol { symbol = "sample/Interface#foo()." kind = Kind.Method + enclosingSymbol = "sample/Interface#" displayName = "foo" signatureText = "public abstract fun foo(): Unit\n" }, scipSymbol { symbol = "sample/Class#" kind = Kind.Class + enclosingSymbol = "sample/" displayName = "Class" signatureText = "public final class Class : Interface" addOverriddenSymbols("sample/Interface#") @@ -437,6 +446,7 @@ class AnalyzerTest { scipSymbol { symbol = "sample/Class#foo()." kind = Kind.Method + enclosingSymbol = "sample/Class#" displayName = "foo" signatureText = "public open override fun foo(): Unit" addOverriddenSymbols("sample/Interface#foo().") @@ -635,12 +645,14 @@ class AnalyzerTest { scipSymbol { symbol = "sample/Interface#" kind = Kind.Interface + enclosingSymbol = "sample/" displayName = "Interface" signatureText = "public abstract interface Interface : Any" }, scipSymbol { symbol = "local 1" kind = Kind.Class + enclosingSymbol = "local 0" displayName = "" signatureText = "object : Interface" addOverriddenSymbols("sample/Interface#") @@ -648,6 +660,7 @@ class AnalyzerTest { scipSymbol { symbol = "local 3" kind = Kind.Method + enclosingSymbol = "local 1" displayName = "foo" signatureText = "public open override fun foo(): Unit" addOverriddenSymbols("sample/Interface#foo().") @@ -655,6 +668,7 @@ class AnalyzerTest { scipSymbol { symbol = "local 5" kind = Kind.Class + enclosingSymbol = "local 4" displayName = "" signatureText = "object : Interface" addOverriddenSymbols("sample/Interface#") @@ -662,6 +676,7 @@ class AnalyzerTest { scipSymbol { symbol = "local 7" kind = Kind.Method + enclosingSymbol = "local 5" displayName = "foo" signatureText = "public open override fun foo(): Unit" addOverriddenSymbols("sample/Interface#foo().") @@ -1317,6 +1332,7 @@ class AnalyzerTest { scipSymbol { symbol = "hello/sample/Apple#" kind = Kind.Class + enclosingSymbol = "hello/sample/" displayName = "Apple" signatureText = "public final class Apple : Any" } @@ -1403,12 +1419,14 @@ class AnalyzerTest { scipSymbol { symbol = "sample/Banana#" kind = Kind.Class + enclosingSymbol = "sample/" displayName = "Banana" signatureText = "public final class Banana : Any" }, scipSymbol { symbol = "sample/Banana#foo()." kind = Kind.Method + enclosingSymbol = "sample/Banana#" displayName = "foo" signatureText = "public final fun foo(): Unit" }, diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipBuilders.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipBuilders.kt index a7d549dd7..fe8bdebc7 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipBuilders.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipBuilders.kt @@ -77,6 +77,7 @@ class ScipOccurrenceBuilder { class ScipSymbolInformationBuilder { var symbol: String = "" var kind: SymbolInformation.Kind = SymbolInformation.Kind.UnspecifiedKind + var enclosingSymbol: String = "" var displayName: String = "" var signatureText: String? = null private val docs = mutableListOf() @@ -97,6 +98,7 @@ class ScipSymbolInformationBuilder { internal fun build(): SymbolInformation = symbolInformation { symbol = this@ScipSymbolInformationBuilder.symbol kind = this@ScipSymbolInformationBuilder.kind + enclosingSymbol = this@ScipSymbolInformationBuilder.enclosingSymbol if (this@ScipSymbolInformationBuilder.displayName.isNotEmpty()) { displayName = this@ScipSymbolInformationBuilder.displayName } diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipSymbolsTest.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipSymbolsTest.kt index b4c7035b8..0bc8349bf 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipSymbolsTest.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/ScipSymbolsTest.kt @@ -777,6 +777,7 @@ class ScipSymbolsTest { scipSymbol { symbol = "x." kind = Kind.Property + enclosingSymbol = "_root_/" displayName = "x" signatureText = "public final val x: String" documentation("hello world\n test content") @@ -784,6 +785,7 @@ class ScipSymbolsTest { scipSymbol { symbol = "x.get()." kind = Kind.Method + enclosingSymbol = "x." displayName = "x" signatureText = "public get(): String" documentation("hello world\n test content") From a517885e1a7a7708aa56fa14fb3a91d055a6168a Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Fri, 3 Jul 2026 15:31:59 +0200 Subject: [PATCH 05/18] scip-kotlinc: Ignore FirFileSymbols without warning Reduces the debug log clutter. Ported from https://github.com//mozsearch/semanticdb-kotlinc/commit/4b7afc21337cba75c2c7b724043bcc9204eb9eb0 --- .../main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt index c7eb0f9a6..04469bd1b 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt @@ -158,6 +158,7 @@ class GlobalSymbolsCache(testing: Boolean = false) : Iterable { symbol is FirValueParameterSymbol -> ScipSymbolDescriptor(Kind.PARAMETER, symbol.name.toString()) symbol is FirVariableSymbol -> ScipSymbolDescriptor(Kind.TERM, symbol.name.toString()) + symbol is FirFileSymbol -> ScipSymbolDescriptor.NONE else -> { err.println("unknown symbol kind ${symbol.javaClass.simpleName}") ScipSymbolDescriptor.NONE From 0f0231df4cb2e6c58a6197292a236964ccd21eca Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Fri, 3 Jul 2026 15:33:14 +0200 Subject: [PATCH 06/18] scip-kotlinc: Clear AnalyzerCheckers.visitors after consuming them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should reduce memory usage a bit and avoids lots of “given file is not under the sourceroot” clutter when running the tests. Ported from https://github.com//mozsearch/semanticdb-kotlinc/commit/65b1898de26c02929f1fbd445a3c9c92e8fd3bab --- .../org/scip_code/scip_java/kotlinc/PostAnalysisExtension.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/PostAnalysisExtension.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/PostAnalysisExtension.kt index 54ea38fa6..af74b5786 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/PostAnalysisExtension.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/PostAnalysisExtension.kt @@ -45,6 +45,7 @@ class PostAnalysisExtension( } catch (e: Exception) { handleException(e) } + AnalyzerCheckers.visitors.clear() } private fun scipShardPathForFile(file: KtSourceFile): Path? { From 65a6e998e91e297f79bd130bdcb4b3f92bfc67b1 Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Fri, 3 Jul 2026 15:43:13 +0200 Subject: [PATCH 07/18] Update to Kotlin 2.3.10 scip-kotlinc changes: getContainingSymbol was moved from o.j.k.fir.analysis.checkers to org.jetbrains.kotlin.fir.resolve. CompilerPluginRegistrar now has a virtual pluginId which must match the CommandLineProcessor. Ported from https://github.com/mozsearch/semanticdb-kotlinc/commit/cde86db9a62d29afa680de072ace1c60d117c37f --- gradle/libs.versions.toml | 4 ++-- .../fixtures/gradle/kotlin-jvm-toolchains/build.gradle | 2 +- .../src/test/resources/fixtures/gradle/kotlin2/build.gradle | 2 +- .../org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt | 2 +- .../scip_java/kotlinc/AnalyzerCommandLineProcessor.kt | 4 +++- .../org/scip_code/scip_java/kotlinc/AnalyzerRegistrar.kt | 2 ++ .../kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt | 2 +- .../test/kotlin/org/scip_code/scip_java/kotlinc/test/Utils.kt | 2 ++ 8 files changed, 13 insertions(+), 7 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 54c8d6f36..0ee952e62 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,9 +2,9 @@ clikt = "5.1.0" gradle-api = "8.11.1" junit-jupiter = "5.11.4" -kctfork = "0.7.1" +kctfork = "0.12.1" kotest = "6.2.1" -kotlin = "2.2.20" +kotlin = "2.3.10" kotlinx-serialization = "1.11.0" lombok = "1.18.46" maven-plugin-annotations = "3.15.2" diff --git a/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle b/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle index 712fe1a60..13a1ab323 100644 --- a/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle +++ b/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.jetbrains.kotlin.jvm' version '2.2.20' + id 'org.jetbrains.kotlin.jvm' version '2.3.10' } java { toolchain { diff --git a/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle b/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle index 43952f0bc..42f3cbd18 100644 --- a/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle +++ b/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'org.jetbrains.kotlin.jvm' version '2.2.20' + id 'org.jetbrains.kotlin.jvm' version '2.3.10' } kotlin { jvmToolchain(17) diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt index 3258bc0c6..40675112c 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt @@ -12,7 +12,6 @@ import org.jetbrains.kotlin.fir.analysis.checkers.declaration.* import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirQualifiedAccessExpressionChecker import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirTypeOperatorCallChecker -import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol import org.jetbrains.kotlin.fir.analysis.checkers.toClassLikeSymbol import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension import org.jetbrains.kotlin.fir.declarations.* @@ -20,6 +19,7 @@ import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression import org.jetbrains.kotlin.fir.expressions.FirTypeOperatorCall import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.calls.FirSyntheticFunctionSymbol +import org.jetbrains.kotlin.fir.resolve.getContainingClassSymbol import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider import org.jetbrains.kotlin.fir.resolve.toClassLikeSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirAnonymousObjectSymbol diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCommandLineProcessor.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCommandLineProcessor.kt index 45bb987b2..601511099 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCommandLineProcessor.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCommandLineProcessor.kt @@ -15,9 +15,11 @@ val KEY_SOURCES = CompilerConfigurationKey(VAL_SOURCES) const val VAL_TARGET = "targetroot" val KEY_TARGET = CompilerConfigurationKey(VAL_TARGET) +const val PLUGIN_ID = "scip-kotlinc" + @OptIn(ExperimentalCompilerApi::class) class AnalyzerCommandLineProcessor : CommandLineProcessor { - override val pluginId: String = "scip-kotlinc" + override val pluginId: String = PLUGIN_ID override val pluginOptions: Collection = listOf( CliOption( diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerRegistrar.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerRegistrar.kt index 289a0cf4e..0b05005e4 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerRegistrar.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerRegistrar.kt @@ -26,6 +26,8 @@ class AnalyzerRegistrar(private val callback: (Document) -> Unit = {}) : Compile ) } + override val pluginId = PLUGIN_ID + override val supportsK2: Boolean get() = true } diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt index 04469bd1b..73e4be90c 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt @@ -2,13 +2,13 @@ package org.scip_code.scip_java.kotlinc import java.lang.System.err import org.jetbrains.kotlin.fir.analysis.checkers.declaration.isLocalMember -import org.jetbrains.kotlin.fir.analysis.checkers.getContainingSymbol import org.jetbrains.kotlin.fir.declarations.DirectDeclarationsAccess import org.jetbrains.kotlin.fir.declarations.FirClass import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin import org.jetbrains.kotlin.fir.declarations.utils.memberDeclarationNameOrNull import org.jetbrains.kotlin.fir.packageFqName import org.jetbrains.kotlin.fir.resolve.getContainingDeclaration +import org.jetbrains.kotlin.fir.resolve.getContainingSymbol import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.SymbolInternals diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/Utils.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/Utils.kt index 4576e9893..0d3da7c4a 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/Utils.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/Utils.kt @@ -200,6 +200,8 @@ fun scipVisitorAnalyzer( ) } + override val pluginId = PLUGIN_ID + override val supportsK2: Boolean get() = true } From 1e8c06a03dc475ea28386890283a9ef9d3b397d5 Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Fri, 3 Jul 2026 15:52:32 +0200 Subject: [PATCH 08/18] scip-kotlinc: Fix compiler warnings - Remove unnecessary cast to FirClass in SymbolsCache (FirClassSymbol.fir already returns FirClass) - Use parameterless toClassLikeSymbol() overload in AnalyzerCheckers - Remove unnecessary inline modifier from test snippet Ported from https://github.com/mozsearch/semanticdb-kotlinc/commit/eb683777e27a40bee4771b5b1200aaf83ef71bae --- .../org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt | 3 +-- .../kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt | 4 +--- .../org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt index 40675112c..5e120bda5 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt @@ -388,8 +388,7 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio resolvedSymbol.origin == FirDeclarationOrigin.SamConstructor && resolvedSymbol is FirSyntheticFunctionSymbol ) { - val referencedKlass = - resolvedSymbol.resolvedReturnType.toClassLikeSymbol(context.session) + val referencedKlass = resolvedSymbol.resolvedReturnType.toClassLikeSymbol() if (referencedKlass != null) { visitor?.visitClassReference( referencedKlass, diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt index 73e4be90c..bfff11f0b 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt @@ -3,7 +3,6 @@ package org.scip_code.scip_java.kotlinc import java.lang.System.err import org.jetbrains.kotlin.fir.analysis.checkers.declaration.isLocalMember import org.jetbrains.kotlin.fir.declarations.DirectDeclarationsAccess -import org.jetbrains.kotlin.fir.declarations.FirClass import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin import org.jetbrains.kotlin.fir.declarations.utils.memberDeclarationNameOrNull import org.jetbrains.kotlin.fir.packageFqName @@ -172,8 +171,7 @@ class GlobalSymbolsCache(testing: Boolean = false) : Iterable { val siblings = when (val containingSymbol = symbol.getContainingSymbol(session)) { - is FirClassSymbol -> - (containingSymbol.fir as FirClass).declarations.map { it.symbol } + is FirClassSymbol -> containingSymbol.fir.declarations.map { it.symbol } is FirFileSymbol -> containingSymbol.fir.declarations.map { it.symbol } null -> symbol.moduleData.session.symbolProvider.getTopLevelCallableSymbols( diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt index ade938cfe..7e36e926f 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt @@ -1451,7 +1451,7 @@ class AnalyzerTest { * Example method docstring * **/ - inline fun docstrings(msg: String): Int { return msg.length } + fun docstrings(msg: String): Int { return msg.length } """ .trimIndent(), ) From 9118d4c8b72aeedc44fa76e2ab0ca50e9a27ce9f Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Fri, 3 Jul 2026 15:57:15 +0200 Subject: [PATCH 09/18] scip-kotlinc: Fix PostAnalysisExtension to use the compilation's message collector MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously handleException constructed a fresh CompilerConfiguration() to retrieve the message collector key, which always fell back to PrintingMessageCollector(System.err) — ignoring whatever collector the actual compilation was configured with. Pass the real CompilerConfiguration into PostAnalysisExtension so that exception messages are routed through the same collector as all other diagnostics. Also change the severity from EXCEPTION to WARNING, since EXCEPTION is treated as isError=true by the Kotlin compiler and would cause the build to fail — contrary to the plugin's intentional "log-and-continue" behaviour. The exception test now captures compiler output via result.messages and asserts that the warning is actually emitted with the expected content, rather than only checking the exit code. Ported from https://github.com/mozsearch/semanticdb-kotlinc/commit/e6b72f5267de9f28813594a5cd290437b4d67d56 --- .../scip_code/scip_java/kotlinc/AnalyzerRegistrar.kt | 1 + .../scip_java/kotlinc/PostAnalysisExtension.kt | 12 ++++++------ .../scip_code/scip_java/kotlinc/test/AnalyzerTest.kt | 4 ++++ .../org/scip_code/scip_java/kotlinc/test/Utils.kt | 1 + 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerRegistrar.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerRegistrar.kt index 0b05005e4..5833d8883 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerRegistrar.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerRegistrar.kt @@ -19,6 +19,7 @@ class AnalyzerRegistrar(private val callback: (Document) -> Unit = {}) : Compile FirExtensionRegistrarAdapter.registerExtension(AnalyzerFirExtensionRegistrar(options)) IrGenerationExtension.registerExtension( PostAnalysisExtension( + configuration = configuration, sourceRoot = options.sourceroot, targetRoot = options.targetroot, callback = callback, diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/PostAnalysisExtension.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/PostAnalysisExtension.kt index af74b5786..e814439c5 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/PostAnalysisExtension.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/PostAnalysisExtension.kt @@ -25,6 +25,7 @@ import org.scip_code.scip_java.shared.ScipShardWriter * outside the source root are skipped with a stderr warning. */ class PostAnalysisExtension( + private val configuration: CompilerConfiguration, private val sourceRoot: Path, private val targetRoot: Path, private val callback: (Document) -> Unit, @@ -61,11 +62,10 @@ class PostAnalysisExtension( } private val messageCollector = - CompilerConfiguration() - .get( - CommonConfigurationKeys.MESSAGE_COLLECTOR_KEY, - PrintingMessageCollector(System.err, MessageRenderer.PLAIN_FULL_PATHS, false), - ) + configuration.get( + CommonConfigurationKeys.MESSAGE_COLLECTOR_KEY, + PrintingMessageCollector(System.err, MessageRenderer.PLAIN_FULL_PATHS, false), + ) private fun handleException(e: Exception) { val writer = @@ -74,7 +74,7 @@ class PostAnalysisExtension( val buf = StringBuffer() override fun close() = - messageCollector.report(CompilerMessageSeverity.EXCEPTION, buf.toString()) + messageCollector.report(CompilerMessageSeverity.WARNING, buf.toString()) override fun flush() = Unit diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt index 7e36e926f..c8f2bd055 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt @@ -7,6 +7,7 @@ import io.kotest.assertions.fail import io.kotest.matchers.collections.shouldContainAll import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.string.shouldContain import java.io.File import java.nio.file.Path import kotlin.test.Test @@ -807,6 +808,7 @@ class AnalyzerTest { compilerPluginRegistrars = listOf(AnalyzerRegistrar { throw Exception("sample text") }) verbose = false + messageOutputStream = java.io.OutputStream.nullOutputStream() pluginOptions = listOf( PluginOption("scip-kotlinc", "sourceroot", path.toString()), @@ -818,6 +820,8 @@ class AnalyzerTest { .compile() result.exitCode shouldBe KotlinCompilation.ExitCode.OK + result.messages shouldContain "Exception in scip-kotlin compiler plugin:" + result.messages shouldContain "sample text" } @Test diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/Utils.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/Utils.kt index 0d3da7c4a..5622fe594 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/Utils.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/Utils.kt @@ -193,6 +193,7 @@ fun scipVisitorAnalyzer( ) IrGenerationExtension.registerExtension( PostAnalysisExtension( + configuration = configuration, sourceRoot = sourceroot, targetRoot = Paths.get(""), callback = hook, From a0a70886473f5ffabf38ed6d87496c63ccbef79c Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Fri, 3 Jul 2026 16:06:37 +0200 Subject: [PATCH 10/18] scip-kotlinc: Add test for lambda parameters This is a test for 88f6272784c5e56eb135019a4b0dda5dd0808017. Ported from https://github.com/mozsearch/semanticdb-kotlinc/commit/2c1f17047f1cb4ed15e0f53332737f82cbbcdc8b --- .../scip_java/kotlinc/test/AnalyzerTest.kt | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt index c8f2bd055..1f37b561a 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt @@ -315,6 +315,108 @@ class AnalyzerTest { document.symbolsList.shouldContainAll(*symbols) } + @Test + fun `lambda parameters`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + fun use() { + val f = { n: Int -> n * 2 } + } + """, + ) + + val occurrences = + arrayOf( + // val f is a local variable — gets local0 via isLocalMember + scipOccurrence { + role = DEFINITION + symbol = "local 0" + range { + startLine = 3 + startCharacter = 8 + endLine = 3 + endCharacter = 9 + } + enclosingRange { + startLine = 3 + startCharacter = 4 + endLine = 3 + endCharacter = 31 + } + }, + // n is a lambda parameter — gets local1 via the owner == Symbol.NONE path + // (the containing FirAnonymousFunction is skipped, yielding Symbol.NONE as owner) + scipOccurrence { + role = DEFINITION + symbol = "local 1" + range { + startLine = 3 + startCharacter = 14 + endLine = 3 + endCharacter = 15 + } + enclosingRange { + startLine = 3 + startCharacter = 14 + endLine = 3 + endCharacter = 20 + } + }, + // explicit type annotation on n emits a class reference + scipOccurrence { + role = REFERENCE + symbol = "kotlin/Int#" + range { + startLine = 3 + startCharacter = 17 + endLine = 3 + endCharacter = 20 + } + }, + // reference to n in the lambda body uses the same local symbol + scipOccurrence { + role = REFERENCE + symbol = "local 1" + range { + startLine = 3 + startCharacter = 24 + endLine = 3 + endCharacter = 25 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/use()." + kind = Kind.Method + enclosingSymbol = "sample/" + displayName = "use" + signatureText = "public final fun use(): Unit" + }, + scipSymbol { + symbol = "local 0" + kind = Kind.Property + enclosingSymbol = "sample/use()." + displayName = "f" + signatureText = "local val f: (Int) -> Int" + }, + scipSymbol { + symbol = "local 1" + kind = Kind.Parameter + displayName = "n" + signatureText = "n: Int" + }, + ) + document.symbolsList.shouldContainAll(*symbols) + } + @Test fun overrides(@TempDir path: Path) { val document = From 430a5828f096e13e27b5045548250741417d7962 Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Fri, 3 Jul 2026 16:18:26 +0200 Subject: [PATCH 11/18] scip-kotlinc: Add tests for local functions and user-defined class return types - local functions: verifies that named functions declared inside a function body receive local symbols (local0, local1), that explicit return type references are emitted (kotlin/Int#), and that call-site references resolve to the same local symbol. Also adds a SemanticdbSymbolsTest entry confirming the locals counter increments correctly. - user-defined class as return type: verifies that SemanticSimple- FunctionChecker emits a REFERENCE occurrence for a user-defined class appearing in the return-type position (sample/MyClass#), exercising the returnTypeRef path that built-in types do not reach. Ported from https://github.com/mozsearch/semanticdb-kotlinc/commit/ff23a7ec482dcd7ab2d79ac3004ffb91e2d92d60 --- .../scip_java/kotlinc/test/AnalyzerTest.kt | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt index 1f37b561a..514dda5c2 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt @@ -417,6 +417,178 @@ class AnalyzerTest { document.symbolsList.shouldContainAll(*symbols) } + @Test + fun `local functions`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + fun outer() { + fun inner() {} + fun innerWithReturnType(): Int = 42 + inner() + } + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/outer()." + range { + startLine = 2 + startCharacter = 4 + endLine = 2 + endCharacter = 9 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 6 + endCharacter = 1 + } + }, + // inner() — local named function gets a local symbol + scipOccurrence { + role = DEFINITION + symbol = "local 0" + range { + startLine = 3 + startCharacter = 8 + endLine = 3 + endCharacter = 13 + } + enclosingRange { + startLine = 3 + startCharacter = 4 + endLine = 3 + endCharacter = 18 + } + }, + // innerWithReturnType() — local named function with explicit return type + scipOccurrence { + role = DEFINITION + symbol = "local 1" + range { + startLine = 4 + startCharacter = 8 + endLine = 4 + endCharacter = 27 + } + enclosingRange { + startLine = 4 + startCharacter = 4 + endLine = 4 + endCharacter = 39 + } + }, + // Int return-type reference + scipOccurrence { + role = REFERENCE + symbol = "kotlin/Int#" + range { + startLine = 4 + startCharacter = 31 + endLine = 4 + endCharacter = 34 + } + }, + // call site inner() references the same local symbol + scipOccurrence { + role = REFERENCE + symbol = "local 0" + range { + startLine = 5 + startCharacter = 4 + endLine = 5 + endCharacter = 9 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "local 0" + kind = Kind.Method + enclosingSymbol = "sample/outer()." + displayName = "inner" + signatureText = "local final fun inner(): Unit" + }, + scipSymbol { + symbol = "local 1" + kind = Kind.Method + enclosingSymbol = "sample/outer()." + displayName = "innerWithReturnType" + signatureText = "local final fun innerWithReturnType(): Int" + }, + ) + document.symbolsList.shouldContainAll(*symbols) + } + + @Test + fun `user-defined class as return type`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + class MyClass + + fun bar(): MyClass = MyClass() + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/bar()." + range { + startLine = 4 + startCharacter = 4 + endLine = 4 + endCharacter = 7 + } + enclosingRange { + startLine = 4 + startCharacter = 0 + endLine = 4 + endCharacter = 30 + } + }, + // MyClass in the return type position generates a class reference + scipOccurrence { + role = REFERENCE + symbol = "sample/MyClass#" + range { + startLine = 4 + startCharacter = 11 + endLine = 4 + endCharacter = 18 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/bar()." + kind = Kind.Method + enclosingSymbol = "sample/" + displayName = "bar" + signatureText = "public final fun bar(): MyClass" + } + ) + document.symbolsList.shouldContainAll(*symbols) + } + @Test fun overrides(@TempDir path: Path) { val document = From f360ab652b764636aca63c7d2a1dbd117b62761a Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Fri, 3 Jul 2026 16:20:29 +0200 Subject: [PATCH 12/18] scip-kotlinc: Fix displayName() for type aliases and type parameters displayName() in SemanticdbTextDocumentBuilder fell through to `firBasedSymbol.toString()` for two symbol types, producing strings like "FirTypeAliasSymbol sample/MyAlias" and "FirTypeParameterSymbol T" instead of the short name. - Broaden the FirClassSymbol branch to FirClassLikeSymbol so that FirTypeAliasSymbol (a subtype of FirClassLikeSymbol but not of FirClassSymbol) also uses classId.shortClassName. - Add an explicit FirTypeParameterSymbol branch that returns symbol.name.asString(). Add tests that exercise the corrected paths: - typealias: first test for SemanticTypeAliasChecker; verifies the DEFINITION occurrence and SymbolInformation (including the now-correct displayName "MyAlias"). Notes that type-alias references in value declarations are not currently tracked (the property checker resolves aliases to their expansion). - type parameters: first test for SemanticTypeParameterChecker; verifies the DEFINITION occurrence and SymbolInformation (displayName "T"). Notes that T in type-annotation positions does not produce REFERENCE occurrences because toClassLikeSymbol() returns null for type parameters. Ported from https://github.com/mozsearch/semanticdb-kotlinc/commit/20b1d20d53232a00388a6036cf39caf484a71b99 --- .../kotlinc/ScipTextDocumentBuilder.kt | 4 +- .../scip_java/kotlinc/test/AnalyzerTest.kt | 131 ++++++++++++++++++ 2 files changed, 134 insertions(+), 1 deletion(-) diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt index 3ed4578a5..ad09c9dc0 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt @@ -170,6 +170,7 @@ class ScipTextDocumentBuilder( private fun scipKind(element: FirElement?): Kind = when (element) { is FirClass if element.isInterface -> Kind.Interface + is FirTypeAlias -> Kind.TypeAlias is FirClassLikeDeclaration -> Kind.Class is FirConstructor -> Kind.Constructor is FirTypeParameter -> Kind.TypeParameter @@ -210,12 +211,13 @@ class ScipTextDocumentBuilder( @OptIn(SymbolInternals::class, RenderingInternals::class) private fun displayName(firBasedSymbol: FirBasedSymbol<*>): String = when (firBasedSymbol) { - is FirClassSymbol -> firBasedSymbol.classId.shortClassName.asString() + is FirClassLikeSymbol -> firBasedSymbol.classId.shortClassName.asString() is FirPropertyAccessorSymbol -> firBasedSymbol.fir.propertySymbol.name.asString() is FirFunctionSymbol -> firBasedSymbol.callableId.callableName.asString() is FirPropertySymbol -> firBasedSymbol.callableIdForRendering.callableName.asString() is FirVariableSymbol -> firBasedSymbol.name.asString() + is FirTypeParameterSymbol -> firBasedSymbol.name.asString() else -> firBasedSymbol.toString() } } diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt index 514dda5c2..20fcaf0f3 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt @@ -589,6 +589,137 @@ class AnalyzerTest { document.symbolsList.shouldContainAll(*symbols) } + @Test + fun `typealias`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + typealias MyAlias = Int + val x: MyAlias = 42 + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/MyAlias#" + range { + startLine = 2 + startCharacter = 10 + endLine = 2 + endCharacter = 17 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 2 + endCharacter = 23 + } + }, + // Note: val x: MyAlias does not emit a REFERENCE for sample/MyAlias# because the + // property checker resolves the type alias to its expansion (Int). + scipOccurrence { + role = DEFINITION + symbol = "sample/x." + range { + startLine = 3 + startCharacter = 4 + endLine = 3 + endCharacter = 5 + } + enclosingRange { + startLine = 3 + startCharacter = 0 + endLine = 3 + endCharacter = 19 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/MyAlias#" + kind = Kind.TypeAlias + enclosingSymbol = "sample/" + displayName = "MyAlias" + signatureText = "public final typealias MyAlias = Int\n" + } + ) + document.symbolsList.shouldContainAll(*symbols) + } + + @Test + fun `type parameters`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + fun identity(x: T): T = x + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/identity()." + range { + startLine = 2 + startCharacter = 8 + endLine = 2 + endCharacter = 16 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 2 + endCharacter = 29 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/identity().[T]" + range { + startLine = 2 + startCharacter = 5 + endLine = 2 + endCharacter = 6 + } + enclosingRange { + startLine = 2 + startCharacter = 5 + endLine = 2 + endCharacter = 6 + } + }, + // Note: T in type-annotation positions (x: T and return type : T) does not produce + // REFERENCE occurrences. The checkers use toClassLikeSymbol() to detect type + // references, which returns null for type parameters, so those usages are not + // currently tracked. + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/identity().[T]" + kind = Kind.TypeParameter + enclosingSymbol = "sample/identity()." + displayName = "T" + signatureText = "T" + } + ) + document.symbolsList.shouldContainAll(*symbols) + } + @Test fun overrides(@TempDir path: Path) { val document = From 89128a05891f1edda80b1530a5b9fee7e8f83c7b Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Fri, 3 Jul 2026 17:12:41 +0200 Subject: [PATCH 13/18] scip-kotlinc: Fix extension receiver, enum entry, and is/as type occurrences - Fix SemanticSimpleFunctionChecker and SemanticPropertyChecker to emit REFERENCE occurrences for extension receiver types - Add SemanticEnumEntryChecker to emit DEFINITION occurrences for enum entries (previously missing from AnalyzerDeclarationCheckers) - Fix semanticdbKind() to return Kind.ENUM_MEMBER for FirEnumEntry (FirEnumEntry extends FirVariable, which would otherwise map to Kind.LOCAL) - Fix SemanticClassReferenceExpressionChecker (is/as operators) to use the already-extracted typeRef and source locals consistently rather than re-accessing expression.conversionTypeRef on each line - Extract emitTypeRef() helper to reduce duplication across checkers; use it for supertype references in SemanticClassLikeChecker so they get the same fake-source guard as other type reference emissions - Add tests for extension receivers, enum entries, named/unnamed companion objects, string template references, and is/as type references Ported from https://github.com/mozsearch/semanticdb-kotlinc/commit/d823d350cee7d182706781e6e8c5c32582960d9b --- .../scip_java/kotlinc/AnalyzerCheckers.kt | 126 +-- .../kotlinc/ScipTextDocumentBuilder.kt | 1 + .../scip_java/kotlinc/ScipVisitor.kt | 11 + .../scip_java/kotlinc/SymbolsCache.kt | 73 +- .../scip_java/kotlinc/test/AnalyzerTest.kt | 765 +++++++++++++++++- .../common/src/main/kotlin/snapshots/Class.kt | 2 + .../main/kotlin/snapshots/CompanionOwner.kt | 11 +- .../src/main/kotlin/snapshots/Lambdas.kt | 4 +- 8 files changed, 922 insertions(+), 71 deletions(-) diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt index 5e120bda5..e5675daaa 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt @@ -11,11 +11,14 @@ import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.declaration.* import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirQualifiedAccessExpressionChecker +import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirResolvedQualifierChecker import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirTypeOperatorCallChecker import org.jetbrains.kotlin.fir.analysis.checkers.toClassLikeSymbol import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.declarations.utils.isCompanion import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression +import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier import org.jetbrains.kotlin.fir.expressions.FirTypeOperatorCall import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.calls.FirSyntheticFunctionSymbol @@ -24,6 +27,7 @@ import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider import org.jetbrains.kotlin.fir.resolve.toClassLikeSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirAnonymousObjectSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol +import org.jetbrains.kotlin.fir.types.FirTypeRef import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName @@ -36,6 +40,15 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio element.treeStructure .findChildByType(element.lighterASTNode, KtTokens.IDENTIFIER) ?.toKtLightSourceElement(element.treeStructure) ?: element + + context(context: CheckerContext) + private fun ScipVisitor.emitTypeRef(typeRef: FirTypeRef) { + val klass = typeRef.toClassLikeSymbol(context.session) + val source = typeRef.source + if (klass != null && source != null && source.kind !is KtFakeSourceElementKind) { + visitClassReference(klass, getIdentifier(source)) + } + } } override val declarationCheckers: DeclarationCheckers @@ -48,8 +61,10 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio Set = setOf(SemanticQualifiedAccessExpressionChecker()) - override val typeOperatorCallCheckers: - Set = + override val resolvedQualifierCheckers: Set = + setOf(SemanticResolvedQualifierChecker()) + + override val typeOperatorCallCheckers: Set = setOf(SemanticClassReferenceExpressionChecker()) } @@ -71,6 +86,7 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio override val typeAliasCheckers: Set = setOf(SemanticTypeAliasChecker()) override val propertyAccessorCheckers: Set = setOf(SemanticPropertyAccessorChecker()) + override val enumEntryCheckers: Set = setOf(SemanticEnumEntryChecker()) } private class SemanticFileChecker(private val sourceroot: Path) : @@ -189,19 +205,32 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio } else { null } + val identifierSource = getIdentifier(source) + // For unnamed companion objects, getIdentifier() falls back to source (no IDENTIFIER + // token). Use the 'companion' keyword as the range instead. The COMPANION_KEYWORD is + // inside a MODIFIER_LIST child, so we use findDescendantByType instead of + // findChildByType. + val companionKeyword = + if ( + identifierSource === source && + declaration is FirRegularClass && + declaration.isCompanion + ) { + source.treeStructure + .findDescendantByType(source.lighterASTNode, KtTokens.COMPANION_KEYWORD) + ?.toKtLightSourceElement(source.treeStructure) + } else { + null + } visitor?.visitClassOrObject( declaration, - objectKeyword ?: getIdentifier(source), + objectKeyword ?: companionKeyword ?: identifierSource, enclosingSource = source, ) if (declaration is FirClass) { for (superType in declaration.superTypeRefs) { - val superSymbol = superType.toClassLikeSymbol(context.session) - val superSource = superType.source - if (superSymbol != null && superSource != null) { - visitor?.visitClassReference(superSymbol, superSource) - } + visitor?.emitTypeRef(superType) } } } @@ -260,14 +289,8 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio getIdentifier(source), enclosingSource = source, ) - - val klass = declaration.returnTypeRef.toClassLikeSymbol(context.session) - val klassSource = declaration.returnTypeRef.source - if ( - klass != null && klassSource != null && klassSource.kind !is KtFakeSourceElementKind - ) { - visitor?.visitClassReference(klass, getIdentifier(klassSource)) - } + visitor?.emitTypeRef(declaration.returnTypeRef) + declaration.receiverParameter?.typeRef?.let { visitor?.emitTypeRef(it) } } } @@ -289,14 +312,8 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] visitor?.visitProperty(declaration, getIdentifier(source), enclosingSource = source) - - val klass = declaration.returnTypeRef.toClassLikeSymbol(context.session) - val klassSource = declaration.returnTypeRef.source - if ( - klass != null && klassSource != null && klassSource.kind !is KtFakeSourceElementKind - ) { - visitor?.visitClassReference(klass, getIdentifier(klassSource)) - } + visitor?.emitTypeRef(declaration.returnTypeRef) + declaration.receiverParameter?.typeRef?.let { visitor?.emitTypeRef(it) } } } @@ -307,14 +324,7 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] visitor?.visitParameter(declaration, getIdentifier(source), enclosingSource = source) - - val klass = declaration.returnTypeRef.toClassLikeSymbol(context.session) - val klassSource = declaration.returnTypeRef.source - if ( - klass != null && klassSource != null && klassSource.kind !is KtFakeSourceElementKind - ) { - visitor?.visitClassReference(klass, getIdentifier(klassSource)) - } + visitor?.emitTypeRef(declaration.returnTypeRef) } } @@ -366,6 +376,29 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio } } + private class SemanticEnumEntryChecker : FirEnumEntryChecker(MppCheckerKind.Common) { + context(context: CheckerContext, reporter: DiagnosticReporter) + override fun check(declaration: FirEnumEntry) { + val source = declaration.source ?: return + val ktFile = context.containingFileSymbol?.sourceFile ?: return + val visitor = visitors[ktFile] + visitor?.visitEnumEntry(declaration, getIdentifier(source), enclosingSource = source) + } + } + + private class SemanticResolvedQualifierChecker : + FirResolvedQualifierChecker(MppCheckerKind.Common) { + context(context: CheckerContext, reporter: DiagnosticReporter) + override fun check(expression: FirResolvedQualifier) { + val symbol = expression.symbol ?: return + val source = expression.source ?: return + if (source.kind is KtFakeSourceElementKind) return + val ktFile = context.containingFileSymbol?.sourceFile ?: return + val visitor = visitors[ktFile] + visitor?.visitClassReference(symbol, getIdentifier(source)) + } + } + private class SemanticQualifiedAccessExpressionChecker : FirQualifiedAccessExpressionChecker(MppCheckerKind.Common) { context(context: CheckerContext, reporter: DiagnosticReporter) @@ -378,10 +411,8 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] - visitor?.visitSimpleNameExpression( - calleeReference, - getIdentifier(calleeReference.source ?: source), - ) + val identifierSource = getIdentifier(calleeReference.source ?: source) + visitor?.visitSimpleNameExpression(calleeReference, identifierSource) val resolvedSymbol = calleeReference.resolvedSymbol if ( @@ -390,10 +421,7 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio ) { val referencedKlass = resolvedSymbol.resolvedReturnType.toClassLikeSymbol() if (referencedKlass != null) { - visitor?.visitClassReference( - referencedKlass, - getIdentifier(calleeReference.source ?: source), - ) + visitor?.visitClassReference(referencedKlass, identifierSource) } } @@ -401,16 +429,10 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio // symbols if (resolvedSymbol is FirPropertySymbol) { resolvedSymbol.getterSymbol?.let { - visitor?.visitCallableReference( - it, - getIdentifier(calleeReference.source ?: source), - ) + visitor?.visitCallableReference(it, identifierSource) } resolvedSymbol.setterSymbol?.let { - visitor?.visitCallableReference( - it, - getIdentifier(calleeReference.source ?: source), - ) + visitor?.visitCallableReference(it, identifierSource) } } } @@ -422,15 +444,11 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio override fun check(expression: FirTypeOperatorCall) { val typeRef = expression.conversionTypeRef val source = typeRef.source ?: return - val classSymbol = - expression.conversionTypeRef.toClassLikeSymbol(context.session) ?: return + val classSymbol = typeRef.toClassLikeSymbol(context.session) ?: return val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] - visitor?.visitClassReference( - classSymbol, - getIdentifier(expression.conversionTypeRef.source ?: source), - ) + visitor?.visitClassReference(classSymbol, getIdentifier(source)) } } } diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt index ad09c9dc0..948497611 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt @@ -177,6 +177,7 @@ class ScipTextDocumentBuilder( is FirValueParameter -> Kind.Parameter is FirField -> Kind.Field is FirProperty -> Kind.Property + is FirEnumEntry -> Kind.EnumMember is FirVariable -> Kind.Variable is FirCallableDeclaration -> Kind.Method is FirPackageDirective -> Kind.Package diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipVisitor.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipVisitor.kt index d5d668864..5cfbe6c5a 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipVisitor.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipVisitor.kt @@ -168,6 +168,17 @@ class ScipVisitor( .emitAll(source, isDefinition = true, enclosingSource) } + context(context: CheckerContext) + fun visitEnumEntry( + firEnumEntry: FirEnumEntry, + source: KtSourceElement, + enclosingSource: KtSourceElement? = null, + ) { + cache[firEnumEntry.symbol] + .with(firEnumEntry.symbol) + .emitAll(source, isDefinition = true, enclosingSource) + } + context(context: CheckerContext) fun visitSimpleNameExpression( firResolvedNamedReference: FirResolvedNamedReference, diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt index bfff11f0b..b628e4527 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt @@ -12,6 +12,8 @@ import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.SymbolInternals import org.jetbrains.kotlin.fir.symbols.impl.* +import org.jetbrains.kotlin.fir.types.classId +import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.name.FqName import org.scip_code.scip_java.kotlinc.ScipSymbolDescriptor.Kind import org.scip_code.scip_java.shared.LocalSymbolsCache as SharedLocalSymbolsCache @@ -116,8 +118,27 @@ class GlobalSymbolsCache(testing: Boolean = false) : Iterable { is FirPropertyAccessorSymbol -> return getSymbol(symbol.propertySymbol, locals) is FirCallableSymbol -> { val session = symbol.fir.moduleData.session - return symbol.getContainingSymbol(session)?.let { getSymbol(it, locals) } - ?: getSymbol(symbol.packageFqName()) + val containingSymbol = symbol.getContainingSymbol(session) + // For top-level extension functions/properties (containingSymbol is file or null), + // use the receiver type as a synthetic parent within the package + // (e.g. sample/String#foo().). + if (containingSymbol == null || containingSymbol is FirFileSymbol) { + val receiverClassId = symbol.fir.receiverParameter?.typeRef?.coneType?.classId + if (receiverClassId != null) { + val packageSymbol = getSymbol(symbol.packageFqName()) + return Symbol.createGlobal( + packageSymbol, + ScipSymbolDescriptor( + Kind.TYPE, + receiverClassId.shortClassName.asString(), + ), + ) + } + } + containingSymbol?.let { + return getSymbol(it, locals) + } + return getSymbol(symbol.packageFqName()) } is FirClassLikeSymbol -> { val session = symbol.fir.moduleData.session @@ -172,12 +193,42 @@ class GlobalSymbolsCache(testing: Boolean = false) : Iterable { val siblings = when (val containingSymbol = symbol.getContainingSymbol(session)) { is FirClassSymbol -> containingSymbol.fir.declarations.map { it.symbol } - is FirFileSymbol -> containingSymbol.fir.declarations.map { it.symbol } - null -> - symbol.moduleData.session.symbolProvider.getTopLevelCallableSymbols( - symbol.packageFqName(), - symbol.name, - ) + is FirFileSymbol, + null -> { + // For top-level extension functions, siblings are the receiver class members + // (if in the same package) followed by other extension functions on the same + // receiver type in this package. This ensures consistent disambiguation + // when both a class member and an extension share the same parent namespace + // (e.g. sample/MyClass#foo(). vs sample/MyClass#foo(+1).). + val receiverClassId = symbol.fir.receiverParameter?.typeRef?.coneType?.classId + if (receiverClassId != null) { + val receiverClass = + session.symbolProvider.getClassLikeSymbolByClassId(receiverClassId) + as? FirClassSymbol<*> + val classMembers = + if (receiverClass?.packageFqName() == symbol.packageFqName()) { + receiverClass.fir.declarations.map { it.symbol } + } else { + emptyList() + } + val extensionFns = + session.symbolProvider + .getTopLevelCallableSymbols(symbol.packageFqName(), symbol.name) + .filter { + it is FirFunctionSymbol<*> && + it.fir.receiverParameter?.typeRef?.coneType?.classId == + receiverClassId + } + classMembers + extensionFns + } else if (containingSymbol is FirFileSymbol) { + containingSymbol.fir.declarations.map { it.symbol } + } else { + session.symbolProvider.getTopLevelCallableSymbols( + symbol.packageFqName(), + symbol.name, + ) + } + } else -> return "()" } @@ -194,7 +245,11 @@ class GlobalSymbolsCache(testing: Boolean = false) : Iterable { } } - if (count == 0 || !found) return "()" + if (!found) { + err.println("methodDisambiguator: ${symbol.callableId} not found in sibling list") + return "()" + } + if (count == 0) return "()" return "(+${count})" } diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt index 20fcaf0f3..76a039d51 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt @@ -1868,10 +1868,773 @@ class AnalyzerTest { document.assertDocumentation("sample/docstrings().", "Example method docstring") } + @Test + fun `extension receiver type reference`(@TempDir path: Path) { + // String is from the kotlin package; our extension in sample gets + // symbol sample/String#foo(). — distinct from kotlin/String#foo(). + // This means extensions on cross-package types never collide with + // the receiver class's own methods in the symbol table. + val document = + compileScip( + path, + """ + package sample + + fun String.foo(): Int = 42 + fun use(s: String) = s.foo() + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/String#foo()." + range { + startLine = 2 + startCharacter = 11 + endLine = 2 + endCharacter = 14 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 2 + endCharacter = 26 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "kotlin/String#" + range { + startLine = 2 + startCharacter = 4 + endLine = 2 + endCharacter = 10 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "kotlin/Int#" + range { + startLine = 2 + startCharacter = 18 + endLine = 2 + endCharacter = 21 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/String#foo()." + range { + startLine = 3 + startCharacter = 23 + endLine = 3 + endCharacter = 26 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/String#foo()." + kind = Kind.Method + enclosingSymbol = "sample/" + displayName = "foo" + signatureText = "public final fun String.foo(): Int" + } + ) + document.symbolsList.shouldContainAll(*symbols) + } + + @Test + fun `extension property receiver type reference`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + val Int.asString: String get() = this.toString() + fun use() = 42.asString + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/Int#asString." + range { + startLine = 2 + startCharacter = 8 + endLine = 2 + endCharacter = 16 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 2 + endCharacter = 48 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "kotlin/Int#" + range { + startLine = 2 + startCharacter = 4 + endLine = 2 + endCharacter = 7 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "kotlin/String#" + range { + startLine = 2 + startCharacter = 18 + endLine = 2 + endCharacter = 24 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Int#asString." + range { + startLine = 3 + startCharacter = 15 + endLine = 3 + endCharacter = 23 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/Int#asString." + kind = Kind.Property + enclosingSymbol = "sample/" + displayName = "asString" + signatureText = "public final val Int.asString: String" + } + ) + document.symbolsList.shouldContainAll(*symbols) + } + + @Test + fun `extension overload disambiguator`(@TempDir path: Path) { + // When a class already has a member named foo() and an extension also + // adds foo(), the extension is counted after the member in the combined + // sibling list (class members + same-package extensions on the same + // receiver type), so the extension gets the (+1) disambiguator and the + // two produce distinct symbols. + val document = + compileScip( + path, + """ + package sample + + class MyClass { + fun foo() {} + } + fun MyClass.foo(x: Int) {} + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/MyClass#foo()." + range { + startLine = 3 + startCharacter = 8 + endLine = 3 + endCharacter = 11 + } + enclosingRange { + startLine = 3 + startCharacter = 4 + endLine = 3 + endCharacter = 16 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/MyClass#foo(+1)." + range { + startLine = 5 + startCharacter = 12 + endLine = 5 + endCharacter = 15 + } + enclosingRange { + startLine = 5 + startCharacter = 0 + endLine = 5 + endCharacter = 26 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + } + + @Test + fun `enum entry definitions`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + enum class Color { RED, GREEN, BLUE } + + fun useEnum(): Color = Color.RED + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/Color#" + range { + startLine = 2 + startCharacter = 11 + endLine = 2 + endCharacter = 16 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 2 + endCharacter = 37 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Color#RED." + range { + startLine = 2 + startCharacter = 19 + endLine = 2 + endCharacter = 22 + } + enclosingRange { + startLine = 2 + startCharacter = 19 + endLine = 2 + endCharacter = 23 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Color#GREEN." + range { + startLine = 2 + startCharacter = 24 + endLine = 2 + endCharacter = 29 + } + enclosingRange { + startLine = 2 + startCharacter = 24 + endLine = 2 + endCharacter = 30 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Color#BLUE." + range { + startLine = 2 + startCharacter = 31 + endLine = 2 + endCharacter = 35 + } + enclosingRange { + startLine = 2 + startCharacter = 31 + endLine = 2 + endCharacter = 35 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Color#" + range { + startLine = 4 + startCharacter = 23 + endLine = 4 + endCharacter = 28 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Color#RED." + range { + startLine = 4 + startCharacter = 29 + endLine = 4 + endCharacter = 32 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/Color#" + kind = Kind.Class + enclosingSymbol = "sample/" + displayName = "Color" + addOverriddenSymbols("kotlin/Enum#") + signatureText = "public final enum class Color : Enum" + }, + scipSymbol { + symbol = "sample/Color#RED." + kind = Kind.EnumMember + enclosingSymbol = "sample/Color#" + displayName = "RED" + signatureText = "public final static enum entry RED: Color" + }, + scipSymbol { + symbol = "sample/Color#GREEN." + kind = Kind.EnumMember + enclosingSymbol = "sample/Color#" + displayName = "GREEN" + signatureText = "public final static enum entry GREEN: Color" + }, + scipSymbol { + symbol = "sample/Color#BLUE." + kind = Kind.EnumMember + enclosingSymbol = "sample/Color#" + displayName = "BLUE" + signatureText = "public final static enum entry BLUE: Color" + }, + ) + document.symbolsList.shouldContainAll(*symbols) + } + + @Test + fun `named object declarations`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + object MySingleton { + fun hello(): String = "hi" + } + fun use() = MySingleton.hello() + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/MySingleton#" + range { + startLine = 2 + startCharacter = 7 + endLine = 2 + endCharacter = 18 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 4 + endCharacter = 1 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/MySingleton#hello()." + range { + startLine = 3 + startCharacter = 8 + endLine = 3 + endCharacter = 13 + } + enclosingRange { + startLine = 3 + startCharacter = 4 + endLine = 3 + endCharacter = 30 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/MySingleton#" + range { + startLine = 5 + startCharacter = 12 + endLine = 5 + endCharacter = 23 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/MySingleton#hello()." + range { + startLine = 5 + startCharacter = 24 + endLine = 5 + endCharacter = 29 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/MySingleton#" + kind = Kind.Class + enclosingSymbol = "sample/" + displayName = "MySingleton" + signatureText = "public final object MySingleton : Any" + }, + scipSymbol { + symbol = "sample/MySingleton#hello()." + kind = Kind.Method + enclosingSymbol = "sample/MySingleton#" + displayName = "hello" + signatureText = "public final fun hello(): String" + }, + ) + document.symbolsList.shouldContainAll(*symbols) + } + + @Test + fun `companion object`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + class Foo { + companion object Factory { + fun create(): Foo = Foo() + } + } + fun use() = Foo.Factory.create() + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/Foo#" + range { + startLine = 2 + startCharacter = 6 + endLine = 2 + endCharacter = 9 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 6 + endCharacter = 1 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Foo#Factory#" + range { + startLine = 3 + startCharacter = 21 + endLine = 3 + endCharacter = 28 + } + enclosingRange { + startLine = 3 + startCharacter = 4 + endLine = 5 + endCharacter = 5 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Foo#Factory#create()." + range { + startLine = 4 + startCharacter = 12 + endLine = 4 + endCharacter = 18 + } + enclosingRange { + startLine = 4 + startCharacter = 8 + endLine = 4 + endCharacter = 33 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Foo#" + range { + startLine = 7 + startCharacter = 12 + endLine = 7 + endCharacter = 15 + } + }, + // Foo.Factory is a FirResolvedQualifier spanning the full qualifier expression + scipOccurrence { + role = REFERENCE + symbol = "sample/Foo#Factory#" + range { + startLine = 7 + startCharacter = 12 + endLine = 7 + endCharacter = 23 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Foo#Factory#create()." + range { + startLine = 7 + startCharacter = 24 + endLine = 7 + endCharacter = 30 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/Foo#" + kind = Kind.Class + enclosingSymbol = "sample/" + displayName = "Foo" + signatureText = "public final class Foo : Any" + }, + scipSymbol { + symbol = "sample/Foo#Factory#" + kind = Kind.Class + enclosingSymbol = "sample/Foo#" + displayName = "Factory" + signatureText = "public final companion object Factory : Any" + }, + scipSymbol { + symbol = "sample/Foo#Factory#create()." + kind = Kind.Method + enclosingSymbol = "sample/Foo#Factory#" + displayName = "create" + signatureText = "public final fun create(): Foo" + }, + ) + document.symbolsList.shouldContainAll(*symbols) + } + + @Test + fun `unnamed companion object`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + class Bar { + companion object { + fun instance(): Bar = Bar() + } + } + fun use() = Bar.instance() + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/Bar#" + range { + startLine = 2 + startCharacter = 6 + endLine = 2 + endCharacter = 9 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 6 + endCharacter = 1 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Bar#Companion#instance()." + range { + startLine = 4 + startCharacter = 12 + endLine = 4 + endCharacter = 20 + } + enclosingRange { + startLine = 4 + startCharacter = 8 + endLine = 4 + endCharacter = 35 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Bar#" + range { + startLine = 7 + startCharacter = 12 + endLine = 7 + endCharacter = 15 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Bar#Companion#instance()." + range { + startLine = 7 + startCharacter = 16 + endLine = 7 + endCharacter = 24 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Bar#Companion#" + range { + startLine = 3 + startCharacter = 4 + endLine = 3 + endCharacter = 13 + } + enclosingRange { + startLine = 3 + startCharacter = 4 + endLine = 5 + endCharacter = 5 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/Bar#" + kind = Kind.Class + enclosingSymbol = "sample/" + displayName = "Bar" + signatureText = "public final class Bar : Any" + }, + scipSymbol { + symbol = "sample/Bar#Companion#" + kind = Kind.Class + enclosingSymbol = "sample/Bar#" + displayName = "Companion" + signatureText = "public final companion object Companion : Any" + }, + scipSymbol { + symbol = "sample/Bar#Companion#instance()." + kind = Kind.Method + enclosingSymbol = "sample/Bar#Companion#" + displayName = "instance" + signatureText = "public final fun instance(): Bar" + }, + ) + document.symbolsList.shouldContainAll(*symbols) + } + + @Test + fun `string template references`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + fun greet(name: String) = "Hello, ${'$'}name!" + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/greet()." + range { + startLine = 2 + startCharacter = 4 + endLine = 2 + endCharacter = 9 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 2 + endCharacter = 41 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/greet().(name)" + range { + startLine = 2 + startCharacter = 10 + endLine = 2 + endCharacter = 14 + } + enclosingRange { + startLine = 2 + startCharacter = 10 + endLine = 2 + endCharacter = 22 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/greet().(name)" + range { + startLine = 2 + startCharacter = 35 + endLine = 2 + endCharacter = 39 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/greet()." + kind = Kind.Method + enclosingSymbol = "sample/" + displayName = "greet" + signatureText = "public final fun greet(name: String): String" + } + ) + document.symbolsList.shouldContainAll(*symbols) + } + private fun Document.assertDocumentation(symbol: String, expectedDocumentation: String) { val info = this.symbolsList.find { it.symbol == symbol } - ?: fail("no SymbolInformation for symbol $symbol") + ?: fail("no scipSymbol for symbol $symbol") val obtainedDocumentation = info.documentationList.joinToString("\n").trim() assertEquals(expectedDocumentation, obtainedDocumentation) } diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt index b5da6c141..288589564 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Class.kt @@ -105,6 +105,7 @@ // display_name doStuff // signature_documentation // > public final fun doStuff(): Unit +// ^^^^ reference scip-java maven . . kotlin/Unit# // ⌃ enclosing_range_end local 2 } // ⌃ enclosing_range_end scip-java maven . . snapshots/Class#asdf. @@ -148,6 +149,7 @@ // > public final fun run(): Unit println(Class::class) // ^^^^^^^ reference scip-java maven . . kotlin/io/println(). +// ^^^^^ reference scip-java maven . . snapshots/Class# println("I eat $banana for lunch") // ^^^^^^^ reference scip-java maven . . kotlin/io/println(). // ^^^^^^ reference scip-java maven . . snapshots/Class#banana. diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/CompanionOwner.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/CompanionOwner.kt index f16b420f1..db2d168b9 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/CompanionOwner.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/CompanionOwner.kt @@ -17,11 +17,11 @@ // ⌄ enclosing_range_start scip-java maven . . snapshots/CompanionOwner#Companion# // ⌄ enclosing_range_start scip-java maven . . snapshots/CompanionOwner#Companion#``(). companion object { -// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/CompanionOwner#Companion# -// kind Class -// display_name Companion -// signature_documentation -// > public final companion object Companion : Any +// ^^^^^^^^^ definition scip-java maven . . snapshots/CompanionOwner#Companion# +// kind Class +// display_name Companion +// signature_documentation +// > public final companion object Companion : Any // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . snapshots/CompanionOwner#Companion#``(). // kind Constructor // display_name Companion @@ -48,6 +48,7 @@ // signature_documentation // > public final fun create(): Int // ^^^ reference scip-java maven . . kotlin/Int# +// ^^^^^^^^^^^^^^ reference scip-java maven . . snapshots/CompanionOwner# // ^^^^^^ reference scip-java maven . . snapshots/CompanionOwner#Companion#create(). // ^^^^^^^^ reference scip-java maven . . kotlin/Any#hashCode(). // ⌃ enclosing_range_end scip-java maven . . snapshots/CompanionOwner#create(). diff --git a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt index ffcfb7682..1748c0b3b 100644 --- a/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt +++ b/scip-snapshots/expected/kotlin/common/scip-snapshots/cases/kotlin/common/src/main/kotlin/snapshots/Lambdas.kt @@ -17,7 +17,7 @@ // signature_documentation // > public get(): Unit // ^^^^^^^^^^^ reference scip-java maven . . kotlin/collections/arrayListOf(). -// ^^^^^^^^^^^^^^ reference scip-java maven . . kotlin/collections/forEachIndexed(+9). +// ^^^^^^^^^^^^^^ reference scip-java maven . . kotlin/collections/Iterable#forEachIndexed(). // ^ definition local 0 // kind Parameter // display_name i @@ -50,7 +50,7 @@ // signature_documentation // > public get(): ByteArray // ^^^ reference scip-java maven . . kotlin/run(+1). -// ^^^^^^^^^^^ reference scip-java maven . . kotlin/text/toByteArray(). +// ^^^^^^^^^^^ reference scip-java maven . . kotlin/text/String#toByteArray(). // ⌃ enclosing_range_end scip-java maven . . snapshots/y. // ⌃ enclosing_range_end scip-java maven . . snapshots/y.get(). From 358dea9cd996f3a8b608532e7ad0265760acf555 Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Fri, 3 Jul 2026 17:22:24 +0200 Subject: [PATCH 14/18] scip-kotlinc: Add tests for multiple supertypes and overload disambiguators - multiple supertype references: verifies that SemanticClassLikeChecker emits REFERENCE occurrences for every entry in superTypeRefs, and that SymbolInformation.overriddenSymbols is populated for all supertypes - three-way overload disambiguator: extends the existing two-overload coverage to three, verifying the (+1)/(+2) suffix counting logic in methodDisambiguator() Ported from https://github.com/mozsearch/semanticdb-kotlinc/commit/24b64333c0ac45cd0a6bb1021423881133d2e755 --- .../scip_java/kotlinc/test/AnalyzerTest.kt | 247 ++++++++++++++++++ 1 file changed, 247 insertions(+) diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt index 76a039d51..1b91275b9 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt @@ -1174,6 +1174,13 @@ class AnalyzerTest { else -> x as Float } } + + class Wrapper + fun classify(x: Any) { + if (x is Wrapper) {} + val s = x as? String + val w = x as Wrapper + } """, ) @@ -1199,6 +1206,36 @@ class AnalyzerTest { endCharacter = 26 } }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Wrapper#" + range { + startLine = 11 + startCharacter = 13 + endLine = 11 + endCharacter = 20 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "kotlin/String#" + range { + startLine = 12 + startCharacter = 18 + endLine = 12 + endCharacter = 24 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Wrapper#" + range { + startLine = 13 + startCharacter = 17 + endLine = 13 + endCharacter = 24 + } + }, ) document.occurrencesList.shouldContainAll(*occurrences) } @@ -2631,6 +2668,216 @@ class AnalyzerTest { document.symbolsList.shouldContainAll(*symbols) } + @Test + fun `multiple supertype references`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + interface Named + interface Speakable + class Person : Named, Speakable + fun use(p: Person): Named = p + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/Person#" + range { + startLine = 4 + startCharacter = 6 + endLine = 4 + endCharacter = 12 + } + enclosingRange { + startLine = 4 + startCharacter = 0 + endLine = 4 + endCharacter = 31 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Named#" + range { + startLine = 4 + startCharacter = 15 + endLine = 4 + endCharacter = 20 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Speakable#" + range { + startLine = 4 + startCharacter = 22 + endLine = 4 + endCharacter = 31 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/Named#" + range { + startLine = 5 + startCharacter = 20 + endLine = 5 + endCharacter = 25 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/Person#" + kind = Kind.Class + enclosingSymbol = "sample/" + displayName = "Person" + addOverriddenSymbols("sample/Named#") + addOverriddenSymbols("sample/Speakable#") + signatureText = "public final class Person : Named, Speakable" + } + ) + document.symbolsList.shouldContainAll(*symbols) + } + + @Test + fun `three-way overload disambiguator`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + fun add(x: Int, y: Int): Int = x + y + fun add(x: Double, y: Double): Double = x + y + fun add(x: String, y: String): String = x + y + fun use() { + add(1, 2) + add(1.0, 2.0) + add("a", "b") + } + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/add()." + range { + startLine = 2 + startCharacter = 4 + endLine = 2 + endCharacter = 7 + } + enclosingRange { + startLine = 2 + startCharacter = 0 + endLine = 2 + endCharacter = 36 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/add(+1)." + range { + startLine = 3 + startCharacter = 4 + endLine = 3 + endCharacter = 7 + } + enclosingRange { + startLine = 3 + startCharacter = 0 + endLine = 3 + endCharacter = 45 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/add(+2)." + range { + startLine = 4 + startCharacter = 4 + endLine = 4 + endCharacter = 7 + } + enclosingRange { + startLine = 4 + startCharacter = 0 + endLine = 4 + endCharacter = 45 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/add()." + range { + startLine = 6 + startCharacter = 4 + endLine = 6 + endCharacter = 7 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/add(+1)." + range { + startLine = 7 + startCharacter = 4 + endLine = 7 + endCharacter = 7 + } + }, + scipOccurrence { + role = REFERENCE + symbol = "sample/add(+2)." + range { + startLine = 8 + startCharacter = 4 + endLine = 8 + endCharacter = 7 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + scipSymbol { + symbol = "sample/add()." + kind = Kind.Method + enclosingSymbol = "sample/" + displayName = "add" + signatureText = "public final fun add(x: Int, y: Int): Int" + }, + scipSymbol { + symbol = "sample/add(+1)." + kind = Kind.Method + enclosingSymbol = "sample/" + displayName = "add" + signatureText = "public final fun add(x: Double, y: Double): Double" + }, + scipSymbol { + symbol = "sample/add(+2)." + kind = Kind.Method + enclosingSymbol = "sample/" + displayName = "add" + signatureText = "public final fun add(x: String, y: String): String" + }, + ) + document.symbolsList.shouldContainAll(*symbols) + } + private fun Document.assertDocumentation(symbol: String, expectedDocumentation: String) { val info = this.symbolsList.find { it.symbol == symbol } From 4d6bb1e07233ac103dd6580611ddcc1dbcda537b Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Fri, 3 Jul 2026 17:24:25 +0200 Subject: [PATCH 15/18] scip-kotlinc: Fix misleading LineMap docstrings The "non-0-based" phrasing predates this branch but is misleading: line helpers return 1-based values (callers subtract 1 for protobuf), while column helpers already return 0-based values. Make the docstrings say what they actually return. Ported from https://github.com/mozsearch/semanticdb-kotlinc/commit/305951777e98bfe80dfa5a2cf159fd940dab3bec --- .../kotlin/org/scip_code/scip_java/kotlinc/LineMap.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/LineMap.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/LineMap.kt index c2da05fdf..afd51bd0a 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/LineMap.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/LineMap.kt @@ -10,22 +10,22 @@ class LineMap(private val file: FirFile) { private fun offsetToLineAndCol(offset: Int): Pair? = file.sourceFileLinesMapping?.getLineAndColumnByOffset(offset) - /** Returns the non-0-based line number for a given offset */ + /** Returns the 1-based line number for a given offset (subtract 1 for protobuf). */ fun lineNumberForOffset(offset: Int): Int = file.sourceFileLinesMapping?.getLineByOffset(offset)?.let { it + 1 } ?: 0 - /** Returns the non-0-based column number for a given offset */ + /** Returns the 0-based column number for a given offset. */ fun columnForOffset(offset: Int): Int = offsetToLineAndCol(offset)?.second ?: 0 - /** Returns the non-0-based start character */ + /** Returns the 0-based start character. */ fun startCharacter(element: KtSourceElement): Int = offsetToLineAndCol(element.startOffset)?.second ?: 0 - /** Returns the non-0-based end character */ + /** Returns the 0-based end character. */ fun endCharacter(element: KtSourceElement): Int = startCharacter(element) + nameForOffset(element).length - /** Returns the non-0-based line number */ + /** Returns the 1-based line number (subtract 1 for protobuf). */ fun lineNumber(element: KtSourceElement): Int = file.sourceFileLinesMapping?.getLineByOffset(element.startOffset)?.let { it + 1 } ?: 0 From fec69895256490b765288d32cead50cd93f3cffe Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Fri, 3 Jul 2026 17:28:51 +0200 Subject: [PATCH 16/18] scip-kotlinc: Extend enclosing_range test coverage Add two test cases: - enum entry with body (Op.PLUS { override ... }): the body is modeled as a synthetic anonymous subclass, so the entry gets a multi-line enclosing_range and the overridden member surfaces as a local symbol rather than a sample/Op#PLUS.apply() global. Asserts both. - multi-line generic class declaration: general coverage we lacked for generic classes. Asserts enclosing_range for the class, its type parameter, and a member. Ported from https://github.com/mozsearch/semanticdb-kotlinc/commit/979f1f392a82c0d3cf8c17673ee3af20bb6b73c3 --- .../scip_java/kotlinc/test/AnalyzerTest.kt | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt index 1b91275b9..4f77e9a82 100644 --- a/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt +++ b/scip-kotlinc/src/test/kotlin/org/scip_code/scip_java/kotlinc/test/AnalyzerTest.kt @@ -720,6 +720,73 @@ class AnalyzerTest { document.symbolsList.shouldContainAll(*symbols) } + @Test + fun `generic class declaration`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + class Box(val value: T) { + fun unwrap(): T = value + } + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/Box#" + range { + startLine = 2 + startCharacter = 6 + endLine = 2 + endCharacter = 9 + } + enclosingRange { + startLine = 2 + endLine = 4 + endCharacter = 1 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Box#[T]" + range { + startLine = 2 + startCharacter = 10 + endLine = 2 + endCharacter = 11 + } + enclosingRange { + startLine = 2 + startCharacter = 10 + endLine = 2 + endCharacter = 11 + } + }, + scipOccurrence { + role = DEFINITION + symbol = "sample/Box#unwrap()." + range { + startLine = 3 + startCharacter = 8 + endLine = 3 + endCharacter = 14 + } + enclosingRange { + startLine = 3 + startCharacter = 4 + endLine = 3 + endCharacter = 27 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + } + @Test fun overrides(@TempDir path: Path) { val document = @@ -2259,6 +2326,91 @@ class AnalyzerTest { document.symbolsList.shouldContainAll(*symbols) } + @Test + fun `enum entry with body`(@TempDir path: Path) { + val document = + compileScip( + path, + """ + package sample + + enum class Op { + PLUS { + override fun apply(a: Int, b: Int) = a + b + }; + + abstract fun apply(a: Int, b: Int): Int + } + """, + ) + + val occurrences = + arrayOf( + scipOccurrence { + role = DEFINITION + symbol = "sample/Op#PLUS." + range { + startLine = 3 + startCharacter = 4 + endLine = 3 + endCharacter = 8 + } + // Body-having enum entry: enclosing_range covers PLUS through the + // trailing `};` that terminates the entry list. + enclosingRange { + startLine = 3 + startCharacter = 4 + endLine = 5 + endCharacter = 6 + } + }, + // An enum entry with a body is modeled as a synthetic anonymous subclass, + // so its overridden member is a local symbol (local2), not a global + // sample/Op#PLUS.apply(). It still gets an enclosing_range spanning the + // function declaration. + scipOccurrence { + role = DEFINITION + symbol = "local 2" + range { + startLine = 4 + startCharacter = 21 + endLine = 4 + endCharacter = 26 + } + enclosingRange { + startLine = 4 + startCharacter = 8 + endLine = 4 + endCharacter = 50 + } + }, + ) + document.occurrencesList.shouldContainAll(*occurrences) + + val symbols = + arrayOf( + // The entry body becomes an anonymous class enclosed by the entry... + scipSymbol { + symbol = "local 0" + kind = Kind.Class + enclosingSymbol = "sample/Op#PLUS." + displayName = "" + addOverriddenSymbols("sample/Op#") + signatureText = "object : Op" + }, + // ...and the override is a method of that anonymous class. + scipSymbol { + symbol = "local 2" + kind = Kind.Method + enclosingSymbol = "local 0" + displayName = "apply" + addOverriddenSymbols("sample/Op#apply().") + signatureText = "public open override fun apply(a: Int, b: Int): Int" + }, + ) + document.symbolsList.shouldContainAll(*symbols) + } + @Test fun `named object declarations`(@TempDir path: Path) { val document = From 6e94365ac9ca43c8b7d7ec6ab5b7e9dda36d863d Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Fri, 3 Jul 2026 17:36:07 +0200 Subject: [PATCH 17/18] Update to Kotlin 2.3.20 Changes in scip-kotlinc: - Rename FirSimpleFunction -> FirNamedFunction in SemanticSimpleFunctionChecker - Replace removed isLocalMember with isLocalDeclaredInBlock (moved within org.jetbrains.kotlin.fir.analysis.checkers.declaration) Ported from https://github.com/mozsearch/semanticdb-kotlinc/commit/832bf44ec114c14d2c65826a920d057d120dae0c --- gradle/libs.versions.toml | 2 +- .../fixtures/gradle/kotlin-jvm-toolchains/build.gradle | 2 +- .../src/test/resources/fixtures/gradle/kotlin2/build.gradle | 2 +- .../org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt | 2 +- .../kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0ee952e62..3f942792b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ gradle-api = "8.11.1" junit-jupiter = "5.11.4" kctfork = "0.12.1" kotest = "6.2.1" -kotlin = "2.3.10" +kotlin = "2.3.20" kotlinx-serialization = "1.11.0" lombok = "1.18.46" maven-plugin-annotations = "3.15.2" diff --git a/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle b/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle index 13a1ab323..74aa8b743 100644 --- a/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle +++ b/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.jetbrains.kotlin.jvm' version '2.3.10' + id 'org.jetbrains.kotlin.jvm' version '2.3.20' } java { toolchain { diff --git a/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle b/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle index 42f3cbd18..eccb6c797 100644 --- a/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle +++ b/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'org.jetbrains.kotlin.jvm' version '2.3.10' + id 'org.jetbrains.kotlin.jvm' version '2.3.20' } kotlin { jvmToolchain(17) diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt index e5675daaa..1251f3a8f 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/AnalyzerCheckers.kt @@ -280,7 +280,7 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio private class SemanticSimpleFunctionChecker : FirSimpleFunctionChecker(MppCheckerKind.Common) { context(context: CheckerContext, reporter: DiagnosticReporter) - override fun check(declaration: FirSimpleFunction) { + override fun check(declaration: FirNamedFunction) { val source = declaration.source ?: return val ktFile = context.containingFileSymbol?.sourceFile ?: return val visitor = visitors[ktFile] diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt index b628e4527..7ba63f961 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/SymbolsCache.kt @@ -1,7 +1,7 @@ package org.scip_code.scip_java.kotlinc import java.lang.System.err -import org.jetbrains.kotlin.fir.analysis.checkers.declaration.isLocalMember +import org.jetbrains.kotlin.fir.analysis.checkers.declaration.isLocalDeclaredInBlock import org.jetbrains.kotlin.fir.declarations.DirectDeclarationsAccess import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin import org.jetbrains.kotlin.fir.declarations.utils.memberDeclarationNameOrNull @@ -80,7 +80,7 @@ class GlobalSymbolsCache(testing: Boolean = false) : Iterable { private fun uncachedSymbol(symbol: FirBasedSymbol<*>?, locals: LocalSymbolsCache): Symbol { if (symbol == null || symbol is FirAnonymousFunctionSymbol) return Symbol.NONE - if (symbol.fir.isLocalMember) return locals + symbol + if (symbol.fir.isLocalDeclaredInBlock) return locals + symbol val owner = getParentSymbol(symbol, locals) From d778a65fefc6762daa72efe11f761d7fc41c04cb Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Fri, 3 Jul 2026 17:38:05 +0200 Subject: [PATCH 18/18] Update to Kotlin 2.4.0 Changes to scip-kotlinc: - -Xcontext-parameters argument isn't required anymore - FirAllModifierRenderer now takes an argument Ported from https://github.com/mozsearch/semanticdb-kotlinc/commit/89427a5a132de2e706c1e84256023709cc79da51 --- gradle/libs.versions.toml | 4 ++-- .../fixtures/gradle/kotlin-jvm-toolchains/build.gradle | 2 +- .../src/test/resources/fixtures/gradle/kotlin2/build.gradle | 2 +- scip-kotlinc/build.gradle.kts | 4 ---- .../scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt | 2 +- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3f942792b..a028c2915 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,9 +2,9 @@ clikt = "5.1.0" gradle-api = "8.11.1" junit-jupiter = "5.11.4" -kctfork = "0.12.1" +kctfork = "0.13.0" kotest = "6.2.1" -kotlin = "2.3.20" +kotlin = "2.4.0" kotlinx-serialization = "1.11.0" lombok = "1.18.46" maven-plugin-annotations = "3.15.2" diff --git a/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle b/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle index 74aa8b743..e097254af 100644 --- a/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle +++ b/scip-java/src/test/resources/fixtures/gradle/kotlin-jvm-toolchains/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.jetbrains.kotlin.jvm' version '2.3.20' + id 'org.jetbrains.kotlin.jvm' version '2.4.0' } java { toolchain { diff --git a/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle b/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle index eccb6c797..b559907c9 100644 --- a/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle +++ b/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'org.jetbrains.kotlin.jvm' version '2.3.20' + id 'org.jetbrains.kotlin.jvm' version '2.4.0' } kotlin { jvmToolchain(17) diff --git a/scip-kotlinc/build.gradle.kts b/scip-kotlinc/build.gradle.kts index 699d24dd9..aad935acf 100644 --- a/scip-kotlinc/build.gradle.kts +++ b/scip-kotlinc/build.gradle.kts @@ -24,10 +24,6 @@ dependencies { testImplementation(libs.kctfork.core) } -tasks.withType().configureEach { - compilerOptions.freeCompilerArgs.add("-Xcontext-parameters") -} - tasks.named("test") { maxHeapSize = "2g" } diff --git a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt index 948497611..4e904b3ef 100644 --- a/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt +++ b/scip-kotlinc/src/main/kotlin/org/scip_code/scip_java/kotlinc/ScipTextDocumentBuilder.kt @@ -154,7 +154,7 @@ class ScipTextDocumentBuilder( bodyRenderer = null, propertyAccessorRenderer = null, callArgumentsRenderer = FirCallNoArgumentsRenderer(), - modifierRenderer = FirAllModifierRenderer(), + modifierRenderer = FirAllModifierRenderer(FirModifierRenderer.StaticPolicy.Default), callableSignatureRenderer = FirCallableSignatureRendererForReadability(), declarationRenderer = FirDeclarationRenderer("local "), )