mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 07:55:56 +03:00
Added support for type guard forms x is ...
, x is not ...
, x == ...
and x != ...
. Support for these were recently added to mypy. This addresses https://github.com/microsoft/pyright/issues/4397.
This commit is contained in:
parent
7be1d77360
commit
c7626944f1
@ -168,6 +168,8 @@ In addition to assignment-based type narrowing, Pyright supports the following t
|
|||||||
|
|
||||||
* `x is None` and `x is not None`
|
* `x is None` and `x is not None`
|
||||||
* `x == None` and `x != None`
|
* `x == None` and `x != None`
|
||||||
|
* `x is ...` and `x is not ...`
|
||||||
|
* `x == ...` and `x != ...`
|
||||||
* `type(x) is T` and `type(x) is not T`
|
* `type(x) is T` and `type(x) is not T`
|
||||||
* `x is E` and `x is not E` (where E is a literal enum or bool)
|
* `x is E` and `x is not E` (where E is a literal enum or bool)
|
||||||
* `x == L` and `x != L` (where L is an expression that evaluates to a literal type)
|
* `x == L` and `x != L` (where L is an expression that evaluates to a literal type)
|
||||||
|
@ -164,6 +164,22 @@ export function getTypeNarrowingCallback(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Look for "X is ...", "X is not ...", "X == ...", and "X != ...".
|
||||||
|
if (testExpression.rightExpression.nodeType === ParseNodeType.Ellipsis) {
|
||||||
|
// Allow the LHS to be either a simple expression or an assignment
|
||||||
|
// expression that assigns to a simple name.
|
||||||
|
let leftExpression = testExpression.leftExpression;
|
||||||
|
if (leftExpression.nodeType === ParseNodeType.AssignmentExpression) {
|
||||||
|
leftExpression = leftExpression.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ParseTreeUtils.isMatchingExpression(reference, leftExpression)) {
|
||||||
|
return (type: Type) => {
|
||||||
|
return narrowTypeForIsEllipsis(evaluator, type, adjIsPositiveTest);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Look for "type(X) is Y" or "type(X) is not Y".
|
// Look for "type(X) is Y" or "type(X) is not Y".
|
||||||
if (isOrIsNotOperator && testExpression.leftExpression.nodeType === ParseNodeType.Call) {
|
if (isOrIsNotOperator && testExpression.leftExpression.nodeType === ParseNodeType.Call) {
|
||||||
if (
|
if (
|
||||||
@ -876,6 +892,49 @@ function narrowTypeForIsNone(evaluator: TypeEvaluator, type: Type, isPositiveTes
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle type narrowing for expressions of the form "x is ..." and "x is not ...".
|
||||||
|
function narrowTypeForIsEllipsis(evaluator: TypeEvaluator, type: Type, isPositiveTest: boolean) {
|
||||||
|
const expandedType = mapSubtypes(type, (subtype) => {
|
||||||
|
return transformPossibleRecursiveTypeAlias(subtype);
|
||||||
|
});
|
||||||
|
|
||||||
|
return evaluator.mapSubtypesExpandTypeVars(
|
||||||
|
expandedType,
|
||||||
|
/* conditionFilter */ undefined,
|
||||||
|
(subtype, unexpandedSubtype) => {
|
||||||
|
if (isAnyOrUnknown(subtype)) {
|
||||||
|
// We need to assume that "Any" is always both None and not None,
|
||||||
|
// so it matches regardless of whether the test is positive or negative.
|
||||||
|
return subtype;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a TypeVar that isn't constrained, use the unexpanded
|
||||||
|
// TypeVar. For all other cases (including constrained TypeVars),
|
||||||
|
// use the expanded subtype.
|
||||||
|
const adjustedSubtype =
|
||||||
|
isTypeVar(unexpandedSubtype) && unexpandedSubtype.details.constraints.length === 0
|
||||||
|
? unexpandedSubtype
|
||||||
|
: subtype;
|
||||||
|
|
||||||
|
// See if it's a match for object.
|
||||||
|
if (isClassInstance(subtype) && ClassType.isBuiltIn(subtype, 'object')) {
|
||||||
|
return isPositiveTest
|
||||||
|
? addConditionToType(NoneType.createInstance(), subtype.condition)
|
||||||
|
: adjustedSubtype;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isEllipsis = isClassInstance(subtype) && ClassType.isBuiltIn(subtype, 'ellipsis');
|
||||||
|
|
||||||
|
// See if it's a match for "...".
|
||||||
|
if (isEllipsis === isPositiveTest) {
|
||||||
|
return subtype;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// The "isinstance" and "issubclass" calls support two forms - a simple form
|
// The "isinstance" and "issubclass" calls support two forms - a simple form
|
||||||
// that accepts a single class, and a more complex form that accepts a tuple
|
// that accepts a single class, and a more complex form that accepts a tuple
|
||||||
// of classes (including arbitrarily-nested tuples). This method determines
|
// of classes (including arbitrarily-nested tuples). This method determines
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
# This sample tests the type analyzer's type narrowing logic for
|
||||||
|
# conditions of the form "X is ...", "X is not ...",
|
||||||
|
# "X == .." and "X != ...".
|
||||||
|
|
||||||
|
import types
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
|
|
||||||
|
_T = TypeVar("_T", str, ellipsis)
|
||||||
|
|
||||||
|
|
||||||
|
def func1(val: int | ellipsis):
|
||||||
|
if val is not ...:
|
||||||
|
reveal_type(val, expected_text="int")
|
||||||
|
else:
|
||||||
|
reveal_type(val, expected_text="ellipsis")
|
||||||
|
|
||||||
|
|
||||||
|
def func2(val: _T):
|
||||||
|
if val is ...:
|
||||||
|
reveal_type(val, expected_text="ellipsis*")
|
||||||
|
else:
|
||||||
|
reveal_type(val, expected_text="str*")
|
||||||
|
|
||||||
|
|
||||||
|
def func3(val: int | types.EllipsisType):
|
||||||
|
if val != ...:
|
||||||
|
reveal_type(val, expected_text="int")
|
||||||
|
else:
|
||||||
|
reveal_type(val, expected_text="ellipsis")
|
||||||
|
|
||||||
|
|
||||||
|
def func4(val: int | ellipsis):
|
||||||
|
if not val == ...:
|
||||||
|
reveal_type(val, expected_text="int")
|
||||||
|
else:
|
||||||
|
reveal_type(val, expected_text="ellipsis")
|
@ -321,6 +321,12 @@ test('TypeNarrowingIsNoneTuple2', () => {
|
|||||||
TestUtils.validateResults(analysisResults, 0);
|
TestUtils.validateResults(analysisResults, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('TypeNarrowingIsEllipsis1', () => {
|
||||||
|
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeNarrowingIsEllipsis1.py']);
|
||||||
|
|
||||||
|
TestUtils.validateResults(analysisResults, 0);
|
||||||
|
});
|
||||||
|
|
||||||
test('TypeNarrowingLiteral1', () => {
|
test('TypeNarrowingLiteral1', () => {
|
||||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeNarrowingLiteral1.py']);
|
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeNarrowingLiteral1.py']);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user