diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java index 4c4cdba98a24..53f1d9a98d91 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java @@ -1,18 +1,4 @@ -/* - * 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. - */ +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.jetbrains.python.codeInsight.stdlib; import com.intellij.codeInsight.lookup.LookupElementBuilder; @@ -24,6 +10,7 @@ import com.jetbrains.python.psi.PyCallSiteExpression; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyExpression; import com.jetbrains.python.psi.types.*; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -86,7 +73,8 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType return new PyNamedTupleType(myClass, myDeclaration, myName, myFields, DefinitionLevel.NEW_TYPE); } else if (myDefinitionLevel == DefinitionLevel.NEW_TYPE) { - return new PyNamedTupleType(myClass, myDeclaration, myName, myFields, DefinitionLevel.INSTANCE); + final Map fields = takeFieldsTypesFromCallSiteIfNeeded(context, callSite); + return new PyNamedTupleType(myClass, myDeclaration, myName, fields, DefinitionLevel.INSTANCE); } return null; @@ -161,6 +149,30 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType : null; } + @NotNull + private Map takeFieldsTypesFromCallSiteIfNeeded(@NotNull TypeEvalContext context, + @NotNull PyCallSiteExpression callSite) { + if (StreamEx.ofValues(myFields).allMatch(typeAndValue -> typeAndValue.getType() == null)) { + final List arguments = callSite.getArguments(null); + + if (arguments.size() == myFields.size()) { + final Map result = new HashMap<>(); + + for (Map.Entry entry : StreamEx.ofKeys(myFields).zipWith(StreamEx.of(arguments))) { + final String name = entry.getKey(); + final PyType type = context.getType(entry.getValue()); + final PyExpression value = myFields.get(name).getDefaultValue(); + + result.put(name, new FieldTypeAndDefaultValue(type, value)); + } + + return result; + } + } + + return myFields; + } + @NotNull private static PyCallableParameter fieldToCallableParameter(@NotNull String name, @NotNull FieldTypeAndDefaultValue typeAndDefaultValue) { return PyCallableParameterImpl.nonPsi(name, typeAndDefaultValue.getType(), typeAndDefaultValue.getDefaultValue()); diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java index 6468818ab4d4..5ffbce905e68 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java @@ -84,9 +84,9 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { } } - final PyType fieldTypeForTypingNTTarget = getFieldTypeForTypingNTTarget(referenceExpression, context); - if (fieldTypeForTypingNTTarget != null) { - return fieldTypeForTypingNTTarget; + final PyType fieldTypeForNamedTuple = getFieldTypeForNamedTupleAsTarget(referenceExpression, context); + if (fieldTypeForNamedTuple != null) { + return fieldTypeForNamedTuple; } final PyCallableType namedTupleTypeForCallee = getNamedTupleTypeForCallee(referenceExpression, context); @@ -173,8 +173,8 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { } @Nullable - private static PyType getFieldTypeForTypingNTTarget(@NotNull PyReferenceExpression referenceExpression, - @NotNull TypeEvalContext context) { + private static PyType getFieldTypeForNamedTupleAsTarget(@NotNull PyReferenceExpression referenceExpression, + @NotNull TypeEvalContext context) { final PyExpression qualifier = referenceExpression.getQualifier(); if (qualifier != null) { final PyType qualifierType = context.getType(qualifier); diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 98958d68dba2..a27a607d9b90 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -2198,6 +2198,25 @@ public class PyTypeTest extends PyTestCase { ); } + // PY-4351 + public void testCollectionsNTInheritorField() { + // Seems that this case won't be supported because + // it requires to update ancestor, not class itself, for every `User(...)` call + doTest("Any", + "from collections import namedtuple\n" + + "class User(namedtuple(\"User\", \"name age\")):\n" + + " pass\n" + + "expr = User(\"name\", 13).age"); + } + + // PY-4351 + public void testCollectionsNTTargetField() { + doTest("int", + "from collections import namedtuple\n" + + "User = namedtuple(\"User\", \"name age\")\n" + + "expr = User(\"name\", 13).age"); + } + // PY-18791 public void testCallOnProperty() { runWithLanguageLevel(