diff --git a/python/python-psi-impl/resources/inspectionDescriptions/PyTypeAliasRedeclarationInspection.html b/python/python-psi-impl/resources/inspectionDescriptions/PyTypeAliasRedeclarationInspection.html new file mode 100644 index 000000000000..a81b16744bbe --- /dev/null +++ b/python/python-psi-impl/resources/inspectionDescriptions/PyTypeAliasRedeclarationInspection.html @@ -0,0 +1,10 @@ + + +

Reports redeclarations of type aliases.

+

Example:

+

+type A = int
+type A = str
+
+ + diff --git a/python/python-psi-impl/resources/intellij.python.psi.impl.xml b/python/python-psi-impl/resources/intellij.python.psi.impl.xml index 6b2cbe9f9e5d..80092c77feb9 100644 --- a/python/python-psi-impl/resources/intellij.python.psi.impl.xml +++ b/python/python-psi-impl/resources/intellij.python.psi.impl.xml @@ -205,6 +205,7 @@ + diff --git a/python/python-psi-impl/resources/messages/PyPsiBundle.properties b/python/python-psi-impl/resources/messages/PyPsiBundle.properties index b4b281cb7a5e..c66d14466b89 100644 --- a/python/python-psi-impl/resources/messages/PyPsiBundle.properties +++ b/python/python-psi-impl/resources/messages/PyPsiBundle.properties @@ -594,6 +594,10 @@ INSP.decorator.receives.unexpected.builtin=This decorator will not receive the c INSP.NAME.redeclaration=Redeclared names without usages INSP.redeclared.name=Redeclared ''{0}'' defined above without usage +# PyTypeAliasRedeclarationInspection +INSP.NAME.type.alias.redeclaration=Redeclared type alias +INSP.redeclared.type.alias=Name ''{0}'' already defined + # PyInterpreterInspection INSP.NAME.invalid.interpreter=An invalid interpreter INSP.interpreter.pipenv.interpreter.associated.with.another.project=Pipenv interpreter is associated with another project: ''{0}'' diff --git a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyTypeAliasRedeclarationInspection.kt b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyTypeAliasRedeclarationInspection.kt new file mode 100644 index 000000000000..56150f3583a1 --- /dev/null +++ b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyTypeAliasRedeclarationInspection.kt @@ -0,0 +1,95 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.inspections + +import com.intellij.codeInsight.controlflow.ControlFlowUtil +import com.intellij.codeInsight.controlflow.Instruction +import com.intellij.codeInspection.LocalInspectionToolSession +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiElementVisitor +import com.intellij.psi.PsiNameIdentifierOwner +import com.jetbrains.python.PyPsiBundle +import com.jetbrains.python.PythonUiService +import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache +import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction +import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil +import com.jetbrains.python.psi.PyTypeAliasStatement +import com.jetbrains.python.psi.types.TypeEvalContext + +/** + * Annotates type alias re-declarations + */ +class PyTypeAliasRedeclarationInspection : PyInspection() { + override fun buildVisitor( + holder: ProblemsHolder, + isOnTheFly: Boolean, + session: LocalInspectionToolSession + ): PsiElementVisitor { + return Visitor(holder, PyInspectionVisitor.getContext(session)) + } + + private class Visitor(holder: ProblemsHolder?, context: TypeEvalContext) : PyInspectionVisitor(holder, context) { + override fun visitPyTypeAliasStatement(node: PyTypeAliasStatement) { + reportRedeclaration(node) + } + + fun reportRedeclaration(element: PsiNameIdentifierOwner) { + val name = element.getName() + var writeElement: PsiElement? = null + if (name != null) { + val owner = ScopeUtil.getScopeOwner(element) + if (owner != null) { + val instructions = ControlFlowCache.getControlFlow(owner).getInstructions() + val startInstruction = ControlFlowUtil.findInstructionNumberByElement(instructions, element) + if (startInstruction >= 0) { + ControlFlowUtil.iteratePrev(startInstruction, instructions) { instruction: Instruction? -> + if (instruction is ReadWriteInstruction && instruction.num() != startInstruction) { + if (name == instruction.name) { + val originalElement = instruction.element + if (originalElement != null) { + if (instruction.access.isWriteAccess && originalElement !== element) { + writeElement = originalElement + return@iteratePrev ControlFlowUtil.Operation.BREAK + } + } + } + } + ControlFlowUtil.Operation.NEXT + } + } + } + } + + if (writeElement == null) { + return + } + val quickFixes: MutableList = ArrayList() + val quickFix = PythonUiService.getInstance().createPyRenameElementQuickFix(element) + if (quickFix != null) { + quickFixes.add(quickFix) + } + val identifier = element.getNameIdentifier() + registerProblem(identifier ?: element, + PyPsiBundle.message("INSP.redeclared.type.alias", name), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + null, + *quickFixes.toTypedArray()) + } + } +} diff --git a/python/testData/inspections/PyRedeclarationInspection/redeclaredTopLevel.py b/python/testData/inspections/PyRedeclarationInspection/redeclaredTopLevel.py index c5f73954cc89..f251bb5daf78 100644 --- a/python/testData/inspections/PyRedeclarationInspection/redeclaredTopLevel.py +++ b/python/testData/inspections/PyRedeclarationInspection/redeclaredTopLevel.py @@ -7,4 +7,4 @@ def TopLevelBoo(): class TopLevelBoo: - pass \ No newline at end of file + pass diff --git a/python/testData/inspections/PyTypeAliasRedeclarationInspection/typeAliasRedeclaration.py b/python/testData/inspections/PyTypeAliasRedeclarationInspection/typeAliasRedeclaration.py new file mode 100644 index 000000000000..695d4c9000d7 --- /dev/null +++ b/python/testData/inspections/PyTypeAliasRedeclarationInspection/typeAliasRedeclaration.py @@ -0,0 +1,6 @@ +type UnusedAlias = str +type UnusedAlias = str + +type UsedAlias = int +print(UsedAlias) +type UsedAlias = int \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeAliasRedeclarationInspectionTest.kt b/python/testSrc/com/jetbrains/python/inspections/PyTypeAliasRedeclarationInspectionTest.kt new file mode 100644 index 000000000000..0d322c7922c6 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeAliasRedeclarationInspectionTest.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.inspections + +import com.jetbrains.python.fixtures.PyInspectionTestCase + +class PyTypeAliasRedeclarationInspectionTest : PyInspectionTestCase() { + // PY-76851 + fun testTypeAliasRedeclaration() { + doTest() + } + + override fun getInspectionClass(): Class { + return PyTypeAliasRedeclarationInspection::class.java + } +} diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypingConformanceTest.kt b/python/testSrc/com/jetbrains/python/inspections/PyTypingConformanceTest.kt index 5c990067b5af..77038f9e0814 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypingConformanceTest.kt +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypingConformanceTest.kt @@ -42,6 +42,7 @@ private val inspections PyTypeCheckerInspection(), PyTypeHintsInspection(), PyUnresolvedReferencesInspection(), + PyTypeAliasRedeclarationInspection(), ) @RunWith(Parameterized::class)