Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion conformance/results/mypy/constructors_call_init.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@ conformant = "Partial"
notes = """
Does not report errors during binding to self parameter of `__init__` method.
Does not reject use of class-scoped type variables in annotation of self parameter in `__init__` method.
Does not solve a subclass's type parameters from the self annotation of an inherited `__init__` method.
"""
conformance_automated = "Fail"
errors_diff = """
Line 56: Expected 1 errors
Line 107: Expected 1 errors
Line 145: Expected 1 errors
Line 157: Expected 1 errors
Line 73: Unexpected errors ['constructors_call_init.py:73: error: Expression is of type "Class5[list[int]]", not "Class5[int]" [assert-type]']
Line 127: Unexpected errors ['constructors_call_init.py:127: error: Expression is of type "Class13[Never, Never]", not "Class13[int, str]" [assert-type]']
Line 134: Unexpected errors ['constructors_call_init.py:134: error: Expression is of type "Class14[Never, Never, Never]", not "Class14[Any, str, int]" [assert-type]']
"""
output = """
constructors_call_init.py:21: error: Argument 1 to "Class1" has incompatible type "float"; expected "int" [arg-type]
constructors_call_init.py:42: error: Argument 1 to "Class3" has incompatible type "Class2[Never]"; expected "Class3 | None" [arg-type]
constructors_call_init.py:73: error: Expression is of type "Class5[list[int]]", not "Class5[int]" [assert-type]
constructors_call_init.py:130: error: Too many arguments for "Class11" [call-arg]
constructors_call_init.py:127: error: Expression is of type "Class13[Never, Never]", not "Class13[int, str]" [assert-type]
constructors_call_init.py:134: error: Expression is of type "Class14[Never, Never, Never]", not "Class14[Any, str, int]" [assert-type]
constructors_call_init.py:179: error: Too many arguments for "Class11" [call-arg]
"""
15 changes: 13 additions & 2 deletions conformance/results/pycroscope/constructors_call_init.toml
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]
"""
4 changes: 3 additions & 1 deletion conformance/results/pyrefly/constructors_call_init.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ ERROR constructors_call_init.py:21:13-16: Argument `float` is not assignable to
ERROR constructors_call_init.py:42:8-20: Argument `Class2[@_]` is not assignable to parameter `x` with type `Class3 | None` in function `Class2.__init__` [bad-argument-type]
ERROR constructors_call_init.py:56:12-14: Argument `Class4[str]` is not assignable to parameter `self` with type `Class4[int]` in function `Class4.__init__` [bad-argument-type]
ERROR constructors_call_init.py:107:9-17: `__init__` method self type cannot reference class type parameters `T2`, `T1` [invalid-annotation]
ERROR constructors_call_init.py:130:9-10: Expected 0 positional arguments, got 1 in function `object.__init__` [bad-argument-count]
ERROR constructors_call_init.py:145:8-10: Argument `Class15[@_]` is not assignable to parameter `self` with type `Class12[int, str]` in function `Class12.__init__` [bad-argument-type]
ERROR constructors_call_init.py:157:9-10: Argument `Literal[1]` is not assignable to parameter `value` with type `str` in function `Class16.__init__` [bad-argument-type]
ERROR constructors_call_init.py:179:9-10: Expected 0 positional arguments, got 1 in function `object.__init__` [bad-argument-count]
"""
17 changes: 14 additions & 3 deletions conformance/results/pyright/constructors_call_init.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
conformant = "Pass"
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 an error when a subclass's specialization of a base class is not assignable to the self annotation of an inherited `__init__` method.
"""
conformance_automated = "Fail"
errors_diff = """
Line 145: Expected 1 errors
Line 127: Unexpected errors ['constructors_call_init.py:127:13 - error: "assert_type" mismatch: expected "Class13[int, str]" but received "Class13[Unknown, Unknown]" (reportAssertTypeFailure)']
Line 134: Unexpected errors ['constructors_call_init.py:134:13 - error: "assert_type" mismatch: expected "Class14[Any, str, int]" but received "Class14[Unknown, Unknown, Unknown]" (reportAssertTypeFailure)']
"""
output = """
constructors_call_init.py:21:13 - error: Argument of type "float" cannot be assigned to parameter "x" of type "int" in function "__init__"
Expand All @@ -14,5 +21,9 @@ constructors_call_init.py:56:1 - error:
    "Class4[str]" is not assignable to "Class4[int]"
      Type parameter "T@Class4" is invariant, but "str" is not the same as "int" (reportGeneralTypeIssues)
