Skip to content

Commit 19a646b

Browse files
committed
Address PR review feedback for ClassVar self-references
- Replace class_body_is_ext with class_body_ir to avoid duplicating ClassIR.is_ext_class - Remove unnecessary save/restore of class body context (nested classes are rejected early) - Fix non-ext test to use native_class=False instead of allow_interpreted_subclasses=True - Add irbuild tests confirming CPyObject_GetAttr (ext) and CPyDict_GetItem (non-ext) generation
1 parent 24c6a56 commit 19a646b

5 files changed

Lines changed: 308 additions & 13 deletions

File tree

mypyc/irbuild/builder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ def __init__(
239239
# Without this, mypyc looks up such names in module globals, which fails.
240240
self.class_body_classvars: dict[str, None] = {}
241241
self.class_body_obj: Value | None = None
242-
self.class_body_is_ext: bool = False
242+
self.class_body_ir: ClassIR | None = None
243243

244244
# This list operates similarly to a function call stack for nested functions. Whenever a
245245
# function definition begins to be generated, a FuncInfo instance is added to the stack,

mypyc/irbuild/classdef.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,9 @@ def transform_class_def(builder: IRBuilder, cdef: ClassDef) -> None:
140140
# Set up class body context so that intra-class ClassVar references
141141
# (e.g. C = A | B where A is defined earlier in the same class) can be
142142
# resolved from the class being built instead of module globals.
143-
saved_classvars = builder.class_body_classvars
144-
saved_obj = builder.class_body_obj
145-
saved_is_ext = builder.class_body_is_ext
146143
builder.class_body_classvars = {}
147144
builder.class_body_obj = cls_builder.class_body_obj()
148-
builder.class_body_is_ext = ir.is_ext_class
145+
builder.class_body_ir = ir
149146

150147
for stmt in cdef.defs.body:
151148
if (
@@ -199,10 +196,10 @@ def transform_class_def(builder: IRBuilder, cdef: ClassDef) -> None:
199196
else:
200197
builder.error("Unsupported statement in class body", stmt.line)
201198

202-
# Restore previous class body context (handles nested classes).
203-
builder.class_body_classvars = saved_classvars
204-
builder.class_body_obj = saved_obj
205-
builder.class_body_is_ext = saved_is_ext
199+
# Clear class body context (nested classes are rejected above, so no need to save/restore).
200+
builder.class_body_classvars = {}
201+
builder.class_body_obj = None
202+
builder.class_body_ir = None
206203

207204
# Generate implicit property setters/getters
208205
for name, decl in ir.method_decls.items():

mypyc/irbuild/expression.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ def transform_name_expr(builder: IRBuilder, expr: NameExpr) -> Value:
217217
# in the same class, load it from the class being built (type object for ext classes,
218218
# class dict for non-ext classes) instead of module globals.
219219
if builder.class_body_obj is not None and expr.name in builder.class_body_classvars:
220-
if builder.class_body_is_ext:
220+
if builder.class_body_ir is not None and builder.class_body_ir.is_ext_class:
221221
return builder.py_get_attr(builder.class_body_obj, expr.name, expr.line)
222222
else:
223223
return builder.primitive_op(

mypyc/test-data/irbuild-classes.test

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2901,3 +2901,301 @@ class InvalidKwarg:
29012901
@mypyc_attr(str()) # E: All "mypyc_attr" positional arguments must be string literals.
29022902
class InvalidLiteral:
29032903
pass
2904+
2905+
[case testClassVarSelfReferenceExt_toplevel]
2906+
from typing import ClassVar, Set
2907+
2908+
class Ext:
2909+
A: ClassVar[Set[int]] = {1, 2}
2910+
B: ClassVar[Set[int]] = A | {3}
2911+
[out]
2912+
def __top_level__():
2913+
r0, r1 :: object
2914+
r2 :: bit
2915+
r3 :: str
2916+
r4, r5 :: object
2917+
r6 :: str
2918+
r7 :: dict
2919+
r8, r9 :: object
2920+
r10 :: str
2921+
r11, r12 :: object
2922+
r13, r14 :: bool
2923+
r15 :: str
2924+
r16 :: tuple
2925+
r17 :: i32
2926+
r18 :: bit
2927+
r19 :: dict
2928+
r20 :: str
2929+
r21 :: i32
2930+
r22 :: bit
2931+
r23 :: object
2932+
r24 :: set
2933+
r25 :: object
2934+
r26 :: i32
2935+
r27 :: bit
2936+
r28 :: object
2937+
r29 :: i32
2938+
r30 :: bit
2939+
r31 :: str
2940+
r32 :: i32
2941+
r33 :: bit
2942+
r34 :: object
2943+
r35 :: str
2944+
r36 :: object
2945+
r37, r38 :: set
2946+
r39 :: object
2947+
r40 :: i32
2948+
r41 :: bit
2949+
r42 :: object
2950+
r43 :: set
2951+
r44 :: str
2952+
r45 :: i32
2953+
r46 :: bit
2954+
r47 :: bool
2955+
L0:
2956+
r0 = builtins :: module
2957+
r1 = load_address _Py_NoneStruct
2958+
r2 = r0 != r1
2959+
if r2 goto L2 else goto L1 :: bool
2960+
L1:
2961+
r3 = 'builtins'
2962+
r4 = PyImport_Import(r3)
2963+
builtins = r4 :: module
2964+
L2:
2965+
r5 = ('ClassVar', 'Set')
2966+
r6 = 'typing'
2967+
r7 = __main__.globals :: static
2968+
r8 = CPyImport_ImportFromMany(r6, r5, r5, r7)
2969+
typing = r8 :: module
2970+
r9 = <error> :: object
2971+
r10 = '__main__'
2972+
r11 = __main__.Ext_template :: type
2973+
r12 = CPyType_FromTemplate(r11, r9, r10)
2974+
r13 = Ext_trait_vtable_setup()
2975+
r14 = Ext_coroutine_setup(r12)
2976+
r15 = '__mypyc_attrs__'
2977+
r16 = CPyTuple_LoadEmptyTupleConstant()
2978+
r17 = PyObject_SetAttr(r12, r15, r16)
2979+
r18 = r17 >= 0 :: signed
2980+
__main__.Ext = r12 :: type
2981+
r19 = __main__.globals :: static
2982+
r20 = 'Ext'
2983+
r21 = PyDict_SetItem(r19, r20, r12)
2984+
r22 = r21 >= 0 :: signed
2985+
r23 = __main__.Ext :: type
2986+
r24 = PySet_New(0)
2987+
r25 = object 1
2988+
r26 = PySet_Add(r24, r25)
2989+
r27 = r26 >= 0 :: signed
2990+
r28 = object 2
2991+
r29 = PySet_Add(r24, r28)
2992+
r30 = r29 >= 0 :: signed
2993+
r31 = 'A'
2994+
r32 = PyObject_SetAttr(r23, r31, r24)
2995+
r33 = r32 >= 0 :: signed
2996+
r34 = __main__.Ext :: type
2997+
r35 = 'A'
2998+
r36 = CPyObject_GetAttr(r12, r35)
2999+
r37 = cast(set, r36)
3000+
r38 = PySet_New(0)
3001+
r39 = object 3
3002+
r40 = PySet_Add(r38, r39)
3003+
r41 = r40 >= 0 :: signed
3004+
r42 = PyNumber_Or(r37, r38)
3005+
r43 = cast(set, r42)
3006+
r44 = 'B'
3007+
r45 = PyObject_SetAttr(r34, r44, r43)
3008+
r46 = r45 >= 0 :: signed
3009+
r47 = CPy_InitSubclass(r12)
3010+
return 1
3011+
3012+
[case testClassVarSelfReferenceNonExt_toplevel]
3013+
from typing import ClassVar, Set
3014+
from mypy_extensions import mypyc_attr
3015+
3016+
@mypyc_attr(native_class=False)
3017+
class NonExt:
3018+
A: ClassVar[Set[str]] = {"a"}
3019+
B: ClassVar[Set[str]] = A | {"b"}
3020+
[out]
3021+
def __top_level__():
3022+
r0, r1 :: object
3023+
r2 :: bit
3024+
r3 :: str
3025+
r4, r5 :: object
3026+
r6 :: str
3027+
r7 :: dict
3028+
r8, r9 :: object
3029+
r10 :: str
3030+
r11 :: dict
3031+
r12 :: object
3032+
r13 :: tuple
3033+
r14, r15 :: object
3034+
r16 :: str
3035+
r17 :: bool
3036+
r18, r19 :: str
3037+
r20 :: object
3038+
r21 :: object[2]
3039+
r22 :: object_ptr
3040+
r23 :: object
3041+
r24, r25, r26, r27 :: dict
3042+
r28 :: object
3043+
r29 :: str
3044+
r30 :: i32
3045+
r31 :: bit
3046+
r32 :: str
3047+
r33 :: set
3048+
r34 :: i32
3049+
r35 :: bit
3050+
r36 :: str
3051+
r37 :: i32
3052+
r38 :: bit
3053+
r39 :: object
3054+
r40 :: str
3055+
r41 :: i32
3056+
r42 :: bit
3057+
r43 :: str
3058+
r44 :: object
3059+
r45 :: set
3060+
r46 :: str
3061+
r47 :: set
3062+
r48 :: i32
3063+
r49 :: bit
3064+
r50 :: object
3065+
r51 :: set
3066+
r52 :: str
3067+
r53 :: i32
3068+
r54 :: bit
3069+
r55, r56 :: str
3070+
r57 :: i32
3071+
r58 :: bit
3072+
r59, r60 :: str
3073+
r61 :: i32
3074+
r62 :: bit
3075+
r63, r64 :: str
3076+
r65 :: i32
3077+
r66 :: bit
3078+
r67 :: object[3]
3079+
r68 :: object_ptr
3080+
r69 :: object
3081+
r70 :: dict
3082+
r71 :: str
3083+
r72, r73 :: object
3084+
r74 :: object[1]
3085+
r75 :: object_ptr
3086+
r76, r77 :: object
3087+
r78 :: object[1]
3088+
r79 :: object_ptr
3089+
r80 :: object
3090+
r81 :: dict
3091+
r82 :: str
3092+
r83 :: i32
3093+
r84 :: bit
3094+
r85 :: object
3095+
L0:
3096+
r0 = builtins :: module
3097+
r1 = load_address _Py_NoneStruct
3098+
r2 = r0 != r1
3099+
if r2 goto L2 else goto L1 :: bool
3100+
L1:
3101+
r3 = 'builtins'
3102+
r4 = PyImport_Import(r3)
3103+
builtins = r4 :: module
3104+
L2:
3105+
r5 = ('ClassVar', 'Set')
3106+
r6 = 'typing'
3107+
r7 = __main__.globals :: static
3108+
r8 = CPyImport_ImportFromMany(r6, r5, r5, r7)
3109+
typing = r8 :: module
3110+
r9 = ('mypyc_attr',)
3111+
r10 = 'mypy_extensions'
3112+
r11 = __main__.globals :: static
3113+
r12 = CPyImport_ImportFromMany(r10, r9, r9, r11)
3114+
mypy_extensions = r12 :: module
3115+
r13 = CPyTuple_LoadEmptyTupleConstant()
3116+
r14 = load_address PyType_Type
3117+
r15 = CPy_CalculateMetaclass(r14, r13)
3118+
r16 = '__prepare__'
3119+
r17 = PyObject_HasAttr(r15, r16)
3120+
if r17 goto L3 else goto L4 :: bool
3121+
L3:
3122+
r18 = 'NonExt'
3123+
r19 = '__prepare__'
3124+
r20 = CPyObject_GetAttr(r15, r19)
3125+
r21 = [r18, r13]
3126+
r22 = load_address r21
3127+
r23 = PyObject_Vectorcall(r20, r22, 2, 0)
3128+
keep_alive r18, r13
3129+
r24 = cast(dict, r23)
3130+
r25 = r24
3131+
goto L5
3132+
L4:
3133+
r26 = PyDict_New()
3134+
r25 = r26
3135+
L5:
3136+
r27 = PyDict_New()
3137+
r28 = load_address PySet_Type
3138+
r29 = 'A'
3139+
r30 = PyDict_SetItem(r27, r29, r28)
3140+
r31 = r30 >= 0 :: signed
3141+
r32 = 'a'
3142+
keep_alive r14, r13
3143+
r33 = PySet_New(0)
3144+
r34 = PySet_Add(r33, r32)
3145+
r35 = r34 >= 0 :: signed
3146+
r36 = 'A'
3147+
r37 = CPyDict_SetItem(r25, r36, r33)
3148+
r38 = r37 >= 0 :: signed
3149+
r39 = load_address PySet_Type
3150+
r40 = 'B'
3151+
r41 = PyDict_SetItem(r27, r40, r39)
3152+
r42 = r41 >= 0 :: signed
3153+
r43 = 'A'
3154+
r44 = CPyDict_GetItem(r25, r43)
3155+
r45 = cast(set, r44)
3156+
r46 = 'b'
3157+
r47 = PySet_New(0)
3158+
r48 = PySet_Add(r47, r46)
3159+
r49 = r48 >= 0 :: signed
3160+
r50 = PyNumber_Or(r45, r47)
3161+
r51 = cast(set, r50)
3162+
r52 = 'B'
3163+
r53 = CPyDict_SetItem(r25, r52, r51)
3164+
r54 = r53 >= 0 :: signed
3165+
r55 = 'NonExt'
3166+
r56 = '__annotations__'
3167+
r57 = CPyDict_SetItem(r25, r56, r27)
3168+
r58 = r57 >= 0 :: signed
3169+
r59 = 'mypyc filler docstring'
3170+
r60 = '__doc__'
3171+
r61 = CPyDict_SetItem(r25, r60, r59)
3172+
r62 = r61 >= 0 :: signed
3173+
r63 = '__main__'
3174+
r64 = '__module__'
3175+
r65 = CPyDict_SetItem(r25, r64, r63)
3176+
r66 = r65 >= 0 :: signed
3177+
r67 = [r55, r13, r25]
3178+
r68 = load_address r67
3179+
r69 = PyObject_Vectorcall(r15, r68, 3, 0)
3180+
keep_alive r55, r13, r25
3181+
r70 = __main__.globals :: static
3182+
r71 = 'mypyc_attr'
3183+
r72 = CPyDict_GetItem(r70, r71)
3184+
r73 = box(bool, 0)
3185+
r74 = [r73]
3186+
r75 = load_address r74
3187+
r76 = ('native_class',)
3188+
r77 = PyObject_Vectorcall(r72, r75, 0, r76)
3189+
keep_alive r73
3190+
r78 = [r69]
3191+
r79 = load_address r78
3192+
r80 = PyObject_Vectorcall(r77, r79, 1, 0)
3193+
keep_alive r69
3194+
__main__.NonExt = r80 :: type
3195+
r81 = __main__.globals :: static
3196+
r82 = 'NonExt'
3197+
r83 = PyDict_SetItem(r81, r82, r80)
3198+
r84 = r83 >= 0 :: signed
3199+
r85 = __main__.NonExt :: type
3200+
return 1
3201+

mypyc/test-data/run-classes.test

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5862,18 +5862,18 @@ assert ExtSub.E == {7, 8}
58625862
from typing import ClassVar, Dict, Set
58635863
from mypy_extensions import mypyc_attr
58645864

5865-
@mypyc_attr(allow_interpreted_subclasses=True)
5865+
@mypyc_attr(native_class=False)
58665866
class NonExt:
58675867
A: ClassVar[Set[str]] = {"a", "b"}
58685868
B: ClassVar[Set[str]] = {"c"}
58695869
C: ClassVar[Set[str]] = A | B
58705870

5871-
@mypyc_attr(allow_interpreted_subclasses=True)
5871+
@mypyc_attr(native_class=False)
58725872
class NonExtDict:
58735873
BASE: ClassVar[Dict[str, int]] = {"x": 1}
58745874
EXTENDED: ClassVar[Dict[str, int]] = {**BASE, "y": 2}
58755875

5876-
@mypyc_attr(allow_interpreted_subclasses=True)
5876+
@mypyc_attr(native_class=False)
58775877
class NonExtChained:
58785878
X: ClassVar[Set[int]] = {10}
58795879
Y: ClassVar[Set[int]] = X | {20}

0 commit comments

Comments
 (0)