-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Feat/transitive deprecated ctors #20131
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
Draft
bzoracler
wants to merge
12
commits into
python:master
Choose a base branch
from
bzoracler:feat/transitive-deprecated-ctors
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
0332484
feat: report `@deprecated()` on non-overloaded class constructors
bzoracler d497f37
Add simple subclass tests
bzoracler 53622b1
fix error message typo
bzoracler 39cd5f9
Merge branch 'python:master' into fix-20103
bzoracler a473e52
Save an unnecessary instance check
bzoracler 8f08c60
Use the constructor definition chosen by mypy rather than walking 2 MROs
bzoracler b0ffd6b
Warn deprecated constructor calls from old-style type aliases
bzoracler c954f78
fix unexpanded type check error
bzoracler 00897ab
fix unexpanded type check error
bzoracler 39e752f
Warn usage of types with deprecated constructors in callable-like con…
bzoracler 0950c97
Avoid invalid deprecation checks of in the `else` branch of condition…
bzoracler 56be189
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -102,7 +102,7 @@ | |
| YieldExpr, | ||
| YieldFromExpr, | ||
| ) | ||
| from mypy.options import PRECISE_TUPLE_TYPES | ||
| from mypy.options import PRECISE_TUPLE_TYPES, Options | ||
| from mypy.plugin import ( | ||
| FunctionContext, | ||
| FunctionSigContext, | ||
|
|
@@ -286,6 +286,8 @@ class ExpressionChecker(ExpressionVisitor[Type], ExpressionCheckerSharedApi): | |
| plugin: Plugin | ||
|
|
||
| _arg_infer_context_cache: ArgumentInferContext | None | ||
| # Used to prevent generating redundant or invalid `@deprecated()` reports | ||
| _valid_pep702_type_context: bool | ||
|
|
||
| def __init__( | ||
| self, | ||
|
|
@@ -322,6 +324,7 @@ def __init__( | |
| type_state.infer_polymorphic = not self.chk.options.old_type_inference | ||
|
|
||
| self._arg_infer_context_cache = None | ||
| self._valid_pep702_type_context = True | ||
| self.expr_cache: dict[ | ||
| tuple[Expression, Type | None], | ||
| tuple[int, Type, list[ErrorInfo], dict[Expression, Type]], | ||
|
|
@@ -375,7 +378,15 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: | |
| # Unknown reference; use any type implicitly to avoid | ||
| # generating extra type errors. | ||
| result = AnyType(TypeOfAny.from_error) | ||
| if isinstance(node, TypeInfo): | ||
| if self._valid_pep702_type_context and isinstance(node, TypeInfo): | ||
| if self.type_context[-1] is not None: | ||
| proper_result = get_proper_type(result) | ||
| if isinstance(proper_result, (CallableType, Overloaded)): | ||
| ctor_type = constructor_type_in_callable_context( | ||
| proper_result, get_proper_type(self.type_context[-1]), self.chk.options | ||
| ) | ||
| if ctor_type is not None: | ||
| self.chk.check_deprecated(ctor_type.definition, e) | ||
| if isinstance(result, CallableType) and isinstance( # type: ignore[misc] | ||
| result.ret_type, Instance | ||
| ): | ||
|
|
@@ -1671,9 +1682,24 @@ def check_callable_call( | |
| ret_type = get_proper_type(callee.ret_type) | ||
| if callee.is_type_obj() and isinstance(ret_type, Instance): | ||
| callable_name = ret_type.type.fullname | ||
| if isinstance(callable_node, RefExpr) and callable_node.fullname in ENUM_BASES: | ||
| # An Enum() call that failed SemanticAnalyzerPass2.check_enum_call(). | ||
| return callee.ret_type, callee | ||
| if isinstance(callable_node, RefExpr): | ||
| # Check implicit calls to deprecated class constructors. | ||
| # Only the non-overload case is handled here. Overloaded constructors are handled | ||
| # separately during overload resolution. `callable_node` is `None` for an overload | ||
| # item so deprecation checks are not duplicated. | ||
| callable_info: TypeInfo | None = None | ||
| if isinstance(callable_node.node, TypeInfo): | ||
| callable_info = callable_node.node | ||
| elif isinstance(callable_node.node, TypeAlias): | ||
| alias_target = get_proper_type(callable_node.node.target) | ||
| if isinstance(alias_target, Instance) and isinstance(alias_target.type, TypeInfo): | ||
| callable_info = alias_target.type | ||
| if callable_info is not None: | ||
| self.chk.check_deprecated(callee.definition, context) | ||
|
|
||
| if callable_node.fullname in ENUM_BASES: | ||
| # An Enum() call that failed SemanticAnalyzerPass2.check_enum_call(). | ||
| return callee.ret_type, callee | ||
|
|
||
| if ( | ||
| callee.is_type_obj() | ||
|
|
@@ -5938,6 +5964,10 @@ def visit_conditional_expr(self, e: ConditionalExpr, allow_none_return: bool = F | |
| e.else_expr, | ||
| context=if_type_fallback, | ||
| allow_none_return=allow_none_return, | ||
| # `@deprecated()` is already properly reported in the else branch when obtaining | ||
| # `full_context_else_type`. Reporting it again is redundant, and also invalid when | ||
| # analysing reference expressions here because the full type context is not used. | ||
| valid_pep702_type_context=False, | ||
| ) | ||
|
|
||
| # In most cases using if_type as a context for right branch gives better inferred types. | ||
|
|
@@ -5963,17 +5993,26 @@ def analyze_cond_branch( | |
| context: Type | None, | ||
| allow_none_return: bool = False, | ||
| suppress_unreachable_errors: bool = True, | ||
| valid_pep702_type_context: bool = True, | ||
| ) -> Type: | ||
| with self.chk.binder.frame_context(can_skip=True, fall_through=0): | ||
| _valid_pep702_context = self._valid_pep702_type_context | ||
| self._valid_pep702_type_context = valid_pep702_type_context | ||
| result: Type | ||
| if map is None: | ||
| # We still need to type check node, in case we want to | ||
| # process it for isinstance checks later. Since the branch was | ||
| # determined to be unreachable, any errors should be suppressed. | ||
| with self.msg.filter_errors(filter_errors=suppress_unreachable_errors): | ||
| self.accept(node, type_context=context, allow_none_return=allow_none_return) | ||
| return UninhabitedType() | ||
| self.chk.push_type_map(map) | ||
| return self.accept(node, type_context=context, allow_none_return=allow_none_return) | ||
| result = UninhabitedType() | ||
| else: | ||
| self.chk.push_type_map(map) | ||
| result = self.accept( | ||
| node, type_context=context, allow_none_return=allow_none_return | ||
| ) | ||
| self._valid_pep702_type_context = _valid_pep702_context | ||
| return result | ||
|
|
||
| def _combined_context(self, ty: Type | None) -> Type | None: | ||
| ctx_items = [] | ||
|
|
@@ -6767,3 +6806,53 @@ def is_type_type_context(context: Type | None) -> bool: | |
| if isinstance(context, UnionType): | ||
| return any(is_type_type_context(item) for item in context.items) | ||
| return False | ||
|
|
||
|
|
||
| def constructor_type_in_callable_context( | ||
| constructor_type: CallableType | Overloaded, | ||
| context: ProperType, | ||
| options: Options, | ||
| /, | ||
| *, | ||
| _check_subtyping: bool = False, | ||
| ) -> CallableType | None: | ||
| """ | ||
| Gets a class constructor type if it's used in a valid callable type context. | ||
| Considers the following cases as valid contexts: | ||
|
|
||
| * A plain `Callable` context is always treated as a valid context. | ||
|
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 did not want to add subtype checks for this case, as this seems like the most common case and at worst the user will get both a "deprecated" report and an "incompatible types in assignment" report. |
||
| * A union type context requires at least one of the union items to be a supertype of | ||
| the class type, in addition to being a `Callable` or callable `Protocol`. | ||
| * A callable `Protocol` context is only treated as a valid context if the | ||
| constructor type is a subtype of the protocol or overloaded type. | ||
|
|
||
| If the class type is overloaded, use the first overload which is in a valid context. | ||
| """ | ||
|
|
||
| item: Type | ||
| if isinstance(constructor_type, Overloaded): | ||
| for item in constructor_type.items: | ||
| result = constructor_type_in_callable_context( | ||
| item, context, options, _check_subtyping=True | ||
| ) | ||
| if result is not None: | ||
| return result | ||
| elif isinstance(context, CallableType): | ||
| if (not _check_subtyping) or is_subtype(constructor_type, context, options=options): | ||
| return constructor_type | ||
| elif isinstance(context, UnionType): | ||
| for item in context.items: | ||
| result = constructor_type_in_callable_context( | ||
| constructor_type, get_proper_type(item), options, _check_subtyping=True | ||
| ) | ||
| if result is not None: | ||
| return result | ||
| elif isinstance(context, Instance): | ||
| if ( | ||
| context.type.is_protocol | ||
| and ("__call__" in context.type.protocol_members) | ||
| and is_subtype(constructor_type, context, options=options) | ||
| ): | ||
| return constructor_type | ||
|
|
||
| return None | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 0950c97 the same effect (avoiding false positives in the
elsebranch of a conditional expression) could have also been implemented by doing this instead of having the boolean flag memberself._valid_pep702_type_context:I chose not to do this because it would just compute the deprecation warnings, which involves subtype checking, and discard it without usage.