Handle namedtuples unpacking (PY-4351)

This commit is contained in:
Semyon Proshev
2017-11-15 18:21:57 +03:00
committed by Semyon Proshev
parent f397c98170
commit 80b3375629
7 changed files with 82 additions and 42 deletions
@@ -19,7 +19,7 @@ import java.util.*;
/**
* @author yole
*/
public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType {
public class PyNamedTupleType extends PyTupleType implements PyCallableType {
@NotNull
private final PsiElement myDeclaration;
@@ -38,7 +38,11 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType
@NotNull String name,
@NotNull Map<String, FieldTypeAndDefaultValue> fields,
@NotNull DefinitionLevel definitionLevel) {
super(tupleClass, definitionLevel != DefinitionLevel.INSTANCE);
super(tupleClass,
Collections.unmodifiableList(ContainerUtil.map(fields.values(), typeAndValue -> typeAndValue.getType())),
false,
definitionLevel != DefinitionLevel.INSTANCE);
myDeclaration = declaration;
myFields = Collections.unmodifiableMap(fields);
myName = name;
@@ -127,10 +131,6 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType
return result;
}
public int getElementCount() {
return myFields.size();
}
@NotNull
public Map<String, FieldTypeAndDefaultValue> getFields() {
return myFields;
@@ -152,7 +152,7 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType
@NotNull
private Map<String, FieldTypeAndDefaultValue> takeFieldsTypesFromCallSiteIfNeeded(@NotNull TypeEvalContext context,
@NotNull PyCallSiteExpression callSite) {
if (StreamEx.ofValues(myFields).allMatch(typeAndValue -> typeAndValue.getType() == null)) {
if (StreamEx.of(getElementTypes()).allMatch(Objects::isNull)) {
final List<PyExpression> arguments = callSite.getArguments(null);
if (arguments.size() == myFields.size()) {
@@ -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.documentation;
import com.google.common.collect.Collections2;
@@ -21,6 +7,7 @@ import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.stdlib.PyNamedTupleType;
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.types.*;
@@ -233,7 +220,10 @@ public class PyTypeModelBuilder {
myVisited.put(type, null); //mark as evaluating
TypeModel result = null;
if (type instanceof PyTupleType) {
if (type instanceof PyNamedTupleType) {
result = NamedType.nameOrAny(type);
}
else if (type instanceof PyTupleType) {
final PyTupleType tupleType = (PyTupleType)type;
final List<PyType> elementTypes = tupleType.isHomogeneous()
@@ -183,8 +183,8 @@ public class PyTargetExpressionImpl extends PyBaseElementImpl<PyTargetExpression
final PyTupleExpression targetTuple = PsiTreeUtil.findChildOfType(lhs, PyTupleExpression.class, false);
if (value != null && targetTuple != null) {
final PyType assignedType = PyTypeChecker.toNonWeakType(context.getType(value), context);
if (assignedType instanceof PyTupleType) {
final PyType t = PyTypeChecker.getTargetTypeFromTupleAssignment(this, targetTuple, (PyTupleType)assignedType);
if (assignedType != null) {
final PyType t = PyTypeChecker.getTargetTypeFromTupleAssignment(this, targetTuple, assignedType, context);
if (t != null) {
return t;
}
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2016 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.psi.types;
import com.intellij.openapi.util.text.StringUtil;
@@ -54,11 +40,16 @@ public class PyTupleType extends PyClassTypeImpl implements PyCollectionType {
}
public PyTupleType(@NotNull PyClass tupleClass, @NotNull List<PyType> elementTypes, boolean homogeneous) {
super(tupleClass, false);
this(tupleClass, elementTypes, homogeneous, false);
}
protected PyTupleType(@NotNull PyClass tupleClass, @NotNull List<PyType> elementTypes, boolean homogeneous, boolean isDefinition) {
super(tupleClass, isDefinition);
myElementTypes = elementTypes;
myHomogeneous = homogeneous;
}
@Override
@NotNull
public String getName() {
if (myHomogeneous) {
@@ -7,6 +7,7 @@ import com.intellij.psi.PsiFile;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.stdlib.PyNamedTupleType;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.impl.PyTypeProvider;
@@ -704,6 +705,26 @@ public class PyTypeChecker {
return null;
}
@Nullable
public static PyType getTargetTypeFromTupleAssignment(@NotNull PyTargetExpression target,
@NotNull PyTupleExpression parentTuple,
@NotNull PyType assignedType,
@NotNull TypeEvalContext context) {
if (assignedType instanceof PyTupleType) {
return getTargetTypeFromTupleAssignment(target, parentTuple, (PyTupleType)assignedType);
}
else if (assignedType instanceof PyClassLikeType) {
return StreamEx
.of(((PyClassLikeType)assignedType).getAncestorTypes(context))
.select(PyNamedTupleType.class)
.findFirst()
.map(t -> getTargetTypeFromTupleAssignment(target, parentTuple, t))
.orElse(null);
}
return null;
}
@Nullable
public static PyType getTargetTypeFromTupleAssignment(@NotNull PyTargetExpression target, @NotNull PyTupleExpression parentTuple,
@NotNull PyTupleType assignedTupleType) {
@@ -37,7 +37,7 @@ def list_tuple(cond):
"%s %s" % named_tuple_func(True)
"%s %s" % named_tuple_func(False)
"%s %s %s" % named_tuple_func(False)
"%s %s %s" % <warning descr="Too few arguments for format string">named_tuple_func(False)</warning>
"%s" % primitive_types_func(True)
"%s %s" % <warning descr="Too few arguments for format string">primitive_types_func(True)</warning>
@@ -2217,6 +2217,44 @@ public class PyTypeTest extends PyTestCase {
"expr = User(\"name\", 13).age");
}
// PY-4351
public void testTypingNTInheritorUnpacking() {
doTest("int",
"from typing import NamedTuple\n" +
"class User(NamedTuple(\"User\", [(\"name\", str), (\"age\", int)])):\n" +
" pass\n" +
"y2, expr = User(\"name\", 13)");
}
// PY-4351
public void testTypingNTTargetUnpacking() {
doTest("int",
"from typing import NamedTuple\n" +
"Point2 = NamedTuple('Point', [('x', int), ('y', str)])\n" +
"p2 = Point2(1, \"1\")\n" +
"expr, y2 = p2");
}
// PY-4351
public void testCollectionsNTInheritorUnpacking() {
// 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 ags\")):\n" +
" pass\n" +
"y1, expr = User(\"name\", 13)");
}
// PY-4351
public void testCollectionsNTTargetUnpacking() {
doTest("int",
"from collections import namedtuple\n" +
"Point = namedtuple('Point', ['x', 'y'])\n" +
"p1 = Point(1, '1')\n" +
"expr, y1 = p1");
}
// PY-18791
public void testCallOnProperty() {
runWithLanguageLevel(