Skip to content

Commit 398d70e

Browse files
author
Pablo Galindo Salgado
committed
Fix overload resolution for type aliases to Any
Aliases like `Incomplete: TypeAlias = Any` were being downgraded to TypeOfAny.special_form, the same flavor used for NewType-style higher-kinded types. has_any_type ignores special_form Anys when checking for overload ambiguity, which meant a 2-arg call to os.environ.get with an alias-Any default would silently match the `default: None = None` overload and return `str | None` instead of Any. Add a dedicated TypeOfAny.from_alias_target flavor that suppresses --disallow-any-explicit at use sites (the original reason for the downgrade) but still counts as a real Any everywhere else. Stats reporting and the Literal[...] arg check are extended to keep their existing behavior for the new flavor.
1 parent da21dae commit 398d70e

6 files changed

Lines changed: 187 additions & 3 deletions

File tree

mypy/semanal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8269,7 +8269,7 @@ def make_any_non_explicit(t: Type) -> Type:
82698269
class MakeAnyNonExplicit(TrivialSyntheticTypeTranslator):
82708270
def visit_any(self, t: AnyType) -> Type:
82718271
if t.type_of_any == TypeOfAny.explicit:
8272-
return t.copy_modified(TypeOfAny.special_form)
8272+
return t.copy_modified(TypeOfAny.from_alias_target)
82738273
return t
82748274

82758275
def visit_type_alias_type(self, t: TypeAliasType) -> Type:

mypy/stats.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,10 @@ def is_complex(t: Type) -> bool:
483483

484484

485485
def is_special_form_any(t: AnyType) -> bool:
486-
return get_original_any(t).type_of_any == TypeOfAny.special_form
486+
return get_original_any(t).type_of_any in (
487+
TypeOfAny.special_form,
488+
TypeOfAny.from_alias_target,
489+
)
487490

488491

489492
def get_original_any(t: AnyType) -> AnyType:

