diff --git a/.gitignore b/.gitignore index 5b8baae2..31dd2c8e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ semanticdb-*.jar *.lsif generated/* -*.svg \ No newline at end of file +*.svg +/.kotlin/ +/semanticdb-kotlinc/META-INF/ diff --git a/build.gradle.kts b/build.gradle.kts index 97a0f995..30fa457f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,11 @@ import com.palantir.gradle.gitversion.VersionDetails +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import groovy.lang.Closure import org.gradle.jvm.toolchain.internal.CurrentJvmToolchainSpec plugins { - kotlin("jvm") version "2.1.20" + kotlin("jvm") version "2.2.0" id("com.github.johnrengelman.shadow") version "8.1.1" id("com.palantir.git-version") version "3.1.0" id("io.github.gradle-nexus.publish-plugin") version "1.1.0" @@ -65,11 +66,10 @@ subprojects { allprojects { afterEvaluate { - tasks.withType { - kotlinOptions.jvmTarget = "1.8" - } - kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_1_8 + } jvmToolchain { (this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(8)) } diff --git a/debug-project/build.gradle.kts b/debug-project/build.gradle.kts index 5f9fe2c4..67b5bcd5 100644 --- a/debug-project/build.gradle.kts +++ b/debug-project/build.gradle.kts @@ -1,3 +1,4 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { @@ -24,12 +25,14 @@ dependencies { } tasks.withType { - kotlinOptions.jvmTarget = "1.8" dependsOn(":${projects.semanticdbKotlinc.name}:shadowJar") +} + +kotlin { val targetroot = File(project.buildDir, "semanticdb-targetroot") - kotlinOptions { - jvmTarget = "11" - freeCompilerArgs = freeCompilerArgs + listOf( + compilerOptions { + jvmTarget = JvmTarget.JVM_11 + freeCompilerArgs.addAll( "-Xplugin=${semanticdbJar.first()}", "-P", "plugin:semanticdb-kotlinc:sourceroot=${projectDir.path}", diff --git a/semanticdb-kotlinc/build.gradle.kts b/semanticdb-kotlinc/build.gradle.kts index d641f936..7ca5adfb 100644 --- a/semanticdb-kotlinc/build.gradle.kts +++ b/semanticdb-kotlinc/build.gradle.kts @@ -1,4 +1,5 @@ import java.net.URI +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.publish.maven.MavenPublication @@ -40,7 +41,7 @@ dependencies { // https://github.com/tschuchortdev/kotlin-compile-testing/issues/390 // Until then, we use the fork from https://github.com/ZacSweers/kotlin-compile-testing instead. // testImplementation("com.github.tschuchortdev", "kotlin-compile-testing", "1.5.0") - testImplementation("dev.zacsweers.kctfork", "core", "0.7.0") + testImplementation("dev.zacsweers.kctfork", "core", "0.7.1") testImplementation("org.junit.jupiter", "junit-jupiter-params", "5.8.1") testImplementation("org.jetbrains.kotlin", "kotlin-stdlib-jdk8", "1.5.0") { @@ -56,8 +57,14 @@ dependencies { tasks.withType { dependsOn(":${projects.semanticdbKotlin.name}:build") - kotlinOptions { - freeCompilerArgs = freeCompilerArgs + listOf("-Xinline-classes") +} + +kotlin { + compilerOptions { + freeCompilerArgs.addAll( + "-Xinline-classes", + "-Xcontext-parameters", + ) } } @@ -203,10 +210,13 @@ subprojects { tasks.withType { dependsOn(projects.semanticdbKotlinc.dependencyProject.tasks.shadowJar.get().path) outputs.upToDateWhen { false } + } + + kotlin { val pluginJar = semanticdbJar.incoming.artifacts.artifactFiles.first().path - kotlinOptions { - jvmTarget = "1.8" - freeCompilerArgs = freeCompilerArgs + listOf( + compilerOptions { + jvmTarget = JvmTarget.JVM_1_8 + freeCompilerArgs.addAll( "-Xplugin=$pluginJar", "-P", "plugin:semanticdb-kotlinc:sourceroot=${sourceroot}", diff --git a/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/AnalyzerCheckers.kt b/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/AnalyzerCheckers.kt index b3cbe859..62d32f98 100644 --- a/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/AnalyzerCheckers.kt +++ b/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/AnalyzerCheckers.kt @@ -2,6 +2,7 @@ package com.sourcegraph.semanticdb_kotlinc import java.nio.file.Path import kotlin.contracts.ExperimentalContracts +import kotlin.math.exp import org.jetbrains.kotlin.* import org.jetbrains.kotlin.com.intellij.lang.LighterASTNode import org.jetbrains.kotlin.com.intellij.util.diff.FlyweightCapableTreeStructure @@ -11,18 +12,26 @@ import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind 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.FirBasicExpressionChecker +import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirClassReferenceExpressionChecker 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.* +import org.jetbrains.kotlin.fir.expressions.FirClassReferenceExpression import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression +import org.jetbrains.kotlin.fir.expressions.FirStatement +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.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.coneType +import org.jetbrains.kotlin.fir.types.resolvedType import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName @@ -47,6 +56,9 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio override val qualifiedAccessExpressionCheckers: Set = setOf(SemanticQualifiedAccessExpressionChecker()) + + override val typeOperatorCallCheckers: + Set = setOf(SemanticClassReferenceExpressionChecker()) } open class AnalyzerDeclarationCheckers(sourceroot: Path) : DeclarationCheckers() { @@ -77,11 +89,8 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio } @OptIn(ExperimentalContracts::class) - override fun check( - declaration: FirFile, - context: CheckerContext, - reporter: DiagnosticReporter - ) { + context(context: CheckerContext, reporter: DiagnosticReporter) + override fun check(declaration: FirFile) { val ktFile = declaration.sourceFile ?: return val lineMap = LineMap(declaration) val visitor = SemanticdbVisitor(sourceroot, ktFile, lineMap, globals) @@ -91,11 +100,8 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio class SemanticImportsChecker : FirFileChecker(MppCheckerKind.Common) { @OptIn(ExperimentalContracts::class) - override fun check( - declaration: FirFile, - context: CheckerContext, - reporter: DiagnosticReporter - ) { + context(context: CheckerContext, reporter: DiagnosticReporter) + override fun check(declaration: FirFile) { val ktFile = declaration.sourceFile ?: return val visitor = visitors[ktFile] @@ -169,11 +175,8 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio private class SemanticClassLikeChecker : FirClassLikeChecker(MppCheckerKind.Common) { @OptIn(ExperimentalContracts::class) - override fun check( - declaration: FirClassLikeDeclaration, - context: CheckerContext, - reporter: DiagnosticReporter - ) { + context(context: CheckerContext, reporter: DiagnosticReporter) + override fun check(declaration: FirClassLikeDeclaration) { val source = declaration.source ?: return val ktFile = context.containingFile?.sourceFile ?: return val visitor = visitors[ktFile] @@ -201,11 +204,8 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio private class SemanticConstructorChecker : FirConstructorChecker(MppCheckerKind.Common) { @OptIn(ExperimentalContracts::class) - override fun check( - declaration: FirConstructor, - context: CheckerContext, - reporter: DiagnosticReporter - ) { + context(context: CheckerContext, reporter: DiagnosticReporter) + override fun check(declaration: FirConstructor) { val source = declaration.source ?: return val ktFile = context.containingFile?.sourceFile ?: return val visitor = visitors[ktFile] @@ -239,11 +239,8 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio private class SemanticSimpleFunctionChecker : FirSimpleFunctionChecker(MppCheckerKind.Common) { @OptIn(ExperimentalContracts::class) - override fun check( - declaration: FirSimpleFunction, - context: CheckerContext, - reporter: DiagnosticReporter - ) { + context(context: CheckerContext, reporter: DiagnosticReporter) + override fun check(declaration: FirSimpleFunction) { val source = declaration.source ?: return val ktFile = context.containingFile?.sourceFile ?: return val visitor = visitors[ktFile] @@ -260,11 +257,8 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio private class SemanticAnonymousFunctionChecker : FirAnonymousFunctionChecker(MppCheckerKind.Common) { @OptIn(ExperimentalContracts::class) - override fun check( - declaration: FirAnonymousFunction, - context: CheckerContext, - reporter: DiagnosticReporter - ) { + context(context: CheckerContext, reporter: DiagnosticReporter) + override fun check(declaration: FirAnonymousFunction) { val source = declaration.source ?: return val ktFile = context.containingFile?.sourceFile ?: return val visitor = visitors[ktFile] @@ -274,11 +268,8 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio private class SemanticPropertyChecker : FirPropertyChecker(MppCheckerKind.Common) { @OptIn(ExperimentalContracts::class) - override fun check( - declaration: FirProperty, - context: CheckerContext, - reporter: DiagnosticReporter - ) { + context(context: CheckerContext, reporter: DiagnosticReporter) + override fun check(declaration: FirProperty) { val source = declaration.source ?: return val ktFile = context.containingFile?.sourceFile ?: return val visitor = visitors[ktFile] @@ -294,11 +285,8 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio private class SemanticValueParameterChecker : FirValueParameterChecker(MppCheckerKind.Common) { @OptIn(ExperimentalContracts::class) - override fun check( - declaration: FirValueParameter, - context: CheckerContext, - reporter: DiagnosticReporter - ) { + context(context: CheckerContext, reporter: DiagnosticReporter) + override fun check(declaration: FirValueParameter) { val source = declaration.source ?: return val ktFile = context.containingFile?.sourceFile ?: return val visitor = visitors[ktFile] @@ -314,11 +302,8 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio private class SemanticTypeParameterChecker : FirTypeParameterChecker(MppCheckerKind.Common) { @OptIn(ExperimentalContracts::class) - override fun check( - declaration: FirTypeParameter, - context: CheckerContext, - reporter: DiagnosticReporter - ) { + context(context: CheckerContext, reporter: DiagnosticReporter) + override fun check(declaration: FirTypeParameter) { val source = declaration.source ?: return val ktFile = context.containingFile?.sourceFile ?: return val visitor = visitors[ktFile] @@ -328,11 +313,8 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio private class SemanticTypeAliasChecker : FirTypeAliasChecker(MppCheckerKind.Common) { @OptIn(ExperimentalContracts::class) - override fun check( - declaration: FirTypeAlias, - context: CheckerContext, - reporter: DiagnosticReporter - ) { + context(context: CheckerContext, reporter: DiagnosticReporter) + override fun check(declaration: FirTypeAlias) { val source = declaration.source ?: return val ktFile = context.containingFile?.sourceFile ?: return val visitor = visitors[ktFile] @@ -343,11 +325,8 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio private class SemanticPropertyAccessorChecker : FirPropertyAccessorChecker(MppCheckerKind.Common) { @OptIn(ExperimentalContracts::class) - override fun check( - declaration: FirPropertyAccessor, - context: CheckerContext, - reporter: DiagnosticReporter - ) { + context(context: CheckerContext, reporter: DiagnosticReporter) + override fun check(declaration: FirPropertyAccessor) { val source = declaration.source ?: return val ktFile = context.containingFile?.sourceFile ?: return val visitor = visitors[ktFile] @@ -375,11 +354,8 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio private class SemanticQualifiedAccessExpressionChecker : FirQualifiedAccessExpressionChecker(MppCheckerKind.Common) { @OptIn(ExperimentalContracts::class) - override fun check( - expression: FirQualifiedAccessExpression, - context: CheckerContext, - reporter: DiagnosticReporter - ) { + context(context: CheckerContext, reporter: DiagnosticReporter) + override fun check(expression: FirQualifiedAccessExpression) { val source = expression.source ?: return val calleeReference = expression.calleeReference if ((calleeReference as? FirResolvedNamedReference) == null) { @@ -409,4 +385,19 @@ open class AnalyzerCheckers(session: FirSession) : FirAdditionalCheckersExtensio } } } + + private class SemanticClassReferenceExpressionChecker : + FirTypeOperatorCallChecker(MppCheckerKind.Common) { + @OptIn(ExperimentalContracts::class) + context(context: CheckerContext, reporter: DiagnosticReporter) + override fun check(expression: FirTypeOperatorCall) { + val typeRef = expression.conversionTypeRef + val source = typeRef.source ?: return + val classSymbol = expression.conversionTypeRef.toClassLikeSymbol(context.session) ?: return + val ktFile = context.containingFile?.sourceFile ?: return + val visitor = visitors[ktFile] + + visitor?.visitClassReference(classSymbol, getIdentifier(expression.conversionTypeRef.source ?: source), context) + } + } } diff --git a/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SemanticdbTextDocumentBuilder.kt b/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SemanticdbTextDocumentBuilder.kt index eb371e7e..edfaaa26 100644 --- a/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SemanticdbTextDocumentBuilder.kt +++ b/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SemanticdbTextDocumentBuilder.kt @@ -10,7 +10,7 @@ import org.jetbrains.kotlin.KtSourceFile import org.jetbrains.kotlin.com.intellij.lang.java.JavaLanguage import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext -import org.jetbrains.kotlin.fir.analysis.checkers.getDirectOverriddenSymbols +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.renderer.* @@ -80,7 +80,7 @@ class SemanticdbTextDocumentBuilder( .filterNotNull() .flatMap { cache[it] } is FirFunctionSymbol<*> -> - firBasedSymbol.fir.getDirectOverriddenSymbols(context).flatMap { cache[it] } + firBasedSymbol.directOverriddenSymbolsSafe(context).flatMap { cache[it] } else -> emptyList().asIterable() } return SymbolInformation { diff --git a/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SymbolsCache.kt b/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SymbolsCache.kt index 24a30e20..6b085981 100644 --- a/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SymbolsCache.kt +++ b/semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SymbolsCache.kt @@ -6,6 +6,7 @@ import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract 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 @@ -174,7 +175,7 @@ class GlobalSymbolsCache(testing: Boolean = false) : Iterable { } } - @OptIn(SymbolInternals::class) + @OptIn(SymbolInternals::class, DirectDeclarationsAccess::class) private fun methodDisambiguator(symbol: FirFunctionSymbol<*>): String { val session = symbol.moduleData.session diff --git a/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/AnalyzerTest.kt b/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/AnalyzerTest.kt index f0e78e3a..5d10fdb9 100644 --- a/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/AnalyzerTest.kt +++ b/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/AnalyzerTest.kt @@ -688,6 +688,50 @@ class AnalyzerTest { } } + @Test + fun `type operators`(@TempDir path: Path) { + val document = + compileSemanticdb( + path, + """ + package sample + + fun foo(x: Any) { + when (x) { + is Int -> true + else -> x as Float + } + } + """) + + val occurrences = + arrayOf( + SymbolOccurrence { + role = Role.REFERENCE + symbol = "kotlin/Int#" + range { + startLine = 4 + startCharacter = 11 + endLine = 4 + endCharacter = 14 + } + }, + SymbolOccurrence { + role = Role.REFERENCE + symbol = "kotlin/Float#" + range { + startLine = 5 + startCharacter = 21 + endLine = 5 + endCharacter = 26 + } + }, + ) + assertSoftly(document.occurrencesList) { + withClue(this) { occurrences.forEach(::shouldContain) } + } + } + @Test fun `exception test`(@TempDir path: Path) { val buildPath = File(path.resolve("build").toString()).apply { mkdir() } diff --git a/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/Utils.kt b/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/Utils.kt index aec3674b..77ffc63d 100644 --- a/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/Utils.kt +++ b/semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/Utils.kt @@ -25,7 +25,6 @@ import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirFileChecker import org.jetbrains.kotlin.fir.declarations.FirFile import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter -import org.jetbrains.kotlin.fir.extensions.FirExtensionSessionComponent.Factory import org.junit.jupiter.api.Assumptions.assumeFalse import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.DynamicTest.dynamicTest @@ -133,11 +132,8 @@ private class TestAnalyzerDeclarationCheckers( override val fileCheckers: Set = setOf( object : FirFileChecker(MppCheckerKind.Common) { - override fun check( - declaration: FirFile, - context: CheckerContext, - reporter: DiagnosticReporter - ) { + context(context: CheckerContext, reporter: DiagnosticReporter) + override fun check(declaration: FirFile) { val ktFile = declaration.sourceFile ?: return val lineMap = LineMap(declaration) val visitor = SemanticdbVisitor(sourceRoot, ktFile, lineMap, globals, locals)