constructors_call_init.py:107:24 - warning: Type annotation for "self" parameter of "__init__" method cannot contain class-scoped type variables (reportInvalidTypeVarUse)
constructors_call_init.py:130:9 - error: Expected 0 positional arguments (reportCallIssue)
constructors_call_init.py:127:13 - error: "assert_type" mismatch: expected "Class13[int, str]" but received "Class13[Unknown, Unknown]" (reportAssertTypeFailure)
constructors_call_init.py:134:13 - error: "assert_type" mismatch: expected "Class14[Any, str, int]" but received "Class14[Unknown, Unknown, Unknown]" (reportAssertTypeFailure)
constructors_call_init.py:157:9 - error: Argument of type "Literal[1]" cannot be assigned to parameter "value" of type "str" in function "__init__"
  "Literal[1]" is not assignable to "str" (reportArgumentType)
constructors_call_init.py:179:9 - error: Expected 0 positional arguments (reportCallIssue)
"""
25 changes: 19 additions & 6 deletions conformance/results/results.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion conformance/results/ty/constructors_call_init.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ constructors_call_init.py:21:13: error[invalid-argument-type] Argument to `Class
constructors_call_init.py:56:1: error[invalid-argument-type] Argument to `Class4.__init__` is incorrect: Expected `Class4[int]`, found `Class4[str]`
constructors_call_init.py:107:32: error[invalid-init-type-variable] First parameter of `__init__` cannot use type variable `T2` from an outer scope
constructors_call_init.py:107:36: error[invalid-init-type-variable] First parameter of `__init__` cannot use type variable `T1` from an outer scope
constructors_call_init.py:130:9: error[too-many-positional-arguments] Too many positional arguments to `object.__init__`: expected 1, got 2
constructors_call_init.py:145:1: error[invalid-argument-type] Argument to `Class12.__init__` is incorrect: Expected `Class12[int, str]`, found `Class15[str]`
constructors_call_init.py:157:9: error[invalid-argument-type] Argument to `Class16.__init__` is incorrect: Expected `str`, found `Literal[1]`
constructors_call_init.py:179:9: error[too-many-positional-arguments] Too many positional arguments to `object.__init__`: expected 1, got 2
"""
4 changes: 3 additions & 1 deletion conformance/results/zuban/constructors_call_init.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ constructors_call_init.py:42: error: Argument 1 to "Class3" has incompatible typ
constructors_call_init.py:56: error: Invalid self type in __init__ [call-arg]
constructors_call_init.py:107: error: The type of self "T1" has type vars in non standard positions for class "Class8" [misc]
constructors_call_init.py:107: error: The type of self "T2" has type vars in non standard positions for class "Class8" [misc]
constructors_call_init.py:130: error: Too many arguments for "Class11" [call-arg]
constructors_call_init.py:145: error: Invalid self type in __init__ [call-arg]
constructors_call_init.py:157: error: Argument 1 to "Class17" has incompatible type "int"; expected "str" [arg-type]
constructors_call_init.py:179: error: Too many arguments for "Class11" [call-arg]
"""
49 changes: 49 additions & 0 deletions conformance/tests/constructors_call_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -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: ...

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.



class Class13(Class12[T1, T2]):
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 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
Expand Down
29 changes: 26 additions & 3 deletions docs/spec/constructors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

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]().

``__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
----------------------------------------------------
Expand All @@ -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]
Expand Down
Loading