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
21 changes: 13 additions & 8 deletions conformance/results/mypy/overloads_evaluation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ notes = """
Does not expand boolean arguments to Literal[True] and Literal[False].
Does not expand enum arguments to literal variants.
Does not expand tuple arguments to possible combinations.
Does not handle unpacked arguments when checking for parameter type equivalence.
Does not evaluate Any in some cases where overload is ambiguous.
Evaluates Any in some cases where overload is not ambiguous.
"""
Expand All @@ -14,10 +15,12 @@ Line 161: Unexpected errors ['overloads_evaluation.py:161: error: No overload va
Line 162: Unexpected errors ['overloads_evaluation.py:162: error: Expression is of type "Any", not "Literal[0, 1]" [assert-type]']
Line 205: Unexpected errors ['overloads_evaluation.py:205: error: Argument 1 to "expand_tuple" has incompatible type "tuple[int, int | str]"; expected "tuple[int, int]" [arg-type]']
Line 206: Unexpected errors ['overloads_evaluation.py:206: error: Expression is of type "int", not "int | str" [assert-type]']
Line 265: Unexpected errors ['overloads_evaluation.py:265: error: Expression is of type "list[Any]", not "Any" [assert-type]']
Line 281: Unexpected errors ['overloads_evaluation.py:281: error: Expression is of type "list[Any]", not "Any" [assert-type]']
Line 303: Unexpected errors ['overloads_evaluation.py:303: error: Expression is of type "Any", not "float" [assert-type]']
Line 347: Unexpected errors ['overloads_evaluation.py:347: error: Expression is of type "list[Any]", not "Any" [assert-type]']
Line 268: Unexpected errors ['overloads_evaluation.py:268: error: Expression is of type "list[Any]", not "Any" [assert-type]']
Line 284: Unexpected errors ['overloads_evaluation.py:284: error: Expression is of type "list[Any]", not "Any" [assert-type]']
Line 306: Unexpected errors ['overloads_evaluation.py:306: error: Expression is of type "Any", not "float" [assert-type]']
Line 350: Unexpected errors ['overloads_evaluation.py:350: error: Expression is of type "list[Any]", not "Any" [assert-type]']
Line 395: Unexpected errors ['overloads_evaluation.py:395: error: Expression is of type "Any", not "int" [assert-type]']
Line 439: Unexpected errors ['overloads_evaluation.py:439: error: Expression is of type "Any", not "bool" [assert-type]']
"""
output = """
overloads_evaluation.py:38: error: All overload variants of "example1_1" require at least one argument [call-overload]
Expand Down Expand Up @@ -46,8 +49,10 @@ overloads_evaluation.py:161: note: def expand_enum(x: Literal[Color.BLUE]) -
overloads_evaluation.py:162: error: Expression is of type "Any", not "Literal[0, 1]" [assert-type]
overloads_evaluation.py:205: error: Argument 1 to "expand_tuple" has incompatible type "tuple[int, int | str]"; expected "tuple[int, int]" [arg-type]
overloads_evaluation.py:206: error: Expression is of type "int", not "int | str" [assert-type]
overloads_evaluation.py:265: error: Expression is of type "list[Any]", not "Any" [assert-type]
overloads_evaluation.py:281: error: Expression is of type "list[Any]", not "Any" [assert-type]
overloads_evaluation.py:303: error: Expression is of type "Any", not "float" [assert-type]
overloads_evaluation.py:347: error: Expression is of type "list[Any]", not "Any" [assert-type]
overloads_evaluation.py:268: error: Expression is of type "list[Any]", not "Any" [assert-type]
overloads_evaluation.py:284: error: Expression is of type "list[Any]", not "Any" [assert-type]
overloads_evaluation.py:306: error: Expression is of type "Any", not "float" [assert-type]
overloads_evaluation.py:350: error: Expression is of type "list[Any]", not "Any" [assert-type]
overloads_evaluation.py:395: error: Expression is of type "Any", not "int" [assert-type]
overloads_evaluation.py:439: error: Expression is of type "Any", not "bool" [assert-type]
"""
7 changes: 5 additions & 2 deletions conformance/results/pyright/overloads_evaluation.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
conformant = "Partial"
notes = """
Does not evaluate Any in some cases where overload is ambiguous.
Picks first overload instead of most general return type in some cases where overload is ambiguous.
"""
conformance_automated = "Fail"
errors_diff = """
Line 281: Unexpected errors ['overloads_evaluation.py:281:17 - error: "assert_type" mismatch: expected "Any" but received "list[int]" (reportAssertTypeFailure)']
Line 284: Unexpected errors ['overloads_evaluation.py:284:17 - error: "assert_type" mismatch: expected "Any" but received "list[int]" (reportAssertTypeFailure)']
Line 466: Unexpected errors ['overloads_evaluation.py:466:17 - error: "assert_type" mismatch: expected "A[Any]" but received "A[None]" (reportAssertTypeFailure)']
"""
output = """
overloads_evaluation.py:38:1 - error: No overloads for "example1_1" match the provided arguments
Expand All @@ -20,5 +22,6 @@ overloads_evaluation.py:116:14 - error: Argument of type "int | str" cannot be a
overloads_evaluation.py:116:17 - error: Argument of type "int | str" cannot be assigned to parameter "y" of type "int" in function "example2"
  Type "int | str" is not assignable to type "int"
    "str" is not assignable to "int" (reportArgumentType)
overloads_evaluation.py:281:17 - error: "assert_type" mismatch: expected "Any" but received "list[int]" (reportAssertTypeFailure)
overloads_evaluation.py:284:17 - error: "assert_type" mismatch: expected "Any" but received "list[int]" (reportAssertTypeFailure)
overloads_evaluation.py:466:17 - error: "assert_type" mismatch: expected "A[Any]" but received "A[None]" (reportAssertTypeFailure)
"""
8 changes: 4 additions & 4 deletions conformance/results/results.html
Original file line number Diff line number Diff line change
Expand Up @@ -794,11 +794,11 @@ <h3>Python Type System Conformance Test Results</h3>
<th class="column col2 conformant">Pass</th>
</tr>
<tr><th class="column col1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;overloads_evaluation</th>
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not expand boolean arguments to Literal[True] and Literal[False].</p><p>Does not expand enum arguments to literal variants.</p><p>Does not expand tuple arguments to possible combinations.</p><p>Does not evaluate Any in some cases where overload is ambiguous.</p><p>Evaluates Any in some cases where overload is not ambiguous.</p></span></div></th>
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not evaluate Any in some cases where overload is ambiguous.</p></span></div></th>
<th class="column col2 conformant">Pass</th>
<th class="column col2 conformant">Pass</th>
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not expand boolean arguments to Literal[True] and Literal[False].</p><p>Does not expand enum arguments to literal variants.</p><p>Does not expand tuple arguments to possible combinations.</p><p>Does not handle unpacked arguments when checking for parameter type equivalence.</p><p>Does not evaluate Any in some cases where overload is ambiguous.</p><p>Evaluates Any in some cases where overload is not ambiguous.</p></span></div></th>
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not evaluate Any in some cases where overload is ambiguous.</p><p>Picks first overload instead of most general return type in some cases where overload is ambiguous.</p></span></div></th>
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not handle unpacked arguments when checking for parameter type equivalence.</p><p>Returns Any instead of most general return type for ambiguous calls.</p></span></div></th>
<th class="column col2 conformant">Pass</th>
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Returns Any instead of most general return type for ambiguous calls.</p></span></div></th>
</tr>
<tr><th class="column" colspan="6">
<a class="test_group" href="https://typing.readthedocs.io/en/latest/spec/exceptions.html">Exceptions</a>
Expand Down
10 changes: 9 additions & 1 deletion conformance/results/ty/overloads_evaluation.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
conformance_automated = "Pass"
conformant = "Partial"
notes = """
Returns Any instead of most general return type for ambiguous calls.
"""
conformance_automated = "Fail"
errors_diff = """
Line 395: Unexpected errors ['overloads_evaluation.py:395:5: error[type-assertion-failure] Type `Unknown` does not match asserted type `int`']
Line 466: Unexpected errors ['overloads_evaluation.py:466:5: error[type-assertion-failure] Type `Unknown` does not match asserted type `A[Any]`']
"""
output = """
overloads_evaluation.py:38:1: error[no-matching-overload] No overload of function `example1_1` matches arguments
overloads_evaluation.py:46:15: error[invalid-argument-type] Argument to function `example1_1` is incorrect: Expected `str`, found `Literal[1]`
overloads_evaluation.py:51:12: error[invalid-argument-type] Argument to function `example1_1` is incorrect: Expected `str`, found `Literal[1]`
overloads_evaluation.py:116:5: error[no-matching-overload] No overload of function `example2` matches arguments
overloads_evaluation.py:395:5: error[type-assertion-failure] Type `Unknown` does not match asserted type `int`
overloads_evaluation.py:466:5: error[type-assertion-failure] Type `Unknown` does not match asserted type `A[Any]`
"""
13 changes: 12 additions & 1 deletion conformance/results/zuban/overloads_evaluation.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
conformance_automated = "Pass"
conformant = "Partial"
notes = """
Does not handle unpacked arguments when checking for parameter type equivalence.
Returns Any instead of most general return type for ambiguous calls.
"""
conformance_automated = "Fail"
errors_diff = """
Line 395: Unexpected errors ['overloads_evaluation.py:395: error: Expression is of type "Any", not "int" [misc]']
Line 439: Unexpected errors ['overloads_evaluation.py:439: error: Expression is of type "Any", not "bool" [misc]']
Line 466: Unexpected errors ['overloads_evaluation.py:466: error: Expression is of type "Any", not "A[Any]" [misc]']
"""
output = """
overloads_evaluation.py:38: error: All overload variants of "example1_1" require at least one argument [call-overload]
Expand All @@ -16,4 +24,7 @@ overloads_evaluation.py:51: note: def example1_1(x: int, y: str) -> int
overloads_evaluation.py:51: note: def example1_1(x: str) -> str
overloads_evaluation.py:116: error: Argument 1 to "example2" has incompatible type "int | str"; expected "int" [arg-type]
overloads_evaluation.py:116: error: Argument 2 to "example2" has incompatible type "int | str"; expected "str" [arg-type]
overloads_evaluation.py:395: error: Expression is of type "Any", not "int" [misc]
overloads_evaluation.py:439: error: Expression is of type "Any", not "bool" [misc]
overloads_evaluation.py:466: error: Expression is of type "Any", not "A[Any]" [misc]
"""
127 changes: 123 additions & 4 deletions conformance/tests/overloads_evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,13 @@ def check_variadic(v: list[int]) -> None:
assert_type(ret1, int)


# > Step 5: For all arguments, determine whether all possible
# > :term:`materializations <materialize>` of the argument's type are assignable to
# > the corresponding parameter type for each of the remaining overloads. If so,
# > eliminate all of the subsequent remaining overloads.
# > Step 5: For each of the remaining overloads, determine whether all
# > arguments satisfy at least one of the following conditions:
# > - All possible :term:`materializations <materialize>` of the argument's type are
# > assignable to the corresponding parameter type, or
# > - The parameter types corresponding to this argument in all of the remaining overloads
# > are :term:`equivalent`.
# > If so, eliminate all of the subsequent remaining overloads.


@overload
Expand Down Expand Up @@ -345,3 +348,119 @@ def check_example7(v1: list[Any], v2: Any) -> None:

ret3 = example7(v1, v2)
assert_type(ret3, Any)


@overload
def example8(x: str, y: Literal['o1']) -> bool: ...


@overload
def example8(x: str, y: str) -> int: ...


def example8(x: str, y: str) -> bool | int:
return True


def check_example8(x: Any):
# The parameter type corresponding to argument `x` is `str` in both
# overloads, and all materializations of argument `y`'s type of
# `Literal['o1']` match the first overload, so the second overload can be
# eliminated.
ret = example8(x, 'o1')
assert_type(ret, bool)


@overload
def example9(x: str, y: Literal['o1']) -> bool: ...


@overload
def example9(x: bytes, y: Literal['o1', 'o2']) -> bool: ...


@overload
def example9(x: bytes, y: str) -> int: ...


def example9(x: str | bytes, y: str) -> bool | int:
return True


def check_example9(x: Any):
# All three overloads are candidates. The parameter types corresponding to
# argument `x` are `str` and `bytes`, which are not equivalent, so none of
# the overloads can be eliminated. We pick the most general return type.
ret1 = example9(x, 'o1')
assert_type(ret1, int)
# The second and third overload are candidates. The parameter type
# corresponding to argument `x` is `bytes` in both candidates, so we can
# eliminate the third overload.
ret2 = example9(x, 'o2')
assert_type(ret2, bool)


@overload
def example10(x: int) -> bool: ...


@overload
def example10(*args: int) -> int: ...


def example10(*args: int, **kwargs: int) -> int:
return 0


def check_example10(x: Any):
# The parameters corresponding to argument `x` (`x` in the first overload
# and `*args` in the second) both have type `int`, so the second overload
# can be eliminated.
ret = example10(x)
assert_type(ret, bool)


@overload
def example11(x: Literal['o1'], y: int, z: str) -> bool: ...


@overload
def example11(x: str, y: int, z: str) -> int: ...


def example11(x: str, y: int, z: str) -> bool | int:
return True


def check_example11(x: Any):
# `*x` maps to `(y: int, z: str)` in both overloads, so the second overload
# can be eliminated.
ret = example11('o1', *x)
assert_type(ret, bool)


class A[T]:
x: T

def f(self) -> T:
return self.x


@overload
def example12(x: A[None]) -> A[None]: ...


@overload
def example12(x: A[Any]) -> A[Any]: ...


def example12(x: A[Any]) -> A[Any]:
return x


def check_example12(x: Any):
# Step 5 eliminates the first overload because there exists a
# materialization of `A[Any]` that is not assignable to `A[None]`. Step 6
# picks the second overload.
ret = example12(x)
assert_type(ret, A[Any])
75 changes: 59 additions & 16 deletions docs/spec/overload.rst
Original file line number Diff line number Diff line change
Expand Up @@ -268,39 +268,64 @@ If so, eliminate overloads that do not have a variadic parameter.
Step 5
~~~~~~

For all arguments, determine whether all possible
:term:`materializations <materialize>` of the argument's type are assignable to
the corresponding parameter type for each of the remaining overloads. If so,
eliminate all of the subsequent remaining overloads.
For each of the candidate overloads, determine whether all arguments satisfy at
least one of the following conditions:

Consider the following example::
- All possible :term:`materializations <materialize>` of the argument's type
are assignable to the corresponding parameter type, or
- The parameter types corresponding to this argument in all of the candidate
overloads are :term:`equivalent`. For an unpacked argument, this condition is
satisfied if the argument maps to the same number of parameters in all
candidate overloads, and for each parameter, the types in all candidate
overloads are equivalent.

If so, eliminate all of the subsequent candidate overloads.

Consider the following examples::

@overload
def example(x: list[int]) -> int: ...
def example1(x: list[int]) -> int: ...
@overload
def example(x: list[Any]) -> str: ...
def example1(x: list[Any]) -> str: ...
@overload
def example(x: Any) -> Any: ...
def example1(x: Any) -> Any: ...

def test(a: list[Any]):
# All materializations of list[Any] will match either the first or
# second overload, so the third overload can be eliminated.
example(a)
example1(a)

and::

@overload
def example2(x: str, y: Literal['o1']) -> bool: ...
@overload
def example2(x: str, y: str) -> int: ...

def test(x: Any):
# The parameter type corresponding to argument `x` is `str` in both
# overloads, and all materializations of argument `y`'s type of
# `Literal['o1']` match the first overload, so the second overload can be
# eliminated.
example2(x, 'o1')

This rule eliminates overloads that will never be chosen even if the
caller eliminates types that include ``Any``.

If the call involves more than one argument, all possible materializations of
every argument type must be assignable to its corresponding parameter type.
If this condition exists, all subsequent remaining overloads should be eliminated.
If the call involves more than one argument, every argument must satisfy one of
the above conditions.

Once this filtering process is applied for all arguments, examine the return
types of the remaining overloads. If these return types include type variables,
they should be replaced with their solved types. If the resulting return types
for all remaining overloads are :term:`equivalent`, proceed to step 6.
they should be replaced with their solved types. Eliminate every overload for
which there exists a :term:`materialization <materialize>` of another
overload's return type that is not assignable to this overload's return type.

If the return types are not equivalent, overload matching is ambiguous. In
this case, assume a return type of ``Any`` and stop.
This rule picks the most general return type, if one exists.

- If no candidate overloads remain, overload matching is ambiguous. In this
case, assume a return type of ``Any`` and stop.
- If one or more candidate overloads remain, proceed to step 6.

Step 6
~~~~~~
Expand Down Expand Up @@ -397,6 +422,24 @@ Example 4::
r2 = example4(v2, 1)
reveal_type(r2) # Should reveal Any

Example 5::

class A[T]:
x: T
def f(self) -> T:
return self.x

@overload
def example5(x: A[None]) -> A[None]: ...
@overload
def example5(x: A[Any]) -> A[Any]: ...

def test(x: Any):
# Step 5 eliminates the first overload because there exists a
# materialization of `A[Any]` that is not assignable to `A[None]`. Step 6
# picks the second overload.
reveal_type(example5(x)) # Should reveal `A[Any]`


.. _argument-type-expansion:

Expand Down