# Constraining generic argument types

**URL:** <https://discuss.python.org/t/constraining-generic-argument-types/56809>\
**Category:** Typing\
**Tags:** typing\
**Created:** [June 27, 2024, 8:42am UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809 "2024-06-27T08:42:20Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![srittau](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/srittau/32/237_2.png) [@srittau](https://discuss.python.org/u/srittau)\
**Post date:** [June 27, 2024, 8:42am UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/1 "2024-06-27T08:42:20Z")

</div>

Please consider the following code:

```python
from typing import Callable, TypeVar

_T = TypeVar("_T")

def simple(x: _T, y: _T) -> _T:
    return x

def complex(x: Callable[[str], _T], y: _T) -> _T:
    return y

reveal_type(simple(123, ""))
reveal_type(complex(int, ""))

```

Running pyright on it returns a type of `int | str` for both functions, generating a union of the actual argument types. mypy on the other hand is inconsistent here. In the `simple` case it infers a return type of `object` (which is mypy’s version of combining the types from both arguments), while it errors out in the `complex` case:

```python
Argument 1 to "complex" has incompatible type "type[int]"; expected "Callable[[str], str]"

```

What is the correct behavior here? It doesn’t seem to be [spec’ed](https://typing.readthedocs.io/en/latest/spec/generics.html). I naïvely assumed that mypy’s handling of the `complex` case is the correct one: The types of all supplied arguments must match. Otherwise it’s hard to enforce matching generic argument types. (As in the case of the original issue where this problem arose: [https://github.com/python/typeshed/pull/12211](https://github.com/python/typeshed/pull/12211).) mypy’s behavior in the `complex` case would agree. But both pyright’s behavior as well as mypy’s behavior in the `simple` case indicate otherwise.

---

<div class="post-metadata">

**Author:** ![mikeshardmind](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/mikeshardmind/32/14381_2.png) [@mikeshardmind](https://discuss.python.org/u/mikeshardmind)\
**Post date:** [June 27, 2024, 9:42am UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/2 "2024-06-27T09:42:36Z")

</div>

I like what Pyright does here and would lean towards codifying something like it.

* * *

I also did a quick check of what other type checkers are doing here:

pyre also [agrees with pyright for this behavior](https://pyre-check.org/play?input=from%20typing%20import%20Callable%2C%20TypeVar%0A%0A_T%20%3D%20TypeVar(%22_T%22)%0A%0Adef%20simple(x%3A%20_T%2C%20y%3A%20_T)%20-%3E%20_T%3A%0A%20%20%20%20return%20x%0A%0Adef%20complex(x%3A%20Callable%5B%5Bstr%5D%2C%20_T%5D%2C%20y%3A%20_T)%20-%3E%20_T%3A%0A%20%20%20%20return%20y%0A%0Areveal_type(simple(123%2C%20%22%22))%0Areveal_type(complex(int%2C%20%22%22))%0A)

pytype determines `Any` for the simple case, which while that avoids false positives, also significantly increases the potential for false negatives. (whereas mypy determining `object`, and pyre and pyright choosing the union is less likely to have false positives or false negatives since the union should need disambiguation by users to safely use the parts that don’t come from object)

(I’m unaware of a pytype playground to link to, I took your example and added an import from typing extensions for reveal\_type, not other modifications)

it similarly errors for the complex case, but continues to consider that as having type of `Any`.

```python
File "/home/michael/test.py", line 12, in <module>: Any [reveal-type]
File "/home/michael/test.py", line 13, in <module>: Function complex was called with the wrong arguments [wrong-arg-types]
         Expected: (x, y: int)
  Actually passed: (x, y: str)
File "/home/michael/test.py", line 13, in <module>: Any [reveal-type]

```

---

<div class="post-metadata">

**Author:** ![JamesParrott](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jamesparrott/32/10534_2.png) [@JamesParrott](https://discuss.python.org/u/JamesParrott)\
**Post date:** [June 27, 2024, 9:43am UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/3 "2024-06-27T09:43:18Z")

</div>

The built-in `complex` is being shadowed. Might that confuse type checkers?

If the intent really is to describe not just two generic functions, but a generic pair of functions, liked by a single generic type, I would put them on a class (a generic class) or a namespace.

Otherwise, I don’t know if this will fix it, but I would’ve thought it’s better to remove the coupling, and define two type variables, one for each.

---

<div class="post-metadata">

**Author:** ![srittau](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/srittau/32/237_2.png) [@srittau](https://discuss.python.org/u/srittau)\
**Post date:** [June 27, 2024, 9:50am UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/4 "2024-06-27T09:50:56Z")

</div>

> [@mikeshardmind](#):
>
> I like what Pyright does here and would lean towards codifying something like it.

The problem with that behavior is that it prevents us from enforcing that two arguments have the same, generic type. For example, this is the current annotation for `shutil.copyfileobj`:

```python
def copyfileobj(fsrc: SupportsRead[AnyStr], fdst: SupportsWrite[AnyStr], length: int = 0) -> None: ...

```

This is meant to enforce that both arguments are either text or binary streams. Mixing them is not possible. Annotations like this are fairly common.

On the other hand, I’m not sure what we gain from the “merging” behavior.

---

<div class="post-metadata">

**Author:** ![mikeshardmind](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/mikeshardmind/32/14381_2.png) [@mikeshardmind](https://discuss.python.org/u/mikeshardmind)\
**Post date:** [June 27, 2024, 10:03am UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/5 "2024-06-27T10:03:08Z")

</div>

> [@srittau](#):
>
> The problem with that behavior is that it prevents us from enforcing that two arguments have the same, generic type. For example, this is the current annotation for `shutil.copyfileobj`:

I don’t particularly like this example within the current specification, but I understand the need for it. This seems like trying to use the way generics work in classes (1 type to many uses relationship) but there’s no way to really spell this out right now for functions (subscriptable functions should cause this to have the behavior you want here) and I think it’s generally better to just leave things untyped and use that as a motivation to improve available typing constructs when we have cases like this.

> [@srittau](#):
>
> On the other hand, I’m not sure what we gain from the “merging” behavior.

Better overall inference in my experience. This was a dealbreaker for me at one point on using mypy, while I don’t have a good example that I am permitted to share, this difference in behavior caused pyright to handle an async scheduling dispatch mechanism significantly better than mypy.

---

<div class="post-metadata">

**Author:** ![NeilGirdhar](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/neilgirdhar/32/5776_2.png) [@NeilGirdhar](https://discuss.python.org/u/NeilGirdhar)\
**Post date:** [June 27, 2024, 10:07am UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/6 "2024-06-27T10:07:48Z")

</div>

> [@srittau](#):
>
> On the other hand, I’m not sure what we gain from the “merging” behavior.

Without the merging behavior code that passes type-checking could produce type errors if assertions (that pass at runtime) were added. This would be extremely perplexing because it would mean you could make a function call pass type checking by broadening its input arguments!

For example:

```python
from typing import TypeVar, reveal_type

_T = TypeVar("_T")

def f(x: _T, y: _T) -> _T:
    return x

a: int | str = 1
b: int | str = ""
reveal_type(f(a, b))
assert(isinstance(a, int))
assert(isinstance(b, str))
# Do various things with a and b that require these types.
reveal_type(f(a, b)) # Would be broken without merging.

```

---

<div class="post-metadata">

**Author:** ![mikeshardmind](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/mikeshardmind/32/14381_2.png) [@mikeshardmind](https://discuss.python.org/u/mikeshardmind)\
**Post date:** [June 27, 2024, 10:16am UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/7 "2024-06-27T10:16:36Z")

</div>

To me, there’s a “direction of code-flow” based answer here

```python
def foo(x: T, y: T) -> T:
    ...

```

is a many-to-1 relationship.

```py
class foo(Protocol[T]):
    def __call__ (self, x: T, y: T):
        ...

```

is a 1-to-many relationship

```python
def foo(x: T, y: T) -> None:

```

is ambiguous here, there’s no other point in code flow to have a relationship, so there’s not a way to say whether these have the same type by virtue of just picking a type they share or have a different type because at runtime they are slightly different.

---

<div class="post-metadata">

**Author:** ![MegaIng](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/megaing/32/16162_2.png) [@MegaIng](https://discuss.python.org/u/MegaIng)\
**Post date:** [June 27, 2024, 10:32am UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/8 "2024-06-27T10:32:39Z")

</div>

> [@srittau](#):
>
> The problem with that behavior is that it prevents us from enforcing that two arguments have the same, generic type. For example, this is the current annotation for `shutil.copyfileobj`:
> 
> ```python
> def copyfileobj(fsrc: SupportsRead[AnyStr], fdst: SupportsWrite[AnyStr], length: int = 0) -> None: ...
> 
> ```
> 
> This is meant to enforce that both arguments are either text or binary streams. Mixing them is not possible. Annotations like this are fairly common.

Surely no compliant type checker can ever assign `str | bytes` (or `Any` for that matter) to `AnyStr`? Since it’s specifically defined as `TypeVar('AnyStr', str, bytes)`, i.e. a constraint type variable.

---

<div class="post-metadata">

**Author:** ![srittau](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/srittau/32/237_2.png) [@srittau](https://discuss.python.org/u/srittau)\
**Post date:** [June 27, 2024, 10:37am UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/9 "2024-06-27T10:37:49Z")

</div>

The problem with the `copyfileobj` example is that a type checker doesn’t catch an error when using merging behavior:

```python
from _typeshed import SupportsRead, SupportsWrite
from shutil import copyfileobj

x: SupportsRead[str]
y: SupportsWrite[bytes]
copyfileobj(x, y) # mypy: Cannot infer type argument 1 of "copyfileobj"

```

pyright currently accepts this, mypy doesn’t.

Of course, in the specific case of `AnyStr`, this is fairly easily worked around using overloads, but overloads won’t work in other cases. For example the fairly common case where one argument is passed into another argument, which is a callable.

---

<div class="post-metadata">

**Author:** ![mikeshardmind](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/mikeshardmind/32/14381_2.png) [@mikeshardmind](https://discuss.python.org/u/mikeshardmind)\
**Post date:** [June 27, 2024, 10:42am UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/10 "2024-06-27T10:42:29Z")

</div>

So pyre has the same beahvior as pyright for your original unconstrained case (complex) but actually detects issues with that matching [constraints in type variables](https://pyre-check.org/play?input=%23%20Pyre%20is%20being%20run%20in%20gradual%20typing%20mode%3A%20https%3A%2F%2Fpyre-check.org%2Fdocs%2Ftypes-in-python%2F%23gradual-typing%0A%23%20Use%20the%20%60%23%20pyre-strict%60%20header%20to%20run%20in%20strict%20mode%2C%20which%20requires%20annotations.%0A%0Afrom%20typing%20import%20*%0A%0AT%20%3D%20TypeVar(%22T%22%2C%20str%2C%20bytes)%0A%0Adef%20test(x%3A%20T%2C%20y%3A%20T)%3A%0A%20%20%20%20pass%0A%0A%0Atest(%22this%22%2C%20b%22that%22)%0A) still. It detects this isn’t possible to make a Union out of. Of all of the behaviors observed for this, I think Pyre would be the clear one to model in specification.

Edit: It looks like pyright is getting this right too, I had assumed from context that it wasn’t.

---

<div class="post-metadata">

**Author:** ![erictraut](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/erictraut/32/15190_2.png) [@erictraut](https://discuss.python.org/u/erictraut)\
**Post date:** [June 27, 2024, 11:11am UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/11 "2024-06-27T11:11:08Z")

</div>

At a high level, the job of a constraint solver is to collect all of the constraints on a type variable and determine whether there is _some_ type that satisfies all of those constraints. If such a type exists but a constraint solver fails to find it, that’s arguably a bug in the constraint solver. From that perspective, this simply looks like a bug in mypy.

Constraint solving can be very costly, so implementations take certain shortcuts and make certain assumptions. These can fail to find certain solutions. I suspect that’s what’s happening in this case for mypy.

Constraint solving behaviors are not discussed in the typing spec currently, and I think it will be a long time (if ever) before they are. Spec’ing this behavior would be very difficult as it involves many heuristics, edge cases, behaviors that differ between type checkers, and limitations dictated by internal implementation choices. It also relies on other concepts (like value-constrained TypeVars) that are not yet well spec’ed. If you want to pursue standardization in this space, it will take _significant_ time and effort.

---

<div class="post-metadata">

**Author:** ![srittau](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/srittau/32/237_2.png) [@srittau](https://discuss.python.org/u/srittau)\
**Post date:** [June 27, 2024, 11:22am UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/12 "2024-06-27T11:22:23Z")

</div>

> [@erictraut](#):
>
> If you want to pursue standardization in this space, it will take _significant_ time and effort.

While a full specification of the type resolver might be a complicated task, deciding the correct behavior in this user visible case of generic function signatures would be important – even without a full specification. Currently, quite a few annotations in typeshed work on the assumption that the “matching” behavior (partly implemented by mypy for complex cases) as opposed to the “merging” behavior (as implemented by pyright and by mypy for simple cases) is the correct one.

If we decide that the merging behavior is the correct one, we’d need to rethink those annotations, as they currently don’t work correctly with pyright (and possibly mypy). On the other hand, it would also important to know if the matching behavior is correct, so we can continue to use it.

---

<div class="post-metadata">

**Author:** ![NeilGirdhar](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/neilgirdhar/32/5776_2.png) [@NeilGirdhar](https://discuss.python.org/u/NeilGirdhar)\
**Post date:** [June 27, 2024, 11:31am UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/13 "2024-06-27T11:31:26Z")

</div>

> [@srittau](#):
>
> on. Currently, quite a few annotations in typeshed work on the assumption that the “matching” behavior (partly implemented by mypy for complex cases) as opposed to the

Could you provide an example? It doesn’t seem to me that linked one is problematic.

Just as food for thought, notice how PyRight does the right think with this:

```python
def g(x: Callable[[], _T], y: MutableSequence[_T]) -> None:
    y.append(x())

s: list[str] = []
g(int, s) # Error, as expected.

```

So, Pyright is ensuring that when no `_T` works, there is an error. And it is ensuring (for the cases we’ve seen so far) that when some `_T` works , there is no error.

---

<div class="post-metadata">

**Author:** ![srittau](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/srittau/32/237_2.png) [@srittau](https://discuss.python.org/u/srittau)\
**Post date:** [June 27, 2024, 11:33am UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/14 "2024-06-27T11:33:58Z")

</div>

> [@NeilGirdhar](#):
>
> Could you provide an example? It doesn’t seem to me that linked one is problematic.

See the linked PR in the original post, where the tests currently fail for pyright, but not mypy, and the `copyfileobj()` example.

---

<div class="post-metadata">

**Author:** ![NeilGirdhar](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/neilgirdhar/32/5776_2.png) [@NeilGirdhar](https://discuss.python.org/u/NeilGirdhar)\
**Post date:** [June 27, 2024, 11:51am UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/15 "2024-06-27T11:51:23Z")

</div>

> See the linked PR in the original post,

Did you mean this?

```python
    @overload
    def add_argument(
        self,
        *name_or_flags: str,
        action: _ActionStr | type[Action] = ...,
        nargs: int | _NArgsStr | _SUPPRESS_T | None = None,
        const: Any = ...,
        default: Any = ...,
        type: Callable[[str], _T],
        choices: Iterable[_T] | None = ...,

```

Can you elaborate on why you think this should be an error?

> the `copyfileobj()` example

```python
a: SupportsRead[bytes] = BinaryIO()
b: SupportsWrite[str] = TextIO()

copyfileobj(a, b)

```

gives “Incompatible arguments” in PyRight and MyPy. Did you mean something else?

---

<div class="post-metadata">

**Author:** ![srittau](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/srittau/32/237_2.png) [@srittau](https://discuss.python.org/u/srittau)\
**Post date:** [June 27, 2024, 11:58am UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/16 "2024-06-27T11:58:00Z")

</div>

Sorry, I don’t see the value in discussing the specifics of individual examples, especially as I’ve already done so above. In both cases, mypy and pyright come to a different conclusion. That’s the point of this discussion.

---

<div class="post-metadata">

**Author:** ![NeilGirdhar](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/neilgirdhar/32/5776_2.png) [@NeilGirdhar](https://discuss.python.org/u/NeilGirdhar)\
**Post date:** [June 27, 2024, 12:04pm UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/17 "2024-06-27T12:04:16Z")

</div>

Sorry, I think I’m confused about what your post is about then. You asked “what is the correct behavior [for the given example]?”. And you also said that “quite a few annotations work based on the matching behavior”. Then don’t you think we should examine the examples to see what the behavior should be?

It seems to me that Pyright is doing the right thing in both of the examples you gave. If you think MyPy’s behavior is right, I’m curious to know why. It seems to me, just based on what you’ve linked, that these are simply bugs in MyPy coupled with misunderstandings?

> I’ve already done so above.

Just looking at the `copyfileobj` example again, PyRight is giving me an error:

```python
def f(a: SupportsRead[bytes], b: SupportsWrite[str]):
    copyfileobj(a, b) # Error

```

---

<div class="post-metadata">

**Author:** ![srittau](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/srittau/32/237_2.png) [@srittau](https://discuss.python.org/u/srittau)\
**Post date:** [June 27, 2024, 1:10pm UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/18 "2024-06-27T13:10:54Z")

</div>

Sorry Neil, I misread what you were responding to. I was under the impression we were discussing whether it makes sense to find the correct behavior, not which behavior is correct.

But your modified `copyfileobj()` behavior is interesting. It seems that pyright (and mypy) handle constrained type vars differently using the match semantics:

```python
from _typeshed import SupportsRead, SupportsWrite
from typing import TypeVar

_V = TypeVar("_V", bound=str | bytes)
_T = TypeVar("_T", str, bytes)
_U = TypeVar("_U")

def bound(r: _V, w: _V) -> None:
    pass

def constrained(r: _T, w: _T) -> None:
    pass

def unbound(r: _U, w: _U) -> None:
    pass

def call_bound(x: str, y: bytes) -> None:
    bound(x, y)

def call_constrained(x: str, y: bytes) -> None:
    constrained(x, y) # error with both mypy and pyright

def call_unbound(x: str, y: bytes) -> None:
    unbound(x, y)

```

This would make the `AnyStr` cases (fortunately) unproblematic.

While looking for another example, I came across an interesting difference:

```python
from typing import Callable, TypeVar

_T = TypeVar("_T")

def arg(it: _T, cb: Callable[[_T], int]) -> _T: ...
def ret(it: _T, cb: Callable[[str], _T]) -> _T: ...

def int_to_int(x: int) -> int:
    return x + 1

def str_to_int(x: str) -> int:
    return 42

reveal_type(arg(1, int_to_int)) # int
reveal_type(arg(1, str_to_int)) # error
reveal_type(ret(1, int)) # int  
reveal_type(ret(1, str)) # pyright int | str, mypy error

```

So it seems that pyright does indeed sometimes use “matching” behavior. In this case if the type var is used as an input type to the callable.

All of this is quite confusing.

---

<div class="post-metadata">

**Author:** ![NeilGirdhar](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/neilgirdhar/32/5776_2.png) [@NeilGirdhar](https://discuss.python.org/u/NeilGirdhar)\
**Post date:** [June 27, 2024, 1:41pm UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/19 "2024-06-27T13:41:54Z")

</div>

> [@srittau](#):
>
> So it seems that pyright does indeed sometimes use “matching” behavior. In this case if the type var is used as an input type to the callable.
> 
> All of this is quite confusing.

Thank you for taking the time to analyze the problem 😄

I think this has to do with covariance and contravariance. Consider what Eric said about type checking being constraint solving. The constraint in this case:

```python
reveal_type(arg(1, str_to_int)) # error

```

is to find a type form `_T` such that `int < _T` and `Callable[[str], int] < Callable[[_T], int]`. `int` doesn’t work, and neither does `str | int`. This is because if a function needs a `Callabe[[str | int], int]`, then a `Callable[[str], int]` isn’t good enough.

Whereas here:

```python
reveal_type(ret(1, str)) # pyright int | str, mypy error

```

The analogous constraint can be solved since if the function needs `Callable[[int], int | str]`, then passing `Callable[[int], str]` is fine.

---

<div class="post-metadata">

**Author:** ![erictraut](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/erictraut/32/15190_2.png) [@erictraut](https://discuss.python.org/u/erictraut)\
**Post date:** [June 27, 2024, 2:20pm UTC](https://discuss.python.org/t/constraining-generic-argument-types/56809/20 "2024-06-27T14:20:57Z")

</div>

As I mentioned above, the job of a constraint solver is to determine all of the constraints on a type variable for a given function call. Based on these constraints, it then attempts to find some type (preferably, an “optimal” type by some definition of the word “optimal”) that satisfies all of the constraints.

These constraints come from various sources:

1. Upper bounds on the type variable, in the case of normal TypeVars
2. Value constraints, in the case of value-constrained TypeVars
3. Variance rules
4. (Bidirectional) inference context
5. Explicit specialization, in the case of constructor calls
6. Arguments passed to the call

Let’s examine a few of your examples in detail. This might help clarify the behaviors in your mind.

* * *

**Example 1: `constrained(x, y)`**

Here we are using a value-constrained TypeVar, and the solution must match one of the specified value constraints exactly. That means we start with the constraint that the solution to `_T` must be either `str` or `int`.

The first argument `x` imposes the constraint `_T = str`. The second argument imposes the constraint `_T = int`. There is no possible type that satisfies these constraints, so this is an error.

* * *

**Example 2: `arg(1, str_to_int)`**

In this case, there is no upper bound, value constraint, inference context, or explicit specialization. All of the constraints come from arguments and variance rules.

The first argument `1` is of type `int`, and it imposes the constraint `_T ≥ int`. This is a covariant context.

The second argument `str_to_int` is of type `(x: str) -> int`, and it imposes the constraint `_T ≤ str`. This is a contravariant context because it’s an input parameter for a Callable. That explains why the constraint uses `≤` rather than `≥`.

There is no type that can possibly satisfy both of these constraints, hence the error.

* * *

**Example 3: `ret(1, str)`**

Once again, all of the constraints come from arguments and variance rules.

The first argument `1` once again imposes the constraint `_T ≥ int`. This is a covariant context.

The second argument `str` is of type `type[str]` and can be converted to a callable type because it’s a class object with a constructor. The converted type is effectively `(object) -> str`. This imposes the constraint `_T ≥ str`. This is a covariant context because it’s a return type for a Callable.

These two constraints allow for solutions `str | int` or `object`.

* * *

Note: I’ve left out a bunch of details above such as handling of literal types and handling of overloaded constructors when converting to a callable, but I wanted to keep the examples relatively simple.

[Next page](https://discuss.python.org/t/constraining-generic-argument-types/56809.md?page=2)
