Spec change: ambiguous arguments in overload call evaluation

After thinking through this more and working on an implementation in ty, I think it is not the case that int is the desired result here. That is, int is not the preferable return type for this scenario, encoded in the proposed conformance suite tests:

@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 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)

Inferring int here violates the gradual principles that motivated the original specification to return Any in this situation, which is that a more dynamic input type should not result in a “stricter” output type that may cause false positives. It may be that in reality the input type here is str, and thus bool is the real output type, and subsequent code may depend on this. Having less static knowledge of the input type should not cause false positives in that scenario.

I think the proper result of gradual_join(bool, int) should be the gradual type with lower bound bool and upper bound int, which is the same type as AnyOf[bool, int], or could be represented as (Any & int) | bool. This type “gradually” permits use as a bool without false positives.

I think the proposed new rule here works well when the “most general” return type is gradually “more general” (that is, less precise), but not when it is a statically wider type. Barring introduction of AnyOf or intersection types, the best available rule, I think, would be to consider a return type “most general” only if it is both assignable from every materialization of every other candidate-overload return type (the currently proposed rule), and it is assignable to every other candidate-overload return type (this would rule out int from being selected from candidates bool and int).

1 Like