Skip to content

Commit ca4c79f

Browse files
committed
[PEP 747] Recognize TypeForm[T] type and values (#9773)
User must opt-in to use TypeForm with --enable-incomplete-feature=TypeForm In particular: * Recognize TypeForm[T] as a kind of type that can be used in a type expression * Recognize a type expression literal as a TypeForm value in: - assignments - function calls - return statements * Define the following relationships between TypeForm values: - is_subtype - join_types - meet_types * Recognize the TypeForm(...) expression * Alter isinstance(typx, type) to narrow TypeForm[T] to Type[T]
1 parent 52c7735 commit ca4c79f

35 files changed

+900
-62
lines changed

mypy/checker.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7786,7 +7786,10 @@ def add_any_attribute_to_type(self, typ: Type, name: str) -> Type:
77867786
fallback = typ.fallback.copy_with_extra_attr(name, any_type)
77877787
return typ.copy_modified(fallback=fallback)
77887788
if isinstance(typ, TypeType) and isinstance(typ.item, Instance):
7789-
return TypeType.make_normalized(self.add_any_attribute_to_type(typ.item, name))
7789+
return TypeType.make_normalized(
7790+
self.add_any_attribute_to_type(typ.item, name),
7791+
is_type_form=typ.is_type_form,
7792+
)
77907793
if isinstance(typ, TypeVarType):
77917794
return typ.copy_modified(
77927795
upper_bound=self.add_any_attribute_to_type(typ.upper_bound, name),

mypy/checkexpr.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
TypeAliasExpr,
9797
TypeApplication,
9898
TypedDictExpr,
99+
TypeFormExpr,
99100
TypeInfo,
100101
TypeVarExpr,
101102
TypeVarTupleExpr,
@@ -4688,6 +4689,10 @@ def visit_cast_expr(self, expr: CastExpr) -> Type:
46884689
)
46894690
return target_type
46904691

4692+
def visit_type_form_expr(self, expr: TypeFormExpr) -> Type:
4693+
typ = expr.type
4694+
return TypeType.make_normalized(typ, line=typ.line, column=typ.column, is_type_form=True)
4695+
46914696
def visit_assert_type_expr(self, expr: AssertTypeExpr) -> Type:
46924697
source_type = self.accept(
46934698
expr.expr,
@@ -5932,6 +5937,7 @@ def accept(
59325937
old_is_callee = self.is_callee
59335938
self.is_callee = is_callee
59345939
try:
5940+
p_type_context = get_proper_type(type_context)
59355941
if allow_none_return and isinstance(node, CallExpr):
59365942
typ = self.visit_call_expr(node, allow_none_return=True)
59375943
elif allow_none_return and isinstance(node, YieldFromExpr):
@@ -5940,6 +5946,17 @@ def accept(
59405946
typ = self.visit_conditional_expr(node, allow_none_return=True)
59415947
elif allow_none_return and isinstance(node, AwaitExpr):
59425948
typ = self.visit_await_expr(node, allow_none_return=True)
5949+
elif (
5950+
isinstance(p_type_context, TypeType) and
5951+
p_type_context.is_type_form and
5952+
node.as_type is not None
5953+
):
5954+
typ = TypeType.make_normalized(
5955+
node.as_type,
5956+
line=node.as_type.line,
5957+
column=node.as_type.column,
5958+
is_type_form=True,
5959+
)
59435960
else:
59445961
typ = node.accept(self)
59455962
except Exception as err:

mypy/copytype.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def visit_overloaded(self, t: Overloaded) -> ProperType:
122122

123123
def visit_type_type(self, t: TypeType) -> ProperType:
124124
# Use cast since the type annotations in TypeType are imprecise.
125-
return self.copy_common(t, TypeType(cast(Any, t.item)))
125+
return self.copy_common(t, TypeType(cast(Any, t.item), is_type_form=t.is_type_form))
126126

127127
def visit_type_alias_type(self, t: TypeAliasType) -> ProperType:
128128
assert False, "only ProperTypes supported"

mypy/erasetype.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ def visit_union_type(self, t: UnionType) -> ProperType:
134134
return make_simplified_union(erased_items)
135135

136136
def visit_type_type(self, t: TypeType) -> ProperType:
137-
return TypeType.make_normalized(t.item.accept(self), line=t.line)
137+
return TypeType.make_normalized(t.item.accept(self), line=t.line, is_type_form=t.is_type_form)
138138

139139
def visit_type_alias_type(self, t: TypeAliasType) -> ProperType:
140140
raise RuntimeError("Type aliases should be expanded before accepting this visitor")

mypy/evalexpr.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ def visit_comparison_expr(self, o: mypy.nodes.ComparisonExpr) -> object:
7575
def visit_cast_expr(self, o: mypy.nodes.CastExpr) -> object:
7676
return o.expr.accept(self)
7777

78+
def visit_type_form_expr(self, o: mypy.nodes.TypeFormExpr) -> object:
79+
return UNKNOWN
80+
7881
def visit_assert_type_expr(self, o: mypy.nodes.AssertTypeExpr) -> object:
7982
return o.expr.accept(self)
8083

mypy/expandtype.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ def visit_type_type(self, t: TypeType) -> Type:
505505
# union of instances or Any). Sadly we can't report errors
506506
# here yet.
507507
item = t.item.accept(self)
508-
return TypeType.make_normalized(item)
508+
return TypeType.make_normalized(item, is_type_form=t.is_type_form)
509509

510510
def visit_type_alias_type(self, t: TypeAliasType) -> Type:
511511
# Target of the type alias cannot contain type variables (not bound by the type

mypy/join.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,11 @@ def visit_partial_type(self, t: PartialType) -> ProperType:
622622

623623
def visit_type_type(self, t: TypeType) -> ProperType:
624624
if isinstance(self.s, TypeType):
625-
return TypeType.make_normalized(join_types(t.item, self.s.item), line=t.line)
625+
return TypeType.make_normalized(
626+
join_types(t.item, self.s.item),
627+
line=t.line,
628+
is_type_form=self.s.is_type_form or t.is_type_form,
629+
)
626630
elif isinstance(self.s, Instance) and self.s.type.fullname == "builtins.type":
627631
return self.s
628632
else:

mypy/literals.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
TypeAliasExpr,
4949
TypeApplication,
5050
TypedDictExpr,
51+
TypeFormExpr,
5152
TypeVarExpr,
5253
TypeVarTupleExpr,
5354
UnaryExpr,
@@ -244,6 +245,9 @@ def visit_slice_expr(self, e: SliceExpr) -> None:
244245
def visit_cast_expr(self, e: CastExpr) -> None:
245246
return None
246247

248+
def visit_type_form_expr(self, e: TypeFormExpr) -> None:
249+
return None
250+
247251
def visit_assert_type_expr(self, e: AssertTypeExpr) -> None:
248252
return None
249253

mypy/meet.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,27 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type:
158158
elif isinstance(narrowed, TypeVarType) and is_subtype(narrowed.upper_bound, declared):
159159
return narrowed
160160
elif isinstance(declared, TypeType) and isinstance(narrowed, TypeType):
161-
return TypeType.make_normalized(narrow_declared_type(declared.item, narrowed.item))
161+
return TypeType.make_normalized(
162+
narrow_declared_type(declared.item, narrowed.item),
163+
is_type_form=declared.is_type_form and narrowed.is_type_form,
164+
)
162165
elif (
163166
isinstance(declared, TypeType)
164167
and isinstance(narrowed, Instance)
165168
and narrowed.type.is_metaclass()
166169
):
170+
if declared.is_type_form:
171+
# The declared TypeForm[T] after narrowing must be a kind of
172+
# type object at least as narrow as Type[T]
173+
return narrow_declared_type(
174+
TypeType.make_normalized(
175+
declared.item,
176+
line=declared.line,
177+
column=declared.column,
178+
is_type_form=False,
179+
),
180+
original_narrowed,
181+
)
167182
# We'd need intersection types, so give up.
168183
return original_declared
169184
elif isinstance(declared, Instance):
@@ -1074,7 +1089,11 @@ def visit_type_type(self, t: TypeType) -> ProperType:
10741089
if isinstance(self.s, TypeType):
10751090
typ = self.meet(t.item, self.s.item)
10761091
if not isinstance(typ, NoneType):
1077-
typ = TypeType.make_normalized(typ, line=t.line)
1092+
typ = TypeType.make_normalized(
1093+
typ,
1094+
line=t.line,
1095+
is_type_form=self.s.is_type_form and t.is_type_form,
1096+
)
10781097
return typ
10791098
elif isinstance(self.s, Instance) and self.s.type.fullname == "builtins.type":
10801099
return t

mypy/messages.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2721,7 +2721,10 @@ def format_literal_value(typ: LiteralType) -> str:
27212721
elif isinstance(typ, UninhabitedType):
27222722
return "Never"
27232723
elif isinstance(typ, TypeType):
2724-
type_name = "type" if options.use_lowercase_names() else "Type"
2724+
if typ.is_type_form:
2725+
type_name = "TypeForm"
2726+
else:
2727+
type_name = "type" if options.use_lowercase_names() else "Type"
27252728
return f"{type_name}[{format(typ.item)}]"
27262729
elif isinstance(typ, FunctionLike):
27272730
func = typ

0 commit comments

Comments
 (0)