Repository navigation
specialize generic subclass from inherited __init__ self annotation #2389
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,21 @@ | ||
| conformance_automated = "Pass" | ||
| conformant = "Partial" | ||
| notes = """ | ||
| Does not solve a subclass's type parameters from the self annotation of an inherited `__init__` method. | ||
| Does not report errors during binding to the self parameter of an inherited `__init__` method. | ||
| """ | ||
| conformance_automated = "Fail" | ||
| errors_diff = """ | ||
| Line 145: Expected 1 errors | ||
| Line 157: Expected 1 errors | ||
| Line 127: Unexpected errors ['./constructors_call_init.py:127:12: ./constructors_call_init.py.Class13~[Any[generic_argument], Any[generic_argument]] is not equivalent to ./constructors_call_init.py.Class13[int, str]'] | ||
| Line 134: Unexpected errors ['./constructors_call_init.py:134:12: ./constructors_call_init.py.Class14~[Any[generic_argument], Any[generic_argument], Any[generic_argument]] is not equivalent to ./constructors_call_init.py.Class14[Any[explicit], str, int]'] | ||
| """ | ||
| output = """ | ||
| ./constructors_call_init.py:21:12: Incompatible argument type for x: expected int but got Literal[1.0] [incompatible_argument] | ||
| ./constructors_call_init.py:42:7: Incompatible argument type for x: expected ./constructors_call_init.py.Class3 | None but got ./constructors_call_init.py.Class2 [incompatible_argument] | ||
| ./constructors_call_init.py:56:0: Missing required positional argument '%self' [incompatible_call] | ||
| ./constructors_call_init.py:107:17: Class-scoped type variables are not allowed in __init__ self annotation [invalid_self_usage] | ||
| ./constructors_call_init.py:130:0: Takes 0 positional arguments but 1 were given [incompatible_call] | ||
| ./constructors_call_init.py:127:12: ./constructors_call_init.py.Class13~[Any[generic_argument], Any[generic_argument]] is not equivalent to ./constructors_call_init.py.Class13[int, str] | ||
| ./constructors_call_init.py:134:12: ./constructors_call_init.py.Class14~[Any[generic_argument], Any[generic_argument], Any[generic_argument]] is not equivalent to ./constructors_call_init.py.Class14[Any[explicit], str, int] | ||
| ./constructors_call_init.py:179:0: Takes 0 positional arguments but 1 were given [incompatible_call] | ||
| """ |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,6 +108,55 @@ def __init__(self: "Class8[T2, T1]") -> None: # E | |
| pass | ||
|
|
||
|
|
||
| # > When the class being constructed is not explicitly specialized and inherits | ||
| # > an __init__ method whose self annotation names a specialization of a base | ||
| # > class, type checkers should solve the type parameters the class passes to | ||
| # > that base class from the annotation. | ||
|
|
||
| T3 = TypeVar("T3") | ||
|
|
||
|
|
||
| class Class12(Generic[T1, T2]): | ||
| def __init__(self: "Class12[int, str]") -> None: ... | ||
|
|
||
|
|
||
| class Class13(Class12[T1, T2]): | ||
| pass | ||
|
|
||
|
|
||
| assert_type(Class13(), Class13[int, str]) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we also check this through the assert_type(accepts_callable(Class13)(), Class13[int, str])Both ty and Pyrefly are good on the direct call here, but infer |
||
|
|
||
|
|
||
| class Class14(Class12[T3, T2], Generic[T1, T2, T3]): | ||
| pass | ||
|
|
||
|
|
||
| assert_type(Class14(), Class14[Any, str, int]) | ||
|
|
||
|
|
||
| # > If the class's specialization of that base class is not assignable to the | ||
| # > annotation, binding fails. | ||
|
|
||
|
|
||
| class Class15(Class12[list[T], T]): | ||
| pass | ||
|
|
||
|
|
||
| Class15() # E | ||
|
|
||
|
|
||
| class Class16(Generic[T]): | ||
| def __init__(self: "Class16[V1]", value: V1) -> None: ... | ||
|
|
||
|
|
||
| class Class17(Class16[str]): | ||
| pass | ||
|
|
||
|
|
||
| Class17("") # OK | ||
| Class17(1) # E | ||
|
|
||
|
|
||
| # > If a class does not define a __new__ method or __init__ method and does | ||
| # > not inherit either of these methods from a base class other than object, | ||
| # > a type checker should evaluate the argument list using the __new__ and | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -273,6 +273,29 @@ within a type annotation for the ``self`` parameter in an ``__init__`` method. | |
| # 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| ``__init__`` method whose ``self`` annotation names a specialization of a base | ||
| class, type checkers should solve the type parameters the class passes to that | ||
| base class from the annotation. If the class's specialization of that base | ||
| class is not assignable to the annotation, binding fails. | ||
|
|
||
| :: | ||
|
|
||
| class MyClass5[T1, T2]: | ||
| def __init__(self: "MyClass5[int, str]") -> None: ... | ||
|
|
||
| class MyClass6[T1, T2](MyClass5[T1, T2]): ... | ||
|
|
||
| assert_type(MyClass6(), MyClass6[int, str]) | ||
|
|
||
| class MyClass7[T1, T2, T3](MyClass5[T3, T2]): ... | ||
|
|
||
| assert_type(MyClass7(), MyClass7[Any, str, int]) | ||
|
|
||
| class MyClass8[T](MyClass5[list[T], T]): ... | ||
|
|
||
| MyClass8() # Type error | ||
|
|
||
|
|
||
| Classes Without ``__new__`` and ``__init__`` Methods | ||
| ---------------------------------------------------- | ||
|
|
@@ -284,11 +307,11 @@ does not inherit either of these methods from a base class other than | |
|
|
||
| :: | ||
|
|
||
| class MyClass5: | ||
| class MyClass9: | ||
| pass | ||
|
|
||
| MyClass5() # OK | ||
| MyClass5(1) # Type error | ||
| MyClass9() # OK | ||
| MyClass9(1) # Type error | ||
|
|
||
|
|
||
| Constructor Calls for type[T] | ||
|
|
||
There was a problem hiding this comment.
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 anull=Falseoverload that leaves the defaults in place and anull=Trueoverload whose annotatedselfmakes the field nullable. Checking both results would cover overload selection and show that only the selected overload contributes its receiver constraints.