@@ -2838,6 +2838,9 @@ def check_metaclass_compatibility(self, typ: TypeInfo) -> None:
28382838 )
28392839
28402840 def visit_import_from (self , node : ImportFrom ) -> None :
2841+ for name , _ in node .names :
2842+ if (sym := self .globals .get (name )) is not None :
2843+ self .warn_deprecated (sym .node , node )
28412844 self .check_import (node )
28422845
28432846 def visit_import_all (self , node : ImportAll ) -> None :
@@ -2926,6 +2929,16 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
29262929
29272930 Handle all kinds of assignment statements (simple, indexed, multiple).
29282931 """
2932+
2933+ if isinstance (s .rvalue , TempNode ) and s .rvalue .no_rhs :
2934+ for lvalue in s .lvalues :
2935+ if (
2936+ isinstance (lvalue , NameExpr )
2937+ and isinstance (var := lvalue .node , Var )
2938+ and isinstance (instance := get_proper_type (var .type ), Instance )
2939+ ):
2940+ self .check_deprecated (instance .type , s )
2941+
29292942 # Avoid type checking type aliases in stubs to avoid false
29302943 # positives about modern type syntax available in stubs such
29312944 # as X | Y.
@@ -4671,6 +4684,16 @@ def visit_operator_assignment_stmt(self, s: OperatorAssignmentStmt) -> None:
46714684 if inplace :
46724685 # There is __ifoo__, treat as x = x.__ifoo__(y)
46734686 rvalue_type , method_type = self .expr_checker .check_op (method , lvalue_type , s .rvalue , s )
4687+ if isinstance (inst := get_proper_type (lvalue_type ), Instance ) and isinstance (
4688+ defn := inst .type .get_method (method ), OverloadedFuncDef
4689+ ):
4690+ for item in defn .items :
4691+ if (
4692+ isinstance (item , Decorator )
4693+ and isinstance (typ := item .func .type , CallableType )
4694+ and (bind_self (typ ) == method_type )
4695+ ):
4696+ self .warn_deprecated (item .func , s )
46744697 if not is_subtype (rvalue_type , lvalue_type ):
46754698 self .msg .incompatible_operator_assignment (s .op , s )
46764699 else :
@@ -5984,7 +6007,9 @@ def has_no_custom_eq_checks(t: Type) -> bool:
59846007 coerce_only_in_literal_context = True
59856008
59866009 expr_types = [operand_types [i ] for i in expr_indices ]
5987- should_narrow_by_identity = all (map (has_no_custom_eq_checks , expr_types ))
6010+ should_narrow_by_identity = all (
6011+ map (has_no_custom_eq_checks , expr_types )
6012+ ) and not is_ambiguous_mix_of_enums (expr_types )
59886013
59896014 if_map : TypeMap = {}
59906015 else_map : TypeMap = {}
@@ -7533,6 +7558,29 @@ def has_valid_attribute(self, typ: Type, name: str) -> bool:
75337558 def get_expression_type (self , node : Expression , type_context : Type | None = None ) -> Type :
75347559 return self .expr_checker .accept (node , type_context = type_context )
75357560
7561+ def check_deprecated (self , node : SymbolNode | None , context : Context ) -> None :
7562+ """Warn if deprecated and not directly imported with a `from` statement."""
7563+ if isinstance (node , Decorator ):
7564+ node = node .func
7565+ if isinstance (node , (FuncDef , OverloadedFuncDef , TypeInfo )) and (
7566+ node .deprecated is not None
7567+ ):
7568+ for imp in self .tree .imports :
7569+ if isinstance (imp , ImportFrom ) and any (node .name == n [0 ] for n in imp .names ):
7570+ break
7571+ else :
7572+ self .warn_deprecated (node , context )
7573+
7574+ def warn_deprecated (self , node : SymbolNode | None , context : Context ) -> None :
7575+ """Warn if deprecated."""
7576+ if isinstance (node , Decorator ):
7577+ node = node .func
7578+ if isinstance (node , (FuncDef , OverloadedFuncDef , TypeInfo )) and (
7579+ (deprecated := node .deprecated ) is not None
7580+ ):
7581+ warn = self .msg .fail if self .options .report_deprecated_as_error else self .msg .note
7582+ warn (deprecated , context , code = codes .DEPRECATED )
7583+
75367584
75377585class CollectArgTypeVarTypes (TypeTraverserVisitor ):
75387586 """Collects the non-nested argument types in a set."""
@@ -8604,3 +8652,47 @@ def visit_starred_pattern(self, p: StarredPattern) -> None:
86048652 self .lvalue = True
86058653 p .capture .accept (self )
86068654 self .lvalue = False
8655+
8656+
8657+ def is_ambiguous_mix_of_enums (types : list [Type ]) -> bool :
8658+ """Do types have IntEnum/StrEnum types that are potentially overlapping with other types?
8659+
8660+ If True, we shouldn't attempt type narrowing based on enum values, as it gets
8661+ too ambiguous.
8662+
8663+ For example, return True if there's an 'int' type together with an IntEnum literal.
8664+ However, IntEnum together with a literal of the same IntEnum type is not ambiguous.
8665+ """
8666+ # We need these things for this to be ambiguous:
8667+ # (1) an IntEnum or StrEnum type
8668+ # (2) either a different IntEnum/StrEnum type or a non-enum type ("<other>")
8669+ #
8670+ # It would be slightly more correct to calculate this separately for IntEnum and
8671+ # StrEnum related types, as an IntEnum can't be confused with a StrEnum.
8672+ return len (_ambiguous_enum_variants (types )) > 1
8673+
8674+
8675+ def _ambiguous_enum_variants (types : list [Type ]) -> set [str ]:
8676+ result = set ()
8677+ for t in types :
8678+ t = get_proper_type (t )
8679+ if isinstance (t , UnionType ):
8680+ result .update (_ambiguous_enum_variants (t .items ))
8681+ elif isinstance (t , Instance ):
8682+ if t .last_known_value :
8683+ result .update (_ambiguous_enum_variants ([t .last_known_value ]))
8684+ elif t .type .is_enum and any (
8685+ base .fullname in ("enum.IntEnum" , "enum.StrEnum" ) for base in t .type .mro
8686+ ):
8687+ result .add (t .type .fullname )
8688+ elif not t .type .is_enum :
8689+ # These might compare equal to IntEnum/StrEnum types (e.g. Decimal), so
8690+ # let's be conservative
8691+ result .add ("<other>" )
8692+ elif isinstance (t , LiteralType ):
8693+ result .update (_ambiguous_enum_variants ([t .fallback ]))
8694+ elif isinstance (t , NoneType ):
8695+ pass
8696+ else :
8697+ result .add ("<other>" )
8698+ return result
0 commit comments