Skip to content
Prev Previous commit
Next Next commit
Clarifications
* "remaining" overloads => "candidate" overloads
* moar tests
  • Loading branch information
rchen152 committed Apr 8, 2026
commit 4e7c475312291159dd0cc6090406a8e94c1b956c
2 changes: 2 additions & 0 deletions conformance/results/mypy/overloads_evaluation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Line 268: Unexpected errors ['overloads_evaluation.py:268: error: Expression is
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]']
"""
output = """
overloads_evaluation.py:38: error: All overload variants of "example1_1" require at least one argument [call-overload]
Expand Down Expand Up @@ -50,4 +51,5 @@ overloads_evaluation.py:268: error: Expression is of type "list[Any]", not "Any"
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]
"""
4 changes: 2 additions & 2 deletions conformance/results/pyright/overloads_evaluation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Picks first overload instead of most general return type in some cases where ove
conformance_automated = "Fail"
errors_diff = """
Line 284: Unexpected errors ['overloads_evaluation.py:284:17 - error: "assert_type" mismatch: expected "Any" but received "list[int]" (reportAssertTypeFailure)']
Line 398: Unexpected errors ['overloads_evaluation.py:398:17 - error: "assert_type" mismatch: expected "A[Any]" but received "A[None]" (reportAssertTypeFailure)']
Line 464: Unexpected errors ['overloads_evaluation.py:464: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 @@ -23,5 +23,5 @@ overloads_evaluation.py:116:17 - error: Argument of type "int | str" cannot be a
  Type "int | str" is not assignable to type "int"
    "str" is not assignable to "int" (reportArgumentType)
overloads_evaluation.py:284:17 - error: "assert_type" mismatch: expected "Any" but received "list[int]" (reportAssertTypeFailure)
overloads_evaluation.py:398:17 - error: "assert_type" mismatch: expected "A[Any]" but received "A[None]" (reportAssertTypeFailure)
overloads_evaluation.py:464:17 - error: "assert_type" mismatch: expected "A[Any]" but received "A[None]" (reportAssertTypeFailure)
"""
6 changes: 4 additions & 2 deletions conformance/results/ty/overloads_evaluation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ Returns Any instead of most general return type for ambiguous calls.
"""
conformance_automated = "Fail"
errors_diff = """
Line 398: Unexpected errors ['overloads_evaluation.py:398:5: error[type-assertion-failure] Type `Unknown` does not match asserted type `A[Any]`']
Line 395: Unexpected errors ['overloads_evaluation.py:395:5: error[type-assertion-failure] Type `Unknown` does not match asserted type `int`']
Line 464: Unexpected errors ['overloads_evaluation.py:464: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:398:5: error[type-assertion-failure] Type `Unknown` does not match asserted type `A[Any]`
overloads_evaluation.py:395:5: error[type-assertion-failure] Type `Unknown` does not match asserted type `int`
overloads_evaluation.py:464:5: error[type-assertion-failure] Type `Unknown` does not match asserted type `A[Any]`
"""
6 changes: 4 additions & 2 deletions conformance/results/zuban/overloads_evaluation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ Returns Any instead of most general return type for ambiguous calls.
"""
conformance_automated = "Fail"
errors_diff = """
Line 398: Unexpected errors ['overloads_evaluation.py:398: error: Expression is of type "Any", not "A[Any]" [misc]']
Line 395: Unexpected errors ['overloads_evaluation.py:395: error: Expression is of type "Any", not "int" [misc]']
Line 464: Unexpected errors ['overloads_evaluation.py:464: 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 @@ -21,5 +22,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:398: error: Expression is of type "Any", not "A[Any]" [misc]
overloads_evaluation.py:395: error: Expression is of type "Any", not "int" [misc]
overloads_evaluation.py:464: error: Expression is of type "Any", not "A[Any]" [misc]
"""
76 changes: 71 additions & 5 deletions conformance/tests/overloads_evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,72 @@ def check_example8(x: Any):
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)
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 implemented in ty the additional rule that a candidate overload return type is only considered "most general" if it is also assignable to all matched overloads. The only expectation in these conformance tests that changes as a result is this one; it reverts back to Unknown/Any.

If you are happy with the int result in this case for pyrefly, I can discuss with the ty team, but I think we might prefer if the conformance suite gave leeway for a type checker to give a more gradual result here. (We might prefer a type that relies on intersections.)

# 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.
assert_type(example10(x), bool)


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


@overload
def example11(**kwargs: int) -> int: ...


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

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


class A[T]:
x: T

Expand All @@ -379,20 +445,20 @@ def f(self) -> T:


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


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


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


def check_example9(x: Any):
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 = example9(x)
ret = example12(x)
assert_type(ret, A[Any])
6 changes: 3 additions & 3 deletions docs/spec/overload.rst
Original file line number Diff line number Diff line change
Expand Up @@ -268,15 +268,15 @@ If so, eliminate overloads that do not have a variadic parameter.
Step 5
~~~~~~

For each of the remaining overloads, determine whether all arguments satisfy at
For each of the candidate 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
- The parameter types corresponding to this argument in all of the candidate overloads
are :term:`equivalent`.
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 this is correct but there's some subtlety in the wording that we should cover in the test cases:

  • "The parameter types corresponding to this argument". That may be a very different parameter in each of the overloads; e.g. maybe one takes *args and the other has explicit arguments. So my reading is that to check this, you have to create for each overload a mapping between argument and parameter type, and consult that. What is the parameter type corresponding to an unpacked argument, though?
  • "the remaining overloads". So we only check this overload and all following overloads, not ones above it?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great points, thanks!

  • I added a clarification about unpacked arguments and some more tests.
  • Ah, I meant all of the candidate overloads that are remaining at the beginning of Step 5, not just the overloads following the one that we're checking. I replaced "remaining overloads" with "candidate overloads" - hopefully that's clearer.


If so, eliminate all of the subsequent remaining overloads.
If so, eliminate all of the subsequent candidate overloads.

Consider the following examples::

Expand Down