Skip to content

[mypyc] Document some of our inheritance conventions #19370

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
65 changes: 61 additions & 4 deletions mypyc/ir/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,31 @@
Opcodes operate on abstract values (Value) in a register machine. Each
value has a type (RType). A value can hold various things, such as:

- local variables (Register)
- local variables or temporaries (Register)
- intermediate values of expressions (RegisterOp subclasses)
- condition flags (true/false)
- literals (integer literals, True, False, etc.)

NOTE: As a convention, we don't create subclasses of concrete Value/Op
subclasses (e.g. you shouldn't define a subclass of Integer, which
is a concrete class).

If you want to introduce a variant of an existing class, you'd
typically add an attribute (e.g. a flag) to an existing concrete
class to enable the new behavior. Sometimes adding a new abstract
base class is also an option, or just creating a new subclass
without any inheritance relationship (some duplication of code
is preferred over introducing complex implementation inheritance).

This makes it possible to use isinstance(x, <concrete Value
subclass>) checks without worrying about potential subclasses.
Comment on lines +22 to +23
Copy link
Member

Choose a reason for hiding this comment

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

Would it be a good idea to mark the concrete classes as @final to document/enforce this?

On a somewhat related noted, can/does mypyc optimize isinstance checks with final types?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I like that, added @final to the concrete classes. By default @final doesn't help with optimizations, since mypyc can perform whole-program analysis. However, @final would help with isinstance checks if doing separate compilation (but this is currently poorly supported).

"""

from __future__ import annotations

from abc import abstractmethod
from collections.abc import Sequence
from typing import TYPE_CHECKING, Final, Generic, NamedTuple, TypeVar, Union
from typing import TYPE_CHECKING, Final, Generic, NamedTuple, TypeVar, Union, final

from mypy_extensions import trait

Expand Down Expand Up @@ -47,6 +61,7 @@
T = TypeVar("T")


@final
class BasicBlock:
"""IR basic block.

Expand Down Expand Up @@ -142,6 +157,7 @@ def is_void(self) -> bool:
return isinstance(self.type, RVoid)


@final
class Register(Value):
"""A Register holds a value of a specific type, and it can be read and mutated.

Expand All @@ -168,6 +184,7 @@ def __repr__(self) -> str:
return f"<Register {self.name!r} at {hex(id(self))}>"


@final
class Integer(Value):
"""Short integer literal.

Expand Down Expand Up @@ -198,6 +215,7 @@ def numeric_value(self) -> int:
return self.value


@final
class Float(Value):
"""Float literal.

Expand Down Expand Up @@ -257,13 +275,14 @@ def accept(self, visitor: OpVisitor[T]) -> T:


class BaseAssign(Op):
"""Base class for ops that assign to a register."""
"""Abstract base class for ops that assign to a register."""

def __init__(self, dest: Register, line: int = -1) -> None:
super().__init__(line)
self.dest = dest


@final
class Assign(BaseAssign):
"""Assign a value to a Register (dest = src)."""

Expand All @@ -286,6 +305,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_assign(self)


@final
class AssignMulti(BaseAssign):
"""Assign multiple values to a Register (dest = src1, src2, ...).

Expand Down Expand Up @@ -320,7 +340,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:


class ControlOp(Op):
"""Control flow operation."""
"""Abstract base class for control flow operations."""

def targets(self) -> Sequence[BasicBlock]:
"""Get all basic block targets of the control operation."""
Expand All @@ -331,6 +351,7 @@ def set_target(self, i: int, new: BasicBlock) -> None:
raise AssertionError(f"Invalid set_target({self}, {i})")


@final
class Goto(ControlOp):
"""Unconditional jump."""

Expand Down Expand Up @@ -360,6 +381,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_goto(self)


@final
class Branch(ControlOp):
"""Branch based on a value.

Expand Down Expand Up @@ -426,6 +448,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_branch(self)


@final
class Return(ControlOp):
"""Return a value from a function."""

Expand Down Expand Up @@ -455,6 +478,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_return(self)


@final
class Unreachable(ControlOp):
"""Mark the end of basic block as unreachable.

Expand Down Expand Up @@ -511,6 +535,7 @@ def can_raise(self) -> bool:
return self.error_kind != ERR_NEVER


@final
class IncRef(RegisterOp):
"""Increase reference count (inc_ref src)."""

Expand All @@ -531,6 +556,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_inc_ref(self)


@final
class DecRef(RegisterOp):
"""Decrease reference count and free object if zero (dec_ref src).

Expand Down Expand Up @@ -559,6 +585,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_dec_ref(self)


@final
class Call(RegisterOp):
"""Native call f(arg, ...).

Expand Down Expand Up @@ -587,6 +614,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_call(self)


@final
class MethodCall(RegisterOp):
"""Native method call obj.method(arg, ...)"""

Expand Down Expand Up @@ -618,6 +646,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_method_call(self)


@final
class PrimitiveDescription:
"""Description of a primitive op.

Expand Down Expand Up @@ -670,6 +699,7 @@ def __repr__(self) -> str:
return f"<PrimitiveDescription {self.name!r}: {self.arg_types}>"


@final
class PrimitiveOp(RegisterOp):
"""A higher-level primitive operation.

Expand Down Expand Up @@ -707,6 +737,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_primitive_op(self)


@final
class LoadErrorValue(RegisterOp):
"""Load an error value.

Expand Down Expand Up @@ -737,6 +768,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_load_error_value(self)


@final
class LoadLiteral(RegisterOp):
"""Load a Python literal object (dest = 'foo' / b'foo' / ...).

Expand Down Expand Up @@ -772,6 +804,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_load_literal(self)


@final
class GetAttr(RegisterOp):
"""obj.attr (for a native object)"""

Expand Down Expand Up @@ -799,6 +832,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_get_attr(self)


@final
class SetAttr(RegisterOp):
"""obj.attr = src (for a native object)

Expand Down Expand Up @@ -850,6 +884,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
NAMESPACE_TYPE_VAR: Final = "typevar"


@final
class LoadStatic(RegisterOp):
"""Load a static name (name :: static).

Expand Down Expand Up @@ -890,6 +925,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_load_static(self)


@final
class InitStatic(RegisterOp):
"""static = value :: static

Expand Down Expand Up @@ -922,6 +958,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_init_static(self)


@final
class TupleSet(RegisterOp):
"""dest = (reg, ...) (for fixed-length tuple)"""

Expand Down Expand Up @@ -954,6 +991,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_tuple_set(self)


@final
class TupleGet(RegisterOp):
"""Get item of a fixed-length tuple (src[index])."""

Expand All @@ -978,6 +1016,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_tuple_get(self)


@final
class Cast(RegisterOp):
"""cast(type, src)

Expand Down Expand Up @@ -1009,6 +1048,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_cast(self)


@final
class Box(RegisterOp):
"""box(type, src)

Expand Down Expand Up @@ -1043,6 +1083,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_box(self)


@final
class Unbox(RegisterOp):
"""unbox(type, src)

Expand All @@ -1069,6 +1110,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_unbox(self)


@final
class RaiseStandardError(RegisterOp):
"""Raise built-in exception with an optional error string.

Expand Down Expand Up @@ -1108,6 +1150,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
StealsDescription = Union[bool, list[bool]]


@final
class CallC(RegisterOp):
"""result = function(arg0, arg1, ...)

Expand Down Expand Up @@ -1162,6 +1205,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_call_c(self)


@final
class Truncate(RegisterOp):
"""result = truncate src from src_type to dst_type

Expand Down Expand Up @@ -1192,6 +1236,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_truncate(self)


@final
class Extend(RegisterOp):
"""result = extend src from src_type to dst_type

Expand Down Expand Up @@ -1226,6 +1271,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_extend(self)


@final
class LoadGlobal(RegisterOp):
"""Load a low-level global variable/pointer.

Expand Down Expand Up @@ -1253,6 +1299,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_load_global(self)


@final
class IntOp(RegisterOp):
"""Binary arithmetic or bitwise op on integer operands (e.g., r1 = r2 + r3).

Expand Down Expand Up @@ -1317,6 +1364,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
int_op_to_id: Final = {op: op_id for op_id, op in IntOp.op_str.items()}


@final
class ComparisonOp(RegisterOp):
"""Low-level comparison op for integers and pointers.

Expand Down Expand Up @@ -1378,6 +1426,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_comparison_op(self)


@final
class FloatOp(RegisterOp):
"""Binary float arithmetic op (e.g., r1 = r2 + r3).

Expand Down Expand Up @@ -1419,6 +1468,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
float_op_to_id: Final = {op: op_id for op_id, op in FloatOp.op_str.items()}


@final
class FloatNeg(RegisterOp):
"""Float negation op (r1 = -r2)."""

Expand All @@ -1439,6 +1489,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_float_neg(self)


@final
class FloatComparisonOp(RegisterOp):
"""Low-level comparison op for floats."""

Expand Down Expand Up @@ -1475,6 +1526,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
float_comparison_op_to_id: Final = {op: op_id for op_id, op in FloatComparisonOp.op_str.items()}


@final
class LoadMem(RegisterOp):
"""Read a memory location: result = *(type *)src.

Expand Down Expand Up @@ -1504,6 +1556,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_load_mem(self)


@final
class SetMem(Op):
"""Write to a memory location: *(type *)dest = src

Expand Down Expand Up @@ -1535,6 +1588,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_set_mem(self)


@final
class GetElementPtr(RegisterOp):
"""Get the address of a struct element.

Expand All @@ -1561,6 +1615,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_get_element_ptr(self)


@final
class LoadAddress(RegisterOp):
"""Get the address of a value: result = (type)&src

Expand Down Expand Up @@ -1595,6 +1650,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_load_address(self)


@final
class KeepAlive(RegisterOp):
"""A no-op operation that ensures source values aren't freed.

Expand Down Expand Up @@ -1642,6 +1698,7 @@ def accept(self, visitor: OpVisitor[T]) -> T:
return visitor.visit_keep_alive(self)


@final
class Unborrow(RegisterOp):
"""A no-op op to create a regular reference from a borrowed one.

Expand Down
Loading