Skip to content

Commit f4e5b93

Browse files
committed
make it more user friendly
1 parent 7bc105a commit f4e5b93

File tree

4 files changed

+70
-9
lines changed

4 files changed

+70
-9
lines changed

mypyc/irbuild/prepare.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def build_type_map(
8888
is_abstract=cdef.info.is_abstract,
8989
is_final_class=cdef.info.is_final,
9090
)
91-
class_ir.is_ext_class = is_extension_class(cdef)
91+
class_ir.is_ext_class = is_extension_class(module.path, cdef, errors)
9292
if class_ir.is_ext_class:
9393
class_ir.deletable = cdef.info.deletable_attributes.copy()
9494
# If global optimizations are disabled, turn of tracking of class children

mypyc/irbuild/util.py

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
)
3030
from mypy.semanal import refers_to_fullname
3131
from mypy.types import FINAL_DECORATOR_NAMES
32+
from mypyc.errors import Errors
3233

3334
DATACLASS_DECORATORS = {"dataclasses.dataclass", "attr.s", "attr.attrs"}
3435

@@ -125,18 +126,53 @@ def get_mypyc_attrs(stmt: ClassDef | Decorator) -> dict[str, Any]:
125126
return attrs
126127

127128

128-
def is_extension_class(cdef: ClassDef) -> bool:
129+
def is_extension_class(path: str, cdef: ClassDef, errors: Errors) -> bool:
130+
# Check for @mypyc_attr(native_class=True/False) decorator.
131+
# Classes with native_class=False are explicitly marked as non extension.
132+
# Classes with native_class=True should be extension classes, but they might
133+
# not be able to be due to other reasons. Print an error in that case.
134+
forced_native_class = False
129135
for d in cdef.decorators:
130136
mypyc_attr_call = get_mypyc_attr_call(d)
131-
# Classes decorated with "@mypyc_attr(non_extension_class=True)" are not extension classes
132-
if mypyc_attr_call and "non_extension_class" in mypyc_attr_call.arg_names:
133-
return False
137+
if mypyc_attr_call:
138+
for i, name in enumerate(mypyc_attr_call.arg_names):
139+
if name != "native_class":
140+
continue
141+
142+
if not isinstance(mypyc_attr_call.args[i], NameExpr):
143+
errors.error(
144+
"native_class must be used with True or False only", path, cdef.line
145+
)
146+
break
147+
148+
if mypyc_attr_call.args[i].name == "False":
149+
return False
150+
elif mypyc_attr_call.args[i].name == "True":
151+
forced_native_class = True
152+
break
153+
else:
154+
errors.error(
155+
"native_class must be used with True or False only", path, cdef.line
156+
)
157+
break
158+
159+
implicit_extension_class = is_implicit_extension_class(cdef)
160+
161+
if forced_native_class and not implicit_extension_class:
162+
errors.error(
163+
"Class is marked as native_class=True but it can't be a native class", path, cdef.line
164+
)
165+
166+
return implicit_extension_class
134167

168+
169+
def is_implicit_extension_class(cdef: ClassDef) -> bool:
170+
for d in cdef.decorators:
135171
# Classes that have any decorator other than supported decorators, are not extension classes
136172
if (
137173
not is_trait_decorator(d)
138174
and not is_dataclass_decorator(d)
139-
and not mypyc_attr_call
175+
and not get_mypyc_attr_call(d)
140176
and not is_final_decorator(d)
141177
):
142178
return False

mypyc/test-data/irbuild-classes.test

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,3 +1345,28 @@ class SomeEnum(Enum):
13451345

13461346
ALIAS = Literal[SomeEnum.AVALUE]
13471347
ALIAS2 = Union[Literal[SomeEnum.AVALUE], None]
1348+
1349+
[case testMypycAttrNativeClassErrors]
1350+
from mypy_extensions import mypyc_attr
1351+
1352+
@mypyc_attr(native_class=False)
1353+
class AnnontatedNonExtensionClass:
1354+
pass
1355+
1356+
@mypyc_attr(native_class=False)
1357+
class DerivedExplicitNonNativeClass(AnnontatedNonExtensionClass):
1358+
pass
1359+
1360+
1361+
def decorator(cls):
1362+
return cls
1363+
1364+
@mypyc_attr(native_class=True)
1365+
@decorator
1366+
class NonNativeClassContradiction(): # E: Class is marked as native_class=True but it can't be a native class
1367+
pass
1368+
1369+
1370+
@mypyc_attr(native_class="yes")
1371+
class BadUse(): # E: native_class must be used with True or False only
1372+
pass

mypyc/test-data/run-classes.test

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2834,14 +2834,14 @@ Exception: e2
28342834
from mypy_extensions import mypyc_attr
28352835
from testutil import assertRaises
28362836

2837-
@mypyc_attr(non_extension_class=True)
2837+
@mypyc_attr(native_class=False)
28382838
class AnnontatedNonExtensionClass:
28392839
pass
28402840

28412841
class DerivedClass(AnnontatedNonExtensionClass):
28422842
pass
28432843

2844-
class ExtensionClass():
2844+
class ImplicitExtensionClass():
28452845
pass
28462846

28472847
def test_function():
@@ -2871,6 +2871,6 @@ def test_function():
28712871
delattr(derived_inst, 'attr_instance')
28722872
assert(hasattr(derived_inst, 'attr_instance') == False)
28732873

2874-
ext_inst = ExtensionClass()
2874+
ext_inst = ImplicitExtensionClass()
28752875
with assertRaises(AttributeError):
28762876
setattr(ext_inst, 'attr_instance', 6)

0 commit comments

Comments
 (0)