Skip to content

Commit 5282a98

Browse files
committed
[mypyc] Don't load forward ref targets while setting up non-ext __annotations__
Take this example: from typing import NamedTuple class VTableMethod(NamedTuple): cls: "ClassIR" class ClassIR: pass In irbuild::classdef::add_non_ext_class_attr_ann(), mypyc tries to assign the ClassIR type object to VTableMethod's __annotations__. This causes a segfault as ClassIR won't be initialized and allocated until *after* the NamedTuple is set up. Fortunately, AssignmentStmt preserves the unanalyzed type (UnboundType). If `stmt.unanalyzed_type.orginal_str_expr` is not None, then we know we're dealing with a forward ref and should just load the string instead. Unfortunately, it seems difficult (or impossible?) to infer whether an annotation is a forward reference when the annotations future is enabled and the annotation isn't a string.
1 parent 2413578 commit 5282a98

File tree

2 files changed

+15
-5
lines changed

2 files changed

+15
-5
lines changed

mypyc/irbuild/classdef.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ def add_non_ext_class_attr_ann(
556556
get_type_info: Callable[[AssignmentStmt], TypeInfo | None] | None = None,
557557
) -> None:
558558
"""Add a class attribute to __annotations__ of a non-extension class."""
559+
# FIXME: try to better preserve the special forms and type parameters of generics.
559560
typ: Value | None = None
560561
if get_type_info is not None:
561562
type_info = get_type_info(stmt)
@@ -565,7 +566,14 @@ def add_non_ext_class_attr_ann(
565566
if typ is None:
566567
# FIXME: if get_type_info is not provided, don't fall back to stmt.type?
567568
ann_type = get_proper_type(stmt.type)
568-
if isinstance(ann_type, Instance):
569+
if stmt.unanalyzed_type.original_str_expr is not None:
570+
# Annotation is a forward reference, so don't attempt to load the actual
571+
# type and load the string instead.
572+
#
573+
# TODO: is it possible to determine whether a non-string annotation is
574+
# actually a forward reference due to the __annotations__ future?
575+
typ = builder.load_str(stmt.unanalyzed_type.original_str_expr)
576+
elif isinstance(ann_type, Instance):
569577
typ = load_type(builder, ann_type.type, stmt.line)
570578
else:
571579
typ = builder.add(LoadAddress(type_object_op.type, type_object_op.src, stmt.line))

mypyc/test-data/run-tuples.test

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,6 @@ assert f(Sub(3, 2)) == 3
9999
[case testNamedTupleClassSyntax]
100100
from typing import Dict, List, NamedTuple, Optional, Tuple, Union
101101

102-
class ClassIR: pass
103-
104102
class FuncIR: pass
105103

106104
StealsDescription = Union[bool, List[bool]]
@@ -119,8 +117,12 @@ class Record(NamedTuple):
119117
ordering: Optional[List[int]]
120118
extra_int_constants: List[Tuple[int]]
121119

120+
# Make sure mypyc loads the annotation string for this forward reference.
121+
# Ref: https://github.com/mypyc/mypyc/issues/938
122+
class ClassIR: pass
123+
122124
[file driver.py]
123-
from typing import Optional
125+
from typing import ForwardRef, Optional
124126
from native import ClassIR, FuncIR, Record
125127

126128
assert Record.__annotations__ == {
@@ -129,7 +131,7 @@ assert Record.__annotations__ == {
129131
'is_borrowed': bool,
130132
'hash': str,
131133
'python_path': tuple,
132-
'type': ClassIR,
134+
'type': ForwardRef('ClassIR'),
133135
'method': FuncIR,
134136
'shadow_method': type,
135137
'classes': dict,

0 commit comments

Comments
 (0)