From cef21f2445ce92989b95bf82b5b8b62f69776124 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Mon, 15 Oct 2018 16:15:08 +0300 Subject: [PATCH] Fix provided type for namedtuple._replace when receiver is namedtuple definition (PY-27148) --- .../stdlib/PyNamedTupleTypeProvider.kt | 12 +++++-- .../collectionsNamedTupleReplace.py | 34 +++++++++++++++---- .../typingNamedTupleReplace.py | 34 +++++++++++++++---- .../paramInfo/CollectionsNamedTupleReplace.py | 5 ++- .../paramInfo/TypingNamedTupleReplace.py | 7 ++-- .../jetbrains/python/PyParameterInfoTest.java | 20 +++++++---- .../com/jetbrains/python/PyTypeTest.java | 16 +++++++++ 7 files changed, 104 insertions(+), 24 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleTypeProvider.kt b/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleTypeProvider.kt index 88f41ea3dd72..ce2a00904f3a 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleTypeProvider.kt +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleTypeProvider.kt @@ -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() + 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() + 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() diff --git a/python/testData/inspections/PyArgumentListInspection/collectionsNamedTupleReplace.py b/python/testData/inspections/PyArgumentListInspection/collectionsNamedTupleReplace.py index e33ced9a783f..f85f6ac9c544 100644 --- a/python/testData/inspections/PyArgumentListInspection/collectionsNamedTupleReplace.py +++ b/python/testData/inspections/PyArgumentListInspection/collectionsNamedTupleReplace.py @@ -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(foo=1) -mt1._replace(1) +mt1._replace(1) +MyTup1._replace(mt1, bar=2) +MyTup1._replace(mt1, baz=1) +MyTup1._replace(mt1, foo=1) +MyTup1._replace(mt1, 1) # two mt1._replace(bar=1, baz=2) mt1._replace(baz=2, bar=1) mt1._replace(baz=2, foo=1) -mt1._replace(2, 1) +mt1._replace(2, 1) +MyTup1._replace(mt1, bar=1, baz=2) +MyTup1._replace(mt1, baz=2, bar=1) +MyTup1._replace(mt1, baz=2, foo=1) +MyTup1._replace(mt1, 2, 1) # two mt1._replace(bar=1, baz=2, foo=3) -mt1._replace(1, 2, 3) +mt1._replace(1, 2, 3) +MyTup1._replace(mt1, bar=1, baz=2, foo=3) +MyTup1._replace(mt1, 1, 2, 3) 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(foo=1) -mt2._replace(1) +mt2._replace(1) +MyTup2._replace(mt2, bar=2) +MyTup2._replace(mt2, baz=1) +MyTup2._replace(mt2, foo=1) +MyTup2._replace(mt2, 1) # two mt2._replace(bar=1, baz=2) mt2._replace(baz=2, bar=1) mt2._replace(baz=2, foo=1) -mt2._replace(2, 1) +mt2._replace(2, 1) +MyTup2._replace(mt2, bar=1, baz=2) +MyTup2._replace(mt2, baz=2, bar=1) +MyTup2._replace(mt2, baz=2, foo=1) +MyTup2._replace(mt2, 2, 1) # two mt2._replace(bar=1, baz=2, foo=3) -mt2._replace(1, 2, 3) \ No newline at end of file +mt2._replace(1, 2, 3) +MyTup2._replace(mt2, bar=1, baz=2, foo=3) +MyTup2._replace(mt2, 1, 2, 3) \ No newline at end of file diff --git a/python/testData/inspections/PyArgumentListInspection/typingNamedTupleReplace.py b/python/testData/inspections/PyArgumentListInspection/typingNamedTupleReplace.py index 125a85f20522..243467b231dd 100644 --- a/python/testData/inspections/PyArgumentListInspection/typingNamedTupleReplace.py +++ b/python/testData/inspections/PyArgumentListInspection/typingNamedTupleReplace.py @@ -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(foo=1) -mt1._replace(1) +mt1._replace(1) +MyTup1._replace(mt1, bar=2) +MyTup1._replace(mt1, baz=1) +MyTup1._replace(mt1, foo=1) +MyTup1._replace(mt1, 1) # two mt1._replace(bar=1, baz=2) mt1._replace(baz=2, bar=1) mt1._replace(baz=2, foo=1) -mt1._replace(2, 1) +mt1._replace(2, 1) +MyTup1._replace(mt1, bar=1, baz=2) +MyTup1._replace(mt1, baz=2, bar=1) +MyTup1._replace(mt1, baz=2, foo=1) +MyTup1._replace(mt1, 2, 1) # two mt1._replace(bar=1, baz=2, foo=3) -mt1._replace(1, 2, 3) +mt1._replace(1, 2, 3) +MyTup1._replace(mt1, bar=1, baz=2, foo=3) +MyTup1._replace(mt1, 1, 2, 3) 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(foo=1) -mt2._replace(1) +mt2._replace(1) +MyTup2._replace(mt2, bar=2) +MyTup2._replace(mt2, baz=1) +MyTup2._replace(mt2, foo=1) +MyTup2._replace(mt2, 1) # two mt2._replace(bar=1, baz=2) mt2._replace(baz=2, bar=1) mt2._replace(baz=2, foo=1) -mt2._replace(2, 1) +mt2._replace(2, 1) +MyTup2._replace(mt2, bar=1, baz=2) +MyTup2._replace(mt2, baz=2, bar=1) +MyTup2._replace(mt2, baz=2, foo=1) +MyTup2._replace(mt2, 2, 1) # two mt2._replace(bar=1, baz=2, foo=3) -mt2._replace(1, 2, 3) \ No newline at end of file +mt2._replace(1, 2, 3) +MyTup2._replace(mt2, bar=1, baz=2, foo=3) +MyTup2._replace(mt2, 1, 2, 3) \ No newline at end of file diff --git a/python/testData/paramInfo/CollectionsNamedTupleReplace.py b/python/testData/paramInfo/CollectionsNamedTupleReplace.py index a49965e1e2f3..1158346ca8e6 100644 --- a/python/testData/paramInfo/CollectionsNamedTupleReplace.py +++ b/python/testData/paramInfo/CollectionsNamedTupleReplace.py @@ -9,4 +9,7 @@ class MyTup2(namedtuple("MyTup2", "bar baz")): MyTup1(1, 2)._replace() -MyTup2(1, 2)._replace() \ No newline at end of file +MyTup2(1, 2)._replace() + +MyTup1._replace(MyTup1(1, 2), ) +MyTup2._replace(MyTup2(1, 2), ) \ No newline at end of file diff --git a/python/testData/paramInfo/TypingNamedTupleReplace.py b/python/testData/paramInfo/TypingNamedTupleReplace.py index 48682dd840d0..7a624ab2e453 100644 --- a/python/testData/paramInfo/TypingNamedTupleReplace.py +++ b/python/testData/paramInfo/TypingNamedTupleReplace.py @@ -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() -MyTup2(1, "")._replace() \ No newline at end of file +MyTup2(1, "")._replace() + +MyTup1._replace(MyTup1(1, ""), ) +MyTup2._replace(MyTup2(1, ""), ) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java index 050d66e98a24..23316b1cd996 100644 --- a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java +++ b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java @@ -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(""); 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(""); 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(""); 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 test = loadTest(2); + final Map test = loadTest(4); - for (int offset : StreamEx.of(test.values()).map(PsiElement::getTextOffset)) { + for (int offset : StreamEx.of("", "").map(test::get).map(PsiElement::getTextOffset)) { feignCtrlP(offset).check("*, bar=..., baz=...", ArrayUtil.EMPTY_STRING_ARRAY); } + + feignCtrlP(test.get("").getTextOffset()).check("self: MyTup1, *, bar=..., baz=...", ArrayUtil.EMPTY_STRING_ARRAY); + feignCtrlP(test.get("").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 test = loadTest(2); + final Map test = loadTest(4); - for (int offset : StreamEx.of(test.values()).map(PsiElement::getTextOffset)) { + for (int offset : StreamEx.of("", "").map(test::get).map(PsiElement::getTextOffset)) { feignCtrlP(offset).check("*, bar: int=..., baz: str=...", ArrayUtil.EMPTY_STRING_ARRAY); } + + feignCtrlP(test.get("").getTextOffset()).check("self: MyTup1, *, bar: int=..., baz: str=...", ArrayUtil.EMPTY_STRING_ARRAY); + feignCtrlP(test.get("").getTextOffset()).check("self: MyTup2, *, bar: int=..., baz: str=...", ArrayUtil.EMPTY_STRING_ARRAY); } ); } diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 1a9166806759..a4648f85a19d 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -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