8000 [PEP 747] Recognize TypeForm[T] type and values (#9773) by davidfstr · Pull Request #18690 · python/mypy · GitHub
[go: up one dir, main page]

Skip to content

[PEP 747] Recognize TypeForm[T] type and values (#9773) #18690

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ca4c79f
[PEP 747] Recognize TypeForm[T] type and values (#9773)
davidfstr Sep 29, 2024
9dcfc23
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 16, 2025
5cb1da5
Eliminate use of Type Parameter Syntax, which only works on Python >=…
davidfstr Feb 19, 2025
3ece208
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 19, 2025
799bc48
Remove INPUTS directory
davidfstr Feb 19, 2025
6bda848
WIP: Don't declare attributes on Expression to avoid confusing mypyc
davidfstr Feb 20, 2025
9df0e6b
SQ -> WIP: Don't declare attributes on Expression to avoid confusing …
davidfstr Feb 21, 2025
a2c318b
SQ -> WIP: Don't declare attributes on Expression to avoid confusing …
davidfstr Feb 21, 2025
f398374
Recognize non-string type expressions everywhere lazily. Optimize eag…
davidfstr Feb 25, 2025
bd5911f
NOMERGE: Disable test that is already failing on master branch
davidfstr Mar 4, 2025
3801bcc
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 4, 2025
c4db784
Fix mypyc errors: Replace EllipsisType with NotParsed type
davidfstr Mar 4, 2025
29fe65a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 4, 2025
4134250
mypy_primer: Enable TypeForm feature when checking effects on open so…
davidfstr Mar 5, 2025
5fb5bd8
Revert "mypy_primer: Enable TypeForm feature when checking effects on…
davidfstr Mar 8, 2025
54cd64d
NOMERGE: mypy_primer: Enable --enable-incomplete-feature=TypeForm whe…
davidfstr Mar 8, 2025
075980b
Ignore warnings like: <type_comment>:1: SyntaxWarning: invalid escape…
davidfstr Mar 14, 2025
6797cac
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 14, 2025
4162a35
Rerun CI
davidfstr Mar 17, 2025
d1aafcd
Improve warning message when string annotation used in TypeForm context
davidfstr Mar 18, 2025
1d9620d
Print error code for the MAYBE_UNRECOGNIZED_STR_TYPEFORM note
davidfstr Mar 26, 2025
76cae61
Document the [maybe-unrecognized-str-typeform] error code
davidfstr Mar 26, 2025
170a5e7
Fix doc generation warning
davidfstr Mar 26, 2025
84c06a6
Merge branch 'master' into f/typeform3
davidfstr Apr 28, 2025
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
Prev Previous commit
Next Next commit
[pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
  • Loading branch information
pre-commit-ci[bot] committed Feb 16, 2025
commit 9dcfc23d13a615522b6db3153948fa1aadcf5ef0
3 changes: 1 addition & 2 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -7787,8 +7787,7 @@ def add_any_attribute_to_type(self, typ: Type, name: str) -> Type:
return typ.copy_modified(fallback=fallback)
if isinstance(typ, TypeType) and isinstance(typ.item, Instance):
return TypeType.make_normalized(
self.add_any_attribute_to_type(typ.item, name),
is_type_form=typ.is_type_form,
self.add_any_attribute_to_type(typ.item, name), is_type_form=typ.is_type_form
)
if isinstance(typ, TypeVarType):
return typ.copy_modified(
Expand Down
6 changes: 3 additions & 3 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -5947,9 +5947,9 @@ def accept(
elif allow_none_return and isinstance(node, AwaitExpr):
typ = self.visit_await_expr(node, allow_none_return=True)
elif (
isinstance(p_type_context, TypeType) and
p_type_context.is_type_form and
node.as_type is not None
isinstance(p_type_context, TypeType)
and p_type_context.is_type_form
and node.as_type is not None
):
typ = TypeType.make_normalized(
node.as_type,
Expand Down
4 changes: 3 additions & 1 deletion mypy/erasetype.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,9 @@ def visit_union_type(self, t: UnionType) -> ProperType:
return make_simplified_union(erased_items)

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

def visit_type_alias_type(self, t: TypeAliasType) -> ProperType:
raise RuntimeError("Type aliases should be expanded before accepting this visitor")
Expand Down
9 changes: 2 additions & 7 deletions mypy/meet.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,7 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type:
# type object at least as narrow as Type[T]
return narrow_declared_type(
TypeType.make_normalized(
declared.item,
line=declared.line,
column=declared.column,
is_type_form=False,
declared.item, line=declared.line, column=declared.column, is_type_form=False
),
original_narrowed,
)
Expand Down Expand Up @@ -1090,9 +1087,7 @@ def visit_type_type(self, t: TypeType) -> ProperType:
typ = self.meet(t.item, self.s.item)
if not isinstance(typ, NoneType):
typ = TypeType.make_normalized(
typ,
line=t.line,
is_type_form=self.s.is_type_form and t.is_type_form,
typ, line=t.line, is_type_form=self.s.is_type_form and t.is_type_form
)
return typ
elif isinstance(self.s, Instance) and self.s.type.fullname == "builtins.type":
Expand Down
2 changes: 1 addition & 1 deletion mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ class Expression(Node):
# a different superclass with its own __slots__. A subclass in
# Python is not allowed to have multiple superclasses that define
# __slots__.
#__slots__ = ('as_type',)
# __slots__ = ('as_type',)

# If this value expression can also be parsed as a valid type expression,
# represents the type denoted by the type expression.
Expand Down
17 changes: 10 additions & 7 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@
type_aliases_source_versions,
typing_extensions_aliases,
)
from mypy.options import Options, TYPE_FORM
from mypy.options import TYPE_FORM, Options
from mypy.patterns import (
AsPattern,
ClassPattern,
Expand Down Expand Up @@ -5799,9 +5799,7 @@ def visit_call_expr(self, expr: CallExpr) -> None:
with self.allow_unbound_tvars_set():
for a in expr.args:
a.accept(self)
elif refers_to_fullname(
expr.callee, ("typing.TypeForm", "typing_extensions.TypeForm")
):
elif refers_to_fullname(expr.callee, ("typing.TypeForm", "typing_extensions.TypeForm")):
# Special form TypeForm(...).
if not self.check_fixed_args(expr, 1, "TypeForm"):
return
Expand Down Expand Up @@ -7620,15 +7618,19 @@ def visit_pass_stmt(self, o: PassStmt, /) -> None:
def visit_singleton_pattern(self, o: SingletonPattern, /) -> None:
return None

def try_parse_as_type_expression(self, maybe_type_expr: Expression) -> Type|None:
def try_parse_as_type_expression(self, maybe_type_expr: Expression) -> Type | None:
"""Try to parse maybe_type_expr as a type expression.
If parsing fails return None and emit no errors."""
# Save SemanticAnalyzer state
original_errors = self.errors # altered by fail()
original_num_incomplete_refs = self.num_incomplete_refs # altered by record_incomplete_ref()
original_num_incomplete_refs = (
self.num_incomplete_refs
) # altered by record_incomplete_ref()
original_progress = self.progress # altered by defer()
original_deferred = self.deferred # altered by defer()
original_deferral_debug_context_len = len(self.deferral_debug_context) # altered by defer()
original_deferral_debug_context_len = len(
self.deferral_debug_context
) # altered by defer()

self.errors = Errors(Options())
try:
Expand All @@ -7649,6 +7651,7 @@ def try_parse_as_type_expression(self, maybe_type_expr: Expression) -> Type|None
del self.deferral_debug_context[original_deferral_debug_context_len:]
return t


def replace_implicit_first_type(sig: FunctionLike, new: Type) -> FunctionLike:
if isinstance(sig, CallableType):
if len(sig.arg_types) == 0:
Expand Down
5 changes: 1 addition & 4 deletions mypy/type_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,10 +326,7 @@ def visit_overloaded(self, t: Overloaded, /) -> Type:

def visit_type_type(self, t: TypeType, /) -> Type:
return TypeType.make_normalized(
t.item.accept(self),
line=t.line,
column=t.column,
is_type_form=t.is_type_form,
t.item.accept(self), line=t.line, column=t.column, is_type_form=t.is_type_form
)

@abstractmethod
Expand Down
8 changes: 5 additions & 3 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
check_arg_names,
get_nongen_builtins,
)
from mypy.options import INLINE_TYPEDDICT, Options, TYPE_FORM
from mypy.options import INLINE_TYPEDDICT, TYPE_FORM, Options
from mypy.plugin import AnalyzeTypeContext, Plugin, TypeAnalyzerPluginInterface
from mypy.semanal_shared import (
SemanticAnalyzerCoreInterface,
Expand Down Expand Up @@ -1363,7 +1363,7 @@ def visit_typeddict_type(self, t: TypedDictType) -> Type:
try:
fallback = self.named_type("typing._TypedDict")
except AssertionError as e:
if str(e) == 'typing._TypedDict':
if str(e) == "typing._TypedDict":
# Can happen when running mypy tests, typing._TypedDict
# is not defined by typing.pyi stubs, and
# try_parse_as_type_expression() is called on an dict
Expand Down Expand Up @@ -1453,7 +1453,9 @@ def visit_ellipsis_type(self, t: EllipsisType) -> Type:
return AnyType(TypeOfAny.from_error)

def visit_type_type(self, t: TypeType) -> Type:
return TypeType.make_normalized(self.anal_type(t.item), line=t.line, is_type_form=t.is_type_form)
return TypeType.make_normalized(
self.anal_type(t.item), line=t.line, is_type_form=t.is_type_form
)

def visit_placeholder_type(self, t: PlaceholderType) -> Type:
n = (
Expand Down
16 changes: 12 additions & 4 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3089,7 +3089,7 @@ class TypeType(ProperType):
assumption).
"""

__slots__ = ("item", "is_type_form",)
__slots__ = ("item", "is_type_form")

# This can't be everything, but it can be a class reference,
# a generic class instance, a union, Any, a type variable...
Expand All @@ -3115,7 +3115,9 @@ def __init__(
self.is_type_form = is_type_form

@staticmethod
def make_normalized(item: Type, *, line: int = -1, column: int = -1, is_type_form: bool = False) -> ProperType:
def make_normalized(
item: Type, *, line: int = -1, column: int = -1, is_type_form: bool = False
) -> ProperType:
item = get_proper_type(item)
if is_type_form:
# Don't convert TypeForm[X | Y] to (TypeForm[X] | TypeForm[Y])
Expand All @@ -3141,12 +3143,18 @@ def __eq__(self, other: object) -> bool:
return self.item == other.item

def serialize(self) -> JsonDict:
return {".class": "TypeType", "item": self.item.serialize(), "is_type_form": self.is_type_form}
return {
".class": "TypeType",
"item": self.item.serialize(),
"is_type_form": self.is_type_form,
}

@classmethod
def deserialize(cls, data: JsonDict) -> Type:
assert data[".class"] == "TypeType"
return TypeType.make_normalized(deserialize_type(data["item"]), is_type_form=data["is_type_form"])
return TypeType.make_normalized(
deserialize_type(data["item"]), is_type_form=data["is_type_form"]
)


class PlaceholderType(ProperType):
Expand Down
Loading
0