PY-77538 Extract PyPositionalVariadicType marker interface

that indicates types compatible with PyTypeVarTupleType: PyTypeVarTupleType and
PyUnpackedTupleType. It will allow introducing a counterpart interface for types
compatible with PyParamSpecType.

GitOrigin-RevId: 0d5c77dd21d2bc7a21b246cfbd951cdd096918e9
This commit is contained in:
Mikhail Golubev
2024-11-21 11:26:23 +00:00
committed by intellij-monorepo-bot
parent 8b87ae9d16
commit 816845add5
10 changed files with 56 additions and 39 deletions
@@ -0,0 +1,17 @@
// 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.ApiStatus;
/**
* Two variants of such types described in <a href="https://peps.python.org/pep-0646/">PEP 646 – Variadic Generics</a> are
* TypeVarTuples and unpacked tuple types.
*
* @see PyTypeVarTupleType
* @see PyUnpackedTupleType
*/
@ApiStatus.Experimental
public interface PyPositionalVariadicType extends PyVariadicType {
}
@@ -23,5 +23,5 @@ package com.jetbrains.python.psi.types;
* @see <a href="https://peps.python.org/pep-0646/#type-variable-tuples">PEP 646 – Variadic Generics</a>
* @see PyUnpackedTupleType
*/
public interface PyTypeVarTupleType extends PyTypeParameterType, PyVariadicType {
public interface PyTypeVarTupleType extends PyTypeParameterType, PyPositionalVariadicType {
}
@@ -17,7 +17,7 @@ import java.util.List;
* @see <a href="https://peps.python.org/pep-0646/#unpacking-tuple-types">PEP 646 – Variadic Generics</a>
* @see PyTypeVarTupleType
*/
public interface PyUnpackedTupleType extends PyVariadicType {
public interface PyUnpackedTupleType extends PyPositionalVariadicType {
/**
* Returns types contained inside this unpacked tuple type.
* <p>
@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 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;
@@ -15,13 +15,11 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* A marker interface for type forms that can be "unpacked" into a series of types.
* A marker interface for type forms that can be "unpacked" into a collection of other types, either a nameless series,
* or associated with parameters of a callable type.
* Normally, such constructs cannot be used on their own in type hints, and can appear only inside other generic types.
* Two variants of such types described in <a href="https://peps.python.org/pep-0646/">PEP 646 – Variadic Generics</a> are
* TypeVarTuples and unpacked tuple types.
*
* @see PyTypeVarTupleType
* @see PyUnpackedTupleType
* @see PyPositionalVariadicType
*/
@ApiStatus.Experimental
public interface PyVariadicType extends PyType {
@@ -1744,7 +1744,7 @@ public final class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<
}
@Nullable
public static PyVariadicType getUnpackedType(@NotNull PsiElement element, @NotNull TypeEvalContext context) {
public static PyPositionalVariadicType getUnpackedType(@NotNull PsiElement element, @NotNull TypeEvalContext context) {
Ref<@Nullable PyType> typeRef = getTypeFromStarExpression(element, context);
if (typeRef == null) {
typeRef = getTypeFromUnpackOperator(element, context);
@@ -386,7 +386,7 @@ 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 PyVariadicType)) continue;
if (unmappedContainer.getName() == null || !(containerType instanceof PyPositionalVariadicType)) continue;
PyType expandedVararg = PyTypeChecker.substitute(containerType, substitutions, myTypeEvalContext);
if (!(expandedVararg instanceof PyUnpackedTupleType unpackedTuple) || unpackedTuple.isUnbound()) continue;
unfilledPositionalVarargs.add(
@@ -450,7 +450,7 @@ public class PyTypeCheckerInspection extends PyInspection {
@NotNull PyTypeChecker.GenericSubstitutions substitutions) {
final PyType expected = container.getArgumentType(myTypeEvalContext);
if (container.isPositionalContainer() && expected instanceof PyVariadicType) {
if (container.isPositionalContainer() && expected instanceof PyPositionalVariadicType) {
PyUnpackedTupleType argumentTypes = PyUnpackedTupleTypeImpl.create(ContainerUtil.map(arguments, myTypeEvalContext::getType));
boolean matched = matchParameterAndArgument(expected, argumentTypes, null, substitutions);
return ContainerUtil.map(arguments, argument -> {
@@ -138,7 +138,7 @@ class PyTypeHintsInspection : PyInspection() {
return
}
if (myTypeEvalContext.getType(node) is PyVariadicType) {
if (myTypeEvalContext.getType(node) is PyPositionalVariadicType) {
checkTypeVarTupleUnpacked(node)
}
@@ -132,7 +132,7 @@ public final class PyTypeChecker {
return Optional.of(match(typeVarTupleType, expected, context));
}
if (expected instanceof PyVariadicType variadic) {
if (expected instanceof PyPositionalVariadicType variadic) {
return Optional.of(match(variadic, actual, context));
}
@@ -291,11 +291,11 @@ public final class PyTypeChecker {
return true;
}
private static boolean match(@NotNull PyVariadicType expected, @Nullable PyType actual, @NotNull MatchContext context) {
private static boolean match(@NotNull PyPositionalVariadicType expected, @Nullable PyType actual, @NotNull MatchContext context) {
if (actual == null) {
return true;
}
if (!(actual instanceof PyVariadicType actualVariadic)) {
if (!(actual instanceof PyPositionalVariadicType actualVariadic)) {
return false;
}
if (expected instanceof PyUnpackedTupleType expectedUnpackedTupleType) {
@@ -326,7 +326,7 @@ public final class PyTypeChecker {
}
// The expected type is just a TypeVarTuple
else {
PyVariadicType substitution = context.mySubstitutions.typeVarTuples.get(expected);
PyPositionalVariadicType substitution = context.mySubstitutions.typeVarTuples.get(expected);
if (substitution != null && !substitution.equals(PyUnpackedTupleTypeImpl.UNSPECIFIED)) {
if (expected.equals(actual) || substitution.equals(expected)) {
return true;
@@ -675,7 +675,7 @@ public final class PyTypeChecker {
.filter(cp -> !(cp.getParameter() instanceof PySlashParameter || cp.getParameter() instanceof PySingleStarParameter))
.map(cp -> {
PyType argType = cp.getArgumentType(context);
if (cp.isPositionalContainer() && !(argType instanceof PyVariadicType)) {
if (cp.isPositionalContainer() && !(argType instanceof PyPositionalVariadicType)) {
return PyUnpackedTupleTypeImpl.createUnbound(argType);
}
return argType;
@@ -1100,7 +1100,7 @@ public final class PyTypeChecker {
if (!substitutions.typeVarTuples.containsKey(typeVarTupleType)) {
return type;
}
PyVariadicType substitution = substitutions.typeVarTuples.get(typeVarTupleType);
PyPositionalVariadicType substitution = substitutions.typeVarTuples.get(typeVarTupleType);
if (!typeVarTupleType.equals(substitution) && hasGenerics(substitution, context)) {
return substitute(substitution, substitutions, context, substituting);
}
@@ -1408,7 +1408,7 @@ public final class PyTypeChecker {
return true;
}
final PyType expectedArgumentType = container.getArgumentType(context);
if (container.isPositionalContainer() && expectedArgumentType instanceof PyVariadicType variadicType) {
if (container.isPositionalContainer() && expectedArgumentType instanceof PyPositionalVariadicType variadicType) {
return match(variadicType, PyUnpackedTupleTypeImpl.create(actualArgumentTypes),
new MatchContext(context, substitutions, false));
}
@@ -1444,7 +1444,7 @@ public final class PyTypeChecker {
for (Map.Entry<PyTypeVarType, PyType> typeVarMapping : newSubstitutions.typeVars.entrySet()) {
substitutions.typeVars.putIfAbsent(typeVarMapping.getKey(), typeVarMapping.getValue());
}
for (Map.Entry<PyTypeVarTupleType, PyVariadicType> typeVarMapping : newSubstitutions.typeVarTuples.entrySet()) {
for (Map.Entry<PyTypeVarTupleType, PyPositionalVariadicType> typeVarMapping : newSubstitutions.typeVarTuples.entrySet()) {
substitutions.typeVarTuples.putIfAbsent(typeVarMapping.getKey(), typeVarMapping.getValue());
}
for (Map.Entry<PyParamSpecType, PyParamSpecType> paramSpecMapping : newSubstitutions.paramSpecs.entrySet()) {
@@ -1708,7 +1708,7 @@ public final class PyTypeChecker {
private final Map<PyTypeVarType, PyType> typeVars;
@NotNull
private final Map<PyTypeVarTupleType, PyVariadicType> typeVarTuples;
private final Map<PyTypeVarTupleType, PyPositionalVariadicType> typeVarTuples;
@NotNull
private final Map<PyParamSpecType, PyParamSpecType> paramSpecs;
@@ -1723,7 +1723,7 @@ public final class PyTypeChecker {
.toCustomMap(LinkedHashMap::new),
EntryStream.of(typeParameters)
.selectKeys(PyTypeVarTupleType.class)
.selectValues(PyVariadicType.class)
.selectValues(PyPositionalVariadicType.class)
.toCustomMap(LinkedHashMap::new),
EntryStream.of(typeParameters)
.selectKeys(PyParamSpecType.class)
@@ -1738,7 +1738,7 @@ public final class PyTypeChecker {
}
private GenericSubstitutions(@NotNull Map<PyTypeVarType, PyType> typeVars,
@NotNull Map<PyTypeVarTupleType, PyVariadicType> typeVarTuples,
@NotNull Map<PyTypeVarTupleType, PyPositionalVariadicType> typeVarTuples,
@NotNull Map<PyParamSpecType, PyParamSpecType> paramSpecs,
@Nullable PyType qualifierType) {
this.typeVars = typeVars;
@@ -1755,7 +1755,7 @@ public final class PyTypeChecker {
return Collections.unmodifiableMap(typeVars);
}
public @NotNull Map<PyTypeVarTupleType, PyVariadicType> getTypeVarTuples() {
public @NotNull Map<PyTypeVarTupleType, PyPositionalVariadicType> getTypeVarTuples() {
return Collections.unmodifiableMap(typeVarTuples);
}
@@ -20,10 +20,10 @@ public final class PyTypeParameterMapping {
for (Couple<PyType> couple : mapping) {
PyType expectedType = couple.getFirst();
PyType actualType = couple.getSecond();
if (expectedType instanceof PyVariadicType && !(actualType instanceof PyVariadicType || actualType == null)) {
if (expectedType instanceof PyPositionalVariadicType && !(actualType instanceof PyPositionalVariadicType || actualType == null)) {
throw new IllegalArgumentException("Variadic type " + expectedType + " cannot be mapped to a non-variadic type " + actualType);
}
if (!(expectedType instanceof PyVariadicType) && actualType instanceof PyVariadicType) {
if (!(expectedType instanceof PyPositionalVariadicType) && actualType instanceof PyPositionalVariadicType) {
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(PyVariadicType.class))
int expectedArity = ContainerUtil.exists(flattenedExpectedParameterTypes, Conditions.instanceOf(PyPositionalVariadicType.class))
? -1
: flattenedExpectedParameterTypes.size();
@@ -74,14 +74,14 @@ public final class PyTypeParameterMapping {
}
if (positionalVarargArgumentTypes.size() > 1 ||
positionalVarargArgumentTypes.size() == 1 && !(positionalVarargArgumentTypes.get(0) instanceof PyVariadicType)) {
positionalVarargArgumentTypes.size() == 1 && !(positionalVarargArgumentTypes.get(0) instanceof PyPositionalVariadicType)) {
requiredPositionalArgumentTypes.addAll(optionalPositionalArgumentTypes);
optionalPositionalArgumentTypes.clear();
requiredPositionalArgumentTypes.addAll(positionalVarargArgumentTypes);
positionalVarargArgumentTypes.clear();
}
int actualArity = ContainerUtil.exists(requiredPositionalArgumentTypes, Conditions.instanceOf(PyVariadicType.class)) ?
int actualArity = ContainerUtil.exists(requiredPositionalArgumentTypes, Conditions.instanceOf(PyPositionalVariadicType.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 PyVariadicType;
assert positionalVarargArgumentTypes.size() == 1 && positionalVarargArgumentTypes.get(0) instanceof PyPositionalVariadicType;
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 PyVariadicType) {
if (leftmostExpected instanceof PyPositionalVariadicType) {
break;
}
// The leftmost expected type is a regular type
PyType leftmostActual = actualTypesDeque.peekFirst();
if (leftmostActual instanceof PyVariadicType) {
if (leftmostActual instanceof PyPositionalVariadicType) {
break;
}
expectedTypesDeque.removeFirst();
@@ -138,12 +138,12 @@ public final class PyTypeParameterMapping {
while (expectedTypesDeque.size() != 0 && actualTypesDeque.size() != 0) {
PyType rightmostExpected = expectedTypesDeque.peekLast();
if (rightmostExpected instanceof PyVariadicType) {
if (rightmostExpected instanceof PyPositionalVariadicType) {
break;
}
expectedTypesDeque.removeLast();
PyType rightmostActual = actualTypesDeque.peekLast();
if (rightmostActual instanceof PyVariadicType rightmostActualVariadic) {
if (rightmostActual instanceof PyPositionalVariadicType rightmostActualVariadic) {
if (rightmostActualVariadic instanceof PyUnpackedTupleType unpackedTupleType && unpackedTupleType.isUnbound()) {
PyType repeatedActualType = unpackedTupleType.getElementTypes().get(0);
rightMappedTypes.add(Couple.of(rightmostExpected, repeatedActualType));
@@ -160,10 +160,10 @@ public final class PyTypeParameterMapping {
}
if (expectedTypesDeque.size() != 0 && actualTypesDeque.size() != 0
&& !(expectedTypesDeque.peekFirst() instanceof PyVariadicType)
&& (actualTypesDeque.peekFirst() instanceof PyVariadicType variadic)) {
&& !(expectedTypesDeque.peekFirst() instanceof PyPositionalVariadicType)
&& (actualTypesDeque.peekFirst() instanceof PyPositionalVariadicType variadic)) {
if (variadic instanceof PyUnpackedTupleType actualUnpackedTupleType && actualUnpackedTupleType.isUnbound()) {
while (expectedTypesDeque.size() != 0 && !(expectedTypesDeque.peekFirst() instanceof PyVariadicType)) {
while (expectedTypesDeque.size() != 0 && !(expectedTypesDeque.peekFirst() instanceof PyPositionalVariadicType)) {
PyType repeatedActualType = actualUnpackedTupleType.getElementTypes().get(0);
leftMappedTypes.add(Couple.of(expectedTypesDeque.peekFirst(), repeatedActualType));
expectedTypesDeque.removeFirst();
@@ -182,11 +182,13 @@ public final class PyTypeParameterMapping {
if (expectedTypesDeque.size() == 0) {
boolean allActualTypesMatched = actualTypesDeque.size() == 0;
boolean onlySingleActualVariadicLeft = actualTypesDeque.size() == 1 &&
actualTypesDeque.peekFirst() instanceof PyVariadicType;
actualTypesDeque.peekFirst() instanceof PyPositionalVariadicType;
sizeMismatch = !(allActualTypesMatched || onlySingleActualVariadicLeft);
}
else if (expectedTypesDeque.size() == 1) {
PyType onlyLeftExpectedType = expectedTypesDeque.peekFirst();
if (onlyLeftExpectedType instanceof PyPositionalVariadicType) {
if (actualTypesDeque.size() == 1 && actualTypesDeque.peekFirst() instanceof PyPositionalVariadicType variadicType) {
if (onlyLeftExpectedType instanceof PyVariadicType) {
// [*Ts] <- [*Ts] or [*Ts] <- [*tuple[T1, ...]]
if (actualTypesDeque.size() == 1 && actualTypesDeque.peekFirst() instanceof PyVariadicType variadicType) {
@@ -22,7 +22,7 @@ public final class PyUnpackedTupleTypeImpl implements PyUnpackedTupleType {
if (elementTypes.size() != 1) {
throw new IllegalArgumentException("Unbounded unpacked tuple type can have only one type parameter");
}
if (elementTypes.get(0) instanceof PyVariadicType) {
if (elementTypes.get(0) instanceof PyPositionalVariadicType) {
throw new IllegalArgumentException("Unbounded unpacked tuple type of a TypeVarTuple or another unpacked tuple type is now allowed");
}
}