IDEA-CR-49176: function inline: import elements from function scope with alias if there is a name clash (PY-36642)

GitOrigin-RevId: 992b1d44d248725f31f599d53efdd4e4b854824a
This commit is contained in:
Aleksei Kniazev
2019-06-26 17:47:46 +03:00
committed by intellij-monorepo-bot
parent f6c70470d6
commit faf4d0b5cc
6 changed files with 50 additions and 2 deletions

View File

@@ -57,6 +57,7 @@ public final class PyClassRefactoringUtil {
private static final Key<PsiNamedElement> ENCODED_IMPORT = Key.create("PyEncodedImport");
private static final Key<Boolean> ENCODED_USE_FROM_IMPORT = Key.create("PyEncodedUseFromImport");
private static final Key<String> ENCODED_IMPORT_AS = Key.create("PyEncodedImportAs");
private static final Key<PyReferenceExpression> REPLACEMENT_EXPRESSION = Key.create("PyReplacementExpression");
private PyClassRefactoringUtil() {
@@ -251,6 +252,7 @@ public final class PyClassRefactoringUtil {
PsiNamedElement target = sourceNode.getCopyableUserData(ENCODED_IMPORT);
final String asName = sourceNode.getCopyableUserData(ENCODED_IMPORT_AS);
final Boolean useFromImport = sourceNode.getCopyableUserData(ENCODED_USE_FROM_IMPORT);
final PyReferenceExpression replacement = sourceNode.getCopyableUserData(REPLACEMENT_EXPRESSION);
if (target instanceof PsiDirectory) {
target = (PsiNamedElement)PyUtil.getPackageElement((PsiDirectory)target, sourceNode);
}
@@ -270,6 +272,9 @@ public final class PyClassRefactoringUtil {
else {
insertImport(targetNode, target, asName, true);
}
if (replacement != null) {
sourceNode.replace(replacement);
}
}
finally {
sourceNode.putCopyableUserData(ENCODED_IMPORT, null);
@@ -428,6 +433,22 @@ public final class PyClassRefactoringUtil {
return ContainerUtil.mapNotNull(expr.getReference().multiResolve(false), result -> result.getElement());
}
/**
* Forces the use of 'import as' when restoring references (i.e. if there are name clashes). Takes an optional replacement expression
* to insert in place of the node after import.
* @param node with encoded import
* @param asName new alias for import
* @param replacement reference after import
*/
public static void forceAsName(@NotNull PyReferenceExpression node, @NotNull String asName, @Nullable PyReferenceExpression replacement) {
if (node.getCopyableUserData(ENCODED_IMPORT) == null) {
LOG.warn("As name is forced on the referenceExpression, that has no encoded import. Forcing it will likely be ignored.");
}
node.putCopyableUserData(ENCODED_IMPORT_AS, asName);
if (replacement != null) {
node.putCopyableUserData(REPLACEMENT_EXPRESSION, replacement);
}
}
public static boolean hasEncodedTarget(@NotNull PyReferenceExpression node) {
return node.getCopyableUserData(ENCODED_IMPORT) != null;

View File

@@ -148,7 +148,9 @@ class PyInlineFunctionProcessor(project: Project,
val argumentReplacements = mutableMapOf<PyReferenceExpression, PyExpression>()
val nameClashes = mutableSetOf<String>()
val importAsTargets = mutableSetOf<String>()
val nameClashRefs = MultiMap.create<String, PyExpression>()
val importAsRefs = MultiMap.create<String, PyReferenceExpression>()
val returnStatements = mutableListOf<PyReturnStatement>()
val mappedArguments = prepareArguments(callSite, declarations, generatedNames, scopeAnchor, reference, languageLevel)
@@ -157,11 +159,12 @@ class PyInlineFunctionProcessor(project: Project,
override fun visitPyReferenceExpression(node: PyReferenceExpression) {
if (!node.isQualified) {
val name = node.name!!
if (name in namesInOuterScope && name !in mappedArguments && !PyClassRefactoringUtil.hasEncodedTarget(node)) {
if (name in namesInOuterScope && name !in mappedArguments) {
val resolved = node.reference.resolve()
val target = (resolved as? PyFunction)?.containingClass ?: resolved
if (!builtinCache.isBuiltin(target) && target !in PyResolveUtil.resolveLocally(refScopeOwner, name)) {
nameClashes.add(name)
if (PyClassRefactoringUtil.hasEncodedTarget(node)) importAsTargets.add(name)
else nameClashes.add(name)
}
}
}
@@ -192,6 +195,7 @@ class PyInlineFunctionProcessor(project: Project,
when (val name = node.name) {
in mappedArguments -> argumentReplacements[node] = mappedArguments[name]!!
in nameClashes -> nameClashRefs.putValue(name!!, node)
in importAsTargets -> importAsRefs.putValue(name!!, node)
}
}
}
@@ -223,6 +227,10 @@ class PyInlineFunctionProcessor(project: Project,
}
}
importAsRefs.entrySet().forEach { (name, elements) ->
val newRef = generateUniqueAssignment(languageLevel, name, generatedNames, scopeAnchor).assignedValue as PyReferenceExpression
elements.forEach { PyClassRefactoringUtil.forceAsName(it, newRef.name!!, newRef) }
}
if (returnStatements.size == 1 && returnStatements[0].expression !is PyTupleExpression) {
// replace single return with expression itself

View File

@@ -0,0 +1,6 @@
from source import foo as foo1
foo = 1
x = foo1()
res = x + 2

View File

@@ -0,0 +1,5 @@
from source import bar
foo = 1
res = ba<caret>r(2)

View File

@@ -0,0 +1,7 @@
def foo():
return 1
def bar(y):
x = foo()
return x + y

View File

@@ -59,6 +59,7 @@ class PyInlineFunctionTest : PyTestCase() {
fun testArgumentExtraction() = doTest()
fun testMultipleReturns() = doTest()
fun testImporting() = doTest()
fun testImportAs() = doTest()
//fun testExistingImports() = doTest()
fun testMethodInsideClass() = doTest()
fun testMethodOutsideClass() = doTest()