Skip to content

Commit c8489a2

Browse files
authored
[mypyc] Handle non extention classes with attribute annotations for forward defined classes (#18577)
This PR makes `add_non_ext_class_attr_ann` behave the same way standard python handles modules with `from __future__ import annotations` by using string types. With this we can reference types declared further in the file. But since this will change in future versions of python, let's only do this for forward references, for types that are defined further down in the same module. This also works with string type annotations. Fixes mypyc/mypyc#992
1 parent 1f509ec commit c8489a2

File tree

2 files changed

+39
-1
lines changed

2 files changed

+39
-1
lines changed

mypyc/irbuild/classdef.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,16 @@ def add_non_ext_class_attr_ann(
625625
if get_type_info is not None:
626626
type_info = get_type_info(stmt)
627627
if type_info:
628-
typ = load_type(builder, type_info, stmt.line)
628+
# NOTE: Using string type information is similar to using
629+
# `from __future__ import annotations` in standard python.
630+
# NOTE: For string types we need to use the fullname since it
631+
# includes the module. If string type doesn't have the module,
632+
# @dataclass will try to get the current module and fail since the
633+
# current module is not in sys.modules.
634+
if builder.current_module == type_info.module_name and stmt.line < type_info.line:
635+
typ = builder.load_str(type_info.fullname)
636+
else:
637+
typ = load_type(builder, type_info, stmt.line)
629638

630639
if typ is None:
631640
# FIXME: if get_type_info is not provided, don't fall back to stmt.type?

mypyc/test-data/run-classes.test

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2719,3 +2719,32 @@ print(native.A(ints=[1, -17]).ints)
27192719

27202720
[out]
27212721
\[1, -17]
2722+
2723+
[case testDataclassClassReference]
2724+
from __future__ import annotations
2725+
from dataclasses import dataclass
2726+
2727+
class BackwardDefinedClass:
2728+
pass
2729+
2730+
@dataclass
2731+
class Data:
2732+
bitem: BackwardDefinedClass
2733+
bitems: 'BackwardDefinedClass'
2734+
fitem: ForwardDefinedClass
2735+
fitems: 'ForwardDefinedClass'
2736+
2737+
class ForwardDefinedClass:
2738+
pass
2739+
2740+
def test_function():
2741+
d = Data(
2742+
bitem=BackwardDefinedClass(),
2743+
bitems=BackwardDefinedClass(),
2744+
fitem=ForwardDefinedClass(),
2745+
fitems=ForwardDefinedClass(),
2746+
)
2747+
assert(isinstance(d.bitem, BackwardDefinedClass))
2748+
assert(isinstance(d.bitems, BackwardDefinedClass))
2749+
assert(isinstance(d.fitem, ForwardDefinedClass))
2750+
assert(isinstance(d.fitems, ForwardDefinedClass))

0 commit comments

Comments
 (0)