Types retrieved from attributes

I would like to be able to use types occurring as (read-only, obviously) class attributes in type annotations, maybe like so:

from typing import ClassVar, TypeVar

class ASTNode:
    def f(self, x: int) -> int:
        return 2 * x

class ASTGenerator:
    ast_type: ClassVar[type[ASTNode]]

GenT = TypeVar("GenT", bound=ASTGenerator)

def generate(gen: GenT) -> GenT.ast_type:  # Currently-invalid return type
    raise NotImplementedError()

(Basedpyright playground link)

It seems that this isn’t legal right now.

  • Am I thinking about this wrong? Is there another way to accomplish an analogous thing that I’m not thinking of?
  • Is this even a sensible thing? (C++ has typename in templates, which at least suggests that there’s some precedent for this.)

The use case is in a compiler/code generator situation, where a “target” knows the type of its AST nodes, as an attribute. A “translation unit” knows its target and may return objects of AST type, but I don’t know how to type functions that take a “translation unit” input and return ASTs.

Since Python doesn’t have associated types like Rust has, I’d go with something like this:

from typing import ClassVar, Protocol

class ASTNode:
    def f(self, x: int) -> int:
        return 2 * x

class ASTGenerator:
    ast_type: ClassVar[type[ASTNode]]


class HasASTType[T: ASTNode](Protocol):
    ast_type: type[T]


def generate[T: ASTNode](gen: HasASTType[T]) -> type[T]:
    return gen.ast_type


reveal_type(generate(ASTGenerator))  # type[ASTNode]

basedpyright playground

5 Likes