@@ -136,11 +136,9 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type:
136136 narrowed_items = narrowed .relevant_items ()
137137 else :
138138 narrowed_items = [narrowed ]
139- return make_simplified_union (
140- [
141- narrow_declared_type (d , n )
142- for d in declared_items
143- for n in narrowed_items
139+ results = []
140+ for d in declared_items :
141+ for n in narrowed_items :
144142 # This (ugly) special-casing is needed to support checking
145143 # branches like this:
146144 # x: Union[float, complex]
@@ -150,12 +148,31 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type:
150148 # x: float | None
151149 # y: int | None
152150 # x = y
153- if (
151+ if not (
154152 is_overlapping_types (d , n , ignore_promotions = True )
155153 or is_subtype (n , d , ignore_promotions = False )
156- )
157- ]
158- )
154+ ):
155+ continue
156+ result = narrow_declared_type (d , n )
157+ # If narrowing a union member d to n returned d unchanged,
158+ # but d is not nominally related to n (only structurally,
159+ # e.g. via __getattr__ returning Any), exclude it.
160+ # Otherwise a class with __getattr__ -> Any leaks back into
161+ # a union narrowed to a protocol it only structurally satisfies.
162+ # See https://github.com/python/mypy/issues/16590
163+ d_proper = get_proper_type (d )
164+ n_proper = get_proper_type (n )
165+ if (
166+ result == d
167+ and d != n
168+ and isinstance (d_proper , Instance )
169+ and isinstance (n_proper , Instance )
170+ and n_proper .type .is_protocol
171+ and not d_proper .type .has_base (n_proper .type .fullname )
172+ ):
173+ continue
174+ results .append (result )
175+ return make_simplified_union (results )
159176 if is_enum_overlapping_union (declared , narrowed ):
160177 # Quick check before reaching `is_overlapping_types`. If it's enum/literal overlap,
161178 # avoid full expansion and make it faster.
0 commit comments