-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Implement TypeIs (PEP 742) #16898
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement TypeIs (PEP 742) #16898
Changes from 18 commits
8a30073
58e8403
c8d2af8
f205910
75c9dec
4666486
25a9c79
faa4a07
c0e0210
f107e5b
34700bb
065ec92
aef3036
4b19c77
6b0e749
c9e53e6
909e53c
eb88371
1b1e368
84c69d2
ae294bf
7fedbcf
dbc229d
8b2fb0b
816fd1a
d6fcc35
ef825ce
d32956d
a36a16a
b32ba80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1209,6 +1209,22 @@ def check_func_def( | |
| # visible from *inside* of this function/method. | ||
| ref_type: Type | None = self.scope.active_self_type() | ||
|
|
||
| if typ.type_is: | ||
| arg_index = 0 | ||
| # For methods and classmethods, we want the second parameter | ||
| if ref_type is not None and (not defn.is_static or defn.name == "__new__"): | ||
| arg_index = 1 | ||
| if arg_index < len(typ.arg_types) and not is_subtype( | ||
| typ.type_is, typ.arg_types[arg_index] | ||
| ): | ||
| self.fail( | ||
| message_registry.TYPE_NARROWER_NOT_SUBTYPE.format( | ||
| format_type(typ.type_is, self.options), | ||
| format_type(typ.arg_types[arg_index], self.options), | ||
| ), | ||
| item, | ||
| ) | ||
|
JelleZijlstra marked this conversation as resolved.
|
||
|
|
||
| # Store argument types. | ||
| for i in range(len(typ.arg_types)): | ||
| arg_type = typ.arg_types[i] | ||
|
|
@@ -2177,6 +2193,8 @@ def check_override( | |
| elif isinstance(original, CallableType) and isinstance(override, CallableType): | ||
| if original.type_guard is not None and override.type_guard is None: | ||
| fail = True | ||
| if original.type_is is not None and override.type_is is None: | ||
| fail = True | ||
|
|
||
| if is_private(name): | ||
| fail = False | ||
|
|
@@ -5629,7 +5647,7 @@ def combine_maps(list_maps: list[TypeMap]) -> TypeMap: | |
| def find_isinstance_check(self, node: Expression) -> tuple[TypeMap, TypeMap]: | ||
| """Find any isinstance checks (within a chain of ands). Includes | ||
| implicit and explicit checks for None and calls to callable. | ||
| Also includes TypeGuard functions. | ||
| Also includes TypeGuard and TypeIs functions. | ||
|
|
||
| Return value is a map of variables to their types if the condition | ||
| is true and a map of variables to their types if the condition is false. | ||
|
|
@@ -5681,7 +5699,7 @@ def find_isinstance_check_helper(self, node: Expression) -> tuple[TypeMap, TypeM | |
| if literal(expr) == LITERAL_TYPE and attr and len(attr) == 1: | ||
| return self.hasattr_type_maps(expr, self.lookup_type(expr), attr[0]) | ||
| elif isinstance(node.callee, RefExpr): | ||
| if node.callee.type_guard is not None: | ||
| if node.callee.type_guard is not None or node.callee.type_is is not None: | ||
| # TODO: Follow *args, **kwargs | ||
| if node.arg_kinds[0] != nodes.ARG_POS: | ||
| # the first argument might be used as a kwarg | ||
|
|
@@ -5707,15 +5725,31 @@ def find_isinstance_check_helper(self, node: Expression) -> tuple[TypeMap, TypeM | |
| # we want the idx-th variable to be narrowed | ||
| expr = collapse_walrus(node.args[idx]) | ||
| else: | ||
| self.fail(message_registry.TYPE_GUARD_POS_ARG_REQUIRED, node) | ||
| kind = ( | ||
| "guard" if node.callee.type_guard is not None else "narrower" | ||
| ) | ||
| self.fail( | ||
| message_registry.TYPE_GUARD_POS_ARG_REQUIRED.format(kind), node | ||
| ) | ||
| return {}, {} | ||
| if literal(expr) == LITERAL_TYPE: | ||
| # Note: we wrap the target type, so that we can special case later. | ||
| # Namely, for isinstance() we use a normal meet, while TypeGuard is | ||
| # considered "always right" (i.e. even if the types are not overlapping). | ||
| # Also note that a care must be taken to unwrap this back at read places | ||
| # where we use this to narrow down declared type. | ||
| return {expr: TypeGuardedType(node.callee.type_guard)}, {} | ||
| if node.callee.type_guard is not None: | ||
| return {expr: TypeGuardedType(node.callee.type_guard)}, {} | ||
| else: | ||
| assert node.callee.type_is is not None | ||
| return conditional_types_to_typemaps( | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the crucial part of the change, which implements the new type narrowing behavior. It's the same as |
||
| expr, | ||
| *self.conditional_types_with_intersection( | ||
| self.lookup_type(expr), | ||
| [TypeRange(node.callee.type_is, is_upper_bound=False)], | ||
| expr, | ||
| ), | ||
| ) | ||
| elif isinstance(node, ComparisonExpr): | ||
| # Step 1: Obtain the types of each operand and whether or not we can | ||
| # narrow their types. (For example, we shouldn't try narrowing the | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -281,5 +281,11 @@ def __hash__(self) -> int: | |
| sub_code_of=MISC, | ||
| ) | ||
|
|
||
| TYPE_NARROWER_NOT_SUBTYPE: Final[ErrorCode] = ErrorCode( | ||
| "type-is-not-subtype", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I propose It would be consistent with: TYPE_NARROWER_NOT_SUBTYPE: Final = ErrorMessage(
"Narrowed type {} is not a subtype of input type {}", codes.TYPE_NARROWER_NOT_SUBTYPE
)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe narrowed-type-not-subtype? Your suggestion sounds like TypeNarrower, which was a rejected name for teh feature. |
||
| "Warn if a TypeIs function's narrowed type is not a subtype of the original type", | ||
| "General", | ||
| ) | ||
|
|
||
| # This copy will not include any error codes defined later in the plugins. | ||
| mypy_error_codes = error_codes.copy() | ||
Uh oh!
There was an error while loading. Please reload this page.