FIR IDE: Add quickfix for INAPPLICABLE_LATEINIT_MODIFIER.

Also changed FE1.0 checker and all related fix factories to report error
on the declaration instead of the lateinit modifier. This is consistent
with the direction of all checkers in FIR (no reporting on modifiers).
This commit is contained in:
Mark Punzalan
2021-02-12 21:56:39 +00:00
committed by Ilya Kirillov
parent b1ab64e854
commit 4e44804c77
18 changed files with 78 additions and 32 deletions
@@ -42,6 +42,7 @@ enum class PositioningStrategy(private val strategy: String) {
WHEN_EXPRESSION("WHEN_EXPRESSION"),
IF_EXPRESSION("IF_EXPRESSION"),
VARIANCE_MODIFIER("VARIANCE_MODIFIER"),
LATEINIT_MODIFIER("LATEINIT_MODIFIER"),
;
@@ -156,7 +156,7 @@ val DIAGNOSTICS_LIST = DiagnosticListBuilder.buildDiagnosticList {
val INAPPLICABLE_CANDIDATE by error<FirSourceElement, PsiElement> {
parameter<AbstractFirBasedSymbol<*>>("candidate")
}
val INAPPLICABLE_LATEINIT_MODIFIER by error<FirSourceElement, PsiElement> {
val INAPPLICABLE_LATEINIT_MODIFIER by error<FirSourceElement, KtModifierListOwner>(PositioningStrategy.LATEINIT_MODIFIER) {
parameter<String>("reason")
}
}
@@ -140,7 +140,7 @@ object FirErrors {
// Applicability
val NONE_APPLICABLE by error1<FirSourceElement, PsiElement, Collection<AbstractFirBasedSymbol<*>>>()
val INAPPLICABLE_CANDIDATE by error1<FirSourceElement, PsiElement, AbstractFirBasedSymbol<*>>()
val INAPPLICABLE_LATEINIT_MODIFIER by error1<FirSourceElement, PsiElement, String>()
val INAPPLICABLE_LATEINIT_MODIFIER by error1<FirSourceElement, KtModifierListOwner, String>(SourceElementPositioningStrategies.LATEINIT_MODIFIER)
// Ambiguity
val AMBIGUITY by error1<FirSourceElement, PsiElement, Collection<AbstractFirBasedSymbol<*>>>()
@@ -590,7 +590,7 @@ public interface Errors {
DiagnosticFactory0<KtProperty> PRIVATE_PROPERTY_IN_INTERFACE = DiagnosticFactory0.create(ERROR, PRIVATE_MODIFIER);
DiagnosticFactory0<KtProperty> BACKING_FIELD_IN_INTERFACE = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE);
DiagnosticFactory1<PsiElement, String> INAPPLICABLE_LATEINIT_MODIFIER = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<KtModifierListOwner, String> INAPPLICABLE_LATEINIT_MODIFIER = DiagnosticFactory1.create(ERROR, LATEINIT_MODIFIER);
DiagnosticFactory0<PsiElement> LATEINIT_INTRINSIC_CALL_ON_NON_LITERAL = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> LATEINIT_INTRINSIC_CALL_ON_NON_LATEINIT = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> LATEINIT_INTRINSIC_CALL_IN_INLINE_FUNCTION = DiagnosticFactory0.create(ERROR);
@@ -32,7 +32,7 @@ import org.jetbrains.kotlin.types.TypeUtils
object LateinitModifierApplicabilityChecker {
fun checkLateinitModifierApplicability(trace: BindingTrace, ktDeclaration: KtCallableDeclaration, descriptor: VariableDescriptor) {
val modifier = ktDeclaration.modifierList?.getModifier(KtTokens.LATEINIT_KEYWORD) ?: return
if (!ktDeclaration.hasModifier(KtTokens.LATEINIT_KEYWORD)) return
val variables = when (descriptor) {
is PropertyDescriptor -> "properties"
@@ -43,37 +43,37 @@ object LateinitModifierApplicabilityChecker {
val type = descriptor.type
if (!descriptor.isVar) {
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(modifier, "is allowed only on mutable $variables"))
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(ktDeclaration, "is allowed only on mutable $variables"))
}
if (type.isInlineClassType()) {
if (UnsignedTypes.isUnsignedType(type)) {
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(modifier, "is not allowed on $variables of unsigned types"))
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(ktDeclaration, "is not allowed on $variables of unsigned types"))
} else {
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(modifier, "is not allowed on $variables of inline class types"))
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(ktDeclaration, "is not allowed on $variables of inline class types"))
}
}
if (type.isMarkedNullable) {
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(modifier, "is not allowed on $variables of nullable types"))
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(ktDeclaration, "is not allowed on $variables of nullable types"))
} else if (TypeUtils.isNullableType(type)) {
trace.report(
Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(
modifier,
ktDeclaration,
"is not allowed on $variables of a type with nullable upper bound"
)
)
}
if (KotlinBuiltIns.isPrimitiveType(type)) {
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(modifier, "is not allowed on $variables of primitive types"))
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(ktDeclaration, "is not allowed on $variables of primitive types"))
}
if (ktDeclaration is KtProperty) {
if (ktDeclaration.hasDelegateExpression()) {
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(modifier, "is not allowed on delegated properties"))
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(ktDeclaration, "is not allowed on delegated properties"))
} else if (ktDeclaration.hasInitializer()) {
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(modifier, "is not allowed on $variables with initializer"))
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(ktDeclaration, "is not allowed on $variables with initializer"))
}
}
@@ -84,28 +84,28 @@ object LateinitModifierApplicabilityChecker {
val hasBackingField = trace.bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, descriptor) ?: false
if (ktDeclaration is KtParameter) {
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(modifier, "is not allowed on primary constructor parameters"))
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(ktDeclaration, "is not allowed on primary constructor parameters"))
}
if (isAbstract) {
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(modifier, "is not allowed on abstract properties"))
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(ktDeclaration, "is not allowed on abstract properties"))
}
if (!hasDelegateExpressionOrInitializer) {
if (hasAccessorImplementation) {
trace.report(
Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(
modifier,
ktDeclaration,
"is not allowed on properties with a custom getter or setter"
)
)
} else if (!isAbstract && !hasBackingField) {
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(modifier, "is not allowed on properties without backing field"))
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(ktDeclaration, "is not allowed on properties without backing field"))
}
}
if (descriptor.extensionReceiverParameter != null) {
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(modifier, "is not allowed on extension properties"))
trace.report(Errors.INAPPLICABLE_LATEINIT_MODIFIER.on(ktDeclaration, "is not allowed on extension properties"))
}
}
}
@@ -1104,6 +1104,7 @@ fun main(args: Array<String>) {
testClass<AbstractHighLevelQuickFixTest> {
val pattern = "^([\\w\\-_]+)\\.kt$"
model("quickfix/lateinit", pattern = pattern, filenameStartsLowerCase = true)
model("quickfix/modifiers", pattern = pattern, filenameStartsLowerCase = true, recursive = false)
model("quickfix/override/typeMismatchOnOverride", pattern = pattern, filenameStartsLowerCase = true, recursive = false)
model("quickfix/variables/changeMutability", pattern = pattern, filenameStartsLowerCase = true, recursive = false)
@@ -37,6 +37,7 @@ class MainKtQuickFixRegistrar : KtQuickFixRegistrar() {
private val mutability = KtQuickFixesListBuilder.registerPsiQuickFix {
registerPsiQuickFix<PsiElement, KtFirDiagnostic.VarOverriddenByVal>(ChangeVariableMutabilityFix.VAR_OVERRIDDEN_BY_VAL_FACTORY)
registerPsiQuickFix<KtModifierListOwner, KtFirDiagnostic.InapplicableLateinitModifier>(ChangeVariableMutabilityFix.LATEINIT_VAL_FACTORY)
}
override val list: KtQuickFixesList = KtQuickFixesList.createCombined(
@@ -19,6 +19,49 @@ import java.util.regex.Pattern;
@SuppressWarnings("all")
@RunWith(JUnit3RunnerWithInners.class)
public class HighLevelQuickFixTestGenerated extends AbstractHighLevelQuickFixTest {
@TestMetadata("idea/testData/quickfix/lateinit")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Lateinit extends AbstractHighLevelQuickFixTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInLateinit() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/lateinit"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), null, true);
}
@TestMetadata("nullable.kt")
public void testNullable() throws Exception {
runTest("idea/testData/quickfix/lateinit/nullable.kt");
}
@TestMetadata("val.kt")
public void testVal() throws Exception {
runTest("idea/testData/quickfix/lateinit/val.kt");
}
@TestMetadata("withGetter.kt")
public void testWithGetter() throws Exception {
runTest("idea/testData/quickfix/lateinit/withGetter.kt");
}
@TestMetadata("withGetterSetter.kt")
public void testWithGetterSetter() throws Exception {
runTest("idea/testData/quickfix/lateinit/withGetterSetter.kt");
}
@TestMetadata("withInitializer.kt")
public void testWithInitializer() throws Exception {
runTest("idea/testData/quickfix/lateinit/withInitializer.kt");
}
@TestMetadata("withSetter.kt")
public void testWithSetter() throws Exception {
runTest("idea/testData/quickfix/lateinit/withSetter.kt");
}
}
@TestMetadata("idea/testData/quickfix/modifiers")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -372,7 +372,7 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
abstract val candidate: KtSymbol
}
abstract class InapplicableLateinitModifier : KtFirDiagnostic<PsiElement>() {
abstract class InapplicableLateinitModifier : KtFirDiagnostic<KtModifierListOwner>() {
override val diagnosticClass get() = InapplicableLateinitModifier::class
abstract val reason: String
}
@@ -596,7 +596,7 @@ internal class InapplicableLateinitModifierImpl(
override val reason: String,
firDiagnostic: FirPsiDiagnostic<*>,
override val token: ValidityToken,
) : KtFirDiagnostic.InapplicableLateinitModifier(), KtAbstractFirDiagnostic<PsiElement> {
) : KtFirDiagnostic.InapplicableLateinitModifier(), KtAbstractFirDiagnostic<KtModifierListOwner> {
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
@@ -64,9 +64,9 @@ class ChangeVariableMutabilityFix(
listOf(ChangeVariableMutabilityFix(psiElement, false))
}
val LATEINIT_VAL_FACTORY: QuickFixesPsiBasedFactory<PsiElement> =
quickFixesPsiBasedFactory { psiElement: PsiElement ->
val property = psiElement.getStrictParentOfType<KtProperty>() ?: return@quickFixesPsiBasedFactory emptyList()
val LATEINIT_VAL_FACTORY: QuickFixesPsiBasedFactory<KtModifierListOwner> =
quickFixesPsiBasedFactory { psiElement: KtModifierListOwner ->
val property = psiElement as? KtProperty ?: return@quickFixesPsiBasedFactory emptyList()
if (property.valOrVarKeyword.text != "val") {
emptyList()
} else {
@@ -121,8 +121,7 @@ class RemoveModifierFix(
fun createRemoveLateinitFactory(): QuickFixesPsiBasedFactory<PsiElement> {
return quickFixesPsiBasedFactory { psiElement: PsiElement ->
val modifierList = psiElement.parent as? KtDeclarationModifierList ?: return@quickFixesPsiBasedFactory emptyList()
val property = modifierList.parent as? KtProperty ?: return@quickFixesPsiBasedFactory emptyList()
val property = psiElement as? KtProperty ?: return@quickFixesPsiBasedFactory emptyList()
if (!property.hasModifier(KtTokens.LATEINIT_KEYWORD)) return@quickFixesPsiBasedFactory emptyList()
listOf(RemoveModifierFix(property, KtTokens.LATEINIT_KEYWORD, isRedundant = false))
}
@@ -40,8 +40,7 @@ class ConvertLateinitPropertyToNotNullDelegateFix(
companion object : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction<KtProperty>? {
val modifierList = diagnostic.psiElement.parent as? KtDeclarationModifierList ?: return null
val property = modifierList.parent as? KtProperty ?: return null
val property = diagnostic.psiElement as? KtProperty ?: return null
if (!property.hasModifier(KtTokens.LATEINIT_KEYWORD) || !property.isVar || property.hasInitializer()) return null
val typeReference = property.typeReference ?: return null
val type = property.analyze(BodyResolveMode.PARTIAL)[BindingContext.TYPE, typeReference] ?: return null
@@ -58,8 +58,7 @@ class RemoveNullableFix(
object LATEINIT_FACTORY : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction<KtNullableType>? {
val lateinitElement = Errors.INAPPLICABLE_LATEINIT_MODIFIER.cast(diagnostic).psiElement
val property = lateinitElement.getStrictParentOfType<KtProperty>() ?: return null
val property = Errors.INAPPLICABLE_LATEINIT_MODIFIER.cast(diagnostic).psiElement as? KtProperty ?: return null
val typeReference = property.typeReference ?: return null
val typeElement = (typeReference.typeElement ?: return null) as? KtNullableType ?: return null
if (typeElement.innerType == null) return null
@@ -118,8 +118,7 @@ open class RemovePartsFromPropertyFix(
object LateInitFactory : KotlinSingleIntentionActionFactory() {
public override fun createAction(diagnostic: Diagnostic): KotlinQuickFixAction<KtProperty>? {
val element = Errors.INAPPLICABLE_LATEINIT_MODIFIER.cast(diagnostic).psiElement
val property = PsiTreeUtil.getParentOfType(element, KtProperty::class.java) ?: return null
val property = Errors.INAPPLICABLE_LATEINIT_MODIFIER.cast(diagnostic).psiElement as? KtProperty ?: return null
val hasInitializer = property.hasInitializer()
val hasGetter = property.getter?.bodyExpression != null
val hasSetter = property.setter?.bodyExpression != null
@@ -1,3 +1,5 @@
// FIR_COMPARISON
class C<V>() {
<error descr="[INAPPLICABLE_LATEINIT_MODIFIER] 'lateinit' modifier is not allowed on properties of a type with nullable upper bound">lateinit</error> var item: V
}
+2 -1
View File
@@ -2,4 +2,5 @@
class A() {
<caret>lateinit val foo: String
}
}
/* FIR_COMPARISON */
+2 -1
View File
@@ -2,4 +2,5 @@
class A() {
<caret>lateinit var foo: String
}
}
/* FIR_COMPARISON */