mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-17 11:17:17 +03:00
Fixed a bug that led to a false negative when determining type compatibility between two unions in an invariant context. This addresses #5534. (#5536)
Co-authored-by: Eric Traut <erictr@microsoft.com>
This commit is contained in:
parent
9855dc9c7f
commit
5569939ee6
@ -21717,10 +21717,14 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
return true;
|
||||
}
|
||||
|
||||
diag?.addMessage(
|
||||
Localizer.DiagnosticAddendum.typeAssignmentMismatch().format(printSrcDestTypes(srcType, destType))
|
||||
);
|
||||
return false;
|
||||
if (!isUnion(destType)) {
|
||||
diag?.addMessage(
|
||||
Localizer.DiagnosticAddendum.typeAssignmentMismatch().format(
|
||||
printSrcDestTypes(srcType, destType)
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -22379,6 +22383,10 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
// whose primary type matches.
|
||||
remainingSrcSubtypes.forEach((srcSubtype) => {
|
||||
const destTypeIndex = remainingDestSubtypes.findIndex((destSubtype) => {
|
||||
if (isTypeSame(destSubtype, srcSubtype)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
isClass(srcSubtype) &&
|
||||
isClass(destSubtype) &&
|
||||
@ -22420,6 +22428,16 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
// If there is are remaining dest subtypes and they're all type variables,
|
||||
// attempt to assign the remaining source subtypes to them.
|
||||
if (canUseFastPath && (remainingDestSubtypes.length !== 0 || remainingSrcSubtypes.length !== 0)) {
|
||||
if ((flags & AssignTypeFlags.EnforceInvariance) !== 0) {
|
||||
// If we have no src subtypes remaining but not all dest types have been subsumed
|
||||
// by other dest types, then the types are not compatible if we're enforcing invariance.
|
||||
if (remainingSrcSubtypes.length === 0) {
|
||||
return remainingDestSubtypes.every((destSubtype) =>
|
||||
isTypeSubsumedByOtherType(destSubtype, destType.subtypes, recursionCount)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const isReversed = (flags & AssignTypeFlags.ReverseTypeVarMatching) !== 0;
|
||||
const effectiveDestSubtypes = isReversed ? remainingSrcSubtypes : remainingDestSubtypes;
|
||||
|
||||
@ -22512,21 +22530,9 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
recursionCount
|
||||
)
|
||||
) {
|
||||
const concreteSubtype = makeTopLevelTypeVarsConcrete(subtype);
|
||||
|
||||
// Determine if the current subtype is subsumed by another subtype
|
||||
// in the same union. If so, we can ignore this.
|
||||
let isSubtypeSubsumed = false;
|
||||
srcType.subtypes.forEach((innerSubtype) => {
|
||||
if (
|
||||
!isSubtypeSubsumed &&
|
||||
!isTypeSame(innerSubtype, subtype) &&
|
||||
!isAnyOrUnknown(innerSubtype) &&
|
||||
isProperSubtype(innerSubtype, concreteSubtype, recursionCount)
|
||||
) {
|
||||
isSubtypeSubsumed = true;
|
||||
}
|
||||
});
|
||||
const isSubtypeSubsumed = isTypeSubsumedByOtherType(subtype, srcType.subtypes, recursionCount);
|
||||
|
||||
// Try again with a concrete version of the subtype.
|
||||
if (
|
||||
@ -22556,6 +22562,24 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
return true;
|
||||
}
|
||||
|
||||
// Determines whether a type is "subsumed by" (i.e. is a proper subtype of) one
|
||||
// of the other types in a list.
|
||||
function isTypeSubsumedByOtherType(type: Type, otherTypes: Type[], recursionCount = 0) {
|
||||
const concreteType = makeTopLevelTypeVarsConcrete(type);
|
||||
|
||||
for (const otherType of otherTypes) {
|
||||
if (isTypeSame(otherType, type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isAnyOrUnknown(otherType) && isProperSubtype(otherType, concreteType, recursionCount)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determines whether the srcType is a subtype of destType but the converse
|
||||
// is not true. It's important that we check both directions to avoid
|
||||
// matches for types like `tuple[Any]` and `tuple[int]` from being considered
|
||||
|
49
packages/pyright-internal/src/tests/samples/genericType45.py
Normal file
49
packages/pyright-internal/src/tests/samples/genericType45.py
Normal file
@ -0,0 +1,49 @@
|
||||
# This sample tests that invariance is properly enforced when appropriate.
|
||||
|
||||
from typing import Any, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def func1(v: list[float | str]):
|
||||
# This should generate an error.
|
||||
x1: list[float] = v
|
||||
|
||||
x2: list[str | float] = v
|
||||
|
||||
# This should generate an error.
|
||||
x3: list[float | str | None] = v
|
||||
|
||||
x4: list[Any | str] = v
|
||||
|
||||
# This should generate an error.
|
||||
x5: list[int | str] = v
|
||||
|
||||
x6: list[float | int | str] = v
|
||||
|
||||
|
||||
def func2(v: list[T]) -> T:
|
||||
x1: list[T] = v
|
||||
|
||||
x2: list[Any | T] = v
|
||||
|
||||
# This should generate an error.
|
||||
x3: list[int | T] = v
|
||||
|
||||
return v[0]
|
||||
|
||||
|
||||
def func3(v: list[float | T]) -> float | T:
|
||||
# This should generate an error.
|
||||
x1: list[T] = v
|
||||
|
||||
x2: list[Any | T] = v
|
||||
|
||||
x3: list[T | float] = v
|
||||
|
||||
# This should generate an error.
|
||||
x4: list[T | int] = v
|
||||
|
||||
x5: list[float | int | T] = v
|
||||
|
||||
return v[0]
|
@ -1013,6 +1013,12 @@ test('GenericType44', () => {
|
||||
TestUtils.validateResults(analysisResults, 0);
|
||||
});
|
||||
|
||||
test('GenericType45', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['genericType45.py']);
|
||||
|
||||
TestUtils.validateResults(analysisResults, 6);
|
||||
});
|
||||
|
||||
test('Protocol1', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['protocol1.py']);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user