Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Re-work overload overlap logic#17392

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
ilevkivskyi merged 13 commits intopython:masterfromilevkivskyi:overload-overlap
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from1 commit
Commits
Show all changes
13 commits
Select commitHold shift + click to select a range
4c5fc17
Re-work overload overlap logic
ilevkivskyiJun 16, 2024
fb427c0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot]Jun 16, 2024
cc122e2
Another experiment
ilevkivskyiJun 17, 2024
7c10a21
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot]Jun 17, 2024
cce3d00
Verify contravariance handling
ilevkivskyiJun 17, 2024
ce5ef00
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot]Jun 17, 2024
3900386
Try a bit more pragmatic approach
ilevkivskyiJun 17, 2024
6f4c29d
Merge remote-tracking branch 'upstream/master' into overload-overlap
ilevkivskyiJun 17, 2024
5ee8826
Update tests
ilevkivskyiJun 17, 2024
05a2086
Add some comments, docs, and tests
ilevkivskyiJun 19, 2024
bf96e6d
Add flip order note
ilevkivskyiJun 19, 2024
c154ad1
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot]Jun 19, 2024
4f1bcf2
Merge branch 'master' into overload-overlap
hauntsaninjaJun 19, 2024
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
PrevPrevious commit
NextNext commit
Add some comments, docs, and tests
  • Loading branch information
