diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index b02ba82678d..722bd20ea0b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -112,6 +112,7 @@ public interface Errors { DiagnosticFactory1 REPEATED_MODIFIER = DiagnosticFactory1.create(ERROR); DiagnosticFactory2 REDUNDANT_MODIFIER = DiagnosticFactory2.create(WARNING); DiagnosticFactory2 WRONG_MODIFIER_TARGET = DiagnosticFactory2.create(ERROR); + DiagnosticFactory2 REDUNDANT_MODIFIER_FOR_TARGET = DiagnosticFactory2.create(WARNING); DiagnosticFactory2 WRONG_MODIFIER_CONTAINING_DECLARATION = DiagnosticFactory2.create(ERROR); DiagnosticFactory0 INAPPLICABLE_PLATFORM_NAME = DiagnosticFactory0.create(ERROR); DiagnosticFactory1 WRONG_ANNOTATION_TARGET = DiagnosticFactory1.create(ERROR); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index f75372ea844..f1828cbb40a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -25,8 +25,6 @@ import org.jetbrains.annotations.TestOnly; import org.jetbrains.kotlin.diagnostics.Diagnostic; import org.jetbrains.kotlin.diagnostics.DiagnosticFactory; import org.jetbrains.kotlin.diagnostics.Errors; -import org.jetbrains.kotlin.lexer.JetKeywordToken; -import org.jetbrains.kotlin.lexer.JetModifierKeywordToken; import org.jetbrains.kotlin.psi.JetExpression; import org.jetbrains.kotlin.psi.JetSimpleNameExpression; import org.jetbrains.kotlin.psi.JetTypeConstraint; @@ -42,7 +40,6 @@ import org.jetbrains.kotlin.util.MappedExtensionProvider; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import static org.jetbrains.kotlin.diagnostics.Errors.*; @@ -122,6 +119,7 @@ public class DefaultErrorMessages { MAP.put(INCOMPATIBLE_MODIFIERS, "Modifier ''{0}'' is incompatible with ''{1}''", TO_STRING, TO_STRING); MAP.put(REPEATED_MODIFIER, "Repeated ''{0}''", TO_STRING); MAP.put(WRONG_MODIFIER_TARGET, "Modifier ''{0}'' is not applicable to ''{1}''", TO_STRING, TO_STRING); + MAP.put(REDUNDANT_MODIFIER_FOR_TARGET, "Modifier ''{0}'' is redundant for ''{1}''", TO_STRING, TO_STRING); MAP.put(WRONG_MODIFIER_CONTAINING_DECLARATION, "Modifier ''{0}'' is not applicable inside ''{1}''", TO_STRING, TO_STRING); MAP.put(INAPPLICABLE_PLATFORM_NAME, "platformName annotation is not applicable to this declaration"); MAP.put(WRONG_ANNOTATION_TARGET, "This annotation is not applicable to target ''{0}''", TO_STRING); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt index 85229f70b02..5bc5aa76eca 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/ModifiersChecker.kt @@ -39,9 +39,9 @@ public object ModifierCheckerCore { private val possibleTargetMap = mapOf>( ENUM_KEYWORD to EnumSet.of(ENUM_CLASS), - ABSTRACT_KEYWORD to EnumSet.of(CLASS, LOCAL_CLASS, INNER_CLASS, MEMBER_PROPERTY, MEMBER_FUNCTION), - OPEN_KEYWORD to EnumSet.of(CLASS, LOCAL_CLASS, INNER_CLASS, MEMBER_PROPERTY, MEMBER_FUNCTION), - FINAL_KEYWORD to EnumSet.of(CLASS, LOCAL_CLASS, INNER_CLASS, MEMBER_PROPERTY, MEMBER_FUNCTION), + ABSTRACT_KEYWORD to EnumSet.of(CLASS, LOCAL_CLASS, INNER_CLASS, INTERFACE, MEMBER_PROPERTY, MEMBER_FUNCTION), + OPEN_KEYWORD to EnumSet.of(CLASS, LOCAL_CLASS, INNER_CLASS, INTERFACE, MEMBER_PROPERTY, MEMBER_FUNCTION), + FINAL_KEYWORD to EnumSet.of(CLASS, LOCAL_CLASS, INNER_CLASS, ENUM_CLASS, OBJECT, MEMBER_PROPERTY, MEMBER_FUNCTION), SEALED_KEYWORD to EnumSet.of(CLASS, LOCAL_CLASS, INNER_CLASS), INNER_KEYWORD to EnumSet.of(INNER_CLASS), OVERRIDE_KEYWORD to EnumSet.of(MEMBER_PROPERTY, MEMBER_FUNCTION), @@ -60,6 +60,13 @@ public object ModifierCheckerCore { COMPANION_KEYWORD to EnumSet.of(OBJECT) ) + // NOTE: redundant targets must be possible! + private val redundantTargetMap = mapOf>( + ABSTRACT_KEYWORD to EnumSet.of(INTERFACE), + OPEN_KEYWORD to EnumSet.of(INTERFACE), + FINAL_KEYWORD to EnumSet.of(ENUM_CLASS, OBJECT) + ) + private val possibleParentTargetMap = mapOf>( INNER_KEYWORD to EnumSet.of(CLASS, INNER_CLASS, LOCAL_CLASS, ENUM_CLASS, ENUM_ENTRY), OVERRIDE_KEYWORD to EnumSet.of(CLASSIFIER, ENUM_ENTRY), @@ -146,9 +153,15 @@ public object ModifierCheckerCore { private fun checkTarget(trace: BindingTrace, node: ASTNode, actualTargets: List): Boolean { val modifier = node.elementType as JetModifierKeywordToken val possibleTargets = possibleTargetMap[modifier] ?: emptySet() - if (actualTargets.any { it in possibleTargets }) return true - trace.report(Errors.WRONG_MODIFIER_TARGET.on(node.psi, modifier, actualTargets.firstOrNull()?.description ?: "this")) - return false + if (!actualTargets.any { it in possibleTargets }) { + trace.report(Errors.WRONG_MODIFIER_TARGET.on(node.psi, modifier, actualTargets.firstOrNull()?.description ?: "this")) + return false + } + val redundantTargets = redundantTargetMap[modifier] ?: emptySet() + if (actualTargets.any { it in redundantTargets}) { + trace.report(Errors.REDUNDANT_MODIFIER_FOR_TARGET.on(node.psi, modifier, actualTargets.firstOrNull()?.description ?: "this")) + } + return true } private fun checkParent(trace: BindingTrace, node: ASTNode, parentDescriptor: DeclarationDescriptor?): Boolean { diff --git a/compiler/testData/diagnostics/tests/declarationChecks/illegalModifiersOnClass.kt b/compiler/testData/diagnostics/tests/declarationChecks/illegalModifiersOnClass.kt index 950324590e7..7d89c18274d 100644 --- a/compiler/testData/diagnostics/tests/declarationChecks/illegalModifiersOnClass.kt +++ b/compiler/testData/diagnostics/tests/declarationChecks/illegalModifiersOnClass.kt @@ -6,13 +6,13 @@ override companion object {} open companion object {} abstract companion object {} - final companion object {} + final companion object {} } override object B1 {} open object B2 {} abstract object B3 {} -final object B4 {} +final object B4 {} override enum class C {} override interface D {} diff --git a/compiler/testData/diagnostics/tests/modifiers/redundantTargets.kt b/compiler/testData/diagnostics/tests/modifiers/redundantTargets.kt new file mode 100644 index 00000000000..f2335767c41 --- /dev/null +++ b/compiler/testData/diagnostics/tests/modifiers/redundantTargets.kt @@ -0,0 +1,10 @@ +open interface First + +abstract interface Second + +final enum class Third { + FOURTH, + FIFTH +} + +final object Sixth \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/modifiers/redundantTargets.txt b/compiler/testData/diagnostics/tests/modifiers/redundantTargets.txt new file mode 100644 index 00000000000..5312b0bb3de --- /dev/null +++ b/compiler/testData/diagnostics/tests/modifiers/redundantTargets.txt @@ -0,0 +1,54 @@ +package + +internal interface First { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +internal interface Second { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +internal object Sixth { + private constructor Sixth() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +internal final enum class Third : kotlin.Enum { + public enum entry FOURTH : Third { + private constructor FOURTH() + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Third): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final override /*1*/ /*fake_override*/ fun name(): kotlin.String + public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public enum entry FIFTH : Third { + private constructor FIFTH() + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Third): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final override /*1*/ /*fake_override*/ fun name(): kotlin.String + public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + private constructor Third() + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: Third): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final override /*1*/ /*fake_override*/ fun name(): kotlin.String + public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): Third + public final /*synthesized*/ fun values(): kotlin.Array +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java index e7ef5e784c6..44fce6d35ac 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -8850,6 +8850,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("redundantTargets.kt") + public void testRedundantTargets() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/modifiers/redundantTargets.kt"); + doTest(fileName); + } + @TestMetadata("repeatedModifiers.kt") public void testRepeatedModifiers() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/modifiers/repeatedModifiers.kt"); diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index a04e282e555..75efc52f5ad 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -113,6 +113,7 @@ public class QuickFixRegistrar : QuickFixContributor { GETTER_VISIBILITY_DIFFERS_FROM_PROPERTY_VISIBILITY.registerFactory(removeModifierFactory) REDUNDANT_MODIFIER_IN_GETTER.registerFactory(removeRedundantModifierFactory) WRONG_MODIFIER_TARGET.registerFactory(removeModifierFactory) + REDUNDANT_MODIFIER_FOR_TARGET.registerFactory(removeModifierFactory) WRONG_MODIFIER_CONTAINING_DECLARATION.registerFactory(removeModifierFactory) REPEATED_MODIFIER.registerFactory(removeModifierFactory)