Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
cce8669
[mypyc] fix: reject invalid `mypyc_attr` args
BobTheBuidler Oct 1, 2025
d4631ac
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 1, 2025
0cc6c5d
fix: add key 'free_list_len'
BobTheBuidler Oct 1, 2025
4aefb1b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 1, 2025
3c27112
fix mypy errs
BobTheBuidler Oct 1, 2025
c2f3d36
Update util.py
BobTheBuidler Oct 1, 2025
0e1c740
Update util.py
BobTheBuidler Oct 1, 2025
0de2627
Update util.py
BobTheBuidler Oct 1, 2025
4a88449
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 1, 2025
7932b81
Update util.py
BobTheBuidler Oct 1, 2025
593c5ee
Update util.py
BobTheBuidler Oct 1, 2025
9d12459
fix mypy err
BobTheBuidler Oct 1, 2025
f7468d9
fix mypy err
BobTheBuidler Oct 1, 2025
71473d3
Update util.py
BobTheBuidler Oct 3, 2025
123b740
test error message
BobTheBuidler Oct 3, 2025
3e98dce
Update irbuild-classes.test
BobTheBuidler Oct 3, 2025
dfe19bb
add note to tests
BobTheBuidler Oct 3, 2025
16fb0f3
Update irbuild-classes.test
BobTheBuidler Oct 3, 2025
b220df5
Update irbuild-classes.test
BobTheBuidler Oct 3, 2025
cae1c85
Merge branch 'master' into reject-invalid-mypyc-attr
BobTheBuidler Oct 4, 2025
8caceb9
use double quotes
BobTheBuidler Oct 6, 2025
e6670cf
another double quote
BobTheBuidler Oct 6, 2025
c663542
Update util.py
BobTheBuidler Oct 6, 2025
2a0bfbd
Update irbuild-classes.test
BobTheBuidler Oct 6, 2025
ac891b9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 6, 2025
0c23635
Update util.py
BobTheBuidler Oct 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mypyc/irbuild/prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ def prepare_class_def(
ir = mapper.type_to_ir[cdef.info]
info = cdef.info

attrs, attrs_lines = get_mypyc_attrs(cdef)
attrs, attrs_lines = get_mypyc_attrs(cdef, path, errors)
if attrs.get("allow_interpreted_subclasses") is True:
ir.allow_interpreted_subclasses = True
if attrs.get("serializable") is True:
Expand Down
56 changes: 45 additions & 11 deletions mypyc/irbuild/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

from __future__ import annotations

from typing import Any
from typing import Any, Final, Literal, TypedDict, cast
from typing_extensions import NotRequired

from mypy.nodes import (
ARG_NAMED,
Expand Down Expand Up @@ -31,7 +32,23 @@
from mypy.types import FINAL_DECORATOR_NAMES
from mypyc.errors import Errors

DATACLASS_DECORATORS = {"dataclasses.dataclass", "attr.s", "attr.attrs"}
MYPYC_ATTRS: Final[frozenset[MypycAttr]] = frozenset(
["native_class", "allow_interpreted_subclasses", "serializable", "free_list_len"]
)

DATACLASS_DECORATORS: Final = frozenset(["dataclasses.dataclass", "attr.s", "attr.attrs"])


MypycAttr = Literal[
"native_class", "allow_interpreted_subclasses", "serializable", "free_list_len"
]


class MypycAttrs(TypedDict):
native_class: NotRequired[bool]
allow_interpreted_subclasses: NotRequired[bool]
serializable: NotRequired[bool]
free_list_len: NotRequired[int]


def is_final_decorator(d: Expression) -> bool:
Expand Down Expand Up @@ -112,21 +129,38 @@ def get_mypyc_attr_call(d: Expression) -> CallExpr | None:
return None


def get_mypyc_attrs(stmt: ClassDef | Decorator) -> tuple[dict[str, Any], dict[str, int]]:
def get_mypyc_attrs(
stmt: ClassDef | Decorator, path: str, errors: Errors
) -> tuple[MypycAttrs, dict[MypycAttr, int]]:
"""Collect all the mypyc_attr attributes on a class definition or a function."""
attrs: dict[str, Any] = {}
lines: dict[str, int] = {}
attrs: MypycAttrs = {}
lines: dict[MypycAttr, int] = {}

def set_mypyc_attr(key: str, value: Any, line: int) -> None:
if key in MYPYC_ATTRS:
key = cast(MypycAttr, key)
attrs[key] = value
lines[key] = line
else:
errors.error(f"{key!r} is not a supported `mypyc_attr`", path, line)
errors.note(f"supported keys: {', '.join(map(repr, sorted(MYPYC_ATTRS)))}", path, line)

for dec in stmt.decorators:
d = get_mypyc_attr_call(dec)
if d:
if d := get_mypyc_attr_call(dec):
line = d.line
for name, arg in zip(d.arg_names, d.args):
if name is None:
if isinstance(arg, StrExpr):
attrs[arg.value] = True
lines[arg.value] = d.line
set_mypyc_attr(arg.value, True, line)
else:
errors.error(
"All `mypyc_attr` positional arguments must be string literals.",
path,
line,
)
else:
attrs[name] = get_mypyc_attr_literal(arg)
lines[name] = d.line
arg_value = get_mypyc_attr_literal(arg)
set_mypyc_attr(name, arg_value, line)

return attrs, lines

Expand Down
15 changes: 15 additions & 0 deletions mypyc/test-data/irbuild-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -2686,3 +2686,18 @@ L0:
r6 = PyObject_VectorcallMethod(r3, r5, 9223372036854775812, 0)
keep_alive r2, self, key, val
return 1

[case testInvalidMypycAttr]
from mypy_extensions import mypyc_attr

@mypyc_attr("allow_interpreted_subclasses", "invalid_arg") # E: 'invalid_arg' is not a supported `mypyc_attr` \
# N: supported keys: 'allow_interpreted_subclasses', 'free_list_len', 'native_class', 'serializable'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style nit: we usually wrap names in double-quotes in errors/notes, so it should be "invalid_arg", "mypyc_attr", "allow_interpreted_subclasses", etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

class InvalidArg:
pass
@mypyc_attr(invalid_kwarg=True) # E: 'invalid_kwarg' is not a supported `mypyc_attr` \
# N: supported keys: 'allow_interpreted_subclasses', 'free_list_len', 'native_class', 'serializable'
class InvalidKwarg:
pass
@mypyc_attr(str()) # E: All `mypyc_attr` positional arguments must be string literals.
class InvalidLiteral:
pass