Fix provided type for namedtuple._replace when receiver is namedtuple definition (PY-27148)

This commit is contained in:
Semyon Proshev
2018-10-15 16:46:55 +03:00
parent 369f8f3eed
commit cef21f2445
7 changed files with 104 additions and 24 deletions
@@ -221,10 +221,14 @@ class PyNamedTupleTypeProvider : PyTypeProviderBase() {
else null
}
private fun createTypedNamedTupleReplaceType(anchor: PsiElement, fields: ImmutableNTFields, resultType: PyType): PyCallableType {
private fun createTypedNamedTupleReplaceType(anchor: PsiElement, fields: ImmutableNTFields, qualifierType: PyClassLikeType): PyCallableType {
val parameters = mutableListOf<PyCallableParameter>()
val resultType = qualifierType.toInstance()
val elementGenerator = PyElementGenerator.getInstance(anchor.project)
if (qualifierType.isDefinition) {
parameters.add(PyCallableParameterImpl.nonPsi(PyNames.CANONICAL_SELF, resultType))
}
parameters.add(PyCallableParameterImpl.psi(elementGenerator.createSingleStarParameter()))
val ellipsis = elementGenerator.createEllipsis()
@@ -238,11 +242,15 @@ class PyNamedTupleTypeProvider : PyTypeProviderBase() {
private fun createUntypedNamedTupleReplaceType(call: PyCallExpression,
fields: ImmutableNTFields,
resultType: PyType,
qualifierType: PyClassLikeType,
context: TypeEvalContext): PyCallableType {
val parameters = mutableListOf<PyCallableParameter>()
val resultType = qualifierType.toInstance()
val elementGenerator = PyElementGenerator.getInstance(call.project)
if (qualifierType.isDefinition) {
parameters.add(PyCallableParameterImpl.nonPsi(PyNames.CANONICAL_SELF, resultType))
}
parameters.add(PyCallableParameterImpl.psi(elementGenerator.createSingleStarParameter()))
val ellipsis = elementGenerator.createEllipsis()
@@ -6,22 +6,33 @@ mt1 = MyTup1(1, 2)
# empty
mt1._replace()
MyTup1._replace(mt1)
# one
mt1._replace(bar=2)
mt1._replace(baz=1)
mt1._replace(<warning descr="Unexpected argument">foo=1</warning>)
mt1._replace(<warning descr="Unexpected argument">1</warning>)
mt1._replace(<warning descr="Unexpected argument">1</warning>)
MyTup1._replace(mt1, bar=2)
MyTup1._replace(mt1, baz=1)
MyTup1._replace(mt1, <warning descr="Unexpected argument">foo=1</warning>)
MyTup1._replace(mt1, <warning descr="Unexpected argument">1</warning>)
# two
mt1._replace(bar=1, baz=2)
mt1._replace(baz=2, bar=1)
mt1._replace(baz=2, <warning descr="Unexpected argument">foo=1</warning>)
mt1._replace(<warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">1</warning>)
mt1._replace(<warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">1</warning>)
MyTup1._replace(mt1, bar=1, baz=2)
MyTup1._replace(mt1, baz=2, bar=1)
MyTup1._replace(mt1, baz=2, <warning descr="Unexpected argument">foo=1</warning>)
MyTup1._replace(mt1, <warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">1</warning>)
# two
mt1._replace(bar=1, baz=2, <warning descr="Unexpected argument">foo=3</warning>)
mt1._replace(<warning descr="Unexpected argument">1</warning>, <warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">3</warning>)
mt1._replace(<warning descr="Unexpected argument">1</warning>, <warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">3</warning>)
MyTup1._replace(mt1, bar=1, baz=2, <warning descr="Unexpected argument">foo=3</warning>)
MyTup1._replace(mt1, <warning descr="Unexpected argument">1</warning>, <warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">3</warning>)
class MyTup2(namedtuple("MyTup2", "bar baz")):
@@ -30,19 +41,30 @@ mt2 = MyTup2(1, 2)
# empty
mt2._replace()
MyTup2._replace(mt2)
# one
mt2._replace(bar=2)
mt2._replace(baz=1)
mt2._replace(<warning descr="Unexpected argument">foo=1</warning>)
mt2._replace(<warning descr="Unexpected argument">1</warning>)
mt2._replace(<warning descr="Unexpected argument">1</warning>)
MyTup2._replace(mt2, bar=2)
MyTup2._replace(mt2, baz=1)
MyTup2._replace(mt2, <warning descr="Unexpected argument">foo=1</warning>)
MyTup2._replace(mt2, <warning descr="Unexpected argument">1</warning>)
# two
mt2._replace(bar=1, baz=2)
mt2._replace(baz=2, bar=1)
mt2._replace(baz=2, <warning descr="Unexpected argument">foo=1</warning>)
mt2._replace(<warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">1</warning>)
mt2._replace(<warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">1</warning>)
MyTup2._replace(mt2, bar=1, baz=2)
MyTup2._replace(mt2, baz=2, bar=1)
MyTup2._replace(mt2, baz=2, <warning descr="Unexpected argument">foo=1</warning>)
MyTup2._replace(mt2, <warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">1</warning>)
# two
mt2._replace(bar=1, baz=2, <warning descr="Unexpected argument">foo=3</warning>)
mt2._replace(<warning descr="Unexpected argument">1</warning>, <warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">3</warning>)
mt2._replace(<warning descr="Unexpected argument">1</warning>, <warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">3</warning>)
MyTup2._replace(mt2, bar=1, baz=2, <warning descr="Unexpected argument">foo=3</warning>)
MyTup2._replace(mt2, <warning descr="Unexpected argument">1</warning>, <warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">3</warning>)
@@ -6,22 +6,33 @@ mt1 = MyTup1(1, 2)
# empty
mt1._replace()
MyTup1._replace(mt1)
# one
mt1._replace(bar=2)
mt1._replace(baz=1)
mt1._replace(<warning descr="Unexpected argument">foo=1</warning>)
mt1._replace(<warning descr="Unexpected argument">1</warning>)
mt1._replace(<warning descr="Unexpected argument">1</warning>)
MyTup1._replace(mt1, bar=2)
MyTup1._replace(mt1, baz=1)
MyTup1._replace(mt1, <warning descr="Unexpected argument">foo=1</warning>)
MyTup1._replace(mt1, <warning descr="Unexpected argument">1</warning>)
# two
mt1._replace(bar=1, baz=2)
mt1._replace(baz=2, bar=1)
mt1._replace(baz=2, <warning descr="Unexpected argument">foo=1</warning>)
mt1._replace(<warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">1</warning>)
mt1._replace(<warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">1</warning>)
MyTup1._replace(mt1, bar=1, baz=2)
MyTup1._replace(mt1, baz=2, bar=1)
MyTup1._replace(mt1, baz=2, <warning descr="Unexpected argument">foo=1</warning>)
MyTup1._replace(mt1, <warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">1</warning>)
# two
mt1._replace(bar=1, baz=2, <warning descr="Unexpected argument">foo=3</warning>)
mt1._replace(<warning descr="Unexpected argument">1</warning>, <warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">3</warning>)
mt1._replace(<warning descr="Unexpected argument">1</warning>, <warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">3</warning>)
MyTup1._replace(mt1, bar=1, baz=2, <warning descr="Unexpected argument">foo=3</warning>)
MyTup1._replace(mt1, <warning descr="Unexpected argument">1</warning>, <warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">3</warning>)
class MyTup2(typing.NamedTuple):
@@ -31,19 +42,30 @@ mt2 = MyTup2(1, 2)
# empty
mt2._replace()
MyTup2._replace(mt2)
# one
mt2._replace(bar=2)
mt2._replace(baz=1)
mt2._replace(<warning descr="Unexpected argument">foo=1</warning>)
mt2._replace(<warning descr="Unexpected argument">1</warning>)
mt2._replace(<warning descr="Unexpected argument">1</warning>)
MyTup2._replace(mt2, bar=2)
MyTup2._replace(mt2, baz=1)
MyTup2._replace(mt2, <warning descr="Unexpected argument">foo=1</warning>)
MyTup2._replace(mt2, <warning descr="Unexpected argument">1</warning>)
# two
mt2._replace(bar=1, baz=2)
mt2._replace(baz=2, bar=1)
mt2._replace(baz=2, <warning descr="Unexpected argument">foo=1</warning>)
mt2._replace(<warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">1</warning>)
mt2._replace(<warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">1</warning>)
MyTup2._replace(mt2, bar=1, baz=2)
MyTup2._replace(mt2, baz=2, bar=1)
MyTup2._replace(mt2, baz=2, <warning descr="Unexpected argument">foo=1</warning>)
MyTup2._replace(mt2, <warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">1</warning>)
# two
mt2._replace(bar=1, baz=2, <warning descr="Unexpected argument">foo=3</warning>)
mt2._replace(<warning descr="Unexpected argument">1</warning>, <warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">3</warning>)
mt2._replace(<warning descr="Unexpected argument">1</warning>, <warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">3</warning>)
MyTup2._replace(mt2, bar=1, baz=2, <warning descr="Unexpected argument">foo=3</warning>)
MyTup2._replace(mt2, <warning descr="Unexpected argument">1</warning>, <warning descr="Unexpected argument">2</warning>, <warning descr="Unexpected argument">3</warning>)
@@ -9,4 +9,7 @@ class MyTup2(namedtuple("MyTup2", "bar baz")):
MyTup1(1, 2)._replace(<arg1>)
MyTup2(1, 2)._replace(<arg2>)
MyTup2(1, 2)._replace(<arg2>)
MyTup1._replace(MyTup1(1, 2), <arg3>)
MyTup2._replace(MyTup2(1, 2), <arg4>)
@@ -1,7 +1,7 @@
import typing
MyTup1 = typing.NamedTuple("MyTup2", bar=int, baz=str)
MyTup1 = typing.NamedTuple("MyTup1", bar=int, baz=str)
class MyTup2(typing.NamedTuple):
@@ -10,4 +10,7 @@ class MyTup2(typing.NamedTuple):
MyTup1(1, "")._replace(<arg1>)
MyTup2(1, "")._replace(<arg2>)
MyTup2(1, "")._replace(<arg2>)
MyTup1._replace(MyTup1(1, ""), <arg3>)
MyTup2._replace(MyTup2(1, ""), <arg4>)
@@ -38,8 +38,8 @@ import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.File;
import java.util.*;
import java.util.List;
import java.util.*;
/**
* Tests parameter info available via ^P at call sites.
@@ -105,11 +105,11 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
PsiElement arg1 = marks.get("<arg1>");
feignCtrlP(arg1.getTextOffset()).check("a, b, *c", new String[]{"a, "});
feignCtrlP(arg1.getTextOffset()+1).check("a, b, *c", new String[]{"a, "});
PsiElement arg2 = marks.get("<arg2>");
feignCtrlP(arg2.getTextOffset()).check("a, b, *c", new String[]{"b, "});
feignCtrlP(arg2.getTextOffset()+1).check("a, b, *c", new String[]{"b, "});
PsiElement arg3 = marks.get("<arg3>");
feignCtrlP(arg3.getTextOffset()).check("a, b, *c", new String[]{"*c"});
feignCtrlP(arg3.getTextOffset()+1).check("a, b, *c", new String[]{"*c"});
@@ -692,11 +692,14 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
// PY-27148
public void testCollectionsNamedTupleReplace() {
final Map<String, PsiElement> test = loadTest(2);
final Map<String, PsiElement> test = loadTest(4);
for (int offset : StreamEx.of(test.values()).map(PsiElement::getTextOffset)) {
for (int offset : StreamEx.of("<arg1>", "<arg2>").map(test::get).map(PsiElement::getTextOffset)) {
feignCtrlP(offset).check("*, bar=..., baz=...", ArrayUtil.EMPTY_STRING_ARRAY);
}
feignCtrlP(test.get("<arg3>").getTextOffset()).check("self: MyTup1, *, bar=..., baz=...", ArrayUtil.EMPTY_STRING_ARRAY);
feignCtrlP(test.get("<arg4>").getTextOffset()).check("self: MyTup2, *, bar=..., baz=...", ArrayUtil.EMPTY_STRING_ARRAY);
}
// PY-27148
@@ -704,11 +707,14 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
runWithLanguageLevel(
LanguageLevel.PYTHON36,
() -> {
final Map<String, PsiElement> test = loadTest(2);
final Map<String, PsiElement> test = loadTest(4);
for (int offset : StreamEx.of(test.values()).map(PsiElement::getTextOffset)) {
for (int offset : StreamEx.of("<arg1>", "<arg2>").map(test::get).map(PsiElement::getTextOffset)) {
feignCtrlP(offset).check("*, bar: int=..., baz: str=...", ArrayUtil.EMPTY_STRING_ARRAY);
}
feignCtrlP(test.get("<arg3>").getTextOffset()).check("self: MyTup1, *, bar: int=..., baz: str=...", ArrayUtil.EMPTY_STRING_ARRAY);
feignCtrlP(test.get("<arg4>").getTextOffset()).check("self: MyTup2, *, bar: int=..., baz: str=...", ArrayUtil.EMPTY_STRING_ARRAY);
}
);
}
@@ -3070,6 +3070,12 @@ public class PyTypeTest extends PyTestCase {
"from collections import namedtuple\n" +
"Cat = namedtuple(\"Cat\", \"name age\")\n" +
"expr = Cat(\"name\", 5)._replace(age=\"five\").age");
doTest("Cat",
"from collections import namedtuple\n" +
"class Cat(namedtuple(\"Cat\", \"name age\")):\n" +
" pass\n" +
"expr = Cat._replace(Cat(\"name\", 5), name=\"newname\")");
}
// PY-27148
@@ -3099,6 +3105,16 @@ public class PyTypeTest extends PyTestCase {
"Cat = NamedTuple(\"Cat\", name=str, age=int)\n" +
"expr = Cat(\"name\", 5)._replace(age=\"give\").age")
);
runWithLanguageLevel(
LanguageLevel.PYTHON36,
() -> doTest("Cat",
"from typing import NamedTuple\n" +
"class Cat(NamedTuple):\n" +
" name: str\n" +
" age: int\n" +
"expr = Cat._replace(Cat(\"name\", 5), name=\"newname\")")
);
}
// PY-21302