Skip to content

Commit

Permalink
[mypyc] Don't load forward ref targets while setting up non-ext __ann…
Browse files Browse the repository at this point in the history
…otations__

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.
  • Loading branch information
ichard26 committed Jan 6, 2023
1 parent 2413578 commit 5282a98
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 5 deletions.
10 changes: 9 additions & 1 deletion mypyc/irbuild/classdef.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ def add_non_ext_class_attr_ann(
get_type_info: Callable[[AssignmentStmt], TypeInfo | None] | None = None,
) -> None:
"""Add a class attribute to __annotations__ of a non-extension class."""
# FIXME: try to better preserve the special forms and type parameters of generics.
typ: Value | None = None
if get_type_info is not None:
type_info = get_type_info(stmt)
Expand All @@ -565,7 +566,14 @@ def add_non_ext_class_attr_ann(
if typ is None:
# FIXME: if get_type_info is not provided, don't fall back to stmt.type?
ann_type = get_proper_type(stmt.type)
if isinstance(ann_type, Instance):
if stmt.unanalyzed_type.original_str_expr is not None:
# Annotation is a forward reference, so don't attempt to load the actual
# type and load the string instead.
#
# TODO: is it possible to determine whether a non-string annotation is
# actually a forward reference due to the __annotations__ future?
typ = builder.load_str(stmt.unanalyzed_type.original_str_expr)
elif isinstance(ann_type, Instance):
typ = load_type(builder, ann_type.type, stmt.line)
else:
typ = builder.add(LoadAddress(type_object_op.type, type_object_op.src, stmt.line))
Expand Down
10 changes: 6 additions & 4 deletions mypyc/test-data/run-tuples.test
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,6 @@ assert f(Sub(3, 2)) == 3
[case testNamedTupleClassSyntax]
from typing import Dict, List, NamedTuple, Optional, Tuple, Union

class ClassIR: pass

class FuncIR: pass

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

# Make sure mypyc loads the annotation string for this forward reference.
# Ref: https://github.com/mypyc/mypyc/issues/938
class ClassIR: pass

[file driver.py]
from typing import Optional
from typing import ForwardRef, Optional
from native import ClassIR, FuncIR, Record

assert Record.__annotations__ == {
Expand All @@ -129,7 +131,7 @@ assert Record.__annotations__ == {
'is_borrowed': bool,
'hash': str,
'python_path': tuple,
'type': ClassIR,
'type': ForwardRef('ClassIR'),
'method': FuncIR,
'shadow_method': type,
'classes': dict,
Expand Down

0 comments on commit 5282a98

Please sign in to comment.