Skip to content

Commit

Permalink
Fixed a bug that led to a false positive when a constrained type vari…
Browse files Browse the repository at this point in the history
…able was used as a type argument in an annotation for an argument passed to a method that's bound to the same type variable. This addresses #5556. (#5557)

Co-authored-by: Eric Traut <[email protected]>
  • Loading branch information
erictraut and msfterictraut authored Jul 21, 2023
1 parent 19194dd commit 39e355d
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 3 deletions.
62 changes: 60 additions & 2 deletions packages/pyright-internal/src/analyzer/typeEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3607,7 +3607,8 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
function mapSubtypesExpandTypeVars(
type: Type,
conditionFilter: TypeCondition[] | undefined,
callback: (expandedSubtype: Type, unexpandedSubtype: Type, isLastIteration: boolean) => Type | undefined
callback: (expandedSubtype: Type, unexpandedSubtype: Type, isLastIteration: boolean) => Type | undefined,
recursionCount = 0
): Type {
const newSubtypes: Type[] = [];
let typeChanged = false;
Expand All @@ -3619,9 +3620,12 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions

doForEachSubtype(expandedType, (subtype, index, allSubtypes) => {
if (conditionFilter) {
if (!TypeCondition.isCompatible(getTypeCondition(subtype), conditionFilter)) {
const filteredType = applyConditionFilterToType(subtype, conditionFilter, recursionCount);
if (!filteredType) {
return undefined;
}

subtype = filteredType;
}

let transformedType = callback(
Expand Down Expand Up @@ -3669,6 +3673,60 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
return newType;
}

function applyConditionFilterToType(
type: Type,
conditionFilter: TypeCondition[],
recursionCount: number
): Type | undefined {
if (recursionCount > maxTypeRecursionCount) {
return type;
}
recursionCount++;

// If the type has a condition associated with it, make sure it's compatible.
if (!TypeCondition.isCompatible(getTypeCondition(type), conditionFilter)) {
return undefined;
}

// If the type is generic, see if any of its type arguments should be filtered.
// This is possible only in cases where the type parameter is covariant.

// TODO - handle functions and tuples
if (isClass(type) && type.typeArguments && !type.tupleTypeArguments) {
inferTypeParameterVarianceForClass(type);

let typeWasTransformed = false;

const filteredTypeArgs = type.typeArguments.map((typeArg, index) => {
const variance = TypeVarType.getVariance(type.details.typeParameters[index]);
if (variance !== Variance.Covariant) {
return typeArg;
}

const filteredTypeArg = mapSubtypesExpandTypeVars(
typeArg,
conditionFilter,
(expandedSubtype) => {
return expandedSubtype;
},
recursionCount
);

if (filteredTypeArg !== typeArg) {
typeWasTransformed = true;
}

return filteredTypeArg;
});

if (typeWasTransformed) {
return ClassType.cloneForSpecialization(type, filteredTypeArgs, /* isTypeArgumentExplicit */ true);
}
}

return type;
}

function markNamesAccessed(node: ParseNode, names: string[]) {
const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
const scope = ScopeUtils.getScopeForNode(node);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This sample tests the case where a constrained TypeVar is used
# as an argument for a constructor or function call.

from typing import TypeVar, Generic
from typing import AnyStr, Iterable, TypeVar, Generic
from dataclasses import dataclass


Expand All @@ -26,3 +26,7 @@ def func1(a: Data[_T]) -> _T:
reveal_type(value, expected_text="ClassA*")

return value


def func2(val: AnyStr, objs: Iterable[AnyStr]) -> AnyStr:
return val.join(objs)

0 comments on commit 39e355d

Please sign in to comment.