FIR IDE: introduce HLSpecifyExplicitTypeForCallableDeclarationIntention and HLRedundantUnitReturnTypeInspection

This commit is contained in:
Ilya Kirillov
2021-02-07 12:41:28 +01:00
parent a6f76399e2
commit 91e135888d
48 changed files with 706 additions and 67 deletions
@@ -100,12 +100,10 @@ import org.jetbrains.kotlin.idea.highlighter.*
import org.jetbrains.kotlin.idea.imports.AbstractJsOptimizeImportsTest
import org.jetbrains.kotlin.idea.imports.AbstractJvmOptimizeImportsTest
import org.jetbrains.kotlin.idea.index.AbstractKotlinTypeAliasByExpansionShortNameIndexTest
import org.jetbrains.kotlin.idea.inspections.AbstractHLInspectionTest
import org.jetbrains.kotlin.idea.inspections.AbstractLocalInspectionTest
import org.jetbrains.kotlin.idea.inspections.AbstractMultiFileLocalInspectionTest
import org.jetbrains.kotlin.idea.intentions.AbstractConcatenatedStringGeneratorTest
import org.jetbrains.kotlin.idea.intentions.AbstractIntentionTest
import org.jetbrains.kotlin.idea.intentions.AbstractIntentionTest2
import org.jetbrains.kotlin.idea.intentions.AbstractMultiFileIntentionTest
import org.jetbrains.kotlin.idea.intentions.*
import org.jetbrains.kotlin.idea.intentions.declarations.AbstractJoinLinesTest
import org.jetbrains.kotlin.idea.internal.AbstractBytecodeToolWindowTest
import org.jetbrains.kotlin.idea.kdoc.AbstractKDocHighlightingTest
@@ -1108,6 +1106,16 @@ fun main(args: Array<String>) {
model("quickfix/modifiers", pattern = pattern, filenameStartsLowerCase = true, recursive = false)
model("quickfix/override/typeMismatchOnOverride", pattern = pattern, filenameStartsLowerCase = true, recursive = false)
}
testClass<AbstractHLInspectionTest> {
val pattern = "^(inspections\\.test)$"
model("inspections/redundantUnitReturnType", pattern = pattern, singleClass = true)
}
testClass<AbstractHLIntentionTest> {
val pattern = "^([\\w\\-_]+)\\.(kt|kts)$"
model("intentions/specifyTypeExplicitly", pattern = pattern)
}
}
testGroup("idea/idea-fir/tests", "idea/idea-completion/testData") {
@@ -0,0 +1,18 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.fir.applicators
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.fir.api.applicator.applicabilityTarget
import org.jetbrains.kotlin.psi.KtCallableDeclaration
object ApplicabilityRanges {
val SELF = applicabilityTarget<PsiElement> { it }
val CALLABLE_RETURN_TYPE = applicabilityTarget<KtCallableDeclaration> { decalration ->
decalration.typeReference?.typeElement
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.fir.applicators
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicatorInput
import org.jetbrains.kotlin.idea.fir.api.applicator.applicator
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.components.KtTypeRendererOptions
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
import org.jetbrains.kotlin.idea.frontend.api.types.isUnit
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtPsiFactory
object CallableReturnTypeUpdaterApplicator {
val applicator = applicator<KtCallableDeclaration, Type> {
familyName(KotlinBundle.message("fix.change.return.type.family"))
applyTo { declaration, type, project ->
val newTypeRef = if (!declaration.isProcedure(type)) {
// TODO use longTypeRepresentation and the shorten
KtPsiFactory(project ?: declaration.project).createType(type.shortTypeRepresentation)
} else null
runWriteAction {
declaration.typeReference = newTypeRef
}
}
}
private fun KtCallableDeclaration.isProcedure(type: Type) =
type.isUnit && this is KtFunction && hasBlockBody()
class Type(
val isUnit: Boolean,
val longTypeRepresentation: String,
val shortTypeRepresentation: String
) : HLApplicatorInput {
override fun isValidFor(psi: PsiElement): Boolean = true
companion object {
fun KtAnalysisSession.createByKtType(ktType: KtType): Type = Type(
isUnit = ktType.isUnit,
longTypeRepresentation = ktType.render(KtTypeRendererOptions.DEFAULT),
shortTypeRepresentation = ktType.render(KtTypeRendererOptions.SHORT_NAMES),
)
val UNIT = Type(isUnit = true, longTypeRepresentation = "kotlin.Unit", shortTypeRepresentation = "Unit")
}
}
}
@@ -1,43 +0,0 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.fir.inspections
import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.types.*
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtPsiFactory
class AddFunctionReturnTypeIntention :
AbstractHighLevelApiBasedIntention<KtNamedFunction, TypeCandidate>(
KtNamedFunction::class.java,
{ "Specify type explicitly" }
) {
override fun isApplicableByPsi(element: KtNamedFunction): Boolean =
element.typeReference == null && !element.hasBlockBody()
override fun KtAnalysisSession.analyzeAndGetData(element: KtNamedFunction): TypeCandidate? {
val returnType = element.getReturnKtType()
val approximated = approximateTypeToUpperDenotable(returnType) ?: return null
return TypeCandidate(approximated.render())
}
private tailrec fun approximateTypeToUpperDenotable(type: KtType): KtDenotableType? = when (type) {
is KtNonDenotableType -> when (type) {
is KtFlexibleType -> approximateTypeToUpperDenotable(type.upperBound)
is KtIntersectionType -> null
}
is KtDenotableType -> type
else -> null
}
override fun applyTo(element: KtNamedFunction, data: TypeCandidate, editor: Editor?) {
element.typeReference = KtPsiFactory(element).createType(data.candidate)
}
}
data class TypeCandidate(val candidate: String)
@@ -0,0 +1,46 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.fir.inspections.declarations
import com.intellij.codeInspection.CleanupLocalInspectionTool
import com.intellij.codeInspection.ProblemHighlightType
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.fir.api.*
import org.jetbrains.kotlin.idea.fir.api.applicator.inputProvider
import org.jetbrains.kotlin.idea.fir.api.applicator.presentation
import org.jetbrains.kotlin.idea.fir.api.applicator.with
import org.jetbrains.kotlin.idea.fir.applicators.ApplicabilityRanges
import org.jetbrains.kotlin.idea.fir.applicators.CallableReturnTypeUpdaterApplicator
import org.jetbrains.kotlin.idea.frontend.api.types.isUnit
import org.jetbrains.kotlin.psi.KtNamedFunction
internal class HLRedundantUnitReturnTypeInspection :
AbstractHLInspection<KtNamedFunction, CallableReturnTypeUpdaterApplicator.Type>(
KtNamedFunction::class
), CleanupLocalInspectionTool {
override val applicabilityRange = ApplicabilityRanges.CALLABLE_RETURN_TYPE
override val applicator = CallableReturnTypeUpdaterApplicator.applicator.with {
isApplicableByPsi { callable ->
val function = callable as? KtNamedFunction ?: return@isApplicableByPsi false
function.hasBlockBody() && function.typeReference != null
}
familyAndActionName(KotlinBundle.lazyMessage("remove.explicit.type.specification"))
}
override val inputProvider = inputProvider<KtNamedFunction, CallableReturnTypeUpdaterApplicator.Type> { function ->
when {
function.getFunctionSymbol().annotatedType.type.isUnit -> CallableReturnTypeUpdaterApplicator.Type.UNIT
else -> null
}
}
override val presentation = presentation<KtNamedFunction> {
inspectionText(KotlinBundle.message("redundant.unit.return.type"))
highlightType(ProblemHighlightType.LIKE_UNUSED_SYMBOL)
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.fir.intentions.declarations
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.fir.api.*
import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicabilityRange
import org.jetbrains.kotlin.idea.fir.api.applicator.applicabilityTarget
import org.jetbrains.kotlin.idea.fir.api.applicator.inputProvider
import org.jetbrains.kotlin.idea.fir.api.applicator.with
import org.jetbrains.kotlin.idea.fir.applicators.ApplicabilityRanges
import org.jetbrains.kotlin.idea.fir.applicators.CallableReturnTypeUpdaterApplicator
import org.jetbrains.kotlin.idea.frontend.api.types.*
import org.jetbrains.kotlin.psi.*
class HLSpecifyExplicitTypeForCallableDeclarationIntention :
AbstractHLIntention<KtCallableDeclaration, CallableReturnTypeUpdaterApplicator.Type>(
KtCallableDeclaration::class
) {
override val applicator = CallableReturnTypeUpdaterApplicator.applicator.with {
isApplicableByPsi { declaration: KtCallableDeclaration ->
if (declaration is KtConstructor<*> || declaration is KtFunctionLiteral) return@isApplicableByPsi false
declaration.typeReference == null && (declaration as? KtNamedFunction)?.hasBlockBody() != true
}
familyAndActionName(KotlinBundle.lazyMessage("specify.return.type.explicitly"))
}
override val applicabilityRange: HLApplicabilityRange<KtCallableDeclaration> = ApplicabilityRanges.SELF
override val inputProvider = inputProvider<KtCallableDeclaration, CallableReturnTypeUpdaterApplicator.Type> { declaration ->
val returnType = declaration.getReturnKtType()
val denotableType = returnType.approximateToPublicDenotable() ?: return@inputProvider null
with(CallableReturnTypeUpdaterApplicator.Type) { createByKtType(denotableType) }
}
}
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.inspections
import org.jetbrains.kotlin.idea.codeInsight.AbstractInspectionTest
abstract class AbstractHLInspectionTest : AbstractInspectionTest() {
override fun isFirPlugin() = true
override fun inspectionClassDirective() = "// FIR_INSPECTION_CLASS:"
override fun registerGradlPlugin() {}
}
@@ -0,0 +1,36 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.inspections;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.util.KtTestUtil;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/inspections/redundantUnitReturnType")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class HLInspectionTestGenerated extends AbstractHLInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInRedundantUnitReturnType() throws Exception {
KtTestUtil.assertAllTestsPresentInSingleGeneratedClassWithExcluded(this.getClass(), new File("idea/testData/inspections/redundantUnitReturnType"), Pattern.compile("^(inspections\\.test)$"), null);
}
@TestMetadata("inspectionData/inspections.test")
public void testInspectionData_Inspections_test() throws Exception {
runTest("idea/testData/inspections/redundantUnitReturnType/inspectionData/inspections.test");
}
}
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.intentions
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.test.uitls.IgnoreTests
import java.io.File
abstract class AbstractHLIntentionTest : AbstractIntentionTest() {
override fun intentionFileName() = ".firIntention"
override fun isFirPlugin() = true
override fun doTestFor(mainFile: File, pathToFiles: Map<String, PsiFile>, intentionAction: IntentionAction, fileText: String) {
IgnoreTests.runTestIfNotDisabledByFileDirective(mainFile.toPath(), IgnoreTests.DIRECTIVES.IGNORE_FIR) {
super.doTestFor(mainFile, pathToFiles, intentionAction, fileText)
}
}
override fun checkForErrorsAfter(fileText: String) {}
override fun checkForErrorsBefore(fileText: String) {}
}
@@ -0,0 +1,206 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.intentions;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.util.KtTestUtil;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/intentions/specifyTypeExplicitly")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class HLIntentionTestGenerated extends AbstractHLIntentionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInSpecifyTypeExplicitly() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/intentions/specifyTypeExplicitly"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), null, true);
}
@TestMetadata("anonymousObject.kt")
public void testAnonymousObject() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/anonymousObject.kt");
}
@TestMetadata("backticked.kt")
public void testBackticked() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/backticked.kt");
}
@TestMetadata("badCaretPosition.kt")
public void testBadCaretPosition() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/badCaretPosition.kt");
}
@TestMetadata("classNameClashing.kt")
public void testClassNameClashing() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/classNameClashing.kt");
}
@TestMetadata("constructor.kt")
public void testConstructor() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/constructor.kt");
}
@TestMetadata("destructuringInLambda.kt")
public void testDestructuringInLambda() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/destructuringInLambda.kt");
}
@TestMetadata("enumType.kt")
public void testEnumType() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/enumType.kt");
}
@TestMetadata("forAsExpression.kt")
public void testForAsExpression() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/forAsExpression.kt");
}
@TestMetadata("functionType.kt")
public void testFunctionType() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/functionType.kt");
}
@TestMetadata("genericClass.kt")
public void testGenericClass() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/genericClass.kt");
}
@TestMetadata("genericClassWithTypeParameters.kt")
public void testGenericClassWithTypeParameters() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/genericClassWithTypeParameters.kt");
}
@TestMetadata("genericClassWithTypeParameters2.kt")
public void testGenericClassWithTypeParameters2() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/genericClassWithTypeParameters2.kt");
}
@TestMetadata("genericFunction.kt")
public void testGenericFunction() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/genericFunction.kt");
}
@TestMetadata("innerTypeParameter.kt")
public void testInnerTypeParameter() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/innerTypeParameter.kt");
}
@TestMetadata("innerTypeParameter2.kt")
public void testInnerTypeParameter2() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/innerTypeParameter2.kt");
}
@TestMetadata("lambdaParam.kt")
public void testLambdaParam() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/lambdaParam.kt");
}
@TestMetadata("localClass.kt")
public void testLocalClass() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/localClass.kt");
}
@TestMetadata("localClassInSecondTypeParameter.kt")
public void testLocalClassInSecondTypeParameter() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter.kt");
}
@TestMetadata("localClassInSecondTypeParameter2.kt")
public void testLocalClassInSecondTypeParameter2() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter2.kt");
}
@TestMetadata("localClassInSecondTypeParameter3.kt")
public void testLocalClassInSecondTypeParameter3() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter3.kt");
}
@TestMetadata("localClassInTypeParameter.kt")
public void testLocalClassInTypeParameter() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/localClassInTypeParameter.kt");
}
@TestMetadata("loopParameter.kt")
public void testLoopParameter() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/loopParameter.kt");
}
@TestMetadata("outClass.kt")
public void testOutClass() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/outClass.kt");
}
@TestMetadata("outClass2.kt")
public void testOutClass2() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/outClass2.kt");
}
@TestMetadata("outClass3.kt")
public void testOutClass3() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/outClass3.kt");
}
@TestMetadata("overriddenAsNull.kt")
public void testOverriddenAsNull() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/overriddenAsNull.kt");
}
@TestMetadata("overrideNotNullFunction.kt")
public void testOverrideNotNullFunction() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/overrideNotNullFunction.kt");
}
@TestMetadata("overrideNotNullProperty.kt")
public void testOverrideNotNullProperty() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/overrideNotNullProperty.kt");
}
@TestMetadata("overrideNullableFunction.kt")
public void testOverrideNullableFunction() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/overrideNullableFunction.kt");
}
@TestMetadata("overrideNullableProperty.kt")
public void testOverrideNullableProperty() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/overrideNullableProperty.kt");
}
@TestMetadata("propertyTypeFromGetter.kt")
public void testPropertyTypeFromGetter() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/propertyTypeFromGetter.kt");
}
@TestMetadata("publicMember.kt")
public void testPublicMember() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/publicMember.kt");
}
@TestMetadata("stringRedefined.kt")
public void testStringRedefined() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/stringRedefined.kt");
}
@TestMetadata("typeAlreadyProvided.kt")
public void testTypeAlreadyProvided() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/typeAlreadyProvided.kt");
}
@TestMetadata("unitType.kt")
public void testUnitType() throws Exception {
runTest("idea/testData/intentions/specifyTypeExplicitly/unitType.kt");
}
}
@@ -85,6 +85,8 @@ abstract class KtAnalysisSession(final override val token: ValidityToken) : Vali
val builtinTypes: KtBuiltinTypes get() = typeProvider.builtinTypes
fun KtType.approximateToPublicDenotable(): KtType? = typeProvider.approximateToPublicDenotable(this)
fun KtClassOrObjectSymbol.buildSelfClassType(): KtType = typeProvider.buildSelfClassType(this)
fun KtElement.getDiagnostics(): Collection<KtDiagnostic> = diagnosticProvider.getDiagnosticsForElement(this)
@@ -13,6 +13,8 @@ import org.jetbrains.kotlin.idea.frontend.api.types.KtType
abstract class KtTypeProvider : KtAnalysisSessionComponent() {
abstract val builtinTypes: KtBuiltinTypes
abstract fun approximateToPublicDenotable(type: KtType): KtType?
abstract fun buildSelfClassType(symbol: KtClassOrObjectSymbol): KtType
}
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.idea.frontend.api.fir.components
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirPsiDiagnostic
import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic
@@ -17,6 +18,8 @@ import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession
internal interface KtFirAnalysisSessionComponent {
val analysisSession: KtFirAnalysisSession
val rootModuleSession: FirSession get() = analysisSession.firResolveState.rootModuleSession
val firSymbolBuilder get() = analysisSession.firSymbolBuilder
val firResolveState get() = analysisSession.firResolveState
fun ConeKotlinType.asKtType() = analysisSession.firSymbolBuilder.buildKtType(this)
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.idea.frontend.api.fir.components
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.resolve.inference.inferenceComponents
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.fir.types.impl.ConeTypeParameterTypeImpl
import org.jetbrains.kotlin.idea.frontend.api.ValidityToken
@@ -14,9 +15,9 @@ import org.jetbrains.kotlin.idea.frontend.api.components.KtTypeProvider
import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirClassOrObjectSymbol
import org.jetbrains.kotlin.idea.frontend.api.fir.types.KtFirType
import org.jetbrains.kotlin.idea.frontend.api.fir.types.PublicTypeApproximator
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassOrObjectSymbol
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
import org.jetbrains.kotlin.idea.frontend.api.withValidityAssertion
internal class KtFirTypeProvider(
override val analysisSession: KtFirAnalysisSession,
@@ -25,6 +26,18 @@ internal class KtFirTypeProvider(
override val builtinTypes: KtBuiltinTypes =
KtFirBuiltInTypes(analysisSession.firResolveState.rootModuleSession.builtinTypes, analysisSession.firSymbolBuilder, token)
override fun approximateToPublicDenotable(type: KtType): KtType? {
require(type is KtFirType)
val coneType = type.coneType
val approximatedConeType = PublicTypeApproximator.approximateTypeToPublicDenotable(
coneType,
rootModuleSession.inferenceComponents.ctx,
rootModuleSession
)
return approximatedConeType?.asKtType()
}
override fun buildSelfClassType(symbol: KtClassOrObjectSymbol): KtType {
require(symbol is KtFirClassOrObjectSymbol)
val type = symbol.firRef.withFir(FirResolvePhase.TYPES) { firClass ->
@@ -0,0 +1,133 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.frontend.api.fir.types
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.resolve.calls.fullyExpandedClass
import org.jetbrains.kotlin.fir.resolve.lookupSuperTypes
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.Variance
internal object PublicTypeApproximator {
fun approximateTypeToPublicDenotable(
type: ConeKotlinType,
context: ConeInferenceContext,
session: FirSession,
): ConeKotlinType? = approximate(type, context, session) as ConeKotlinType?
private fun approximate(
type: ConeKotlinType,
context: ConeInferenceContext,
session: FirSession,
): ConeTypeProjection? = when (type) {
is ConeFlexibleType -> approximate(type.upperBound, context, session)
is ConeCapturedType -> type
is ConeDefinitelyNotNullType -> ConeDefinitelyNotNullType(
approximate(
type.original,
context,
session,
) as ConeKotlinType
)
is ConeIntegerLiteralType -> type
is ConeIntersectionType -> context.commonSuperTypeOrNull(type.intersectedTypes.toList())?.let { approximate(it, context, session) }
is ConeLookupTagBasedType -> when (type) {
is ConeTypeParameterType -> type
is ConeClassErrorType -> null
is ConeClassLikeTypeImpl -> approximateClassLikeType(type, context, session)
else -> error("Unexpected type ${type::class}")
}
is ConeStubType -> null
}
private fun approximateClassLikeType(
typeImpl: ConeClassLikeTypeImpl,
context: ConeInferenceContext,
session: FirSession,
): ConeTypeProjection? {
val regularClass = typeImpl.classOrAnonymousClass(session) ?: return null
val type = if (needApproximationAsSuperType(regularClass)) {
firstSuperType(regularClass, session) ?: return null
} else {
typeImpl
}
val typeClassifier = type.classOrAnonymousClass(session) ?: return null
val typeArguments = approximateTypeParameters(type, typeClassifier, context, session)?.toTypedArray() ?: return null
return ConeClassLikeTypeImpl(
type.lookupTag,
typeArguments,
type.isNullable,
type.attributes
)
}
private fun approximateTypeParameters(
type: ConeClassLikeTypeImpl,
typeClassifier: FirClass<*>,
context: ConeInferenceContext,
session: FirSession
): List<ConeTypeProjection>? {
val result = mutableListOf<ConeTypeProjection>()
if (type.typeArguments.size != typeClassifier.typeParameters.size) return null
for ((typeArg, typeParam) in type.typeArguments.zip(typeClassifier.typeParameters)) {
val variance = (typeParam as? FirTypeParameter)?.variance ?: return null
result += approximateTypeProjection(typeArg, context, session, variance) ?: return null
}
return result
}
private fun firstSuperType(regularClass: FirClass<*>, session: FirSession): ConeClassLikeTypeImpl? {
val superTypes = lookupSuperTypes(regularClass, lookupInterfaces = true, deep = false, session, substituteTypes = true)
return superTypes.first() as? ConeClassLikeTypeImpl
}
fun ConeClassLikeTypeImpl.classOrAnonymousClass(session: FirSession): FirClass<*>? {
val fir = lookupTag.toSymbol(session)?.fir ?: return null
if (fir is FirAnonymousObject) return fir
else return fir.fullyExpandedClass(session)
}
private fun needApproximationAsSuperType(fir: FirClass<*>) = when (fir) {
is FirRegularClass -> fir.isLocal
is FirAnonymousObject -> true
else -> false
}
private fun approximateTypeProjection(
typeProjection: ConeTypeProjection,
context: ConeInferenceContext,
session: FirSession,
variance: Variance,
): ConeTypeProjection? {
val type = when (typeProjection) {
ConeStarProjection -> return ConeStarProjection
is ConeKotlinTypeProjection -> typeProjection.type
else -> error("Unexpected type ${typeProjection::class}")
}
val newType = when (val new = approximate(type, context, session)) {
ConeStarProjection -> return ConeStarProjection
is ConeKotlinTypeProjection -> new.type
null -> return null
else -> error("Unexpected type ${new::class}")
}
return when (variance) {
Variance.INVARIANT -> when {
with(context) { newType.typeConstructor().isAnyConstructor() } -> ConeStarProjection
AbstractTypeChecker.equalTypes(context, type, newType) -> newType
else -> ConeStarProjection
}
Variance.IN_VARIANCE -> if (AbstractTypeChecker.isSubtypeOf(context, newType, type)) newType else ConeStarProjection
Variance.OUT_VARIANCE -> if (AbstractTypeChecker.isSubtypeOf(context, type, newType)) newType else ConeStarProjection
}
}
}
@@ -0,0 +1,5 @@
<html>
<body>
This inspection reports a redundant <b>Unit</b> return type which can be omitted.
</body>
</html>
@@ -0,0 +1,13 @@
<html>
<body>
<p>Write your description here.
Start the description with a verb in 3rd person singular, like reports, detects, highlights.
In the first sentence, briefly explain what exactly the inspection helps you detect.
Make sure the sentence is not very long and complicated.
The first sentence must be in a dedicated paragraph separated from the rest of the text. This will make the description easier to read.
Make sure the description doesnt just repeat the inspection title.
</p>
<!-- tooltip end -->
<p>Text after this comment will only be shown in the settings of the inspection.</p>
</body>
</html>
@@ -0,0 +1,7 @@
<html>
<body>
<p>
Specify declaration return type explicitly
</p>
</body>
</html>
+1 -1
View File
@@ -2,7 +2,7 @@
<extensionPoints>
<extensionPoint qualifiedName="org.jetbrains.kotlin.ktQuickFixRegistrar"
interface="org.jetbrains.kotlin.idea.quickfix.KtQuickFixRegistrar"
interface="org.jetbrains.kotlin.idea.fir.api.fixes.KtQuickFixRegistrar"
dynamic="true"/>
</extensionPoints>
@@ -0,0 +1,12 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<localInspection implementationClass="org.jetbrains.kotlin.idea.fir.inspections.declarations.HLRedundantUnitReturnTypeInspection"
groupPath="Kotlin"
groupName="Redundant constructs"
enabledByDefault="true"
cleanupTool="true"
level="WARNING"
language="kotlin"
key="inspection.redundant.unit.return.type.display.name" bundle="messages.KotlinBundle"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,8 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<intentionAction>
<className>org.jetbrains.kotlin.idea.fir.intentions.declarations.HLSpecifyExplicitTypeForCallableDeclarationIntention</className>
<category>Kotlin</category>
</intentionAction>
</extensions>
</idea-plugin>
+2
View File
@@ -37,6 +37,8 @@ The Kotlin FIR plugin provides language support in IntelliJ IDEA and Android Stu
<xi:include href="jps.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="idea.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="caches.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="firInspections.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="firIntentions.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="extensions/ide-frontend-independent.xml" xpointer="xpointer(/idea-plugin/*)"/>
@@ -1,5 +0,0 @@
<html>
<body>
This intention adds an explicit type specification for functions.
</body>
</html>
@@ -1 +1,2 @@
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.RedundantUnitReturnTypeInspection
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.RedundantUnitReturnTypeInspection
// FIR_INSPECTION_CLASS: org.jetbrains.kotlin.idea.fir.inspections.declarations.HLRedundantUnitReturnTypeInspection
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.fir.intentions.declarations.HLSpecifyExplicitTypeForCallableDeclarationIntention
@@ -1,3 +1,4 @@
// IGNORE_FIR
class A {
private fun foo() <caret>= { object {} }
}
@@ -1,3 +1,4 @@
// IGNORE_FIR
class A {
private fun foo(): () -> Any<caret> = { object {} }
}
@@ -1,2 +1,3 @@
// IGNORE_FIR
class `1`
val x<caret> = `1`()
@@ -1,2 +1,3 @@
// IGNORE_FIR
class `1`
val x: `1` = `1`()
@@ -1,3 +1,4 @@
// IGNORE_FIR
// IS_APPLICABLE: false
val x = "<caret>"
@@ -1,3 +1,4 @@
// IGNORE_FIR
// WITH_RUNTIME
object Holder {
@@ -1,3 +1,4 @@
// IGNORE_FIR
// WITH_RUNTIME
object Holder {
@@ -1,3 +1,4 @@
// IGNORE_FIR
class A {
private fun bar() <caret>= {
class Local()
@@ -1,5 +1,6 @@
// IGNORE_FIR
class A {
private fun bar(): () -> Any<caret> = {
private fun bar(): () -> Any = {
class Local()
Local()
}
@@ -1,3 +1,4 @@
// IGNORE_FIR
open class F
class TestClass<V, out K>
@@ -1,3 +1,4 @@
// IGNORE_FIR
open class F
class TestClass<V, out K>
@@ -1,3 +1,4 @@
// IGNORE_FIR
// WITH_RUNTIME
import java.util.HashMap
@@ -1,3 +1,4 @@
// IGNORE_FIR
// WITH_RUNTIME
import java.util.HashMap
@@ -7,3 +7,5 @@ interface I {
class Test : I {
override fun foo()<caret> = null
}
// IGNORE_FIR
@@ -7,3 +7,5 @@ interface I {
class Test : I {
override fun foo(): String? = null
}
// IGNORE_FIR
@@ -1,3 +1,4 @@
// IGNORE_FIR
// CHOOSE_NULLABLE_TYPE_IF_EXISTS
// WITH_RUNTIME
interface Base {
@@ -1,3 +1,4 @@
// IGNORE_FIR
// CHOOSE_NULLABLE_TYPE_IF_EXISTS
// WITH_RUNTIME
interface Base {
@@ -1,3 +1,4 @@
// IGNORE_FIR
// CHOOSE_NULLABLE_TYPE_IF_EXISTS
// WITH_RUNTIME
interface Base {
@@ -1,3 +1,4 @@
// IGNORE_FIR
// CHOOSE_NULLABLE_TYPE_IF_EXISTS
// WITH_RUNTIME
interface Base {
@@ -1,3 +1,4 @@
// IGNORE_FIR
class String {}
val <caret>x = ""
@@ -1,5 +1,6 @@
import kotlin.String
// IGNORE_FIR
class String {}
val x: String = ""
@@ -37,13 +37,17 @@ abstract class AbstractInspectionTest : KotlinLightCodeInsightFixtureTestCase()
try {
super.setUp()
EntryPointsManagerBase.getInstance(project).ADDITIONAL_ANNOTATIONS.add(ENTRY_POINT_ANNOTATION)
runWriteAction { FileTypeManager.getInstance().associateExtension(GroovyFileType.GROOVY_FILE_TYPE, "gradle") }
registerGradlPlugin()
} catch (e: Throwable) {
TestLoggerFactory.onTestFinished(false)
throw e
}
}
protected open fun registerGradlPlugin() {
runWriteAction { FileTypeManager.getInstance().associateExtension(GroovyFileType.GROOVY_FILE_TYPE, "gradle") }
}
override fun tearDown() {
EntryPointsManagerBase.getInstance(project).ADDITIONAL_ANNOTATIONS.remove(ENTRY_POINT_ANNOTATION)
super.tearDown()
@@ -55,11 +59,13 @@ abstract class AbstractInspectionTest : KotlinLightCodeInsightFixtureTestCase()
protected open val forceUsePackageFolder: Boolean = false //workaround for IDEA-176033
protected fun doTest(path: String) {
protected open fun inspectionClassDirective(): String = "// INSPECTION_CLASS: "
protected open fun doTest(path: String) {
val optionsFile = File(path)
val options = FileUtil.loadFile(optionsFile, true)
val inspectionClass = Class.forName(InTextDirectivesUtils.findStringWithPrefixes(options, "// INSPECTION_CLASS: ")!!)
val inspectionClass = Class.forName(InTextDirectivesUtils.findStringWithPrefixes(options, inspectionClassDirective())!!)
val fixtureClasses = InTextDirectivesUtils.findListWithPrefixes(options, "// FIXTURE_CLASS: ")
@@ -22,6 +22,7 @@ import com.intellij.refactoring.util.CommonRefactoringUtil
import com.intellij.testFramework.PlatformTestUtil
import junit.framework.ComparisonFailure
import junit.framework.TestCase
import org.jetbrains.annotations.NotNull
import org.jetbrains.kotlin.formatter.FormatSettingsUtil
import org.jetbrains.kotlin.idea.test.*
import org.jetbrains.kotlin.idea.util.application.executeCommand
@@ -109,15 +110,11 @@ abstract class AbstractIntentionTest : KotlinLightCodeInsightFixtureTestCase() {
val minJavaVersion = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// MIN_JAVA_VERSION: ")
if (minJavaVersion != null && !SystemInfo.isJavaVersionAtLeast(minJavaVersion)) return@configureRegistryAndRun
if (file is KtFile && !InTextDirectivesUtils.isDirectiveDefined(fileText, "// SKIP_ERRORS_BEFORE")) {
DirectiveBasedActionUtils.checkForUnexpectedErrors(file as KtFile)
}
checkForErrorsBefore(fileText)
doTestFor(mainFile.name, pathToFiles, intentionAction, fileText)
doTestFor(mainFile, pathToFiles, intentionAction, fileText)
if (file is KtFile && !InTextDirectivesUtils.isDirectiveDefined(fileText, "// SKIP_ERRORS_AFTER")) {
DirectiveBasedActionUtils.checkForUnexpectedErrors(file as KtFile)
}
checkForErrorsAfter(fileText)
} finally {
ConfigLibraryUtil.unconfigureLibrariesByDirective(module, fileText)
}
@@ -126,6 +123,18 @@ abstract class AbstractIntentionTest : KotlinLightCodeInsightFixtureTestCase() {
}
}
protected open fun checkForErrorsAfter(fileText: String) {
if (file is KtFile && !InTextDirectivesUtils.isDirectiveDefined(fileText, "// SKIP_ERRORS_AFTER")) {
DirectiveBasedActionUtils.checkForUnexpectedErrors(file as KtFile)
}
}
protected open fun checkForErrorsBefore(fileText: String) {
if (file is KtFile && !InTextDirectivesUtils.isDirectiveDefined(fileText, "// SKIP_ERRORS_BEFORE")) {
DirectiveBasedActionUtils.checkForUnexpectedErrors(file as KtFile)
}
}
private fun <T> computeUnderProgressIndicatorAndWait(compute: () -> T): T {
val result = CompletableFuture<T>()
val progressIndicator = ProgressIndicatorBase()
@@ -143,7 +152,8 @@ abstract class AbstractIntentionTest : KotlinLightCodeInsightFixtureTestCase() {
}
@Throws(Exception::class)
private fun doTestFor(mainFilePath: String, pathToFiles: Map<String, PsiFile>, intentionAction: IntentionAction, fileText: String) {
protected open fun doTestFor(mainFile: File, pathToFiles: Map<String, PsiFile>, intentionAction: IntentionAction, fileText: String) {
val mainFilePath = mainFile.name
val isApplicableString = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// ${isApplicableDirectiveName()}: ")
val isApplicableExpected = isApplicableString == null || isApplicableString == "true"