Skip to content

Commit

Permalink
Removed support for custom callable type guard pattern. Now that ty…
Browse files Browse the repository at this point in the history
…peshed defines this with a `TypeIs`, we no longer require custom logic here. This addresses #8944. (#8954)
  • Loading branch information
erictraut authored Sep 10, 2024
1 parent c83e30e commit 8698e60
Show file tree
Hide file tree
Showing 3 changed files with 7 additions and 133 deletions.
1 change: 0 additions & 1 deletion docs/type-concepts-advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ In addition to assignment-based type narrowing, Pyright supports the following t
* `S in D` and `S not in D` (where S is a string literal and D is a TypedDict)
* `isinstance(x, T)` (where T is a type or a tuple of types)
* `issubclass(x, T)` (where T is a type or a tuple of types)
* `callable(x)`
* `f(x)` (where f is a user-defined type guard as defined in [PEP 647](https://www.python.org/dev/peps/pep-0647/) or [PEP 742](https://www.python.org/dev/peps/pep-0742))
* `bool(x)` (where x is any expression that is statically verifiable to be truthy or falsey in all cases)
* `x` (where x is any expression that is statically verifiable to be truthy or falsey in all cases)
Expand Down
132 changes: 0 additions & 132 deletions packages/pyright-internal/src/analyzer/typeGuards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ import {
TupleTypeArg,
Type,
TypeBase,
TypeCategory,
TypeCondition,
TypedDictEntry,
TypeVarType,
Expand Down Expand Up @@ -662,42 +661,6 @@ export function getTypeNarrowingCallback(
}
}

// Look for "callable(X)"
if (testExpression.d.args.length === 1) {
const arg0Expr = testExpression.d.args[0].d.valueExpr;
if (ParseTreeUtils.isMatchingExpression(reference, arg0Expr)) {
const callTypeResult = evaluator.getTypeOfExpression(
testExpression.d.leftExpr,
EvalFlags.CallBaseDefaults
);
const callType = callTypeResult.type;

if (isFunction(callType) && FunctionType.isBuiltIn(callType, 'callable')) {
return (type: Type) => {
let narrowedType = narrowTypeForCallable(
evaluator,
type,
isPositiveTest,
testExpression,
/* allowIntersections */ false
);
if (isPositiveTest && isNever(narrowedType)) {
// Try again with intersections allowed.
narrowedType = narrowTypeForCallable(
evaluator,
type,
isPositiveTest,
testExpression,
/* allowIntersections */ true
);
}

return { type: narrowedType, isIncomplete: !!callTypeResult.isIncomplete };
};
}
}
}

// Look for "bool(X)"
if (testExpression.d.args.length === 1 && !testExpression.d.args[0].d.name) {
if (ParseTreeUtils.isMatchingExpression(reference, testExpression.d.args[0].d.valueExpr)) {
Expand Down Expand Up @@ -2570,98 +2533,3 @@ export function enumerateLiteralsForType(evaluator: TypeEvaluator, type: ClassTy

return undefined;
}

// Attempts to narrow a type (make it more constrained) based on a
// call to "callable". For example, if the original type of expression "x" is
// Union[Callable[..., Any], Type[int], int], it would remove the "int" because
// it's not callable.
function narrowTypeForCallable(
evaluator: TypeEvaluator,
type: Type,
isPositiveTest: boolean,
errorNode: ExpressionNode,
allowIntersections: boolean
): Type {
return evaluator.mapSubtypesExpandTypeVars(type, /* options */ undefined, (subtype) => {
switch (subtype.category) {
case TypeCategory.Function:
case TypeCategory.Overloaded: {
return isPositiveTest ? subtype : undefined;
}

case TypeCategory.Module: {
return isPositiveTest ? undefined : subtype;
}

case TypeCategory.Class: {
if (isNoneInstance(subtype)) {
return isPositiveTest ? undefined : subtype;
}

if (TypeBase.isInstantiable(subtype)) {
return isPositiveTest ? subtype : undefined;
}

// See if the object is callable.
const callMemberType = lookUpClassMember(subtype, '__call__', MemberAccessFlags.SkipInstanceMembers);

if (!callMemberType) {
if (!isPositiveTest) {
return subtype;
}

if (allowIntersections) {
// The type appears to not be callable. It's possible that the
// two type is a subclass that is callable. We'll synthesize a
// new intersection type.
const className = `<callable subtype of ${subtype.shared.name}>`;
const fileInfo = getFileInfo(errorNode);
let newClassType = ClassType.createInstantiable(
className,
ParseTreeUtils.getClassFullName(errorNode, fileInfo.moduleName, className),
fileInfo.moduleName,
fileInfo.fileUri,
ClassTypeFlags.None,
ParseTreeUtils.getTypeSourceId(errorNode),
/* declaredMetaclass */ undefined,
subtype.shared.effectiveMetaclass,
subtype.shared.docString
);
newClassType.shared.baseClasses = [ClassType.cloneAsInstantiable(subtype)];
computeMroLinearization(newClassType);

newClassType = addConditionToType(newClassType, subtype.props?.condition);

// Add a __call__ method to the new class.
const callMethod = FunctionType.createSynthesizedInstance('__call__');
const selfParam = FunctionParam.create(
ParamCategory.Simple,
ClassType.cloneAsInstance(newClassType),
FunctionParamFlags.TypeDeclared,
'self'
);
FunctionType.addParam(callMethod, selfParam);
FunctionType.addDefaultParams(callMethod);
callMethod.shared.declaredReturnType = UnknownType.create();
ClassType.getSymbolTable(newClassType).set(
'__call__',
Symbol.createWithType(SymbolFlags.ClassMember, callMethod)
);

return ClassType.cloneAsInstance(newClassType);
}

return undefined;
} else {
return isPositiveTest ? subtype : undefined;
}
}

default: {
// For all other types, we can't determine whether it's
// callable or not, so we can't eliminate them.
return subtype;
}
}
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,10 @@ def test3(v: _T2) -> Union[_T2, int, str]:
else:
reveal_type(v, expected_text="int* | str*")
return v


def test4(v: type[int] | object):
if callable(v):
reveal_type(v, expected_text="type[int] | ((...) -> object)")
else:
reveal_type(v, expected_text="object")

0 comments on commit 8698e60

Please sign in to comment.