diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index c9296bd5e875d..f70dce9047399 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -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: @@ -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(): @@ -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) diff --git a/mypy/semanal.py b/mypy/semanal.py index f9b52a0dcfba8..303185522bd67 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -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()) diff --git a/mypy/stats.py b/mypy/stats.py index e3499d2345635..9141ffa92c917 100644 --- a/mypy/stats.py +++ b/mypy/stats.py @@ -483,7 +483,7 @@ def is_complex(t: Type) -> bool: def is_special_form_any(t: AnyType) -> bool: - 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: diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 3351bc1a2ca61..d6966942bd36e 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -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, diff --git a/mypy/types.py b/mypy/types.py index 40c3839e2efca..5c739a8f7ea59 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -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: diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index fbfb8c11ba8c0..a3a88659dd8be 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -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 diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index f795f1f5b354b..0481cd120fc49 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -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 diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 721ca52f59eed..3e3eb3f187f31 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -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]