mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-86002 Revert also PY-25989 PY-84544 to fix new test failures after the revert to weak unions
Namely, `generics_basic.py` in the conformance test suite and Py3TypeCheckerInspectionTest.testTypeVarWidening. Revert 07803c6243370d55df0d870e2e32b38170c1fb49 (cherry picked from commit 9c334ef32597be1d1e8fbc3251ac267be46555c8) IJ-MR-184619 GitOrigin-RevId: 4b2440c973d9c7a7a42590665bd473c4e7a4c9cf
This commit is contained in:
committed by
intellij-monorepo-bot
parent
7c1fa29f39
commit
060b900cf9
@@ -280,8 +280,10 @@ public final class PyTypeChecker {
|
||||
substitutedRef = null;
|
||||
}
|
||||
}
|
||||
final PyType substitution = Ref.deref(substitutedRef);
|
||||
PyType bound = expected.getBound();
|
||||
List<@Nullable PyType> constraints = expected.getConstraints();
|
||||
int matchedConstraintIndex = -1;
|
||||
// Promote int in Type[TypeVar('T', int)] to Type[int] before checking that bounds match
|
||||
if (expected.isDefinition()) {
|
||||
bound = toClass(bound);
|
||||
@@ -291,25 +293,6 @@ public final class PyTypeChecker {
|
||||
// Remove value-specific components from the actual type to make it safe to propagate
|
||||
PyType safeActual = constraints.isEmpty() && bound instanceof PyLiteralStringType ? actual : replaceLiteralStringWithStr(actual);
|
||||
|
||||
if (substitutedRef != null) {
|
||||
final PyType substitution = substitutedRef.get();
|
||||
if (expected.equals(safeActual) || expected.equals(substitution)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MyMatchHelper matchHelper = new MyMatchHelper(expected, context);
|
||||
if (matchHelper.match(substitution, safeActual)) {
|
||||
return true;
|
||||
}
|
||||
if (context.mySubstitutions.frozenTypeVars.contains(expected)) {
|
||||
return false;
|
||||
}
|
||||
if (!matchHelper.match(safeActual, substitution)) {
|
||||
safeActual = PyUnionType.union(safeActual, substitution);
|
||||
}
|
||||
}
|
||||
|
||||
int matchedConstraintIndex = -1;
|
||||
if (constraints.isEmpty()) {
|
||||
Optional<Boolean> match = match(bound, safeActual, context);
|
||||
if (match.isPresent() && !match.get()) {
|
||||
@@ -317,13 +300,25 @@ public final class PyTypeChecker {
|
||||
}
|
||||
}
|
||||
else {
|
||||
final PyType finalSafeActual = safeActual;
|
||||
matchedConstraintIndex = ContainerUtil.indexOf(constraints, constraint -> match(constraint, finalSafeActual, context).orElse(true));
|
||||
matchedConstraintIndex = ContainerUtil.indexOf(constraints, constraint -> match(constraint, safeActual, context).orElse(true));
|
||||
if (matchedConstraintIndex == -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (substitutedRef != null) {
|
||||
if (expected.equals(safeActual) || expected.equals(substitution)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Optional<Boolean> recursiveMatch = RecursionManager.doPreventingRecursion(
|
||||
expected, false, context.reversedSubstitutions
|
||||
? () -> match(safeActual, substitution, context)
|
||||
: () -> match(substitution, safeActual, context)
|
||||
);
|
||||
return recursiveMatch != null ? recursiveMatch.orElse(false) : false;
|
||||
}
|
||||
|
||||
if (safeActual != null) {
|
||||
PyType type = constraints.isEmpty() ? safeActual : constraints.get(matchedConstraintIndex);
|
||||
context.mySubstitutions.typeVars.put(expected, Ref.create(type));
|
||||
@@ -338,25 +333,6 @@ public final class PyTypeChecker {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final class MyMatchHelper {
|
||||
private final @NotNull Object myKey;
|
||||
private final @NotNull MatchContext myContext;
|
||||
|
||||
MyMatchHelper(@NotNull Object key, @NotNull MatchContext context) {
|
||||
myKey = key;
|
||||
myContext = context;
|
||||
}
|
||||
|
||||
boolean match(@Nullable PyType expected, @Nullable PyType actual) {
|
||||
Optional<Boolean> result = RecursionManager.doPreventingRecursion(
|
||||
myKey, false, myContext.reversedSubstitutions
|
||||
? () -> PyTypeChecker.match(actual, expected, myContext)
|
||||
: () -> PyTypeChecker.match(expected, actual, myContext)
|
||||
);
|
||||
return result != null && result.orElse(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable PyType toClass(@Nullable PyType type) {
|
||||
return PyTypeUtil.toStream(type)
|
||||
.map(t -> t instanceof PyInstantiableType<?> instantiableType ? instantiableType.toClass() : t)
|
||||
@@ -1610,7 +1586,6 @@ public final class PyTypeChecker {
|
||||
}
|
||||
});
|
||||
}
|
||||
substitutions.frozenTypeVars = new HashSet<>(substitutions.typeVars.keySet());
|
||||
return substitutions;
|
||||
}
|
||||
|
||||
@@ -1920,6 +1895,8 @@ public final class PyTypeChecker {
|
||||
|
||||
@ApiStatus.Experimental
|
||||
public static class GenericSubstitutions {
|
||||
|
||||
// Nullable-Nullable because of com.jetbrains.python.psi.types.PyTypeChecker.collectTypeSubstitutions
|
||||
private final @NotNull Map<PyTypeVarType, @Nullable Ref<@Nullable PyType>> typeVars;
|
||||
|
||||
private final @NotNull Map<PyTypeVarTupleType, @Nullable PyPositionalVariadicType> typeVarTuples;
|
||||
@@ -1928,8 +1905,6 @@ public final class PyTypeChecker {
|
||||
|
||||
private @Nullable PyType qualifierType;
|
||||
|
||||
private @NotNull Set<PyTypeVarType> frozenTypeVars = Collections.emptySet();
|
||||
|
||||
public GenericSubstitutions(@NotNull Map<? extends PyTypeParameterType, PyType> typeParameters) {
|
||||
this(
|
||||
EntryStream.of(typeParameters)
|
||||
|
||||
@@ -40,11 +40,7 @@ def test():
|
||||
print(result)
|
||||
print(result + <warning descr="Expected type 'int', got 'str' instead">'foo'</warning>)
|
||||
|
||||
# Bug: Expected error.
|
||||
# Generics are considered to be covariant.
|
||||
# I.e. `list[str]` is assignable to `list[int | str]`.
|
||||
# Thus, substitution `T` -> `int | str` is considered valid.
|
||||
f2(1, ['foo'], 'bar')
|
||||
f2(1, <warning descr="Expected type 'List[int]' (matched generic type 'List[T]'), got 'List[str]' instead">['foo']</warning>, 'bar')
|
||||
|
||||
result = f3(1, 'foo', True)
|
||||
f4(<warning descr="Expected type 'Tuple[bool, int, str]', got 'Tuple[int, str, bool]' instead">result</warning>)
|
||||
|
||||
@@ -3332,33 +3332,4 @@ public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase {
|
||||
var: Template = Concrete()
|
||||
""");
|
||||
}
|
||||
|
||||
// PY-25989 PY-84544
|
||||
public void testTypeVarWidening() {
|
||||
myFixture.enableInspections(PyAssertTypeInspection.class);
|
||||
doTestByText("""
|
||||
from collections.abc import Iterable
|
||||
from typing import assert_type
|
||||
|
||||
|
||||
# PY-84544
|
||||
def foo(iterable: Iterable[int] | Iterable[str]) -> None:
|
||||
assert_type(next(iter(iterable)), int | str)
|
||||
|
||||
|
||||
# PY-25989
|
||||
assert_type(max(1, 2.6), float)
|
||||
assert_type(max(2.6, 1), float)
|
||||
max(1, <warning descr="Expected type 'int' (matched generic type 'SupportsRichComparisonT ≤: SupportsDunderLT[Any] | SupportsDunderGT[Any]'), got 'object' instead">object()</warning>)
|
||||
|
||||
|
||||
def bar[T: int, str](v1: T, v2: T) -> T:
|
||||
if (bool(input())):
|
||||
return v1
|
||||
return v2
|
||||
|
||||
|
||||
_ = bar(1, <warning descr="Expected type 'int' (matched generic type 'T ≤: int'), got 'str' instead">"a"</warning>)
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1347,10 +1347,7 @@ public class PyTypeCheckerInspectionTest extends PyInspectionTestCase {
|
||||
def accepts_anything(x: str) -> None:
|
||||
pass
|
||||
|
||||
# Bug: Expected error.
|
||||
# `Callable[[str], None]` is assignable to `Callable[[int | str], None]`.
|
||||
# Thus, substitution `T` -> `int | str` is considered valid.
|
||||
func(42, accepts_anything)""")
|
||||
func(42, <warning descr="Expected type '(int) -> None' (matched generic type '(T) -> None'), got '(x: str) -> None' instead">accepts_anything</warning>)""")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user