Clarify field types for collections.namedtuples from call site (PY-4351)

It's actual for namedtuples declared as target.
Clarified types are not weak because namedtuples are immutable.
This commit is contained in:
Semyon Proshev
2017-11-15 18:21:17 +03:00
committed by Semyon Proshev
parent 10206b981c
commit f397c98170
3 changed files with 52 additions and 21 deletions
@@ -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<String, FieldTypeAndDefaultValue> 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<String, FieldTypeAndDefaultValue> takeFieldsTypesFromCallSiteIfNeeded(@NotNull TypeEvalContext context,
@NotNull PyCallSiteExpression callSite) {
if (StreamEx.ofValues(myFields).allMatch(typeAndValue -> typeAndValue.getType() == null)) {
final List<PyExpression> arguments = callSite.getArguments(null);
if (arguments.size() == myFields.size()) {
final Map<String, FieldTypeAndDefaultValue> result = new HashMap<>();
for (Map.Entry<String, PyExpression> 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());
@@ -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);
@@ -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(