diff --git a/conformance/results/mypy/overloads_evaluation.toml b/conformance/results/mypy/overloads_evaluation.toml
index 4ddeaca2d..d02df4565 100644
--- a/conformance/results/mypy/overloads_evaluation.toml
+++ b/conformance/results/mypy/overloads_evaluation.toml
@@ -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.
"""
@@ -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]
@@ -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]
"""
diff --git a/conformance/results/pyright/overloads_evaluation.toml b/conformance/results/pyright/overloads_evaluation.toml
index 7b41dc4e1..22388d132 100644
--- a/conformance/results/pyright/overloads_evaluation.toml
+++ b/conformance/results/pyright/overloads_evaluation.toml
@@ -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
@@ -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)
"""
diff --git a/conformance/results/results.html b/conformance/results/results.html
index ee117fc40..8778932d0 100644
--- a/conformance/results/results.html
+++ b/conformance/results/results.html
@@ -794,11 +794,11 @@
Python Type System Conformance Test Results
Pass |
| overloads_evaluation |
-Partial 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 evaluate Any in some cases where overload is ambiguous. Evaluates Any in some cases where overload is not ambiguous. |
-Partial Does not evaluate Any in some cases where overload is ambiguous. |
-Pass |
-Pass |
+Partial 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. |
+Partial 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. |
+Partial Does not handle unpacked arguments when checking for parameter type equivalence. Returns Any instead of most general return type for ambiguous calls. |
Pass |
+Partial Returns Any instead of most general return type for ambiguous calls. |
|
Exceptions
diff --git a/conformance/results/ty/overloads_evaluation.toml b/conformance/results/ty/overloads_evaluation.toml
index 3a9a963c2..3c19a2062 100644
--- a/conformance/results/ty/overloads_evaluation.toml
+++ b/conformance/results/ty/overloads_evaluation.toml
@@ -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]`
"""
diff --git a/conformance/results/zuban/overloads_evaluation.toml b/conformance/results/zuban/overloads_evaluation.toml
index 0e3b48cf8..42cdfce0b 100644
--- a/conformance/results/zuban/overloads_evaluation.toml
+++ b/conformance/results/zuban/overloads_evaluation.toml
@@ -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]
@@ -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]
"""
diff --git a/conformance/tests/overloads_evaluation.py b/conformance/tests/overloads_evaluation.py
index c89f9d0ba..8a6a46758 100644
--- a/conformance/tests/overloads_evaluation.py
+++ b/conformance/tests/overloads_evaluation.py
@@ -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 ` 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 ` 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
@@ -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])
diff --git a/docs/spec/overload.rst b/docs/spec/overload.rst
index 7d9ef1bc1..7751e2dd6 100644
--- a/docs/spec/overload.rst
+++ b/docs/spec/overload.rst
@@ -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 ` 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 ` 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 ` 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
~~~~~~
@@ -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:
|
|---|