Skip to content
Draft
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
2 changes: 1 addition & 1 deletion mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4975,7 +4975,7 @@ class C(Generic[T, Unpack[Ts]]): ...
# This code can be only called either from checking a type application, or from
# checking a type alias (after the caller handles no_args aliases), so we know it
# was initially an IndexExpr, and we allow empty tuple type arguments.
if not validate_instance(fake, self.chk.fail, empty_tuple_index=True):
if not validate_instance(fake, self.chk.fail, indexed=True):
fix_instance(
fake, self.chk.fail, self.chk.note, disallow_any=False, options=self.chk.options
)
Expand Down
15 changes: 7 additions & 8 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4035,8 +4035,8 @@ def analyze_alias(
variadic = True
new_tvar_defs.append(td)

empty_tuple_index = typ.empty_tuple_index if isinstance(typ, UnboundType) else False
return analyzed, new_tvar_defs, depends_on, empty_tuple_index
indexed = bool(isinstance(typ, UnboundType) and (typ.args or typ.empty_tuple_index))
return analyzed, new_tvar_defs, depends_on, indexed

def is_pep_613(self, s: AssignmentStmt) -> bool:
if s.unanalyzed_type is not None and isinstance(s.unanalyzed_type, UnboundType):
Expand Down Expand Up @@ -4135,10 +4135,10 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
res = NoneType()
alias_tvars: list[TypeVarLikeType] = []
depends_on: set[str] = set()
empty_tuple_index = False
indexed = False
else:
tag = self.track_incomplete_refs()
res, alias_tvars, depends_on, empty_tuple_index = self.analyze_alias(
res, alias_tvars, depends_on, indexed = self.analyze_alias(
lvalue.name,
rvalue,
allow_placeholder=True,
Expand Down Expand Up @@ -4178,12 +4178,11 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
no_args = (
isinstance(res, ProperType)
and isinstance(res, Instance)
and not res.args
and not empty_tuple_index
and not pep_695
and not indexed
)
if isinstance(res, ProperType) and isinstance(res, Instance):
if not validate_instance(res, self.fail, empty_tuple_index):
if not validate_instance(res, self.fail, indexed):
fix_instance(res, self.fail, self.note, disallow_any=False, options=self.options)
# Aliases defined within functions can't be accessed outside
# the function, since the symbol table will no longer
Expand Down Expand Up @@ -5668,7 +5667,7 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None:
return

tag = self.track_incomplete_refs()
res, alias_tvars, depends_on, empty_tuple_index = self.analyze_alias(
res, alias_tvars, depends_on, indexed = self.analyze_alias(
s.name.name,
s.value.expr(),
allow_placeholder=True,
Expand Down
12 changes: 11 additions & 1 deletion mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2160,6 +2160,15 @@ def instantiate_type_alias(
# Note that we keep the kind of Any for consistency.
return set_any_tvars(node, [], ctx.line, ctx.column, options, special_form=True)

if (
no_args
and isinstance(node.target, ProperType)
and isinstance(node.target, Instance)
and node.target.type.fullname == "builtins.tuple"
and len(args)
):
no_args = False

max_tv_count = len(node.alias_tvars)
act_len = len(args)
if (
Expand Down Expand Up @@ -2480,13 +2489,14 @@ def make_optional_type(t: Type) -> Type:
return UnionType([t, NoneType()], t.line, t.column)


def validate_instance(t: Instance, fail: MsgCallback, empty_tuple_index: bool) -> bool:
def validate_instance(t: Instance, fail: MsgCallback, indexed: bool) -> bool:
"""Check if this is a well-formed instance with respect to argument count/positions."""
# TODO: combine logic with instantiate_type_alias().
if any(unknown_unpack(a) for a in t.args):
# This type is not ready to be validated, because of unknown total count.
# TODO: is it OK to fill with TypeOfAny.from_error instead of special form?
return False
empty_tuple_index = indexed and not t.args
if t.type.has_type_var_tuple_type:
min_tv_count = sum(
not tv.has_default() and not isinstance(tv, TypeVarTupleType)
Expand Down
1 change: 1 addition & 0 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,7 @@ def __init__(
self,
name: str,
args: Sequence[Type] | None = None,
*,
line: int = -1,
column: int = -1,
optional: bool = False,
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-generics.test
Original file line number Diff line number Diff line change
Expand Up @@ -1002,7 +1002,7 @@ y: U
reveal_type(x) # N: Revealed type is "builtins.int | None"
reveal_type(y) # N: Revealed type is "builtins.int"

U[int] # E: Type application targets a non-generic function or class
U[int] # E: Bad number of arguments for type alias, expected 0, given 1
O[int] # E: Bad number of arguments for type alias, expected 0, given 1 \
# E: Type application is only supported for generic classes

Expand Down
36 changes: 36 additions & 0 deletions test-data/unit/check-python310.test
Original file line number Diff line number Diff line change
Expand Up @@ -1402,6 +1402,42 @@ def print_test(m: object, typ: type[T]) -> T:
reveal_type(m) # N: Revealed type is "builtins.object"
raise

[case testMatchClassPatternTupleAlias]
# flags: --strict-equality --warn-unreachable
from typing import Tuple

tuple_alias = tuple
Tuple_alias = Tuple

def main(m: object):
match m:
case tuple():
reveal_type(m) # N: Revealed type is "builtins.tuple[Any, ...]"
case _:
reveal_type(m) # N: Revealed type is "builtins.object"

match m:
case tuple_alias():
reveal_type(m) # N: Revealed type is "builtins.tuple[Any, ...]"
case _:
reveal_type(m) # N: Revealed type is "builtins.object"

match m:
# With real typeshed you'll get an error like this instead:
# Expected type in class pattern; found "typing._SpecialForm"
case Tuple(): # E: Expected type in class pattern; found "builtins.int"
reveal_type(m) # E: Statement is unreachable
case _:
reveal_type(m) # N: Revealed type is "builtins.object"

match m:
# This is a false negative, it will raise at runtime
case Tuple_alias():
reveal_type(m) # N: Revealed type is "builtins.tuple[Any, ...]"
case _:
reveal_type(m) # N: Revealed type is "builtins.object"
[builtins fixtures/tuple.pyi]

[case testMatchNonFinalMatchArgs]
class A:
__match_args__ = ("a", "b")
Expand Down