From 1b5d272f4e5f4888e96dade141fcf2c050a69ef5 Mon Sep 17 00:00:00 2001 From: Aleksei Kniazev Date: Wed, 26 Jun 2019 12:36:18 +0300 Subject: [PATCH] IDEA-CR-49176: function inline: qualifier is not extracted into declaration if the method does not use 'self' (PY-36471) GitOrigin-RevId: 94a3604d54fd957e9744e7f3f6b632cb7924e4fb --- .../inline/PyInlineFunctionProcessor.kt | 44 ++++++++++--------- .../redundantQualifier/main.after.py | 12 +++++ .../inlineFunction/redundantQualifier/main.py | 12 +++++ .../refactoring/PyInlineFunctionTest.kt | 1 + 4 files changed, 49 insertions(+), 20 deletions(-) create mode 100644 python/testData/refactoring/inlineFunction/redundantQualifier/main.after.py create mode 100644 python/testData/refactoring/inlineFunction/redundantQualifier/main.py diff --git a/python/src/com/jetbrains/python/refactoring/inline/PyInlineFunctionProcessor.kt b/python/src/com/jetbrains/python/refactoring/inline/PyInlineFunctionProcessor.kt index df67e276e295..b5b4fab36028 100644 --- a/python/src/com/jetbrains/python/refactoring/inline/PyInlineFunctionProcessor.kt +++ b/python/src/com/jetbrains/python/refactoring/inline/PyInlineFunctionProcessor.kt @@ -41,6 +41,7 @@ class PyInlineFunctionProcessor(project: Project, private val myFunctionClass = myFunction.containingClass private val myGenerator = PyElementGenerator.getInstance(myProject) private var myRemoveDeclaration = !myInlineThis && removeDeclaration + private var mySelfUsed: Boolean? = null override fun preprocessUsages(refUsages: Ref>): Boolean { if (refUsages.isNull) return false @@ -284,24 +285,23 @@ class PyInlineFunctionProcessor(project: Project, val context = PyResolveContext.noImplicits().withTypeEvalContext(TypeEvalContext.userInitiated(myProject, reference.containingFile)) val mapping = PyCallExpressionHelper.mapArguments(callSite, context).firstOrNull() ?: error("Can't map arguments for ${reference.name}") val mappedParams = mapping.mappedParameters + val firstImplicit = mapping.implicitParameters.firstOrNull() - val self = mapping.implicitParameters.firstOrNull()?.let { first -> - val implicitName = first.name!! - val selfReplacement = reference.qualifier?.let { qualifier -> - myFunctionClass?.let { - when { - qualifier is PyReferenceExpression && !qualifier.isQualified -> qualifier - else -> { - val qualifierDeclaration = generateUniqueAssignment(languageLevel, myFunctionClass.name!!, generatedNames, scopeAnchor) - val newRef = qualifierDeclaration.assignedValue!!.copy() as PyExpression - qualifierDeclaration.assignedValue!!.replace(qualifier) - declarations.add(qualifierDeclaration) - newRef - } - } - } + if (mySelfUsed == null && firstImplicit != null) { + mySelfUsed = SyntaxTraverser.psiTraverser(myFunction.statementList).traverse() + .filter(PyReferenceExpression::class.java) + .filter { !it.isQualified } + .any { it.name == firstImplicit.name } + } + + val self = firstImplicit?.let { first -> + val qualifier = reference.qualifier ?: error("Function $myFunction has first implicit parameter, but no qualifier") + val selfReplacement = when { + mySelfUsed == false -> qualifier + qualifier is PyReferenceExpression && !qualifier.isQualified -> qualifier + else -> extractDeclaration(myFunctionClass?.name!!, qualifier, declarations, generatedNames, scopeAnchor, languageLevel).second } - mapOf(implicitName to selfReplacement!!) + mapOf(first.name!! to selfReplacement) } ?: emptyMap() val passedArguments = mappedParams.asSequence() @@ -323,13 +323,17 @@ class PyInlineFunctionProcessor(project: Project, private fun tryExtractDeclaration(paramName: String, arg: PyExpression, declarations: MutableList, generatedNames: MutableSet, scopeAnchor: PsiElement, languageLevel: LanguageLevel): Pair { if (arg !is PyReferenceExpression && arg !is PyLiteralExpression) { - val statement = generateUniqueAssignment(languageLevel, paramName, generatedNames, scopeAnchor) - statement.assignedValue!!.replace(arg) - declarations.add(statement) - return paramName to statement.targets[0] + return extractDeclaration(paramName, arg, declarations, generatedNames, scopeAnchor, languageLevel) } return paramName to arg + } + private fun extractDeclaration(paramName: String, arg: PyExpression, declarations: MutableList, generatedNames: MutableSet, + scopeAnchor: PsiElement, languageLevel: LanguageLevel): Pair { + val statement = generateUniqueAssignment(languageLevel, paramName, generatedNames, scopeAnchor) + statement.assignedValue!!.replace(arg) + declarations.add(statement) + return paramName to statement.targets[0] } private fun generateUniqueAssignment(level: LanguageLevel, name: String, previouslyGeneratedNames: MutableSet, scopeAnchor: PsiElement): PyAssignmentStatement { diff --git a/python/testData/refactoring/inlineFunction/redundantQualifier/main.after.py b/python/testData/refactoring/inlineFunction/redundantQualifier/main.after.py new file mode 100644 index 000000000000..9696d6310715 --- /dev/null +++ b/python/testData/refactoring/inlineFunction/redundantQualifier/main.after.py @@ -0,0 +1,12 @@ +class MyClass: + def __init__(self, attr): + self.attr = attr + + def __add__(self, other): + return MyClass(self.attr + other.attr) + + def method_no_self(self): + print('Can actually be static') + + +print('Can actually be static') diff --git a/python/testData/refactoring/inlineFunction/redundantQualifier/main.py b/python/testData/refactoring/inlineFunction/redundantQualifier/main.py new file mode 100644 index 000000000000..346622128c7d --- /dev/null +++ b/python/testData/refactoring/inlineFunction/redundantQualifier/main.py @@ -0,0 +1,12 @@ +class MyClass: + def __init__(self, attr): + self.attr = attr + + def __add__(self, other): + return MyClass(self.attr + other.attr) + + def method_no_self(self): + print('Can actually be static') + + +(MyClass(1) + MyClass(2)).method_no_self() \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyInlineFunctionTest.kt b/python/testSrc/com/jetbrains/python/refactoring/PyInlineFunctionTest.kt index 225998d07119..36baf785b6c6 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/PyInlineFunctionTest.kt +++ b/python/testSrc/com/jetbrains/python/refactoring/PyInlineFunctionTest.kt @@ -73,6 +73,7 @@ class PyInlineFunctionTest : PyTestCase() { fun testCallFromStaticMethod() = doTest() fun testCallFromClassMethod() = doTest() fun testComplexQualifier() = doTest() + fun testRedundantQualifier() = doTest() fun testFunctionWithLambda() = doTest() fun testRefInDunderAll() = doTest(inlineThis = false, remove = true) fun testRemovingDocstring() = doTest()