Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 16 additions & 3 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2994,7 +2994,7 @@ def infer_overload_return_type(
matches: list[CallableType] = []
return_types: list[Type] = []
inferred_types: list[Type] = []
args_contain_any = any(map(has_any_type, arg_types))
args_contain_any = any(has_any_type_for_overload(arg_type) for arg_type in arg_types)
type_maps: list[dict[Expression, Type]] = []

for typ in plausible_targets:
Expand Down Expand Up @@ -6597,13 +6597,26 @@ def has_any_type(t: Type, ignore_in_type_obj: bool = False) -> bool:
return t.accept(HasAnyType(ignore_in_type_obj))


def has_any_type_for_overload(t: Type, ignore_in_type_obj: bool = False) -> bool:
"""Like has_any_type, but also counts a top-level alias-to-Any.

has_any_type ignores TypeOfAny.from_alias_target so use sites don't trigger
--disallow-any-explicit, but for overload ambiguity we want an actual argument
that *is* an alias-to-Any to count (without making nested alias-to-Any count).
"""
proper_t = get_proper_type(t)
if isinstance(proper_t, AnyType) and proper_t.type_of_any == TypeOfAny.from_alias_target:
return True
return has_any_type(t, ignore_in_type_obj=ignore_in_type_obj)


class HasAnyType(types.BoolTypeQuery):
def __init__(self, ignore_in_type_obj: bool) -> None:
super().__init__(types.ANY_STRATEGY)
self.ignore_in_type_obj = ignore_in_type_obj

def visit_any(self, t: AnyType) -> bool:
return t.type_of_any != TypeOfAny.special_form # special forms are not real Any types
return t.type_of_any not in (TypeOfAny.special_form, TypeOfAny.from_alias_target)

def visit_callable_type(self, t: CallableType) -> bool:
if self.ignore_in_type_obj and t.is_type_obj():
Expand Down Expand Up @@ -6842,7 +6855,7 @@ def any_causes_overload_ambiguity(
# We ignore Anys in type object callables as ambiguity
# creators, since that can lead to falsely claiming ambiguity
# for overloads between Type and Callable.
if has_any_type(arg_type, ignore_in_type_obj=True):
if has_any_type_for_overload(arg_type, ignore_in_type_obj=True):
matching_formals_unfiltered = [
(item_idx, lookup[arg_idx])
for item_idx, lookup in enumerate(actual_to_formal)
Expand Down
6 changes: 6 additions & 0 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -8263,6 +8263,12 @@ def remove_imported_names_from_symtable(names: SymbolTable, module: str) -> None

def make_any_non_explicit(t: Type) -> Type:
"""Replace all Any types within in with Any that has attribute 'explicit' set to False"""
# An alias whose target is exactly Any should still behave like a real Any when used.
# Explicit Any nested inside a larger alias target is kept as the historical
# non-real Any flavor to avoid making large generic aliases overload-ambiguous.
proper_t = get_proper_type(t)
if isinstance(proper_t, AnyType) and proper_t.type_of_any == TypeOfAny.explicit:
return proper_t.copy_modified(TypeOfAny.from_alias_target)
return t.accept(MakeAnyNonExplicit())


Expand Down
2 changes: 1 addition & 1 deletion mypy/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ def is_complex(t: Type) -> bool:


def is_special_form_any(t: AnyType) -> bool:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: name is now incongruous with body. suggest: is_special_form_or_alias_any

return get_original_any(t).type_of_any == TypeOfAny.special_form
return get_original_any(t).type_of_any in (TypeOfAny.special_form, TypeOfAny.from_alias_target)


def get_original_any(t: AnyType) -> AnyType:
Expand Down
6 changes: 5 additions & 1 deletion mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1760,7 +1760,11 @@ def analyze_literal_param(self, idx: int, arg: Type, ctx: Context) -> list[Type]
#
# TODO: Once we start adding support for enums, make sure we report a custom
# error for case 2 as well.
if arg.type_of_any not in (TypeOfAny.from_error, TypeOfAny.special_form):
if arg.type_of_any not in (
TypeOfAny.from_error,
TypeOfAny.special_form,
TypeOfAny.from_alias_target,
):
self.fail(
f'Parameter {idx} of Literal[...] cannot be of type "Any"',
ctx,
Expand Down
5 changes: 5 additions & 0 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,11 @@ class TypeOfAny:
# used to ignore Anys inserted by the suggestion engine when
# generating constraints.
suggestion_engine: Final = 9
# Does this Any come from a type alias whose target is Any (e.g.
# `Incomplete: TypeAlias = Any`)? This is treated as a real Any for overload
# ambiguity when it appears as an actual argument, but is otherwise ignored like
# special_form to avoid triggering use-site explicit-Any errors.
from_alias_target: Final = 10


def deserialize_type(data: JsonDict | str) -> Type:
Expand Down
16 changes: 16 additions & 0 deletions test-data/unit/check-flags.test
Original file line number Diff line number Diff line change
Expand Up @@ -1381,6 +1381,22 @@ def h(i: int) -> None: # E: Type of decorated function contains type "Any" ("Ca
pass
[builtins fixtures/list.pyi]

[case testDisallowAnyDecoratedAliasToAny]
# flags: --disallow-any-decorated
from typing import Any, Callable, TypeVar
from typing_extensions import TypeAlias

F = TypeVar("F", bound=Callable[..., Any])
AddressFormat: TypeAlias = Any

def d(f: F) -> F:
return f

@d
def f(x: AddressFormat | None = None) -> int:
return 0
[builtins fixtures/list.pyi]

[case testDisallowAnyDecoratedReturnsCallableNoParams]
# flags: --disallow-any-decorated
from typing import Callable
Expand Down
11 changes: 11 additions & 0 deletions test-data/unit/check-literal.test
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,17 @@ reveal_type(b) # N: Revealed type is "Any"
[builtins fixtures/tuple.pyi]
[out]

[case testLiteralAliasToAnyAllowed]
from typing import Any, Literal
from typing_extensions import TypeAlias

Incomplete: TypeAlias = Any

a: Literal[Incomplete]
reveal_type(a) # N: Revealed type is "Any"
[builtins fixtures/tuple.pyi]
[out]

[case testLiteralDisallowActualTypes]
from typing import Literal

Expand Down
68 changes: 68 additions & 0 deletions test-data/unit/check-overloading.test
Original file line number Diff line number Diff line change
Expand Up @@ -6856,3 +6856,71 @@ if isinstance(headers, dict):

reveal_type(headers) # N: Revealed type is "__main__.Headers | typing.Iterable[tuple[builtins.bytes, builtins.bytes]]"
[builtins fixtures/isinstancelist.pyi]

[case testOverloadAliasToAnyArg]
# An alias `Incomplete = Any` used as an overload arg should behave like bare
# Any (engage the ambiguity fallback), not silently match a None-default overload.
from typing import Any, TypeVar, overload
from typing_extensions import TypeAlias

T = TypeVar("T")
Incomplete: TypeAlias = Any

class M:
@overload
def get(self, key: str, default: None = None) -> str | None: ...
@overload
def get(self, key: str, default: str) -> str: ...
@overload
def get(self, key: str, default: T) -> str | T: ...
def get(self, key, default=None): ...

a: Incomplete
b: Any
reveal_type(M().get("k", a)) # N: Revealed type is "Any"
reveal_type(M().get("k", b)) # N: Revealed type is "Any"

# Unambiguous overload: alias-Any picks the matching return type, no false ambiguity.
@overload
def f(x: int, y: int) -> int: ...
@overload
def f(x: object, y: int) -> object: ...
def f(x, y): ...

reveal_type(f(1, a)) # N: Revealed type is "builtins.int"
[builtins fixtures/tuple.pyi]

[case testAliasToAnyDoesNotTriggerDisallowAnyExplicitAtUseSite]
# flags: --disallow-any-explicit --show-error-codes
from typing import Any
from typing_extensions import TypeAlias

Incomplete: TypeAlias = Any # E: Explicit "Any" is not allowed [explicit-any]

def f(x: Incomplete) -> Incomplete: # no error
return x

a: Incomplete = 1 # no error
[builtins fixtures/tuple.pyi]

[case testOverloadNestedAnyDoesNotTriggerAmbiguity]
# Any (explicit or alias-to-Any) nested inside a generic argument should not
# trigger the overload-ambiguity fallback — only a top-level Any actual arg counts.
from typing import Any, Generic, TypeVar, overload
from typing_extensions import TypeAlias

T = TypeVar("T")
Incomplete: TypeAlias = Any

class Array(Generic[T]): pass

class Series(Generic[T]):
@overload
def __add__(self: Series[bool], other: Array[Any]) -> Series[bool]: ...
@overload
def __add__(self: Series[T], other: Array[Any]) -> Series[T]: ...
def __add__(self, other): ...

def f(x: Series[Any], y: Array[Incomplete]) -> None:
reveal_type(x + y) # N: Revealed type is "__main__.Series[builtins.bool]"
[builtins fixtures/tuple.pyi]
Loading