Skip to content

Commit fadc994

Browse files
committed
Fix recognition of several typing special forms, including: Optional, Union, Callable
1 parent 3dfb36a commit fadc994

File tree

2 files changed

+116
-2
lines changed

2 files changed

+116
-2
lines changed

mypy/semanal.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7843,7 +7843,8 @@ def try_parse_as_type_expression(self, maybe_type_expr: Expression) -> None:
78437843
return
78447844
elif isinstance(maybe_type_expr, IndexExpr):
78457845
if isinstance(maybe_type_expr.base, NameExpr):
7846-
if isinstance(maybe_type_expr.base.node, Var):
7846+
if (isinstance(maybe_type_expr.base.node, Var) and
7847+
not self.var_is_typing_special_form(maybe_type_expr.base)):
78477848
# Leftmost part of IndexExpr refers to a Var. Not a valid type.
78487849
maybe_type_expr.as_type = None
78497850
return
@@ -7855,7 +7856,8 @@ def try_parse_as_type_expression(self, maybe_type_expr: Expression) -> None:
78557856
break
78567857
next_leftmost = leftmost
78577858
if isinstance(leftmost, NameExpr):
7858-
if isinstance(leftmost.node, Var):
7859+
if (isinstance(leftmost.node, Var) and
7860+
not self.var_is_typing_special_form(leftmost.node)):
78597861
# Leftmost part of IndexExpr refers to a Var. Not a valid type.
78607862
maybe_type_expr.as_type = None
78617863
return
@@ -7903,6 +7905,21 @@ def try_parse_as_type_expression(self, maybe_type_expr: Expression) -> None:
79037905

79047906
maybe_type_expr.as_type = t
79057907

7908+
@staticmethod
7909+
def var_is_typing_special_form(var: Var) -> bool:
7910+
return (
7911+
var.fullname.startswith('typing') and
7912+
var.fullname in [
7913+
'typing.Annotated', 'typing_extensions.Annotated',
7914+
'typing.Callable',
7915+
'typing.Literal', 'typing_extensions.Literal',
7916+
'typing.Optional',
7917+
'typing.TypeGuard', 'typing_extensions.TypeGuard',
7918+
'typing.TypeIs', 'typing_extensions.TypeIs',
7919+
'typing.Union',
7920+
]
7921+
)
7922+
79067923
@contextmanager
79077924
def isolated_error_analysis(self) -> Iterator[None]:
79087925
"""

test-data/unit/check-typeform.test

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,103 @@ else:
683683
[typing fixtures/typing-full.pyi]
684684

685685

686+
-- Type Expressions Assignable To TypeForm Variable
687+
688+
[case testEveryKindOfTypeExpressionIsAssignableToATypeFormVariable]
689+
# flags: --python-version 3.14 --enable-incomplete-feature=TypeForm
690+
# NOTE: Importing Callable from collections.abc also works OK
691+
from typing import (
692+
Any, Callable, Dict, List, Literal, LiteralString, NoReturn,
693+
Optional, ParamSpec, Self, Type, TypeGuard, TypeVar, Union,
694+
)
695+
from typing_extensions import (
696+
Annotated, Concatenate, Never, TypeAlias, TypeForm, TypeIs,
697+
)
698+
#
699+
class SomeClass:
700+
pass
701+
SomeTypeAlias: TypeAlias = SomeClass
702+
SomeTypeVar = TypeVar('SomeTypeVar')
703+
type IntTuple[*Ts] = tuple[int, *Ts]
704+
P = ParamSpec('P')
705+
R = TypeVar('R')
706+
#
707+
typx: TypeForm
708+
# Begin rules taken from: https://typing.python.org/en/latest/spec/annotations.html#grammar-token-expression-grammar-type_expression
709+
# <Any>
710+
typx = Any
711+
class SelfBinder:
712+
def bind_self(self) -> Self:
713+
typx: TypeForm
714+
# <Self> (valid only in some contexts)
715+
typx = Self
716+
return self
717+
# <LiteralString>
718+
typx = LiteralString
719+
# <NoReturn>
720+
typx = NoReturn
721+
# <Never>
722+
typx = Never
723+
# <None>
724+
typx = None
725+
# name (where name must refer to a valid in-scope class)
726+
typx = SomeClass
727+
# name (where name must refer to a valid in-scope type alias)
728+
typx = SomeTypeAlias
729+
# name (where name must refer to a valid in-scope TypeVar)
730+
# NOTE: Unbound TypeVar isn't currently accepted as a TypeForm. Is that OK?
731+
typx = SomeTypeVar # E: Incompatible types in assignment (expression has type "TypeVar", variable has type "TypeForm[Any]")
732+
# name '[' type_expression (',' type_expression)* ']'
733+
typx = Dict[str, int]
734+
# (TODO: Add: name '[' unpacked ']')
735+
# (TODO: Add: name '[' type_expression_list (',' type_expression_list)* ']')
736+
# name '[' '(' ')' ']' (denoting specialization with an empty TypeVarTuple)
737+
typx = IntTuple[()]
738+
# <Literal> '[' expression (',' expression) ']'
739+
typx = Literal[1]
740+
# type_expression '|' type_expression
741+
typx = int | str
742+
# <Optional> '[' type_expression ']'
743+
typx = Optional[str]
744+
# <Union> '[' type_expression (',' type_expression)* ']'
745+
typx = Union[int, str]
746+
# <type> '[' <Any> ']'
747+
typx = type[Any]
748+
# <type> '[' name ']' (where name must refer to a valid in-scope class)
749+
typx = type[int]
750+
# (TODO: Add: <type> '[' name ']' (where name must refer to a valid in-scope TypeVar))
751+
# <Callable> '[' '...' ',' type_expression ']'
752+
typx = Callable[..., str]
753+
def bind_R(input: R) -> R:
754+
typx: TypeForm
755+
# <Callable> '[' name ',' type_expression ']' (where name must be a valid in-scope ParamSpec)
756+
typx = Callable[P, R]
757+
# <Callable> '[' <Concatenate> '[' (type_expression ',')+
758+
# (name | '...') ']' ',' type_expression ']'
759+
# (where name must be a valid in-scope ParamSpec)
760+
typx = Callable[Concatenate[int, P], R]
761+
return input
762+
# <Callable> '[' '[' maybe_unpacked (',' maybe_unpacked)* ']' ',' type_expression ']'
763+
typx = Callable[[int, str], None]
764+
# <tuple> '[' '(' ')' ']' (representing an empty tuple)
765+
typx = tuple[()]
766+
# <tuple> '[' type_expression ',' '...' ']' (representing an arbitrary-length tuple)
767+
typx = tuple[int, ...]
768+
# <tuple> '[' maybe_unpacked (',' maybe_unpacked)* ']'
769+
typx = tuple[int, str]
770+
# <Annotated> '[' type_expression ',' expression (',' expression)* ']'
771+
typx = Annotated[str, 'uppercase']
772+
# <TypeGuard> '[' type_expression ']' (valid only in some contexts)
773+
typx = TypeGuard[List[str]]
774+
# <TypeIs> '[' type_expression ']' (valid only in some contexts)
775+
typx = TypeIs[List[str]]
776+
# string_annotation (must evaluate to a valid type_expression)
777+
typx = 'int | str'
778+
# End rules
779+
[builtins fixtures/primitives.pyi]
780+
[typing fixtures/typing-full.pyi]
781+
782+
686783
-- Misc
687784

688785
[case testTypeFormHasAllObjectAttributesAndMethods]

0 commit comments

Comments
 (0)