@ilevkivskyi
ilevkivskyi committedJun 19, 2024
commit05a2086b65dbf40985faf0fd5579e0de56b01d97
44 changes: 32 additions & 12 deletionsmypy/checker.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1763,6 +1763,8 @@ def is_unsafe_overlapping_op(
# second operand is the right argument -- we switch the order of
# the arguments of the reverse method.

# TODO: this manipulation is dangerous if callables are generic.
# Shuffling arguments between callables can create meaningless types.
forward_tweaked = forward_item.copy_modified(
arg_types=[forward_base_erased, forward_item.arg_types[0]],
arg_kinds=[nodes.ARG_POS] * 2,
Expand DownExpand Up@@ -7829,14 +7831,18 @@ def are_argument_counts_overlapping(t: CallableType, s: CallableType) -> bool:


def expand_callable_variants(c: CallableType) -> list[CallableType]:
"""Expand a generic callable using all combinations of type variables' values/bounds."""
for tv in c.variables:
# We need to expand self-type before other variables, because this is the only
# type variable that can have other type variables in the upper bound.
if tv.id.is_self():
c = expand_type(c, {tv.id: tv.upper_bound}).copy_modified(
variables=[v for v in c.variables if not v.id.is_self()]
)
break

if not c.is_generic():
# Fast path.
return [c]

tvar_values = []
Expand All@@ -7861,11 +7867,21 @@ def is_unsafe_overlapping_overload_signatures(
) -> bool:
"""Check if two overloaded signatures are unsafely overlapping or partially overlapping.

We consider two functions 's' and 't' to be unsafely overlapping ifboth
of the following are true:
We consider two functions 's' and 't' to be unsafely overlapping ifthree
conditions hold:

1. s's parameters are all more precise or partially overlapping with t's
2. s's return type is NOT a subset of t's.
1. s's parameters are partially overlapping with t's. i.e. there are calls that are
valid for both signatures.
2. for these common calls, some of t's parameters types are wider that s's.
3. s's return type is NOT a subset of t's.

Note that we use subset rather than subtype relationship in these checks because:
* Overload selection happens at runtime, not statically.
* This results in more lenient behavior.
This can cause false negatives (e.g. if overloaded function returns an externally
visible attribute with invariant type), but such situations are rare. In general,
overloads in Python are generally unsafe, so we intentionally try to avoid giving
non-actionable errors (see more details in comments below).

Assumes that 'signature' appears earlier in the list of overload
alternatives then 'other' and that their argument counts are overlapping.
Expand All@@ -7878,17 +7894,11 @@ def is_unsafe_overlapping_overload_signatures(

# Note: We repeat this check twice in both directions compensate for slight
# asymmetries in 'is_callable_compatible'.
#
# Note that we ignore possible overlap between type variables and None. This
# is technically unsafe, but unsafety is tiny and this prevents some common
# use cases like:
# @overload
# def foo(x: None) -> None: ..
# @overload
# def foo(x: T) -> Foo[T]: ...

for sig_variant in expand_callable_variants(signature):
for other_variant in expand_callable_variants(other):
# Using only expanded callables may cause false negatives, we can add
# more variants (e.g. using inference between callables) in the future.
if is_subset_no_promote(sig_variant.ret_type, other_variant.ret_type):
continue
if not (
Expand All@@ -7912,6 +7922,9 @@ def is_unsafe_overlapping_overload_signatures(
)
):
continue
# Using the same `allow_partial_overlap` flag as before, can cause false
# negatives in case where star argument is used in a catch-all fallback overload.
# But again, practicality beats purity here.
if not partial_only or not is_callable_compatible(
other_variant,
sig_variant,
Expand DownExpand Up@@ -8417,6 +8430,13 @@ def is_subset_no_promote(left: Type, right: Type) -> bool:


def is_overlapping_types_for_overload(left: Type, right: Type) -> bool:
# Note that among other effects 'overlap_for_overloads' flag will effectively
# ignore possible overlap between type variables and None. This is technically
# unsafe, but unsafety is tiny and this prevents some common use cases like:
# @overload
# def foo(x: None) -> None: ..
# @overload
# def foo(x: T) -> Foo[T]: ...
return is_overlapping_types(
left,
right,
Expand Down
1 change: 0 additions & 1 deletionmypy/expandtype.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -262,7 +262,6 @@ def visit_param_spec(self, t: ParamSpecType) -> Type:
else:
# We could encode Any as trivial parameters etc., but it would be too verbose.
# TODO: assert this is a trivial type, like Any, Never, or object.
# TODO: actually do object?
return repl

def visit_type_var_tuple(self, t: TypeVarTupleType) -> Type:
Expand Down
23 changes: 14 additions & 9 deletionsmypy/meet.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -257,6 +257,10 @@ def is_literal_in_union(x: ProperType, y: ProperType) -> bool:
)


def is_object(t: ProperType) -> bool:
return isinstance(t, Instance) and t.type.fullname == "builtins.object"


def is_overlapping_types(
left: Type,
right: Type,
Expand All@@ -272,7 +276,7 @@ def is_overlapping_types(
TypeVars (in both strict-optional and non-strict-optional mode).
If 'overlap_for_overloads' is True, we check for overlaps more strictly (to avoid false
positives), for example: None only overlaps with explicitly optional types, Any
doesn't overlap with anything, we don't ignore positional argument names.
doesn't overlap with anything except object, we don't ignore positional argument names.
"""
if isinstance(left, TypeGuardedType) or isinstance( # type: ignore[misc]
right, TypeGuardedType
Expand DownExpand Up@@ -328,7 +332,7 @@ def _is_overlapping_types(left: Type, right: Type) -> bool:

# 'Any' may or may not be overlapping with the other type
if isinstance(left, AnyType) or isinstance(right, AnyType):
return not overlap_for_overloads
return not overlap_for_overloads or is_object(left) or is_object(right)

# We check for complete overlaps next as a general-purpose failsafe.
# If this check fails, we start checking to see if there exists a
Expand DownExpand Up@@ -356,14 +360,17 @@ def is_none_object_overlap(t1: Type, t2: Type) -> bool:
and t2.type.fullname == "builtins.object"
)

# comments and docstrings.
if overlap_for_overloads:
if is_none_object_overlap(left, right) or is_none_object_overlap(right, left):
return False

if is_proper_subtype(left, right, ignore_promotions=ignore_promotions) or is_proper_subtype(
right, left, ignore_promotions=ignore_promotions
):
def _is_subtype(left: Type, right: Type) -> bool:
if overlap_for_overloads:
return is_proper_subtype(left, right, ignore_promotions=ignore_promotions)
else:
return is_subtype(left, right, ignore_promotions=ignore_promotions)

if _is_subtype(left, right) or _is_subtype(right, left):
return True

# See the docstring for 'get_possible_variants' for more info on what the
Expand DownExpand Up@@ -513,9 +520,7 @@ def _type_object_overlap(left: Type, right: Type) -> bool:
if isinstance(left, Instance) and isinstance(right, Instance):
# First we need to handle promotions and structural compatibility for instances
# that came as fallbacks, so simply call is_subtype() to avoid code duplication.
if is_subtype(left, right, ignore_promotions=ignore_promotions) or is_subtype(
right, left, ignore_promotions=ignore_promotions
):
if _is_subtype(left, right) or _is_subtype(right, left):
return True

if right.type.fullname == "builtins.int" and left.type.fullname in MYPYC_NATIVE_INT_NAMES:
Expand Down
4 changes: 2 additions & 2 deletionsmypy/typeshed/stdlib/builtins.pyi
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1572,9 +1572,9 @@ def pow(base: float, exp: complex | _SupportsSomeKindOfPow, mod: None = None) ->
@overload
def pow(base: complex, exp: complex | _SupportsSomeKindOfPow, mod: None = None) -> complex: ...
@overload
def pow(base: _SupportsPow2[_E, _T_co], exp: _E, mod: None = None) -> _T_co: ...
def pow(base: _SupportsPow2[_E, _T_co], exp: _E, mod: None = None) -> _T_co: ... # type: ignore[overload-overlap]
@overload
def pow(base: _SupportsPow3NoneOnly[_E, _T_co], exp: _E, mod: None = None) -> _T_co: ...
def pow(base: _SupportsPow3NoneOnly[_E, _T_co], exp: _E, mod: None = None) -> _T_co: ... # type: ignore[overload-overlap]
@overload
def pow(base: _SupportsPow3[_E, _M, _T_co], exp: _E, mod: _M) -> _T_co: ...
@overload
Expand Down
27 changes: 26 additions & 1 deletiontest-data/unit/check-overloading.test
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -6711,7 +6711,7 @@ class B:
pass
[builtins fixtures/tuple.pyi]

[casetestSafeOverlapAllowed]
[casetestOverloadsSafeOverlapAllowed]
from lib import *
[file lib.pyi]
from typing import overload
Expand All@@ -6720,3 +6720,28 @@ from typing import overload
def bar(x: object) -> object: ...
@overload
def bar(x: int = ...) -> int: ...

[case testOverloadsInvariantOverlapAllowed]
from lib import *
[file lib.pyi]
from typing import overload, List

@overload
def bar(x: List[int]) -> List[int]: ...
@overload
def bar(x: List[object]) -> List[object]: ...

[case testOverloadsNoneAnyOverlapAllowed]
from lib import *
[file lib.pyi]
from typing import overload, Any

@overload
def foo(x: None) -> int: ...
@overload
def foo(x: object) -> str: ...

@overload
def bar(x: int) -> int: ...
@overload
def bar(x: Any) -> str: ...

[8]ページ先頭

©2009-2025 Movatter.jp