Repository: python/mypy

Issue:
generics with a union bound aren't type checked
```py
from typing import Generic, TypeVar, Union


class Foo:
    ...


class Bar:
    ...


T = TypeVar("T", bound=Union[Foo, Bar])


class Baz(Generic[T]):
    def __init__(self, value: T):
        value.foo()  # no error
```
https://mypy-play.net/?mypy=latest&python=3.10&flags=show-column-numbers%2Cshow-error-codes%2Cstrict%2Ccheck-untyped-defs%2Cdisallow-any-decorated%2Cdisallow-any-expr%2Cdisallow-any-explicit%2Cdisallow-any-generics%2Cdisallow-any-unimported%2Cdisallow-incomplete-defs%2Cdisallow-subclassing-any%2Cdisallow-untyped-calls%2Cdisallow-untyped-decorators%2Cdisallow-untyped-defs%2Cwarn-incomplete-stub%2Cwarn-redundant-casts%2Cwarn-return-any%2Cwarn-unreachable%2Cwarn-unused-configs%2Cwarn-unused-ignores&gist=37430e4ccdd3cc15aa76d318000f217b
generics with a union bound aren't type checked
```py
from typing import Generic, TypeVar, Union


class Foo:
    ...


class Bar:
    ...


T = TypeVar("T", bound=Union[Foo, Bar])


class Baz(Generic[T]):
    def __init__(self, value: T):
        value.foo()  # no error
```
https://mypy-play.net/?mypy=latest&python=3.10&flags=show-column-numbers%2Cshow-error-codes%2Cstrict%2Ccheck-untyped-defs%2Cdisallow-any-decorated%2Cdisallow-any-expr%2Cdisallow-any-explicit%2Cdisallow-any-generics%2Cdisallow-any-unimported%2Cdisallow-incomplete-defs%2Cdisallow-subclassing-any%2Cdisallow-untyped-calls%2Cdisallow-untyped-decorators%2Cdisallow-untyped-defs%2Cwarn-incomplete-stub%2Cwarn-redundant-casts%2Cwarn-return-any%2Cwarn-unreachable%2Cwarn-unused-configs%2Cwarn-unused-ignores&gist=37430e4ccdd3cc15aa76d318000f217b


Discussion:
```py
from typing import TypeVar, Union


T = TypeVar("T", bound=Union[int, str])

def foo(t: T) -> None:
    t.foo()  # no error

foo(1)
foo("1")
foo(True)  # no error
```
😬
`foo(True)` shouldn't generate an error because `bool` is a subtype of `int`, so it matches the bound.

I think the other cases (`value.foo` in the first example and `t.foo` in the second example) should generate errors. Pyright generates errors in these cases.
```py
from typing import TypeVar, Union


T = TypeVar("T", bound=Union[int, str])

def foo(t: T) -> None:
    t.foo()  # no error

foo(1)
foo("1")
foo(True)  # no error
```
😬
`foo(True)` shouldn't generate an error because `bool` is a subtype of `int`, so it matches the bound.

I think the other cases (`value.foo` in the first example and `t.foo` in the second example) should generate errors. Pyright generates errors in these cases.

Resolving patch:
diff --git a/mypy/messages.py b/mypy/messages.py
--- a/mypy/messages.py
+++ b/mypy/messages.py
@@ -345,6 +345,16 @@ def has_no_attr(self,
                 self.fail('Item {} of {} has no attribute "{}"{}'.format(
                     typ_format, orig_type_format, member, extra), context,
                     code=codes.UNION_ATTR)
+            elif isinstance(original_type, TypeVarType):
+                bound = get_proper_type(original_type.upper_bound)
+                if isinstance(bound, UnionType):
+                    typ_fmt, bound_fmt = format_type_distinctly(typ, bound)
+                    original_type_fmt = format_type(original_type)
+                    self.fail(
+                        'Item {} of the upper bound {} of type variable {} has no '
+                        'attribute "{}"{}'.format(
+                            typ_fmt, bound_fmt, original_type_fmt, member, extra),
+                        context, code=codes.UNION_ATTR)
         return AnyType(TypeOfAny.from_error)
 
     def unsupported_operand_types(self,


Test patch:
diff --git a/test-data/unit/check-generic-subtyping.test b/test-data/unit/check-generic-subtyping.test
--- a/test-data/unit/check-generic-subtyping.test
+++ b/test-data/unit/check-generic-subtyping.test
@@ -817,4 +817,28 @@ class Y(Generic[T]):
     def f(self) -> T:
         return U()  # E: Incompatible return value type (got "U", expected "T")
 
+
+[case testTypeVarBoundToUnionAttributeAccess]
+from typing import Union, TypeVar
+
+class U:
+    a: float
+class V:
+    b: float
+class W:
+    c: float
+
+T = TypeVar("T", bound=Union[U, V, W])
+
+def f(x: T) -> None:
+    x.a  # E
+    x.b = 1.0  # E
+    del x.c  # E
+
 [out]
+main:13: error: Item "V" of the upper bound "Union[U, V, W]" of type variable "T" has no attribute "a"
+main:13: error: Item "W" of the upper bound "Union[U, V, W]" of type variable "T" has no attribute "a"
+main:14: error: Item "U" of the upper bound "Union[U, V, W]" of type variable "T" has no attribute "b"
+main:14: error: Item "W" of the upper bound "Union[U, V, W]" of type variable "T" has no attribute "b"
+main:15: error: Item "U" of the upper bound "Union[U, V, W]" of type variable "T" has no attribute "c"
+main:15: error: Item "V" of the upper bound "Union[U, V, W]" of type variable "T" has no attribute "c"