PY-53105 PEP 646: Split PyGenericVariadicType into PyUnpackedTupleType and PyTypeVarTupleType

TypeVarTuples, i.e. type parameters, such as *Ts, and unpacked tuples types, i.e.
concrete types, such as *tuple[int, ...], are two independent entities in the type
system. Keeping them both represented as a single type is confusing and introduces
a lot of bookkeeping for accessing their state and filtering out unpacked tuples in
every place where a type parameter is expected.

For cases where both types are applicable, and we need to distinguish them from regular
"non-unpackable" types, PyVariadicType marker interface was introduced.

Also, make the API names more consistent with the PEPs terminology: "unbound" unpacked
tuple types instead of "homogeneous" unpacked tuple types.

GitOrigin-RevId: be77eae46fd78512eaf74d5a9709faacc762e45f
This commit is contained in:
Mikhail Golubev
2023-10-19 22:42:52 +03:00
committed by intellij-monorepo-bot
parent b0b7d9aacd
commit 15c37a5bad
14 changed files with 326 additions and 310 deletions

View File

@@ -0,0 +1,5 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.psi.types;
public interface PyTypeVarTupleType extends PyTypeParameterType, PyVariadicType {
}

View File

@@ -0,0 +1,12 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.psi.types;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public interface PyUnpackedTupleType extends PyVariadicType {
@NotNull List<PyType> getElementTypes();
boolean isUnbound();
}

View File

@@ -0,0 +1,42 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.psi.types;
import com.intellij.psi.PsiElement;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ProcessingContext;
import com.jetbrains.python.psi.AccessDirection;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@ApiStatus.Experimental
public interface PyVariadicType extends PyType {
@Override
default boolean isBuiltin() {
return false;
}
@Override
default void assertValid(String message) {
}
@Override
@Nullable
default List<? extends RatedResolveResult> resolveMember(@NotNull String name,
@Nullable PyExpression location,
@NotNull AccessDirection direction,
@NotNull PyResolveContext resolveContext) {
return null;
}
@Override
default Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context) {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
}

View File

