diff --git a/conformance/results/mypy/overloads_evaluation.toml b/conformance/results/mypy/overloads_evaluation.toml
index 61c5af6a8..660060666 100644
--- a/conformance/results/mypy/overloads_evaluation.toml
+++ b/conformance/results/mypy/overloads_evaluation.toml
@@ -3,8 +3,14 @@ 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.
+<<<<<<< HEAD
+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.
+=======
Does not evaluate `Any` in some cases where overload is ambiguous.
Evaluates `Any` in some cases where overload is not ambiguous.
+>>>>>>> main
"""
conformance_automated = "Fail"
errors_diff = """
@@ -14,10 +20,11 @@ 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 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 +53,9 @@ 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:439: error: Expression is of type "Any", not "bool" [assert-type]
"""
diff --git a/conformance/results/pycroscope/overloads_evaluation.toml b/conformance/results/pycroscope/overloads_evaluation.toml
index f04831ec5..3381b94cc 100644
--- a/conformance/results/pycroscope/overloads_evaluation.toml
+++ b/conformance/results/pycroscope/overloads_evaluation.toml
@@ -1,9 +1,21 @@
-conformance_automated = "Pass"
+conformance_automated = "Fail"
errors_diff = """
+Line 371: Unexpected errors ['./overloads_evaluation.py:371:16: Any[multiple_overload_matches] is not equivalent to bool']
+Line 400: Unexpected errors ['./overloads_evaluation.py:400:16: Any[multiple_overload_matches] is not equivalent to bool']
+Line 420: Unexpected errors ['./overloads_evaluation.py:420:16: Any[multiple_overload_matches] is not equivalent to bool']
+Line 439: Unexpected errors ['./overloads_evaluation.py:439:16: Any[multiple_overload_matches] is not equivalent to bool']
+Line 441: Unexpected errors ['./overloads_evaluation.py:441:16: Any[multiple_overload_matches] is not equivalent to bool']
+Line 468: Unexpected errors ['./overloads_evaluation.py:468:16: Any[multiple_overload_matches] is not equivalent to ./overloads_evaluation.py.A[Any[explicit]]']
"""
output = """
./overloads_evaluation.py:38:0: Cannot call overloaded function [incompatible_call]
./overloads_evaluation.py:46:0: Cannot call overloaded function [incompatible_argument]
./overloads_evaluation.py:51:0: Cannot call overloaded function [incompatible_argument]
./overloads_evaluation.py:116:4: Cannot call overloaded function [incompatible_argument]
+./overloads_evaluation.py:371:16: Any[multiple_overload_matches] is not equivalent to bool
+./overloads_evaluation.py:400:16: Any[multiple_overload_matches] is not equivalent to bool
+./overloads_evaluation.py:420:16: Any[multiple_overload_matches] is not equivalent to bool
+./overloads_evaluation.py:439:16: Any[multiple_overload_matches] is not equivalent to bool
+./overloads_evaluation.py:441:16: Any[multiple_overload_matches] is not equivalent to bool
+./overloads_evaluation.py:468:16: Any[multiple_overload_matches] is not equivalent to ./overloads_evaluation.py.A[Any[explicit]]
"""
diff --git a/conformance/results/pyrefly/overloads_evaluation.toml b/conformance/results/pyrefly/overloads_evaluation.toml
index 6b60e3f4a..d6362efdc 100644
--- a/conformance/results/pyrefly/overloads_evaluation.toml
+++ b/conformance/results/pyrefly/overloads_evaluation.toml
@@ -1,10 +1,12 @@
conformant = "Pass"
-conformance_automated = "Pass"
+conformance_automated = "Fail"
errors_diff = """
+Line 395: Unexpected errors ['assert_type(int, Any) failed [assert-type]']
"""
output = """
ERROR overloads_evaluation.py:38:11-13: No matching overload found for function `example1_1` called with arguments: () [no-matching-overload]
ERROR overloads_evaluation.py:46:15-16: Argument `Literal[1]` is not assignable to parameter `y` with type `str` in function `example1_1` [bad-argument-type]
ERROR overloads_evaluation.py:51:12-13: Argument `Literal[1]` is not assignable to parameter `x` with type `str` in function `example1_1` [bad-argument-type]
ERROR overloads_evaluation.py:116:13-22: No matching overload found for function `example2` called with arguments: (int | str, int | str, Literal[1]) [no-matching-overload]
+ERROR overloads_evaluation.py:395:16-27: assert_type(int, Any) failed [assert-type]
"""
diff --git a/conformance/results/pyright/overloads_evaluation.toml b/conformance/results/pyright/overloads_evaluation.toml
index cb63679bf..7dbb8c78a 100644
--- a/conformance/results/pyright/overloads_evaluation.toml
+++ b/conformance/results/pyright/overloads_evaluation.toml
@@ -1,10 +1,17 @@
conformant = "Partial"
notes = """
+<<<<<<< HEAD
+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.
+=======
Does not evaluate `Any` in some cases where overload is ambiguous.
+>>>>>>> main
"""
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 395: Unexpected errors ['overloads_evaluation.py:395:17 - error: "assert_type" mismatch: expected "Any" but received "int" (reportAssertTypeFailure)']
+Line 468: Unexpected errors ['overloads_evaluation.py:468: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 +27,7 @@ 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:395:17 - error: "assert_type" mismatch: expected "Any" but received "int" (reportAssertTypeFailure)
+overloads_evaluation.py:468: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 7915147fd..2612522a6 100644
--- a/conformance/results/results.html
+++ b/conformance/results/results.html
@@ -1725,29 +1725,79 @@
Python Type System Conformance Test Results
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.
+ <<<<<<< HEAD
+ 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.
+ =======
Does not evaluate Any in some cases where overload is ambiguous.
Evaluates Any in some cases where overload is not ambiguous.
+
+
+
+
+
+
+
+main
+
+
+
+
+
+
+
- Pass |
+ Unknown |
Pass |
Partial
+ - <<<<<<< HEAD
+ - 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.
+ - =======
- Does not evaluate
Any in some cases where overload is ambiguous.
+
+
+
+
+
+
+
+main
+
+
+
+
+
+
+
+
+ |
+
+ Partial
+
+ - Returns Any instead of most general return type for ambiguous calls.
+
+ |
+
+ Partial
+
+ - Does not handle unpacked arguments when checking for parameter type equivalence.
+ - Returns Any instead of most general return type for ambiguous calls.
|
- Pass |
- Pass |
|
3.5 / 5 • 70.0% |
- 3.5 / 5 • 70.0% |
+ 2.5 / 5 • 50.0% |
5 / 5 • 100.0% |
4.5 / 5 • 90.0% |
- 5 / 5 • 100.0% |
- 5 / 5 • 100.0% |
+ 4.5 / 5 • 90.0% |
+ 4.5 / 5 • 90.0% |
@@ -2608,11 +2658,11 @@ Python Type System Conformance Test Results
|
108.5 / 145 • 74.8% |
- 138 / 145 • 95.2% |
+ 137 / 145 • 94.5% |
140.5 / 145 • 96.9% |
135.5 / 145 • 93.4% |
- 132 / 145 • 91.0% |
- 144.5 / 145 • 99.7% |
+ 131.5 / 145 • 90.7% |
+ 144 / 145 • 99.3% |
diff --git a/conformance/results/ty/overloads_evaluation.toml b/conformance/results/ty/overloads_evaluation.toml
index 3a9a963c2..38638ad26 100644
--- a/conformance/results/ty/overloads_evaluation.toml
+++ b/conformance/results/ty/overloads_evaluation.toml
@@ -1,9 +1,15 @@
-conformance_automated = "Pass"
+conformant = "Partial"
+notes = """
+Returns Any instead of most general return type for ambiguous calls.
+"""
+conformance_automated = "Fail"
errors_diff = """
+Line 468: Unexpected errors ['overloads_evaluation.py:468: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:468: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..a27018d53 100644
--- a/conformance/results/zuban/overloads_evaluation.toml
+++ b/conformance/results/zuban/overloads_evaluation.toml
@@ -1,5 +1,12 @@
-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 439: Unexpected errors ['overloads_evaluation.py:439: error: Expression is of type "Any", not "bool" [misc]']
+Line 468: Unexpected errors ['overloads_evaluation.py:468: 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 +23,6 @@ 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:439: error: Expression is of type "Any", not "bool" [misc]
+overloads_evaluation.py:468: 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..dea5b5a22 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,121 @@ 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 fall back to `Any`.
+ ret1 = example9(x, 'o1')
+ assert_type(ret1, Any)
+ # 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.
+ ret1 = example11('o1', *x)
+ assert_type(ret1, bool)
+ ret2 = example11('o1', x, x)
+ assert_type(ret2, 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..fe244eee6 100644
--- a/docs/spec/overload.rst
+++ b/docs/spec/overload.rst
@@ -268,39 +268,66 @@ 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.
-
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.
+types of the remaining overloads. If these return types include type variables
+that are scoped to the called function, they should be replaced with their
+solved types.
-If the return types are not equivalent, overload matching is ambiguous. In
-this case, assume a return type of ``Any`` and stop.
+Next, keep only the overloads for which both of the following are true:
+* All :term:`materializations ` of every other candidate
+ overload's return type are assignable to this overload's return type, and
+* This overload's return type is assignable to every other candidate overload's
+ return type.
+
+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 +424,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: