Skip to content

specialize generic subclass from inherited __init__ self annotation - #2389

Open
crypt0lith wants to merge 1 commit into
python:mainfrom
crypt0lith:constructors-inherited-init-self-annotation
Open

crypt0lith wants to merge 1 commit into
python:mainfrom
crypt0lith:constructors-inherited-init-self-annotation

Conversation

@srittau srittau added Typing Council decision Needs to be approved by the Typing Council. Do not merge until approved. topic: typing spec For improving the typing spec labels Nov 14, 2026
# The ``self`` annotation should result in a type error
def __init__(self: "MyClass4[T2, T1]") -> None: ...

When the class being constructed is not explicitly specialized and inherits an

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should say explicitly that this solves only type arguments still undetermined after evaluating __new__. For example, suppose an invariant Base[T] defines __init__(self: Base[int]), and Child[T](Base[T]) defines __new__ to return Child[str]. The call Child() has no brackets, but its receiver is already specialized as Child[str] when we bind the inherited initializer. I’d expect the same binding error as for Child[str]().

pass


assert_type(Class13(), Class13[int, str])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we also check this through the accepts_callable helper in constructors_callable.py?

assert_type(accepts_callable(Class13)(), Class13[int, str])

Both ty and Pyrefly are good on the direct call here, but infer Class12[int, str] after callable conversion. A caller passing a subclass as a factory should still get its subclass-specific methods on the result. It would help to say explicitly in the spec that the synthesized callable returns the specialized subclass.



class Class12(Generic[T1, T2]):
def __init__(self: "Class12[int, str]") -> None: ...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a pair of calls that select different inherited __init__ overloads on the same subclass? The motivating Django field example has a null=False overload that leaves the defaults in place and a null=True overload whose annotated self makes the field nullable. Checking both results would cover overload selection and show that only the selected overload contributes its receiver constraints.

@carljm

carljm commented Nov 14, 2026

Copy link
Copy Markdown
Member

Thanks for the proposal and PR! I think this makes sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

topic: typing spec For improving the typing spec Typing Council decision Needs to be approved by the Typing Council. Do not merge until approved.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants