Skip to content

Commit c8c2132

Browse files
committed
address review comments
1 parent 2fc7441 commit c8c2132

4 files changed

Lines changed: 246 additions & 3 deletions

File tree

src/bytecode/instr.pxd

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
cdef class InstrLocation:
2+
# Must be `object` (not `int`) because these fields are Optional[int] and can be None.
3+
# `public` (not `readonly`) is required because _from_tuple/__init__ assign via an
4+
# untyped `new` variable; Cython routes those through the Python descriptor, which
5+
# would raise for `readonly`. Immutability is enforced only in pure-Python mode by
6+
# @dataclass(frozen=True).
27
cdef public object lineno
38
cdef public object end_lineno
49
cdef public object col_offset

src/bytecode/instr.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import dis
44
import enum
55
import opcode as _opcode
6-
import sys
6+
import types
77
from abc import abstractmethod
88
from dataclasses import dataclass
99
from functools import cache
@@ -721,8 +721,8 @@ class BaseInstr:
721721

722722
__slots__ = ("_arg", "_location", "_name", "_opcode")
723723

724-
def __class_getitem__(cls, item: Any) -> Any:
725-
return cls
724+
def __class_getitem__(cls, item: Any) -> types.GenericAlias:
725+
return types.GenericAlias(cls, item)
726726

727727
# Work around an issue with the default value of arg
728728
def __init__(

src/bytecode/instr.pyi

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
"""Type stubs for instr.py.
2+
3+
Cython cdef classes cannot inherit from Generic[], so this stub restores the
4+
generic type-checking behaviour for BaseInstr[A] and Instr.
5+
"""
6+
7+
import enum
8+
import types
9+
from typing import Any, Final, Generic, Optional, TypeGuard, TypeVar, Union
10+
11+
import bytecode as _bytecode
12+
13+
# ── type variables ────────────────────────────────────────────────────────────
14+
15+
A = TypeVar("A", bound=object)
16+
T = TypeVar("T", bound="BaseInstr[Any]")
17+
18+
# ── opcode sets / constants ───────────────────────────────────────────────────
19+
20+
MIN_INSTRUMENTED_OPCODE: Final[int]
21+
BITFLAG_OPCODES: Final[set[int]]
22+
BITFLAG2_OPCODES: Final[set[int]]
23+
BINARY_OPS: Final[set[int]]
24+
INTRINSIC_1OP: Final[set[int]]
25+
INTRINSIC_2OP: Final[set[int]]
26+
INTRINSIC: Final[set[int]]
27+
COMMON_CONSTANT_OPS: Final[set[int]]
28+
FORMAT_VALUE_OPS: Final[set[int]]
29+
SMALL_INT_OPS: Final[set[int]]
30+
SPECIAL_OPS: Final[set[int]]
31+
HAS_ABSOLUTE_JUMP: Final[set[int]]
32+
HAS_FORWARD_RELATIVE_JUMP: Final[set[int]]
33+
HAS_BACKWARD_RELATIVE_JUMP: Final[set[int]]
34+
HAS_JUMP: Final[set[int]]
35+
HAS_CONDITIONAL_JUMP: Final[set[int]]
36+
HAS_UNCONDITIONAL_JUMP: Final[set[int]]
37+
IS_INSTR_FINAL: Final[set[int]]
38+
DUAL_ARG_OPCODES: Final[set[int]]
39+
DUAL_ARG_OPCODES_SINGLE_OPS: Final[dict[int, tuple[str, str]]]
40+
EXTENDEDARG_OPCODE: Final[int]
41+
NOP_OPCODE: Final[int]
42+
CACHE_OPCODE: Final[int]
43+
RESUME_OPCODE: Final[int]
44+
STATIC_STACK_EFFECTS: Final[dict[int, tuple[int, int]]]
45+
DYNAMIC_STACK_EFFECTS: Final[dict[int, Any]]
46+
47+
# ── enums ─────────────────────────────────────────────────────────────────────
48+
49+
class Compare(enum.IntEnum):
50+
LT = 0
51+
LE = 1
52+
EQ = 2
53+
NE = 3
54+
GT = 4
55+
GE = 5
56+
LT_CAST = 16
57+
LE_CAST = 17
58+
EQ_CAST = 18
59+
NE_CAST = 19
60+
GT_CAST = 20
61+
GE_CAST = 21
62+
63+
class BinaryOp(enum.IntEnum): ...
64+
class Intrinsic1Op(enum.IntEnum): ...
65+
class Intrinsic2Op(enum.IntEnum): ...
66+
class FormatValue(enum.IntEnum): ...
67+
class SpecialMethod(enum.IntEnum): ...
68+
class CommonConstant(enum.IntEnum): ...
69+
70+
# ── sentinel ──────────────────────────────────────────────────────────────────
71+
72+
class _UNSET(int): ...
73+
74+
UNSET: _UNSET
75+
76+
# ── helpers ───────────────────────────────────────────────────────────────────
77+
78+
def const_key(obj: Any) -> bytes | tuple[type, int]: ...
79+
def _check_arg_int(arg: Any, name: str) -> TypeGuard[int]: ...
80+
def opcode_has_argument(opcode: int) -> bool: ...
81+
82+
# ── label / variable types ────────────────────────────────────────────────────
83+
84+
class Label: ...
85+
86+
PLACEHOLDER_LABEL: Label
87+
88+
class _Variable:
89+
name: str
90+
def __init__(self, name: str) -> None: ...
91+
def __eq__(self, other: Any) -> bool: ...
92+
def __ne__(self, other: Any) -> bool: ...
93+
def __repr__(self) -> str: ...
94+
95+
class CellVar(_Variable): ...
96+
class FreeVar(_Variable): ...
97+
98+
# ── InstrLocation ─────────────────────────────────────────────────────────────
99+
100+
class InstrLocation:
101+
lineno: Optional[int]
102+
end_lineno: Optional[int]
103+
col_offset: Optional[int]
104+
end_col_offset: Optional[int]
105+
def __init__(
106+
self,
107+
lineno: Optional[int],
108+
end_lineno: Optional[int],
109+
col_offset: Optional[int],
110+
end_col_offset: Optional[int],
111+
) -> None: ...
112+
@classmethod
113+
def from_positions(cls, position: Any) -> InstrLocation: ...
114+
@classmethod
115+
def _from_tuple(
116+
cls,
117+
lineno: Optional[int],
118+
end_lineno: Optional[int],
119+
col_offset: Optional[int],
120+
end_col_offset: Optional[int],
121+
) -> InstrLocation: ...
122+
123+
# ── pseudo-instructions ───────────────────────────────────────────────────────
124+
125+
class SetLineno:
126+
def __init__(self, lineno: int) -> None: ...
127+
@property
128+
def lineno(self) -> int: ...
129+
def __eq__(self, other: Any) -> bool: ...
130+
131+
class TryBegin:
132+
target: Label | _bytecode.BasicBlock
133+
push_lasti: bool
134+
stack_depth: int | _UNSET
135+
def __init__(
136+
self,
137+
target: Label | _bytecode.BasicBlock,
138+
push_lasti: bool,
139+
stack_depth: int | _UNSET = ...,
140+
) -> None: ...
141+
def copy(self) -> TryBegin: ...
142+
143+
class TryEnd:
144+
entry: TryBegin
145+
def __init__(self, entry: TryBegin) -> None: ...
146+
def copy(self) -> TryEnd: ...
147+
148+
# ── InstrArg ──────────────────────────────────────────────────────────────────
149+
150+
InstrArg = Union[
151+
int,
152+
str,
153+
Label,
154+
CellVar,
155+
FreeVar,
156+
_bytecode.BasicBlock,
157+
Compare,
158+
FormatValue,
159+
BinaryOp,
160+
Intrinsic1Op,
161+
Intrinsic2Op,
162+
CommonConstant,
163+
SpecialMethod,
164+
tuple[bool, str],
165+
tuple[bool, bool, str],
166+
tuple[bool, FormatValue],
167+
tuple[str | CellVar | FreeVar, str | CellVar | FreeVar],
168+
]
169+
170+
# ── BaseInstr / Instr ─────────────────────────────────────────────────────────
171+
172+
class BaseInstr(Generic[A]):
173+
def __init__(
174+
self,
175+
name: str,
176+
arg: A = ...,
177+
*,
178+
lineno: int | None | _UNSET = ...,
179+
location: Optional[InstrLocation] = None,
180+
) -> None: ...
181+
def __class_getitem__(cls, item: Any) -> types.GenericAlias: ...
182+
def set(self, name: str, arg: A = ...) -> None: ...
183+
def require_arg(self) -> bool: ...
184+
@property
185+
def name(self) -> str: ...
186+
@name.setter
187+
def name(self, name: str) -> None: ...
188+
@property
189+
def opcode(self) -> int: ...
190+
@opcode.setter
191+
def opcode(self, op: int) -> None: ...
192+
@property
193+
def arg(self) -> A: ...
194+
@arg.setter
195+
def arg(self, arg: A) -> None: ...
196+
@property
197+
def lineno(self) -> int | _UNSET | None: ...
198+
@lineno.setter
199+
def lineno(self, lineno: int | _UNSET | None) -> None: ...
200+
@property
201+
def location(self) -> Optional[InstrLocation]: ...
202+
@location.setter
203+
def location(self, location: Optional[InstrLocation]) -> None: ...
204+
def stack_effect(self, jump: Optional[bool] = None) -> int: ...
205+
def pre_and_post_stack_effect(
206+
self, jump: Optional[bool] = None
207+
) -> tuple[int, int]: ...
208+
def copy(self: T) -> T: ...
209+
@classmethod
210+
def _from_trusted(
211+
cls: type[T],
212+
name: str,
213+
opcode: int,
214+
arg: A,
215+
location: Optional[InstrLocation],
216+
) -> T: ...
217+
def has_jump(self) -> bool: ...
218+
def is_cond_jump(self) -> bool: ...
219+
def is_uncond_jump(self) -> bool: ...
220+
def is_abs_jump(self) -> bool: ...
221+
def is_forward_rel_jump(self) -> bool: ...
222+
def is_backward_rel_jump(self) -> bool: ...
223+
def is_final(self) -> bool: ...
224+
225+
class Instr(BaseInstr[InstrArg]): ...

tests/test_instr.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,19 @@ def test_init(self):
113113
else:
114114
InstrLocation(*args)
115115

116+
def test_immutable(self):
117+
import importlib.util
118+
119+
spec = importlib.util.find_spec("bytecode.concrete")
120+
is_pure = spec and spec.origin and spec.origin.endswith(".py")
121+
if not is_pure:
122+
self.skipTest("immutability is only enforced in the pure-Python build")
123+
loc = InstrLocation(1, 2, 3, 4)
124+
with self.assertRaises((AttributeError, TypeError)):
125+
loc.lineno = 99 # type: ignore[misc]
126+
with self.assertRaises((AttributeError, TypeError)):
127+
del loc.lineno # type: ignore[misc]
128+
116129

117130
class InstrTests(TestCase):
118131
def test_constructor(self):

0 commit comments

Comments
 (0)