mypy/typeanal.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1760,7 +1760,11 @@ def analyze_literal_param(self, idx: int, arg: Type, ctx: Context) -> list[Type]
17601760
#
17611761
# TODO: Once we start adding support for enums, make sure we report a custom
17621762
# error for case 2 as well.
1763-
if arg.type_of_any not in (TypeOfAny.from_error, TypeOfAny.special_form):
1763+
if arg.type_of_any not in (
1764+
TypeOfAny.from_error,
1765+
TypeOfAny.special_form,
1766+
TypeOfAny.from_alias_target,
1767+
):
17641768
self.fail(
17651769
f'Parameter {idx} of Literal[...] cannot be of type "Any"',
17661770
ctx,

mypy/types.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,10 @@ class TypeOfAny:
237237
# used to ignore Anys inserted by the suggestion engine when
238238
# generating constraints.
239239
suggestion_engine: Final = 9
240+
# Does this Any come from a type alias whose target is Any (e.g.
241+
# `Incomplete: TypeAlias = Any`)? It is a real Any (unlike special_form),
242+
# but tagged separately so use sites don't trigger --disallow-any-explicit.
243+
from_alias_target: Final = 10
240244

241245

242246
def deserialize_type(data: JsonDict | str) -> Type:

test-data/unit/check-literal.test

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,20 @@ reveal_type(b) # N: Revealed type is "Any"
562562
[builtins fixtures/tuple.pyi]
563563
[out]
564564

565+
[case testLiteralAliasToAnyAllowed]
566+
# Aliases to Any (e.g. typeshed's `Incomplete: TypeAlias = Any`) must not
567+
# trigger the Literal[...] cannot-be-Any error, matching the behavior of
568+
# unfollowed-import aliases.
569+
from typing import Any, Literal
570+
from typing_extensions import TypeAlias
571+
572+
Incomplete: TypeAlias = Any
573+
574+
a: Literal[Incomplete]
575+
reveal_type(a) # N: Revealed type is "Any"
576+
[builtins fixtures/tuple.pyi]
577+
[out]
578+
565579
[case testLiteralDisallowActualTypes]
566580
from typing import Literal
567581

test-data/unit/check-overloading.test

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6856,3 +6856,162 @@ if isinstance(headers, dict):
68566856

68576857
reveal_type(headers) # N: Revealed type is "__main__.Headers | typing.Iterable[tuple[builtins.bytes, builtins.bytes]]"
68586858
[builtins fixtures/isinstancelist.pyi]
6859+
6860+
[case testOverloadAliasToAnyArgWithNoneDefault]
6861+
# An alias `Incomplete = Any` used as an overload arg must not silently match
6862+
# the `default: None = None` overload. It should be treated as Any like a bare
6863+
# Any value, triggering the overload-ambiguity fallback.
6864+
from typing import Any, TypeVar, overload
6865+
from typing_extensions import TypeAlias
6866+
6867+
T = TypeVar("T")
6868+
Incomplete: TypeAlias = Any
6869+
6870+
class M:
6871+
@overload
6872+
def get(self, key: str, default: None = None) -> str | None: ...
6873+
@overload
6874+
def get(self, key: str, default: str) -> str: ...
6875+
@overload
6876+
def get(self, key: str, default: T) -> str | T: ...
6877+
def get(self, key, default=None): ...
6878+
6879+
a: Incomplete
6880+
b: Any
6881+
reveal_type(M().get("k", a)) # N: Revealed type is "Any"
6882+
reveal_type(M().get("k", b)) # N: Revealed type is "Any"
6883+
[builtins fixtures/tuple.pyi]
6884+
6885+
[case testOverloadAliasToAnyArgEquivalentToBareAny]
6886+
# Alias-to-Any and bare Any should produce the same overload results.
6887+
from typing import Any, overload
6888+
from typing_extensions import TypeAlias
6889+
6890+
Incomplete: TypeAlias = Any
6891+
6892+
@overload
6893+
def f(x: int) -> int: ...
6894+
@overload
6895+
def f(x: str) -> str: ...
6896+
def f(x): ...
6897+
6898+
a: Incomplete
6899+
b: Any
6900+
reveal_type(f(a)) # N: Revealed type is "Any"
6901+
reveal_type(f(b)) # N: Revealed type is "Any"
6902+
[builtins fixtures/tuple.pyi]
6903+
6904+
[case testOverloadAliasToAnyArgChainedAlias]
6905+
# A chained alias `Outer = Inner` where `Inner = Any` should still behave
6906+
# like Any, both at the inner and outer level.
6907+
from typing import Any, overload
6908+
from typing_extensions import TypeAlias
6909+
6910+
Inner: TypeAlias = Any
6911+
Outer: TypeAlias = Inner
6912+
6913+
@overload
6914+
def f(x: int) -> int: ...
6915+
@overload
6916+
def f(x: str) -> str: ...
6917+
def f(x): ...
6918+
6919+
inner: Inner
6920+
outer: Outer
6921+
reveal_type(f(inner)) # N: Revealed type is "Any"
6922+
reveal_type(f(outer)) # N: Revealed type is "Any"
6923+
[builtins fixtures/tuple.pyi]
6924+
6925+
[case testOverloadAliasToAnyArgImplicitAlias]
6926+
# `MyAlias = Any` without an explicit TypeAlias annotation also gets the
6927+
# alias-Any treatment and must engage the overload-ambiguity check.
6928+
from typing import Any, overload
6929+
6930+
MyAlias = Any
6931+
6932+
@overload
6933+
def f(x: int) -> int: ...
6934+
@overload
6935+
def f(x: str) -> str: ...
6936+
def f(x): ...
6937+
6938+
a: MyAlias
6939+
reveal_type(f(a)) # N: Revealed type is "Any"
6940+
[builtins fixtures/tuple.pyi]
6941+
6942+
[case testOverloadAliasToAnyArgImported]
6943+
# Alias-to-Any imported from another module must behave the same way.
6944+
from typing import overload
6945+
from other import Incomplete
6946+
6947+
@overload
6948+
def f(x: int) -> int: ...
6949+
@overload
6950+
def f(x: str) -> str: ...
6951+
def f(x): ...
6952+
6953+
a: Incomplete
6954+
reveal_type(f(a)) # N: Revealed type is "Any"
6955+
6956+
[file other.py]
6957+
from typing import Any
6958+
from typing_extensions import TypeAlias
6959+
Incomplete: TypeAlias = Any
6960+
[builtins fixtures/tuple.pyi]
6961+
6962+
[case testOverloadAliasToAnyArgPep695]
6963+
# PEP 695 `type Incomplete = Any` should also engage overload ambiguity.
6964+
# flags: --python-version 3.12
6965+
from typing import Any, overload
6966+
6967+
type Incomplete = Any
6968+
6969+
@overload
6970+
def f(x: int) -> int: ...
6971+
@overload
6972+
def f(x: str) -> str: ...
6973+
def f(x): ...
6974+
6975+
a: Incomplete
6976+
reveal_type(f(a)) # N: Revealed type is "Any"
6977+
[builtins fixtures/tuple.pyi]
6978+
6979+
[case testOverloadAliasToAnyArgUnambiguous]
6980+
# When overloads are unambiguous, alias-to-Any should pick that overload's
6981+
# return type just like bare Any does (no false ambiguity).
6982+
from typing import Any, overload
6983+
from typing_extensions import TypeAlias
6984+
6985+
Incomplete: TypeAlias = Any
6986+
6987+
@overload
6988+
def f(x: int, y: int, z: str) -> int: ...
6989+
@overload
6990+
def f(x: object, y: int, z: str) -> object: ...
6991+
def f(x, y, z): ...
6992+
6993+
a: Incomplete
6994+
b: Any
6995+
# Ambiguous on x: returns Any
6996+
reveal_type(f(a, 1, '')) # N: Revealed type is "Any"
6997+
reveal_type(f(b, 1, '')) # N: Revealed type is "Any"
6998+
# Unambiguous: x is concrete, only y/z are Any
6999+
reveal_type(f(1, a, '')) # N: Revealed type is "builtins.int"
7000+
reveal_type(f(1, b, '')) # N: Revealed type is "builtins.int"
7001+
[builtins fixtures/tuple.pyi]
7002+
7003+
[case testAliasToAnyDoesNotTriggerDisallowAnyExplicitAtUseSite]
7004+
# --disallow-any-explicit must NOT fire at use sites of an `Alias = Any`
7005+
# (the original purpose of make_any_non_explicit).
7006+
# flags: --disallow-any-explicit --show-error-codes
7007+
from typing import Any
7008+
from typing_extensions import TypeAlias
7009+
7010+
Incomplete: TypeAlias = Any # E: Explicit "Any" is not allowed [explicit-any]
7011+
7012+
def f(x: Incomplete) -> Incomplete: # no error
7013+
return x
7014+
7015+
a: Incomplete = 1 # no error
7016+
[builtins fixtures/tuple.pyi]
7017+

0 commit comments

Comments
 (0)