Skip to content

Commit 6bda848

Browse files
committed
WIP: Don't declare attributes on Expression to avoid confusing mypyc
1 parent 799bc48 commit 6bda848

File tree

4 files changed

+31
-32
lines changed

4 files changed

+31
-32
lines changed

mypy/checkexpr.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
LambdaExpr,
7272
ListComprehension,
7373
ListExpr,
74+
MaybeTypeExpression,
7475
MemberExpr,
7576
MypyFile,
7677
NamedTupleExpr,
@@ -5949,6 +5950,7 @@ def accept(
59495950
elif (
59505951
isinstance(p_type_context, TypeType)
59515952
and p_type_context.is_type_form
5953+
and isinstance(node, MaybeTypeExpression)
59525954
and node.as_type is not None
59535955
):
59545956
typ = TypeType.make_normalized(

mypy/nodes.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -201,19 +201,7 @@ def accept(self, visitor: StatementVisitor[T]) -> T:
201201
class Expression(Node):
202202
"""An expression node."""
203203

204-
# NOTE: Cannot use __slots__ because some subclasses also inherit from
205-
# a different superclass with its own __slots__. A subclass in
206-
# Python is not allowed to have multiple superclasses that define
207-
# __slots__.
208-
# __slots__ = ('as_type',)
209-
210-
# If this value expression can also be parsed as a valid type expression,
211-
# represents the type denoted by the type expression.
212-
as_type: mypy.types.Type | None
213-
214-
def __init__(self, *args: Any, **kwargs: Any) -> None:
215-
super().__init__(*args, **kwargs)
216-
self.as_type = None
204+
__slots__ = ()
217205

218206
def accept(self, visitor: ExpressionVisitor[T]) -> T:
219207
raise RuntimeError("Not implemented", type(self))
@@ -2110,6 +2098,7 @@ class OpExpr(Expression):
21102098
"right_always",
21112099
"right_unreachable",
21122100
"analyzed",
2101+
"as_type",
21132102
)
21142103

21152104
__match_args__ = ("left", "op", "right")
@@ -2125,6 +2114,9 @@ class OpExpr(Expression):
21252114
right_unreachable: bool
21262115
# Used for expressions that represent a type "X | Y" in some contexts
21272116
analyzed: TypeAliasExpr | None
2117+
# If this value expression can also be parsed as a valid type expression,
2118+
# represents the type denoted by the type expression.
2119+
as_type: mypy.types.Type | None
21282120

21292121
def __init__(
21302122
self, op: str, left: Expression, right: Expression, analyzed: TypeAliasExpr | None = None
@@ -2137,11 +2129,18 @@ def __init__(
21372129
self.right_always = False
21382130
self.right_unreachable = False
21392131
self.analyzed = analyzed
2132+
self.as_type = None
21402133

21412134
def accept(self, visitor: ExpressionVisitor[T]) -> T:
21422135
return visitor.visit_op_expr(self)
21432136

21442137

2138+
# Expression subtypes that could represent the root of a valid type expression.
2139+
# Always contains an "as_type" attribute.
2140+
# TODO: Make this into a Protocol if mypyc is OK with that.
2141+
MaybeTypeExpression = OpExpr
2142+
2143+
21452144
class ComparisonExpr(Expression):
21462145
"""Comparison expression (e.g. a < b > c < d)."""
21472146

mypy/semanal.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
ListExpr,
137137
Lvalue,
138138
MatchStmt,
139+
MaybeTypeExpression,
139140
MemberExpr,
140141
MypyFile,
141142
NamedTupleExpr,
@@ -3541,7 +3542,7 @@ def analyze_lvalues(self, s: AssignmentStmt) -> None:
35413542

35423543
def analyze_rvalue_as_type_form(self, s: AssignmentStmt) -> None:
35433544
if TYPE_FORM in self.options.enable_incomplete_feature:
3544-
s.rvalue.as_type = self.try_parse_as_type_expression(s.rvalue)
3545+
self.try_parse_as_type_expression(s.rvalue)
35453546

35463547
def apply_dynamic_class_hook(self, s: AssignmentStmt) -> None:
35473548
if not isinstance(s.rvalue, CallExpr):
@@ -5278,7 +5279,7 @@ def visit_return_stmt(self, s: ReturnStmt) -> None:
52785279
if s.expr:
52795280
s.expr.accept(self)
52805281
if TYPE_FORM in self.options.enable_incomplete_feature:
5281-
s.expr.as_type = self.try_parse_as_type_expression(s.expr)
5282+
self.try_parse_as_type_expression(s.expr)
52825283

52835284
def visit_raise_stmt(self, s: RaiseStmt) -> None:
52845285
self.statement = s
@@ -5823,7 +5824,7 @@ def visit_call_expr(self, expr: CallExpr) -> None:
58235824
for a in expr.args:
58245825
a.accept(self)
58255826
if calculate_type_forms:
5826-
a.as_type = self.try_parse_as_type_expression(a)
5827+
self.try_parse_as_type_expression(a)
58275828

58285829
if (
58295830
isinstance(expr.callee, MemberExpr)
@@ -7618,9 +7619,16 @@ def visit_pass_stmt(self, o: PassStmt, /) -> None:
76187619
def visit_singleton_pattern(self, o: SingletonPattern, /) -> None:
76197620
return None
76207621

7621-
def try_parse_as_type_expression(self, maybe_type_expr: Expression) -> Type | None:
7622-
"""Try to parse maybe_type_expr as a type expression.
7623-
If parsing fails return None and emit no errors."""
7622+
def try_parse_as_type_expression(self, maybe_type_expr: Expression) -> None:
7623+
"""Try to parse a value Expression as a type expression.
7624+
If success then annotate the Expression with the type that it spells.
7625+
If fails then emit no errors and take no further action.
7626+
7627+
A value expression that is parsable as a type expression may be used
7628+
where a TypeForm is expected to represent the spelled type."""
7629+
if not isinstance(maybe_type_expr, MaybeTypeExpression):
7630+
return
7631+
76247632
# Save SemanticAnalyzer state
76257633
original_errors = self.errors # altered by fail()
76267634
original_num_incomplete_refs = (
@@ -7649,7 +7657,8 @@ def try_parse_as_type_expression(self, maybe_type_expr: Expression) -> Type | No
76497657
self.progress = original_progress
76507658
self.deferred = original_deferred
76517659
del self.deferral_debug_context[original_deferral_debug_context_len:]
7652-
return t
7660+
7661+
maybe_type_expr.as_type = t
76537662

76547663

76557664
def replace_implicit_first_type(sig: FunctionLike, new: Type) -> FunctionLike:

mypy/typeanal.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,18 +1360,7 @@ def visit_typeddict_type(self, t: TypedDictType) -> Type:
13601360
t,
13611361
)
13621362
required_keys = req_keys
1363-
try:
1364-
fallback = self.named_type("typing._TypedDict")
1365-
except AssertionError as e:
1366-
if str(e) == "typing._TypedDict":
1367-
# Can happen when running mypy tests, typing._TypedDict
1368-
# is not defined by typing.pyi stubs, and
1369-
# try_parse_as_type_expression() is called on an dict
1370-
# expression that looks like an inline TypedDict type.
1371-
self.fail("Internal error: typing._TypedDict not found", t)
1372-
fallback = self.named_type("builtins.object")
1373-
else:
1374-
raise
1363+
fallback = self.named_type("typing._TypedDict")
13751364
for typ in t.extra_items_from:
13761365
analyzed = self.analyze_type(typ)
13771366
p_analyzed = get_proper_type(analyzed)

0 commit comments

Comments
 (0)