Skip to content

Commit

Permalink
use a generic's bound when narrowing with isinstance if it's covari…
Browse files Browse the repository at this point in the history
…ant, or `Never` if it's contravariant
  • Loading branch information
DetachHead committed Oct 6, 2024
1 parent 9383023 commit 13dd380
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 11 deletions.
2 changes: 1 addition & 1 deletion packages/pyright-internal/src/analyzer/patternMatching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@ function narrowTypeBasedOnClassPattern(
// specialize it with Unknown type arguments.
if (isClass(exprType) && !exprType.props?.typeAliasInfo) {
exprType = ClassType.cloneRemoveTypePromotions(exprType);
exprType = specializeWithUnknownTypeArgs(exprType, evaluator.getTupleClassType());
exprType = specializeWithUnknownTypeArgs(exprType, evaluator.getTupleClassType(), evaluator.getObjectType());
}

// Are there any positional arguments? If so, try to get the mappings for
Expand Down
6 changes: 5 additions & 1 deletion packages/pyright-internal/src/analyzer/typeGuards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1135,7 +1135,11 @@ export function getIsInstanceClassTypes(
const addClassTypesToList = (types: Type[]) => {
types.forEach((subtype) => {
if (isClass(subtype)) {
subtype = specializeWithUnknownTypeArgs(subtype, evaluator.getTupleClassType());
subtype = specializeWithUnknownTypeArgs(
subtype,
evaluator.getTupleClassType(),
evaluator.getObjectType()
);

if (isInstantiableClass(subtype) && ClassType.isBuiltIn(subtype, 'Callable')) {
subtype = convertToInstantiable(getUnknownTypeForCallable());
Expand Down
30 changes: 24 additions & 6 deletions packages/pyright-internal/src/analyzer/typeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1053,9 +1053,15 @@ export function getTypeVarScopeIds(type: Type): TypeVarScopeId[] {
return scopeIds;
}

// Specializes the class with "Unknown" type args (or the equivalent for ParamSpecs
// or TypeVarTuples).
export function specializeWithUnknownTypeArgs(type: ClassType, tupleClassType?: ClassType): ClassType {
/**
* Specializes the class with "Unknown" type args (or the equivalent for ParamSpecs or TypeVarTuples), or its
* widest possible type if its variance is known (`object` if the bound if covariant, `Never` if contravariant).
*/
export function specializeWithUnknownTypeArgs(
type: ClassType,
tupleClassType?: ClassType,
objectType?: Type
): ClassType {
if (type.shared.typeParams.length === 0) {
return type;
}
Expand All @@ -1073,14 +1079,17 @@ export function specializeWithUnknownTypeArgs(type: ClassType, tupleClassType?:

return ClassType.specialize(
type,
type.shared.typeParams.map((param) => getUnknownForTypeVar(param, tupleClassType)),
type.shared.typeParams.map((param) => getUnknownForTypeVar(param, tupleClassType, objectType)),
/* isTypeArgExplicit */ false,
/* includeSubclasses */ type.priv.includeSubclasses
);
}

// Returns "Unknown" for simple TypeVars or the equivalent for a ParamSpec.
export function getUnknownForTypeVar(typeVar: TypeVarType, tupleClassType?: ClassType): Type {
/**
* Returns "Unknown" for simple TypeVars or the equivalent for a ParamSpec, or its widest possible type if its
* variance is known (`object` if the bound if covariant, `Never` if contravariant).
*/
export function getUnknownForTypeVar(typeVar: TypeVarType, tupleClassType?: ClassType, objectType?: Type): Type {
if (isParamSpec(typeVar)) {
return ParamSpecType.getUnknown();
}
Expand All @@ -1089,6 +1098,15 @@ export function getUnknownForTypeVar(typeVar: TypeVarType, tupleClassType?: Clas
return getUnknownForTypeVarTuple(tupleClassType);
}

// if there are no usages of the TypeVar on the class and its variance isn't explicitly specified, it won't be
// known yet. https://github.com/DetachHead/basedpyright/issues/744
const variance = TypeVarType.getVariance(typeVar, false);
if (variance === Variance.Covariant) {
return typeVar.shared.boundType ?? objectType ?? UnknownType.create();
}
if (variance === Variance.Contravariant) {
return NeverType.createNever();
}
return UnknownType.create();
}

Expand Down
8 changes: 5 additions & 3 deletions packages/pyright-internal/src/analyzer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3083,11 +3083,13 @@ export namespace TypeVarType {
return type.shared.name;
}

export function getVariance(type: TypeVarType) {
export function getVariance(type: TypeVarType, assertNotAuto = true) {
const variance = type.priv.computedVariance ?? type.shared.declaredVariance;

// By this point, the variance should have been inferred.
assert(variance !== Variance.Auto, 'Expected variance to be inferred');
if (assertNotAuto) {
// By this point, the variance should have been inferred.
assert(variance !== Variance.Auto, 'Expected variance to be inferred');
}

// If we're in the process of computing variance, it will still be
// unknown. Default to covariant in this case.
Expand Down

0 comments on commit 13dd380

Please sign in to comment.