@@ -965,7 +965,7 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
if (type instanceof PyParamSpecType paramSpec) {
return paramSpec.withScopeOwner(getTypeParameterScope(paramSpec.getName(), typeHint, context)).withTargetExpression(targetExpr);
}
if (type instanceof PyGenericVariadicType typeVarTuple) {
if (type instanceof PyTypeVarTupleTypeImpl typeVarTuple) {
return typeVarTuple.withScopeOwner(getTypeParameterScope(typeVarTuple.getName(), typeHint, context)).withTargetExpression(targetExpr);
}
return type;
@@ -1477,7 +1477,7 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
if (firstArgument instanceof PyStringLiteralExpression) {
final String name = ((PyStringLiteralExpression)firstArgument).getStringValue();
if (calleeQNames.contains(TYPE_VAR_TUPLE)) {
return new PyGenericVariadicType(name);
return new PyTypeVarTupleTypeImpl(name);
}
else {
return new PyTypeVarTypeImpl(name, getGenericTypeBound(arguments, context));
@@ -1565,7 +1565,7 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
}
@Nullable
public static PyType getUnpackedType(@NotNull PsiElement element, @NotNull TypeEvalContext context) {
public static PyVariadicType getUnpackedType(@NotNull PsiElement element, @NotNull TypeEvalContext context) {
// TODO Add support for Unpacked here
if (!(element instanceof PyStarExpression starExpression)) return null;
var typeHint = starExpression.getExpression();
@@ -1576,10 +1576,10 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
var expressionType = typeRef.get();
if (expressionType instanceof PyTupleType tupleType) {
return new PyGenericVariadicType("", tupleType.isHomogeneous(), tupleType.getElementTypes());
return new PyUnpackedTupleTypeImpl(tupleType.getElementTypes(), tupleType.isHomogeneous());
}
if (expressionType instanceof PyGenericVariadicType) {
return expressionType;
if (expressionType instanceof PyTypeVarTupleType typeVarTupleType) {
return typeVarTupleType;
}
return null;
}

View File

@@ -355,9 +355,9 @@ public class PyTypeCheckerInspection extends PyInspection {
List<UnfilledPositionalVararg> unfilledPositionalVarargs = new ArrayList<>();
for (var unmappedContainer: mapping.getUnmappedContainerParameters()) {
PyType containerType = unmappedContainer.getArgumentType(myTypeEvalContext);
if (unmappedContainer.getName() == null || !(containerType instanceof PyGenericVariadicType)) continue;
if (unmappedContainer.getName() == null || !(containerType instanceof PyVariadicType)) continue;
PyType expandedVararg = PyTypeChecker.substitute(containerType, substitutions, myTypeEvalContext);
if (!(expandedVararg instanceof PyGenericVariadicType unpackedTuple) || unpackedTuple.isHomogeneous()) continue;
if (!(expandedVararg instanceof PyUnpackedTupleType unpackedTuple) || unpackedTuple.isUnbound()) continue;
unfilledPositionalVarargs.add(
new UnfilledPositionalVararg(unmappedContainer.getName(),
PythonDocumentationProvider.getTypeName(expandedVararg, myTypeEvalContext)));
@@ -419,9 +419,8 @@ public class PyTypeCheckerInspection extends PyInspection {
@NotNull PyTypeChecker.GenericSubstitutions substitutions) {
final PyType expected = container.getArgumentType(myTypeEvalContext);
if (container.isPositionalContainer() && expected instanceof PyGenericVariadicType) {
PyGenericVariadicType argumentTypes =
PyGenericVariadicType.fromElementTypes(ContainerUtil.map(arguments, myTypeEvalContext::getType));
if (container.isPositionalContainer() && expected instanceof PyVariadicType) {
PyUnpackedTupleType argumentTypes = PyUnpackedTupleTypeImpl.create(ContainerUtil.map(arguments, myTypeEvalContext::getType));
boolean matched = matchParameterAndArgument(expected, argumentTypes, null, substitutions);
return ContainerUtil.map(arguments, argument -> {
PyType expectedWithSubstitutions = substituteGenerics(expected, substitutions);

View File

@@ -129,7 +129,7 @@ class PyTypeHintsInspection : PyInspection() {
return
}
if (myTypeEvalContext.getType(node) is PyGenericVariadicType) {
if (myTypeEvalContext.getType(node) is PyVariadicType) {
checkTypeVarTupleUnpacked(node)
}

View File

@@ -219,8 +219,8 @@ public final class PyCallableParameterImpl implements PyCallableParameter {
// *args: str is equivalent to *args: *tuple[str, ...]
// *args: *Ts is equivalent to *args: *tuple[*Ts]
// Convert its type to a more general form of an unpacked tuple
PyGenericVariadicType unpackedTupleType = tupleType.asUnpackedTupleType();
if (unpackedTupleType.isHomogeneous()) {
PyUnpackedTupleType unpackedTupleType = tupleType.asUnpackedTupleType();
if (unpackedTupleType.isUnbound()) {
return unpackedTupleType.getElementTypes().get(0);
}
return unpackedTupleType;

View File

@@ -1,185 +0,0 @@
// Copyright 2000-2019 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;
import com.intellij.psi.PsiElement;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ProcessingContext;
import com.jetbrains.python.psi.AccessDirection;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyQualifiedNameOwner;
import com.jetbrains.python.psi.PyTargetExpression;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public final class PyGenericVariadicType implements PyTypeParameterType {
private final @NotNull String myName;
private final @Nullable PyQualifiedNameOwner myScopeOwner;
private final PyTargetExpression myTarget;
private final @Nullable List<PyType> myElementTypes;
private final boolean myIsHomogeneous;
public PyGenericVariadicType(@NotNull String name) {
this(name, false, null);
}
public PyGenericVariadicType(@NotNull String name, boolean isHomogeneous, @Nullable List<PyType> elementTypes) {
this(name, null, isHomogeneous, elementTypes, null);
}
private PyGenericVariadicType(@NotNull String name, @Nullable PyTargetExpression target,
boolean isHomogeneous, @Nullable List<PyType> elementTypes, @Nullable PyQualifiedNameOwner scopeOwner) {
myName = name;
myTarget = target;
myElementTypes = elementTypes;
myIsHomogeneous = isHomogeneous;
myScopeOwner = scopeOwner;
}
@NotNull
public PyGenericVariadicType withScopeOwner(@Nullable PyQualifiedNameOwner scopeOwner) {
return new PyGenericVariadicType(myName, myTarget, myIsHomogeneous, myElementTypes, scopeOwner);
}
public PyGenericVariadicType withTargetExpression(@Nullable PyTargetExpression targetExpression) {
return new PyGenericVariadicType(myName, targetExpression, myIsHomogeneous, myElementTypes, myScopeOwner);
}
@NotNull
public static PyGenericVariadicType fromElementTypes(@NotNull List<PyType> elementTypes) {
return new PyGenericVariadicType("", false, elementTypes);
}
@NotNull
public static PyGenericVariadicType homogeneous(@Nullable PyType type) {
if (type instanceof PyGenericVariadicType) {
throw new IllegalArgumentException("Unpacked tuple of a TypeVarTuple or another unpacked tuple cannot be constructed");
}
var elementTypes = new ArrayList<PyType>();
elementTypes.add(type);
return new PyGenericVariadicType("", true, elementTypes);
}
@NotNull
@Override
public String getName() {
if (myElementTypes == null) {
return "*" + myName;
}
else {
return "*" + getElementTypesToStr();
}
}
@Override
public @Nullable PyQualifiedNameOwner getScopeOwner() {
return myScopeOwner;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final PyGenericVariadicType type = (PyGenericVariadicType)o;
return myName.equals(type.myName) && Objects.equals(getScopeOwner(), type.getScopeOwner()) &&
Objects.equals(myElementTypes, type.myElementTypes);
}
@Override
public int hashCode() {
int res = myName.hashCode();
if (myElementTypes == null) return res;
return res + myElementTypes.hashCode();
}
@NotNull
public String getElementTypesToStr() {
if (myElementTypes == null) return "";
StringBuilder res = new StringBuilder("tuple[");
StringUtil.join(myElementTypes, type -> type != null ? type.getName() : "Any", ", ", res);
if (isHomogeneous()) {
res.append(", ...");
}
res.append("]");
return res.toString();
}
public boolean isHomogeneous() {
return isUnpackedTupleType() && myIsHomogeneous;
}
public boolean isUnspecified() {
return isHomogeneous() && myElementTypes != null && myElementTypes.size() == 1 && myElementTypes.get(0) == null;
}
@Nullable
public PyType getIteratedItemType() {
if (myElementTypes == null) return null;
return PyUnionType.union(myElementTypes);
}
public @Nullable List<PyType> getElementTypes() {
return myElementTypes != null ? Collections.unmodifiableList(myElementTypes) : null;
}
public boolean isUnpackedTupleType() {
return myName.isEmpty();
}
@Override
public boolean isBuiltin() {
return false;
}
@Override
public void assertValid(String message) {
}
@Override
public @Nullable List<? extends RatedResolveResult> resolveMember(@NotNull String name,
@Nullable PyExpression location,
@NotNull AccessDirection direction,
@NotNull PyResolveContext resolveContext) {
return null;
}
@Override
public Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context) {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
@Override
public String toString() {
if (myName.isEmpty()) {
return "PyGenericVariadicType: " + getElementTypesToStr();
}
else {
String scopeName = myScopeOwner != null ? Objects.requireNonNullElse(myScopeOwner.getQualifiedName(), myScopeOwner.getName()) : null;
return "PyGenericVariadicType: " + (scopeName != null ? scopeName + ":" : "") + myName;
}
}
public @Nullable PyTupleType asTupleType(@NotNull PsiElement anchor) {
if (isUnpackedTupleType()) {
if (isHomogeneous()) {
return PyTupleType.createHomogeneous(anchor, getElementTypes().get(0));
}
else {
return PyTupleType.create(anchor, getElementTypes());
}
}
else {
return PyTupleType.create(anchor, Collections.singletonList(this));
}
}
}

View File

@@ -124,10 +124,10 @@ public class PyTupleType extends PyClassTypeImpl implements PyCollectionType {
return PyUnionType.union(myElementTypes);
}
public @NotNull PyGenericVariadicType asUnpackedTupleType() {
public @NotNull PyUnpackedTupleType asUnpackedTupleType() {
if (isHomogeneous()) {
return PyGenericVariadicType.homogeneous(getElementType(0));
return PyUnpackedTupleTypeImpl.createUnbound(getElementType(0));
}
return PyGenericVariadicType.fromElementTypes(getElementTypes());
return PyUnpackedTupleTypeImpl.create(getElementTypes());
}
}

View File

@@ -121,12 +121,12 @@ public final class PyTypeChecker {
}
}
if (actual instanceof PyGenericVariadicType genericVariadicType && !genericVariadicType.isUnpackedTupleType() && context.reversedSubstitutions) {
return Optional.of(match((PyGenericVariadicType)actual, expected, context));
if (actual instanceof PyTypeVarTupleType typeVarTupleType && context.reversedSubstitutions) {
return Optional.of(match(typeVarTupleType, expected, context));
}
if (expected instanceof PyGenericVariadicType) {
return Optional.of(match((PyGenericVariadicType)expected, actual, context));
if (expected instanceof PyVariadicType variadic) {
return Optional.of(match(variadic, actual, context));
}
if (actual instanceof PyGenericType && context.reversedSubstitutions) {
@@ -270,48 +270,49 @@ public final class PyTypeChecker {
return true;
}
private static boolean match(@NotNull PyGenericVariadicType expected, @Nullable PyType actual, @NotNull MatchContext context) {
private static boolean match(@NotNull PyVariadicType expected, @Nullable PyType actual, @NotNull MatchContext context) {
if (actual == null) {
return true;
}
if (!(actual instanceof PyGenericVariadicType actualVariadic)) {
if (!(actual instanceof PyVariadicType actualVariadic)) {
return false;
}
if (expected.isUnpackedTupleType()) {
if (expected instanceof PyUnpackedTupleType expectedUnpackedTupleType) {
// The actual type is just a TypeVarTuple
if (!actualVariadic.isUnpackedTupleType()) {
if (!(actualVariadic instanceof PyUnpackedTupleType actualUnpackedTupleType)) {
return false;
}
if (expected.isHomogeneous()) {
if (actualVariadic.isHomogeneous()) {
return match(expected.getIteratedItemType(), actualVariadic.getIteratedItemType(), context).orElse(false);
if (expectedUnpackedTupleType.isUnbound()) {
PyType repeatedExpectedType = expectedUnpackedTupleType.getElementTypes().get(0);
if (actualUnpackedTupleType.isUnbound()) {
return match(repeatedExpectedType, actualUnpackedTupleType.getElementTypes().get(0), context).orElse(false);
}
else {
//noinspection DataFlowIssue
return ContainerUtil.all(actualVariadic.getElementTypes(), singleActualType -> match(expected.getIteratedItemType(), singleActualType, context).orElse(false));
return ContainerUtil.all(actualUnpackedTupleType.getElementTypes(),
singleActualType -> match(repeatedExpectedType, singleActualType, context).orElse(false));
}
}
else {
if (actualVariadic.isHomogeneous()) {
//noinspection DataFlowIssue
return ContainerUtil.all(expected.getElementTypes(), singleExpectedType -> match(singleExpectedType, actualVariadic.getIteratedItemType(), context).orElse(false));
if (actualUnpackedTupleType.isUnbound()) {
PyType repeatedActualType = actualUnpackedTupleType.getElementTypes().get(0);
return ContainerUtil.all(expectedUnpackedTupleType.getElementTypes(),
singleExpectedType -> match(singleExpectedType, repeatedActualType, context).orElse(false));
}
else {
//noinspection DataFlowIssue
return matchTypeParameters(expected.getElementTypes(), actualVariadic.getElementTypes(), context);
return matchTypeParameters(expectedUnpackedTupleType.getElementTypes(), actualUnpackedTupleType.getElementTypes(), context);
}
}
}
// The expected type is just a TypeVarTuple
else {
PyGenericVariadicType substitution = context.mySubstitutions.typeVarTuples.get(expected);
if (substitution != null && !substitution.isUnspecified()) {
PyVariadicType substitution = context.mySubstitutions.typeVarTuples.get(expected);
if (substitution != null && !substitution.equals(PyUnpackedTupleTypeImpl.UNSPECIFIED)) {
if (expected.equals(actual) || substitution.equals(expected)) {
return true;
}
return context.reversedSubstitutions ? match(actualVariadic, substitution, context) : match(substitution, actualVariadic, context);
}
context.mySubstitutions.typeVarTuples.put(expected, actualVariadic);
context.mySubstitutions.typeVarTuples.put((PyTypeVarTupleType)expected, actualVariadic);
}
return true;
}
@@ -628,8 +629,8 @@ public final class PyTypeChecker {
List<PyType> expectedElementTypes = ContainerUtil.map(expectedParameters, cp -> {
PyType argType = cp.getArgumentType(context);
if (cp.isPositionalContainer() && !(argType instanceof PyGenericVariadicType)) {
return PyGenericVariadicType.homogeneous(argType);
if (cp.isPositionalContainer() && !(argType instanceof PyVariadicType)) {
return PyUnpackedTupleTypeImpl.createUnbound(argType);
}
return argType;
});
@@ -745,9 +746,9 @@ public final class PyTypeChecker {
if (entry.getKey() instanceof PyGenericType) {
result.typeVars.put((PyGenericType)entry.getKey(), entry.getValue());
}
else if (entry.getKey() instanceof PyGenericVariadicType typeVarTuple) {
assert entry.getValue() instanceof PyGenericVariadicType;
result.typeVarTuples.put(typeVarTuple, (PyGenericVariadicType)entry.getValue());
else if (entry.getKey() instanceof PyTypeVarTupleType typeVarTuple) {
assert entry.getValue() instanceof PyVariadicType;
result.typeVarTuples.put(typeVarTuple, (PyVariadicType)entry.getValue());
}
// TODO Handle ParamSpecs here
}
@@ -758,14 +759,14 @@ public final class PyTypeChecker {
List<PyType> definitionTypeParameters = genericDefinitionType.getElementTypes();
if (!(classType instanceof PyCollectionType genericType)) {
for (PyType typeParameter : definitionTypeParameters) {
if (typeParameter instanceof PyGenericVariadicType gvt) {
result.typeVarTuples.put(gvt, null);
if (typeParameter instanceof PyTypeVarTupleType typeVarTupleType) {
result.typeVarTuples.put(typeVarTupleType, null);
}
else if (typeParameter instanceof PyParamSpecType pst) {
result.paramSpecs.put(pst, null);
else if (typeParameter instanceof PyParamSpecType paramSpecType) {
result.paramSpecs.put(paramSpecType, null);
}
else if (typeParameter instanceof PyTypeVarType tvt) {
result.typeVars.put((PyGenericType)tvt, null);
else if (typeParameter instanceof PyTypeVarType typeVarType) {
result.typeVars.put((PyGenericType)typeVarType, null);
}
}
}
@@ -779,9 +780,9 @@ public final class PyTypeChecker {
if (typeParameter instanceof PyGenericType) {
result.typeVars.put((PyGenericType)typeParameter, typeArgument);
}
else if (typeParameter instanceof PyGenericVariadicType typeVarTuple) {
assert typeArgument instanceof PyGenericVariadicType || typeArgument == null;
result.typeVarTuples.put(typeVarTuple, (PyGenericVariadicType)typeArgument);
else if (typeParameter instanceof PyTypeVarTupleType typeVarTuple) {
assert typeArgument instanceof PyVariadicType || typeArgument == null;
result.typeVarTuples.put(typeVarTuple, (PyVariadicType)typeArgument);
}
else if (typeParameter instanceof PyParamSpecType) {
result.getParamSpecs().put((PyParamSpecType)typeParameter, as(typeArgument, PyParamSpecType.class));
@@ -945,9 +946,7 @@ public final class PyTypeChecker {
@NotNull Generics generics,
@NotNull Set<? super PyType> visited) {
if (type instanceof PyTypeParameterType typeParameter) {
if (!(type instanceof PyGenericVariadicType genericVariadic && genericVariadic.isUnpackedTupleType())) {
generics.allTypeParameters.add(typeParameter);
}
generics.allTypeParameters.add(typeParameter);
}
if (visited.contains(type)) {
return;
@@ -956,8 +955,8 @@ public final class PyTypeChecker {
if (type instanceof PyGenericType) {
generics.typeVars.add((PyGenericType)type);
}
if (type instanceof PyGenericVariadicType genericVariadic && !genericVariadic.isUnpackedTupleType()) {
generics.typeVarTuples.add(genericVariadic);
if (type instanceof PyTypeVarTupleType typeVarTupleType) {
generics.typeVarTuples.add(typeVarTupleType);
}
// TODO Filter out PyParamSpecTypes representing actual lists of parameters, not type parameters declared via ParamSpec
if (type instanceof PyParamSpecType) {
@@ -997,9 +996,8 @@ public final class PyTypeChecker {
}
collectGenerics(callable.getReturnType(context), context, generics, visited);
}
else if (type instanceof PyGenericVariadicType genericVariadicType && genericVariadicType.isUnpackedTupleType()) {
//noinspection DataFlowIssue
for (PyType elementType : genericVariadicType.getElementTypes()) {
else if (type instanceof PyUnpackedTupleType unpackedTupleType) {
for (PyType elementType : unpackedTupleType.getElementTypes()) {
collectGenerics(elementType, context, generics, visited);
}
}
@@ -1010,9 +1008,8 @@ public final class PyTypeChecker {
@NotNull TypeEvalContext context,
@NotNull Set<PyType> substituting) {
PyType substituted = substitute(type, substitutions, context, substituting);
if (substituted instanceof PyGenericVariadicType typeVarTuple && typeVarTuple.isUnpackedTupleType() && !typeVarTuple.isHomogeneous()) {
//noinspection DataFlowIssue
return typeVarTuple.getElementTypes();
if (substituted instanceof PyUnpackedTupleType unpackedTupleType && !unpackedTupleType.isUnbound()) {
return unpackedTupleType.getElementTypes();
}
return Collections.singletonList(substituted);
}
@@ -1033,24 +1030,21 @@ public final class PyTypeChecker {
}
try {
if (hasGenerics(type, context)) {
if (type instanceof PyGenericVariadicType genericVariadicType) {
if (genericVariadicType.isUnpackedTupleType()) {
//noinspection DataFlowIssue
return new PyGenericVariadicType("", genericVariadicType.isHomogeneous(),
ContainerUtil.flatMap(genericVariadicType.getElementTypes(),
t -> substituteExpand(t, substitutions, context, substituting)));
if (type instanceof PyUnpackedTupleType unpackedTupleType) {
return new PyUnpackedTupleTypeImpl(ContainerUtil.flatMap(unpackedTupleType.getElementTypes(),
t -> substituteExpand(t, substitutions, context, substituting)),
unpackedTupleType.isUnbound());
}
if (type instanceof PyTypeVarTupleType typeVarTupleType) {
if (!substitutions.typeVarTuples.containsKey(typeVarTupleType)) {
return type;
}
else {
if (!substitutions.typeVarTuples.containsKey(genericVariadicType)) {
return type;
}
PyGenericVariadicType substitution = substitutions.typeVarTuples.get(genericVariadicType);
if (!genericVariadicType.equals(substitution) && hasGenerics(substitution, context)) {
return substitute(substitution, substitutions, context, substituting);
}
// Replace unknown TypeVarTuples by *tuple[Any, ...] instead of plain Any
return substitution == null ? PyGenericVariadicType.homogeneous(null) : substitution;
PyVariadicType substitution = substitutions.typeVarTuples.get(typeVarTupleType);
if (!typeVarTupleType.equals(substitution) && hasGenerics(substitution, context)) {
return substitute(substitution, substitutions, context, substituting);
}
// Replace unknown TypeVarTuples by *tuple[Any, ...] instead of plain Any
return substitution == null ? PyUnpackedTupleTypeImpl.UNSPECIFIED : substitution;
}
if (type instanceof PyGenericType typeVar) {
// Both mappings of kind {T: T2} (and no mapping for T2) and {T: T} mean the substitution process should stop for T.
@@ -1250,8 +1244,8 @@ public final class PyTypeChecker {
}
final List<PyType> actualArgumentTypes = ContainerUtil.map(arguments, context::getType);
final PyType expectedArgumentType = container.getArgumentType(context);
if (container.isPositionalContainer() && expectedArgumentType instanceof final PyGenericVariadicType genericVariadicType) {
return match(genericVariadicType, PyGenericVariadicType.fromElementTypes(actualArgumentTypes),
if (container.isPositionalContainer() && expectedArgumentType instanceof PyVariadicType variadicType) {
return match(variadicType, PyUnpackedTupleTypeImpl.create(actualArgumentTypes),
new MatchContext(context, substitutions, false));
}
return match(expectedArgumentType, PyUnionType.union(actualArgumentTypes), context, substitutions);
@@ -1277,7 +1271,7 @@ public final class PyTypeChecker {
for (Map.Entry<PyGenericType, PyType> typeVarMapping : newSubstitutions.typeVars.entrySet()) {
substitutions.typeVars.putIfAbsent(typeVarMapping.getKey(), typeVarMapping.getValue());
}
for (Map.Entry<PyGenericVariadicType, PyGenericVariadicType> typeVarMapping : newSubstitutions.typeVarTuples.entrySet()) {
for (Map.Entry<PyTypeVarTupleType, PyVariadicType> typeVarMapping : newSubstitutions.typeVarTuples.entrySet()) {
substitutions.typeVarTuples.putIfAbsent(typeVarMapping.getKey(), typeVarMapping.getValue());
}
for (Map.Entry<PyParamSpecType, PyParamSpecType> paramSpecMapping : newSubstitutions.paramSpecs.entrySet()) {
@@ -1466,9 +1460,9 @@ public final class PyTypeChecker {
if (pair.getFirst() instanceof PyTypeVarType typeVar) {
substitutions.typeVars.put((PyGenericType)typeVar, pair.getSecond());
}
else if (pair.getFirst() instanceof PyGenericVariadicType typeVarTuple) {
assert pair.getSecond() instanceof PyGenericVariadicType;
substitutions.typeVarTuples.put(typeVarTuple, (PyGenericVariadicType)pair.getSecond());
else if (pair.getFirst() instanceof PyTypeVarTupleType typeVarTuple) {
assert pair.getSecond() instanceof PyVariadicType;
substitutions.typeVarTuples.put(typeVarTuple, (PyVariadicType)pair.getSecond());
}
}
}
@@ -1493,7 +1487,7 @@ public final class PyTypeChecker {
private final Set<PyGenericType> typeVars = new LinkedHashSet<>();
@NotNull
private final Set<PyGenericVariadicType> typeVarTuples = new LinkedHashSet<>();
private final Set<PyTypeVarTupleType> typeVarTuples = new LinkedHashSet<>();
@NotNull
private final List<PyTypeParameterType> allTypeParameters = new ArrayList<>();
@@ -1511,7 +1505,7 @@ public final class PyTypeChecker {
return Collections.unmodifiableSet(typeVars);
}
public @NotNull Set<PyGenericVariadicType> getTypeVarTuples() {
public @NotNull Set<PyTypeVarTupleType> getTypeVarTuples() {
return Collections.unmodifiableSet(typeVarTuples);
}
@@ -1543,7 +1537,7 @@ public final class PyTypeChecker {
private final Map<PyGenericType, PyType> typeVars;
@NotNull
private final Map<PyGenericVariadicType, PyGenericVariadicType> typeVarTuples;
private final Map<PyTypeVarTupleType, PyVariadicType> typeVarTuples;
@NotNull
private final Map<PyParamSpecType, PyParamSpecType> paramSpecs;
@@ -1560,7 +1554,7 @@ public final class PyTypeChecker {
}
GenericSubstitutions(@NotNull Map<PyGenericType, PyType> typeVars,
@NotNull Map<PyGenericVariadicType, PyGenericVariadicType> typeVarTuples,
@NotNull Map<PyTypeVarTupleType, PyVariadicType> typeVarTuples,
@NotNull Map<PyParamSpecType, PyParamSpecType> paramSpecs,
@Nullable PyType qualifierType) {
this.typeVars = typeVars;
@@ -1577,7 +1571,7 @@ public final class PyTypeChecker {
return typeVars;
}
public @NotNull Map<PyGenericVariadicType, PyGenericVariadicType> getTypeVarTuples() {
public @NotNull Map<PyTypeVarTupleType, PyVariadicType> getTypeVarTuples() {
return typeVarTuples;
}

View File

@@ -20,10 +20,10 @@ public final class PyTypeParameterMapping {
for (Couple<PyType> couple : mapping) {
PyType expectedType = couple.getFirst();
PyType actualType = couple.getSecond();
if (expectedType instanceof PyGenericVariadicType && !(actualType instanceof PyGenericVariadicType || actualType == null)) {
if (expectedType instanceof PyVariadicType && !(actualType instanceof PyVariadicType || actualType == null)) {
throw new IllegalArgumentException("Variadic type " + expectedType + " cannot be mapped to a non-variadic type " + actualType);
}
if (!(expectedType instanceof PyGenericVariadicType) && actualType instanceof PyGenericVariadicType) {
if (!(expectedType instanceof PyVariadicType) && actualType instanceof PyVariadicType) {
throw new IllegalArgumentException("Non-variadic type " + expectedType + " cannot be mapped to a variadic type " + actualType);
}
}
@@ -34,7 +34,7 @@ public final class PyTypeParameterMapping {
@NotNull List<PyCallableParameter> actualParameters,
@NotNull TypeEvalContext context) {
List<PyType> flattenedExpectedParameterTypes = flattenUnpackedTupleTypes(expectedParameterTypes);
int expectedArity = ContainerUtil.exists(flattenedExpectedParameterTypes, Conditions.instanceOf(PyGenericVariadicType.class))
int expectedArity = ContainerUtil.exists(flattenedExpectedParameterTypes, Conditions.instanceOf(PyVariadicType.class))
? -1
: flattenedExpectedParameterTypes.size();
@@ -74,14 +74,14 @@ public final class PyTypeParameterMapping {
}
if (positionalVarargArgumentTypes.size() > 1 ||
positionalVarargArgumentTypes.size() == 1 && !(positionalVarargArgumentTypes.get(0) instanceof PyGenericVariadicType)) {
positionalVarargArgumentTypes.size() == 1 && !(positionalVarargArgumentTypes.get(0) instanceof PyVariadicType)) {
requiredPositionalArgumentTypes.addAll(optionalPositionalArgumentTypes);
optionalPositionalArgumentTypes.clear();
requiredPositionalArgumentTypes.addAll(positionalVarargArgumentTypes);
positionalVarargArgumentTypes.clear();
}
int actualArity = ContainerUtil.exists(requiredPositionalArgumentTypes, Conditions.instanceOf(PyGenericVariadicType.class)) ?
int actualArity = ContainerUtil.exists(requiredPositionalArgumentTypes, Conditions.instanceOf(PyVariadicType.class)) ?
-1 :
requiredPositionalArgumentTypes.size();
@@ -95,7 +95,7 @@ public final class PyTypeParameterMapping {
0, Math.min(optionalPositionalArgumentTypes.size(), expectedArity - arityAdjustedActualParameterTypes.size())
));
if (!positionalVarargArgumentTypes.isEmpty() && expectedArity - arityAdjustedActualParameterTypes.size() > 0) {
assert positionalVarargArgumentTypes.size() == 1 && positionalVarargArgumentTypes.get(0) instanceof PyGenericVariadicType;
assert positionalVarargArgumentTypes.size() == 1 && positionalVarargArgumentTypes.get(0) instanceof PyVariadicType;
arityAdjustedActualParameterTypes.add(positionalVarargArgumentTypes.get(0));
}
return mapByShape(flattenedExpectedParameterTypes, arityAdjustedActualParameterTypes);
@@ -123,12 +123,12 @@ public final class PyTypeParameterMapping {
while (expectedTypesDeque.size() != 0 && actualTypesDeque.size() != 0) {
PyType leftmostExpected = expectedTypesDeque.peekFirst();
// Either a variadic type parameter *Ts or an unbounded unpacked tuple *tuple[int, ...]
if (leftmostExpected instanceof PyGenericVariadicType) {
if (leftmostExpected instanceof PyVariadicType) {
break;
}
// The leftmost expected type is a regular type
PyType leftmostActual = actualTypesDeque.peekFirst();
if (leftmostActual instanceof PyGenericVariadicType) {
if (leftmostActual instanceof PyVariadicType) {
break;
}
expectedTypesDeque.removeFirst();
@@ -138,15 +138,14 @@ public final class PyTypeParameterMapping {
while (expectedTypesDeque.size() != 0 && actualTypesDeque.size() != 0) {
PyType rightmostExpected = expectedTypesDeque.peekLast();
if (rightmostExpected instanceof PyGenericVariadicType) {
if (rightmostExpected instanceof PyVariadicType) {
break;
}
expectedTypesDeque.removeLast();
PyType rightmostActual = actualTypesDeque.peekLast();
if (rightmostActual instanceof PyGenericVariadicType rightmostActualVariadic) {
if (isUnboundedUnpackedTupleType(rightmostActualVariadic)) {
//noinspection DataFlowIssue
PyType repeatedActualType = rightmostActualVariadic.getElementTypes().get(0);
if (rightmostActual instanceof PyVariadicType rightmostActualVariadic) {
if (rightmostActualVariadic instanceof PyUnpackedTupleType unpackedTupleType && unpackedTupleType.isUnbound()) {
PyType repeatedActualType = unpackedTupleType.getElementTypes().get(0);
rightMappedTypes.add(Couple.of(rightmostExpected, repeatedActualType));
}
else {
@@ -161,12 +160,11 @@ public final class PyTypeParameterMapping {
}
if (expectedTypesDeque.size() != 0 && actualTypesDeque.size() != 0
&& !(expectedTypesDeque.peekFirst() instanceof PyGenericVariadicType)
&& (actualTypesDeque.peekFirst() instanceof PyGenericVariadicType variadic)) {
if (isUnboundedUnpackedTupleType(variadic)) {
while (expectedTypesDeque.size() != 0 && !(expectedTypesDeque.peekFirst() instanceof PyGenericVariadicType)) {
//noinspection DataFlowIssue
PyType repeatedActualType = variadic.getElementTypes().get(0);
&& !(expectedTypesDeque.peekFirst() instanceof PyVariadicType)
&& (actualTypesDeque.peekFirst() instanceof PyVariadicType variadic)) {
if (variadic instanceof PyUnpackedTupleType actualUnpackedTupleType && actualUnpackedTupleType.isUnbound()) {
while (expectedTypesDeque.size() != 0 && !(expectedTypesDeque.peekFirst() instanceof PyVariadicType)) {
PyType repeatedActualType = actualUnpackedTupleType.getElementTypes().get(0);
leftMappedTypes.add(Couple.of(expectedTypesDeque.peekFirst(), repeatedActualType));
expectedTypesDeque.removeFirst();
}
@@ -184,18 +182,18 @@ public final class PyTypeParameterMapping {
if (expectedTypesDeque.size() == 0) {
boolean allActualTypesMatched = actualTypesDeque.size() == 0;
boolean onlySingleActualVariadicLeft = actualTypesDeque.size() == 1 &&
actualTypesDeque.peekFirst() instanceof PyGenericVariadicType;
actualTypesDeque.peekFirst() instanceof PyVariadicType;
sizeMismatch = !(allActualTypesMatched || onlySingleActualVariadicLeft);
}
else if (expectedTypesDeque.size() == 1) {
PyType onlyLeftExpectedType = expectedTypesDeque.peekFirst();
if (onlyLeftExpectedType instanceof PyGenericVariadicType) {
if (actualTypesDeque.size() == 1 && actualTypesDeque.peekFirst() instanceof PyGenericVariadicType variadicType) {
if (onlyLeftExpectedType instanceof PyVariadicType) {
if (actualTypesDeque.size() == 1 && actualTypesDeque.peekFirst() instanceof PyVariadicType variadicType) {
centerMappedTypes.add(Couple.of(onlyLeftExpectedType, variadicType));
}
else {
List<PyType> unmatchedActualTypes = actualTypesDeque.toList();
centerMappedTypes.add(Couple.of(onlyLeftExpectedType, PyGenericVariadicType.fromElementTypes(unmatchedActualTypes)));
centerMappedTypes.add(Couple.of(onlyLeftExpectedType, PyUnpackedTupleTypeImpl.create(unmatchedActualTypes)));
}
sizeMismatch = false;
}
@@ -228,21 +226,13 @@ public final class PyTypeParameterMapping {
private static @NotNull List<PyType> flattenUnpackedTupleTypes(List<? extends PyType> types) {
return ContainerUtil.flatMap(types, type -> {
if (type instanceof PyGenericVariadicType typeVarTuple && isBoundedUnpackedTupleType(typeVarTuple)) {
return flattenUnpackedTupleTypes(typeVarTuple.getElementTypes());
if (type instanceof PyUnpackedTupleType unpackedTupleType && !unpackedTupleType.isUnbound()) {
return flattenUnpackedTupleTypes(unpackedTupleType.getElementTypes());
}
return Collections.singletonList(type);
});
}
private static boolean isBoundedUnpackedTupleType(@NotNull PyGenericVariadicType typeVarTupleType) {
return typeVarTupleType.isUnpackedTupleType() && !typeVarTupleType.isHomogeneous();
}
private static boolean isUnboundedUnpackedTupleType(@NotNull PyGenericVariadicType typeVarTupleType) {
return typeVarTupleType.isUnpackedTupleType() && typeVarTupleType.isHomogeneous();
}
public @NotNull List<Couple<PyType>> getMappedTypes() {
return Collections.unmodifiableList(myMappedTypes);
}

View File

@@ -90,8 +90,11 @@ public final class PyTypeUtil {
@Nullable
public static PyTupleType toPositionalContainerType(@NotNull PsiElement anchor, @Nullable PyType elementType) {
if (elementType instanceof PyGenericVariadicType genericVariadicType) {
return genericVariadicType.asTupleType(anchor);
if (elementType instanceof PyUnpackedTupleTypeImpl unpackedTupleType) {
return unpackedTupleType.asTupleType(anchor);
}
else if (elementType instanceof PyTypeVarTupleType) {
return PyTupleType.create(anchor, Collections.singletonList(elementType));
}
return PyTupleType.createHomogeneous(anchor, elementType);
}

View File

@@ -0,0 +1,68 @@
// Copyright 2000-2019 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.jetbrains.python.psi.PyQualifiedNameOwner;
import com.jetbrains.python.psi.PyTargetExpression;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public final class PyTypeVarTupleTypeImpl implements PyTypeVarTupleType {
private final @NotNull String myName;
private final @Nullable PyQualifiedNameOwner myScopeOwner;
private final @Nullable PyTargetExpression myTarget;
public PyTypeVarTupleTypeImpl(@NotNull String name) {
this(name, null, null);
}
private PyTypeVarTupleTypeImpl(@NotNull String name, @Nullable PyTargetExpression target, @Nullable PyQualifiedNameOwner scopeOwner) {
myName = name;
myTarget = target;
myScopeOwner = scopeOwner;
}
@NotNull
public PyTypeVarTupleTypeImpl withScopeOwner(@Nullable PyQualifiedNameOwner scopeOwner) {
return new PyTypeVarTupleTypeImpl(myName, myTarget, scopeOwner);
}
public PyTypeVarTupleTypeImpl withTargetExpression(@Nullable PyTargetExpression targetExpression) {
return new PyTypeVarTupleTypeImpl(myName, targetExpression, myScopeOwner);
}
@NotNull
@Override
public String getName() {
return "*" + myName;
}
@Override
public @Nullable PyQualifiedNameOwner getScopeOwner() {
return myScopeOwner;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final PyTypeVarTupleTypeImpl type = (PyTypeVarTupleTypeImpl)o;
return myName.equals(type.myName) && Objects.equals(getScopeOwner(), type.getScopeOwner());
}
@Override
public int hashCode() {
return Objects.hash(myName, myScopeOwner);
}
@Override
public String toString() {
String scopeName = myScopeOwner != null ? Objects.requireNonNullElse(myScopeOwner.getQualifiedName(), myScopeOwner.getName()) : null;
return "PyGenericVariadicType: " + (scopeName != null ? scopeName + ":" : "") + myName;
}
}

View File

@@ -0,0 +1,88 @@
package com.jetbrains.python.psi.types;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public final class PyUnpackedTupleTypeImpl implements PyUnpackedTupleType {
public static final PyUnpackedTupleType UNSPECIFIED = new PyUnpackedTupleTypeImpl(Collections.singletonList(null), true);
private final List<PyType> myElementTypes;
private final boolean myIsHomogeneous;
public PyUnpackedTupleTypeImpl(@NotNull List<PyType> elementTypes, boolean isUnbound) {
if (isUnbound) {
if (elementTypes.size() != 1) {
throw new IllegalArgumentException("Unbounded unpacked tuple type can have only one type parameter");
}
if (elementTypes.get(0) instanceof PyVariadicType) {
throw new IllegalArgumentException("Unbounded unpacked tuple type of a TypeVarTuple or another unpacked tuple type is now allowed");
}
}
myElementTypes = new ArrayList<>(elementTypes);
myIsHomogeneous = isUnbound;
}
public static @NotNull PyUnpackedTupleType create(@NotNull List<PyType> elementTypes) {
return new PyUnpackedTupleTypeImpl(elementTypes, false);
}
public static @NotNull PyUnpackedTupleType createUnbound(@Nullable PyType type) {
return new PyUnpackedTupleTypeImpl(Collections.singletonList(type), true);
}
@Override
public @NotNull String getName() {
StringBuilder res = new StringBuilder("*tuple[");
StringUtil.join(myElementTypes, type -> type != null ? type.getName() : "Any", ", ", res);
if (isUnbound()) {
res.append(", ...");
}
res.append("]");
return res.toString();
}
@Override
public @NotNull List<PyType> getElementTypes() {
return Collections.unmodifiableList(myElementTypes);
}
@Override
public boolean isUnbound() {
return myIsHomogeneous;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PyUnpackedTupleTypeImpl type = (PyUnpackedTupleTypeImpl)o;
return myIsHomogeneous == type.myIsHomogeneous && Objects.equals(myElementTypes, type.myElementTypes);
}
@Override
public int hashCode() {
return Objects.hash(myElementTypes, myIsHomogeneous);
}
@Override
public String toString() {
return "PyUnpackedTupleType: " + getName();
}
public @Nullable PyTupleType asTupleType(@NotNull PsiElement anchor) {
if (isUnbound()) {
return PyTupleType.createHomogeneous(anchor, getElementTypes().get(0));
}
else {
return PyTupleType.create(anchor, getElementTypes());
}
}
}