On Fri, Jun 24, 2022 at 12:56 AM Eric Traut <eric@traut.com> wrote:
Yeah, I explored the idea of name mangling, but I couldn't figure out how to make this work with `eval`, which is needed for `typing.get_type_hints()`.

def foo[T](x: "T") -> "list[T]":

get_type_hints(foo) # This would crash because T isn't available.

But do we need to support that? Assuming the SC chooses PEP 649 over 563 for 3.12, there would be no need to use string quotes for forward declarations, (OTOH, if 563 is selected, there would be strings everywhere and we would need to handle this.)
I'm thinking that we need to add a dict of all active type parameters and include this dict as an attribute (e.g. `__type_params__`) for a generic class, function, or type alias. Then `get_type_hints` could access `__type_params__` and merge the type parameter symbols into the locals dictionary before calling `eval`. This way, when it evaluates `list[T]`, it will be able to resolve the name `T` correctly.

class Foo[T]:
    def bar[S](self, a: "T", b: "S") -> "S":

print(Foo.__type_params__) # { "T": ~T }
print(Foo.bar.__type_params__) # { "T": ~T, "S": ~S }
print(get_type_hints(Foo.bar))  # { "a": ~T, "b": ~S, "return": ~S }

What do you think of that?

It might work, but it doesn't feel elegant.

Then again, I worry that we've over-constrained the problem and hence no solution will feel quite right.
I may have also come up with a way to revive the idea of using an extra scope. If the extra scope is introduced within a class body (the problematic case I discussed above), any symbols that are local to the class body but required by the inner scope could be passed in as explicit arguments. They don't need to be cell variables if we assume they are read-only within the inner scope.

class Parent:
    class Child1[T]: ...

    class Child2[T](Child1[T]): ...

Would effectively be translated to:

class Parent:
    def __temp1(T):
        class Child1: ...
        return Child1
    Child1 = __temp1(TypeVar("T"))

    def __temp2(T, Child1):
        class Child2(Child1): ...
        return Child2
    Child2 = __temp2(TypeVar("T"), Child1) # Child1 is passed as an explicit argument


That doesn't look bad, better than mangling the names. We'd basically have to analyze all type parameters *and all default values* looking for variables defined at the class level, and do this to them. It feels related to lambda lifting (https://en.wikipedia.org/wiki/Lambda_lifting).

--Guido van Rossum (python.org/~guido)