Skip to content

Commit d74b387

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 1a2c8e2 commit d74b387

35 files changed

+901
-62
lines changed

mypy/checker.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7459,7 +7459,10 @@ def add_any_attribute_to_type(self, typ: Type, name: str) -> Type:
74597459
fallback = typ.fallback.copy_with_extra_attr(name, any_type)
74607460
return typ.copy_modified(fallback=fallback)
74617461
if isinstance(typ, TypeType) and isinstance(typ.item, Instance):
7462-
return TypeType.make_normalized(self.add_any_attribute_to_type(typ.item, name))
7462+
return TypeType.make_normalized(
7463+
self.add_any_attribute_to_type(typ.item, name),
7464+
is_type_form=typ.is_type_form,
7465+
)
74637466
if isinstance(typ, TypeVarType):
74647467
return typ.copy_modified(
74657468
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
@@ -90,6 +90,7 @@
9090
TypeAliasExpr,
9191
TypeApplication,
9292
TypedDictExpr,
93+
TypeFormExpr,
9394
TypeInfo,
9495
TypeVarExpr,
9596
TypeVarTupleExpr,
@@ -4601,6 +4602,10 @@ def visit_cast_expr(self, expr: CastExpr) -> Type:
46014602
)
46024603
return target_type
46034604

4605+
def visit_type_form_expr(self, expr: TypeFormExpr) -> Type:
4606+
typ = expr.type
4607+
return TypeType.make_normalized(typ, line=typ.line, column=typ.column, is_type_form=True)
4608+
46044609
def visit_assert_type_expr(self, expr: AssertTypeExpr) -> Type:
46054610
source_type = self.accept(
46064611
expr.expr,
@@ -5831,6 +5836,7 @@ def accept(
58315836
old_is_callee = self.is_callee
58325837
self.is_callee = is_callee
58335838
try:
5839+
p_type_context = get_proper_type(type_context)
58345840
if allow_none_return and isinstance(node, CallExpr):
58355841
typ = self.visit_call_expr(node, allow_none_return=True)
58365842
elif allow_none_return and isinstance(node, YieldFromExpr):
@@ -5839,6 +5845,17 @@ def accept(
58395845
typ = self.visit_conditional_expr(node, allow_none_return=True)
58405846
elif allow_none_return and isinstance(node, AwaitExpr):
58415847
typ = self.visit_await_expr(node, allow_none_return=True)
5848+
elif (
5849+
isinstance(p_type_context, TypeType) and
5850+
p_type_context.is_type_form and
5851+
node.as_type is not None
5852+
):
5853+
typ = TypeType.make_normalized(
5854+
node.as_type,
5855+
line=node.as_type.line,
5856+
column=node.as_type.column,
5857+
is_type_form=True,
5858+
)
58425859
else:
58435860
typ = node.accept(self)
58445861
except Exception as err:

mypy/copytype.py

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

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

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

mypy/erasetype.py

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

135135
def visit_type_type(self, t: TypeType) -> ProperType:
136-
return TypeType.make_normalized(t.item.accept(self), line=t.line)
136+
return TypeType.make_normalized(t.item.accept(self), line=t.line, is_type_form=t.is_type_form)
137137

138138
def visit_type_alias_type(self, t: TypeAliasType) -> ProperType:
139139
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
@@ -500,7 +500,7 @@ def visit_type_type(self, t: TypeType) -> Type:
500500
# union of instances or Any). Sadly we can't report errors
501501
# here yet.
502502
item = t.item.accept(self)
503-
return TypeType.make_normalized(item)
503+
return TypeType.make_normalized(item, is_type_form=t.is_type_form)
504504

505505
def visit_type_alias_type(self, t: TypeAliasType) -> Type:
506506
# 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
@@ -657,7 +657,11 @@ def visit_partial_type(self, t: PartialType) -> ProperType:
657657

658658
def visit_type_type(self, t: TypeType) -> ProperType:
659659
if isinstance(self.s, TypeType):
660-
return TypeType.make_normalized(join_types(t.item, self.s.item), line=t.line)
660+
return TypeType.make_normalized(
661+
join_types(t.item, self.s.item),
662+
line=t.line,
663+
is_type_form=self.s.is_type_form or t.is_type_form,
664+
)
661665
elif isinstance(self.s, Instance) and self.s.type.fullname == "builtins.type":
662666
return self.s
663667
else:

mypy/literals.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
TypeAliasExpr,
4848
TypeApplication,
4949
TypedDictExpr,
50+
TypeFormExpr,
5051
TypeVarExpr,
5152
TypeVarTupleExpr,
5253
UnaryExpr,
@@ -230,6 +231,9 @@ def visit_slice_expr(self, e: SliceExpr) -> None:
230231
def visit_cast_expr(self, e: CastExpr) -> None:
231232
return None
232233

234+
def visit_type_form_expr(self, e: TypeFormExpr) -> None:
235+
return None
236+
233237
def visit_assert_type_expr(self, e: AssertTypeExpr) -> None:
234238
return None
235239

mypy/meet.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,27 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type:
157157
elif isinstance(narrowed, TypeVarType) and is_subtype(narrowed.upper_bound, declared):
158158
return narrowed
159159
elif isinstance(declared, TypeType) and isinstance(narrowed, TypeType):
160-
return TypeType.make_normalized(narrow_declared_type(declared.item, narrowed.item))
160+
return TypeType.make_normalized(
161+
narrow_declared_type(declared.item, narrowed.item),
162+
is_type_form=declared.is_type_form and narrowed.is_type_form,
163+
)
161164
elif (
162165
isinstance(declared, TypeType)
163166
and isinstance(narrowed, Instance)
164167
and narrowed.type.is_metaclass()
165168
):
169+
if declared.is_type_form:
170+
# The declared TypeForm[T] after narrowing must be a kind of
171+
# type object at least as narrow as Type[T]
172+
return narrow_declared_type(
173+
TypeType.make_normalized(
174+
declared.item,
175+
line=declared.line,
176+
column=declared.column,
177+
is_type_form=False,
178+
),
179+
original_narrowed,
180+
)
166181
# We'd need intersection types, so give up.
167182
return original_declared
168183
elif isinstance(declared, Instance):
@@ -1039,7 +1054,11 @@ def visit_type_type(self, t: TypeType) -> ProperType:
10391054
if isinstance(self.s, TypeType):
10401055
typ = self.meet(t.item, self.s.item)
10411056
if not isinstance(typ, NoneType):
1042-
typ = TypeType.make_normalized(typ, line=t.line)
1057+
typ = TypeType.make_normalized(
1058+
typ,
1059+
line=t.line,
1060+
is_type_form=self.s.is_type_form and t.is_type_form,
1061+
)
10431062
return typ
10441063
elif isinstance(self.s, Instance) and self.s.type.fullname == "builtins.type":
10451064
return t

mypy/messages.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2675,7 +2675,10 @@ def format_literal_value(typ: LiteralType) -> str:
26752675
elif isinstance(typ, UninhabitedType):
26762676
return "Never"
26772677
elif isinstance(typ, TypeType):
2678-
type_name = "type" if options.use_lowercase_names() else "Type"
2678+
if typ.is_type_form:
2679+
type_name = "TypeForm"
2680+
else:
2681+
type_name = "type" if options.use_lowercase_names() else "Type"
26792682
return f"{type_name}[{format(typ.item)}]"
26802683
elif isinstance(typ, FunctionLike):
26812684
func = typ

0 commit comments

Comments
 (0)