PY-24099 Fixed: False positive: parameter unfilled for NamedTuple with default value

Update PyCallableParameterImpl to be able to store default value and use this in PyNamedTupleType.
Class inherited from typing.NamedTuple passes default values to PyNamedTupleType.
This commit is contained in:
Semyon Proshev
2017-06-09 21:29:12 +03:00
committed by Semyon Proshev
parent 27ad3a3c0a
commit 2447451dc4
9 changed files with 108 additions and 21 deletions
@@ -45,7 +45,7 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType
private final String myName;
@NotNull
private final Map<String, Optional<PyType>> myFields;
private final Map<String, FieldTypeAndDefaultValue> myFields;
@NotNull
private final DefinitionLevel myDefinitionLevel;
@@ -53,7 +53,7 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType
public PyNamedTupleType(@NotNull PyClass tupleClass,
@NotNull PsiElement declaration,
@NotNull String name,
@NotNull Map<String, Optional<PyType>> fields,
@NotNull Map<String, FieldTypeAndDefaultValue> fields,
@NotNull DefinitionLevel definitionLevel) {
super(tupleClass, definitionLevel != DefinitionLevel.INSTANCE);
myDeclaration = declaration;
@@ -149,7 +149,7 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType
}
@NotNull
public Map<String, Optional<PyType>> getFields() {
public Map<String, FieldTypeAndDefaultValue> getFields() {
return myFields;
}
@@ -162,14 +162,43 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType
@Override
public List<PyCallableParameter> getParameters(@NotNull TypeEvalContext context) {
return isCallable()
? ContainerUtil.map(myFields.entrySet(), field -> new PyCallableParameterImpl(field.getKey(), field.getValue().orElse(null)))
? ContainerUtil.map(myFields.entrySet(), field -> fieldToCallableParameter(field.getKey(), field.getValue()))
: null;
}
@NotNull
private static PyCallableParameter fieldToCallableParameter(@NotNull String name, @NotNull FieldTypeAndDefaultValue typeAndDefaultValue) {
return new PyCallableParameterImpl(name, typeAndDefaultValue.getType(), typeAndDefaultValue.getDefaultValue());
}
public enum DefinitionLevel {
AS_SUPERCLASS,
NEW_TYPE,
INSTANCE
}
public static class FieldTypeAndDefaultValue {
@Nullable
private final PyType myType;
@Nullable
private final PyExpression myDefaultValue;
public FieldTypeAndDefaultValue(@Nullable PyType type, @Nullable PyExpression defaultValue) {
myType = type;
myDefaultValue = defaultValue;
}
@Nullable
public PyType getType() {
return myType;
}
@Nullable
public PyExpression getDefaultValue() {
return myDefaultValue;
}
}
}
@@ -404,7 +404,7 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase {
return new PyNamedTupleType(tupleClass,
referenceTarget,
stub.getName(),
parseNamedTupleFieldsTypes(referenceTarget, stub.getFields(), context),
parseNamedTupleFields(referenceTarget, stub.getFields(), context),
definitionLevel);
}
@@ -431,27 +431,33 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase {
}
@NotNull
private static LinkedHashMap<String, Optional<PyType>> parseNamedTupleFieldsTypes(@NotNull PsiElement anchor,
@NotNull Map<String, Optional<String>> fields,
@NotNull TypeEvalContext context) {
final LinkedHashMap<String, Optional<PyType>> result = new LinkedHashMap<>();
private static LinkedHashMap<String, PyNamedTupleType.FieldTypeAndDefaultValue> parseNamedTupleFields(@NotNull PsiElement anchor,
@NotNull Map<String, Optional<String>> fields,
@NotNull TypeEvalContext context) {
final LinkedHashMap<String, PyNamedTupleType.FieldTypeAndDefaultValue> result = new LinkedHashMap<>();
for (Map.Entry<String, Optional<String>> entry : fields.entrySet()) {
result.put(entry.getKey(), entry.getValue().map(type -> parseNamedTupleFieldType(anchor, type, context)));
result.put(entry.getKey(), parseNamedTupleField(anchor, entry.getValue().orElse(null), context));
}
return result;
}
@Nullable
private static PyType parseNamedTupleFieldType(@NotNull PsiElement anchor, @NotNull String type, @NotNull TypeEvalContext context) {
private static PyNamedTupleType.FieldTypeAndDefaultValue parseNamedTupleField(@NotNull PsiElement anchor,
@Nullable String type,
@NotNull TypeEvalContext context) {
if (type == null) return new PyNamedTupleType.FieldTypeAndDefaultValue(null, null);
final PyExpressionCodeFragmentImpl codeFragment = new PyExpressionCodeFragmentImpl(anchor.getProject(), "dummy.py", type, false);
codeFragment.setContext(anchor.getContainingFile());
final PsiElement element = codeFragment.getFirstChild();
if (element instanceof PyExpressionStatement) {
final PyExpression expression = ((PyExpressionStatement)element).getExpression();
return Ref.deref(PyTypingTypeProvider.getType(expression, context));
final PyType pyType = Ref.deref(PyTypingTypeProvider.getType(expression, context));
return new PyNamedTupleType.FieldTypeAndDefaultValue(pyType, null);
}
return null;
@@ -172,16 +172,16 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
}
);
final Collector<PyTargetExpression, ?, LinkedHashMap<String, Optional<PyType>>> toTypedFields =
final Collector<PyTargetExpression, ?, LinkedHashMap<String, PyNamedTupleType.FieldTypeAndDefaultValue>> toNTFields =
Collectors.toMap(PyTargetExpression::getName,
field -> Optional.ofNullable(context.getType(field)),
field -> new PyNamedTupleType.FieldTypeAndDefaultValue(context.getType(field), field.findAssignedValue()),
(v1, v2) -> v2,
LinkedHashMap::new);
return new PyNamedTupleType(tupleClass,
this,
name,
fields.stream().collect(toTypedFields),
fields.stream().collect(toNTFields),
PyNamedTupleType.DefinitionLevel.NEW_TYPE);
}
}
@@ -32,17 +32,24 @@ import java.util.Objects;
public class PyCallableParameterImpl implements PyCallableParameter {
@Nullable private final String myName;
@Nullable private final PyType myType;
@Nullable private final PyExpression myDefaultValue;
@Nullable private final PyParameter myElement;
public PyCallableParameterImpl(@Nullable String name, @Nullable PyType type) {
this(name, type, null);
}
public PyCallableParameterImpl(@Nullable String name, @Nullable PyType type, @Nullable PyExpression defaultValue) {
myName = name;
myType = type;
myDefaultValue = defaultValue;
myElement = null;
}
public PyCallableParameterImpl(@NotNull PyParameter element) {
myName = null;
myType = null;
myDefaultValue = null;
myElement = element;
}
@@ -79,12 +86,12 @@ public class PyCallableParameterImpl implements PyCallableParameter {
@Nullable
@Override
public PyExpression getDefaultValue() {
return myElement == null ? null : myElement.getDefaultValue();
return myElement == null ? myDefaultValue : myElement.getDefaultValue();
}
@Override
public boolean hasDefaultValue() {
return myElement != null && myElement.hasDefaultValue();
return myElement == null ? myDefaultValue != null : myElement.hasDefaultValue();
}
@Override
@@ -0,0 +1,24 @@
import typing
class MyTup5(typing.NamedTuple):
bar: int
baz: str = ""
# empty
MyTup5(<warning descr="Parameter 'bar' unfilled">)</warning>
# one
MyTup5('')
MyTup5(bar='')
MyTup5(baz=''<warning descr="Parameter 'bar' unfilled">)</warning>
# two
MyTup5('', '')
MyTup5(bar='', baz='')
MyTup5(baz='', bar='')
# three
MyTup5(bar='', baz='', <warning descr="Unexpected argument">foo=''</warning>)
MyTup5('', '', <warning descr="Unexpected argument">''</warning>)
@@ -26,9 +26,15 @@ class MyTup6(typing.NamedTuple):
MyTup7 = typing.NamedTuple("MyTup7", names=List[str], ages=List[int])
class MyTup8(typing.NamedTuple):
bar: int
baz: str = ""
MyTup2(<arg1>)
MyTup3(<arg2>)
MyTup4(<arg3>)
MyTup5(<arg4>)
MyTup6(<arg5>)
MyTup7(<arg6>)
MyTup8(<arg7>)
@@ -672,7 +672,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
final Map<String, PsiElement> test = loadTest(6);
final Map<String, PsiElement> test = loadTest(7);
for (int offset : StreamEx.of(1, 2, 3, 4).map(number -> test.get("<arg" + number + ">").getTextOffset())) {
final List<String> texts = Collections.singletonList("bar: int, baz: str");
@@ -688,6 +688,10 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
final List<String> texts2 = Collections.singletonList("names: List[str], ages: List[int]");
final List<String[]> highlighted2 = Collections.singletonList(new String[]{"names: List[str], "});
feignCtrlP(test.get("<arg6>").getTextOffset()).check(texts2, highlighted2, Collections.singletonList(ArrayUtil.EMPTY_STRING_ARRAY));
final List<String> texts3 = Collections.singletonList("bar: int, baz: str=\"\"");
final List<String[]> highlighted3 = Collections.singletonList(new String[]{"bar: int, "});
feignCtrlP(test.get("<arg7>").getTextOffset()).check(texts3, highlighted3, Collections.singletonList(ArrayUtil.EMPTY_STRING_ARRAY));
}
);
}
@@ -648,12 +648,18 @@ public class PyStubsTest extends PyTestCase {
final Iterator<String> fieldsNamesIterator = expectedFieldsNames.iterator();
final Iterator<String> fieldsTypesIterator = expectedFieldsTypes.iterator();
for (Map.Entry<String, Optional<PyType>> entry : namedTupleType.getFields().entrySet()) {
for (Map.Entry<String, PyNamedTupleType.FieldTypeAndDefaultValue> entry : namedTupleType.getFields().entrySet()) {
assertTrue(fieldsNamesIterator.hasNext());
assertTrue(fieldsTypesIterator.hasNext());
assertEquals(fieldsNamesIterator.next(), entry.getKey());
assertEquals(fieldsTypesIterator.next(), entry.getValue().map(PyType::getName).orElse(null));
final String fieldName = entry.getKey();
final PyNamedTupleType.FieldTypeAndDefaultValue fieldTypeAndDefaultValue = entry.getValue();
assertEquals(fieldsNamesIterator.next(), fieldName);
final PyType fieldType = fieldTypeAndDefaultValue.getType();
assertEquals(fieldsTypesIterator.next(), fieldType == null ? null : fieldType.getName());
assertNull(fieldTypeAndDefaultValue.getDefaultValue());
}
assertFalse(fieldsNamesIterator.hasNext());
@@ -278,6 +278,11 @@ public class PyArgumentListInspectionTest extends PyTestCase {
runWithLanguageLevel(LanguageLevel.PYTHON36, this::doTest);
}
// PY-24099
public void testInitializingTypingNamedTupleWithDefaultValues() {
runWithLanguageLevel(LanguageLevel.PYTHON36, this::doTest);
}
// PY-4344, PY-8422, PY-22269, PY-22740
public void testInitializingCollectionsNamedTuple() {
doTest();