indie-status-page/venv/lib/python3.11/site-packages/mypyc/irbuild/specialize.py
IndieStatusBot 902133edd3 feat: indie status page MVP -- FastAPI + SQLite
- 8 DB models (services, incidents, monitors, subscribers, etc.)
- Full CRUD API for services, incidents, monitors
- Public status page with live data
- Incident detail page with timeline
- API key authentication
- Uptime monitoring scheduler
- 13 tests passing
- TECHNICAL_DESIGN.md with full spec
2026-04-25 05:00:00 +00:00

1548 lines
55 KiB
Python

"""Special case IR generation of calls to specific builtin functions.
Most special cases should be handled using the data driven "primitive
ops" system, but certain operations require special handling that has
access to the AST/IR directly and can make decisions/optimizations
based on it. These special cases can be implemented here.
For example, we use specializers to statically emit the length of a
fixed length tuple and to emit optimized code for any()/all() calls with
generator comprehensions as the argument.
See comment below for more documentation.
"""
from __future__ import annotations
from collections.abc import Callable
from typing import Final, cast
from mypy.nodes import (
ARG_NAMED,
ARG_POS,
CallExpr,
DictExpr,
Expression,
GeneratorExpr,
IndexExpr,
IntExpr,
ListExpr,
MemberExpr,
NameExpr,
RefExpr,
StrExpr,
SuperExpr,
TupleExpr,
Var,
)
from mypy.types import AnyType, TypeOfAny
from mypyc.ir.ops import (
BasicBlock,
Call,
Extend,
Integer,
PrimitiveDescription,
RaiseStandardError,
Register,
SetAttr,
Truncate,
Unreachable,
Value,
)
from mypyc.ir.rtypes import (
RInstance,
RPrimitive,
RTuple,
RType,
RVec,
bool_rprimitive,
bytes_rprimitive,
bytes_writer_rprimitive,
c_int_rprimitive,
dict_rprimitive,
int16_rprimitive,
int32_rprimitive,
int64_rprimitive,
int_rprimitive,
is_bool_rprimitive,
is_dict_rprimitive,
is_fixed_width_rtype,
is_float_rprimitive,
is_int16_rprimitive,
is_int32_rprimitive,
is_int64_rprimitive,
is_int_rprimitive,
is_list_rprimitive,
is_sequence_rprimitive,
is_str_rprimitive,
is_tagged,
is_uint8_rprimitive,
list_rprimitive,
object_rprimitive,
set_rprimitive,
str_rprimitive,
string_writer_rprimitive,
uint8_rprimitive,
)
from mypyc.irbuild.builder import IRBuilder
from mypyc.irbuild.constant_fold import constant_fold_expr
from mypyc.irbuild.for_helpers import (
comprehension_helper,
get_expr_length_value,
sequence_from_generator_preallocate_helper,
translate_list_comprehension,
translate_set_comprehension,
)
from mypyc.irbuild.format_str_tokenizer import (
FormatOp,
convert_format_expr_to_str,
join_formatted_strings,
tokenizer_format_call,
)
from mypyc.irbuild.vec import vec_append, vec_pop, vec_remove
from mypyc.primitives.bytearray_ops import isinstance_bytearray
from mypyc.primitives.bytes_ops import (
bytes_adjust_index_op,
bytes_get_item_unsafe_op,
bytes_range_check_op,
isinstance_bytes,
)
from mypyc.primitives.dict_ops import (
dict_items_op,
dict_keys_op,
dict_setdefault_spec_init_op,
dict_values_op,
isinstance_dict,
)
from mypyc.primitives.float_ops import isinstance_float
from mypyc.primitives.generic_ops import generic_setattr, setup_object
from mypyc.primitives.int_ops import (
int_to_big_endian_op,
int_to_bytes_op,
int_to_little_endian_op,
isinstance_int,
)
from mypyc.primitives.librt_strings_ops import (
bytes_writer_adjust_index_op,
bytes_writer_get_item_unsafe_op,
bytes_writer_range_check_op,
bytes_writer_set_item_unsafe_op,
string_writer_adjust_index_op,
string_writer_get_item_unsafe_op,
string_writer_range_check_op,
)
from mypyc.primitives.librt_vecs_ops import isinstance_vec
from mypyc.primitives.list_ops import isinstance_list, new_list_set_item_op
from mypyc.primitives.misc_ops import isinstance_bool
from mypyc.primitives.set_ops import isinstance_frozenset, isinstance_set
from mypyc.primitives.str_ops import (
bytes_decode_ascii_strict,
bytes_decode_latin1_strict,
bytes_decode_utf8_strict,
isinstance_str,
str_adjust_index_op,
str_encode_ascii_strict,
str_encode_latin1_strict,
str_encode_utf8_strict,
str_get_item_unsafe_as_int_op,
str_range_check_op,
)
from mypyc.primitives.tuple_ops import isinstance_tuple, new_tuple_set_item_op
# Specializers are attempted before compiling the arguments to the
# function. Specializers can return None to indicate that they failed
# and the call should be compiled normally. Otherwise they should emit
# code for the call and return a Value containing the result.
#
# Specializers take three arguments: the IRBuilder, the CallExpr being
# compiled, and the RefExpr that is the left hand side of the call.
Specializer = Callable[["IRBuilder", CallExpr, RefExpr], Value | None]
# Dunder specializers are for special method calls like __getitem__, __setitem__, etc.
# that don't naturally map to CallExpr nodes (e.g., from IndexExpr).
#
# They take four arguments: the IRBuilder, the base expression (target object),
# the list of argument expressions (positional arguments to the dunder), and the
# context expression (e.g., IndexExpr) for error reporting.
DunderSpecializer = Callable[["IRBuilder", Expression, list[Expression], Expression], Value | None]
# Dictionary containing all configured specializers.
#
# Specializers can operate on methods as well, and are keyed on the
# name and RType in that case.
specializers: dict[tuple[str, RType | None], list[Specializer]] = {}
# Dictionary containing all configured dunder specializers.
#
# Dunder specializers are keyed on the dunder name and RType (always a method call).
dunder_specializers: dict[tuple[str, RType], list[DunderSpecializer]] = {}
def _apply_specialization(
builder: IRBuilder, expr: CallExpr, callee: RefExpr, name: str | None, typ: RType | None = None
) -> Value | None:
# TODO: Allow special cases to have default args or named args. Currently they don't since
# they check that everything in arg_kinds is ARG_POS.
# If there is a specializer for this function, try calling it.
# Return the first successful one.
if name and (name, typ) in specializers:
for specializer in specializers[name, typ]:
val = specializer(builder, expr, callee)
if val is not None:
return val
return None
def apply_function_specialization(
builder: IRBuilder, expr: CallExpr, callee: RefExpr
) -> Value | None:
"""Invoke the Specializer callback for a function if one has been registered"""
return _apply_specialization(builder, expr, callee, callee.fullname)
def apply_method_specialization(
builder: IRBuilder, expr: CallExpr, callee: MemberExpr, typ: RType | None = None
) -> Value | None:
"""Invoke the Specializer callback for a method if one has been registered"""
name = callee.fullname if typ is None else callee.name
return _apply_specialization(builder, expr, callee, name, typ)
def specialize_function(
name: str, typ: RType | None = None
) -> Callable[[Specializer], Specializer]:
"""Decorator to register a function as being a specializer.
There may exist multiple specializers for one function. When
translating method calls, the earlier appended specializer has
higher priority.
"""
def wrapper(f: Specializer) -> Specializer:
specializers.setdefault((name, typ), []).append(f)
return f
return wrapper
def specialize_dunder(name: str, typ: RType) -> Callable[[DunderSpecializer], DunderSpecializer]:
"""Decorator to register a function as being a dunder specializer.
Dunder specializers handle special method calls like __getitem__ that
don't naturally map to CallExpr nodes.
There may exist multiple specializers for one dunder. When translating
dunder calls, the earlier appended specializer has higher priority.
"""
def wrapper(f: DunderSpecializer) -> DunderSpecializer:
dunder_specializers.setdefault((name, typ), []).append(f)
return f
return wrapper
def apply_dunder_specialization(
builder: IRBuilder,
base_expr: Expression,
args: list[Expression],
name: str,
ctx_expr: Expression,
) -> Value | None:
"""Invoke the DunderSpecializer callback if one has been registered.
Args:
builder: The IR builder
base_expr: The base expression (target object)
args: List of argument expressions (positional arguments to the dunder)
name: The dunder method name (e.g., "__getitem__")
ctx_expr: The context expression for error reporting (e.g., IndexExpr)
Returns:
The specialized value, or None if no specialization was found.
"""
base_type = builder.node_type(base_expr)
# Check if there's a specializer for this dunder method and type
if (name, base_type) in dunder_specializers:
for specializer in dunder_specializers[name, base_type]:
val = specializer(builder, base_expr, args, ctx_expr)
if val is not None:
return val
return None
@specialize_function("builtins.globals")
def translate_globals(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
if len(expr.args) == 0:
return builder.load_globals_dict()
return None
@specialize_function("builtins.abs")
@specialize_function("builtins.int")
@specialize_function("builtins.float")
@specialize_function("builtins.complex")
@specialize_function("mypy_extensions.i64")
@specialize_function("mypy_extensions.i32")
@specialize_function("mypy_extensions.i16")
@specialize_function("mypy_extensions.u8")
def translate_builtins_with_unary_dunder(
builder: IRBuilder, expr: CallExpr, callee: RefExpr
) -> Value | None:
"""Specialize calls on native classes that implement the associated dunder.
E.g. i64(x) gets specialized to x.__int__() if x is a native instance.
"""
if len(expr.args) == 1 and expr.arg_kinds == [ARG_POS] and isinstance(callee, NameExpr):
arg = expr.args[0]
arg_typ = builder.node_type(arg)
shortname = callee.fullname.split(".")[1]
if shortname in ("i64", "i32", "i16", "u8"):
method = "__int__"
else:
method = f"__{shortname}__"
if isinstance(arg_typ, RInstance) and arg_typ.class_ir.has_method(method):
obj = builder.accept(arg)
return builder.gen_method_call(obj, method, [], None, expr.line)
return None
@specialize_function("builtins.len")
def translate_len(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
if len(expr.args) == 1 and expr.arg_kinds == [ARG_POS]:
arg = expr.args[0]
expr_rtype = builder.node_type(arg)
# NOTE (?) I'm not sure if my handling of can_borrow is correct here
obj = builder.accept(
arg, can_borrow=is_list_rprimitive(expr_rtype) or isinstance(expr_rtype, RVec)
)
if is_sequence_rprimitive(expr_rtype) or isinstance(expr_rtype, RTuple):
return get_expr_length_value(builder, arg, obj, expr.line, use_pyssize_t=False)
else:
# TODO: Decide type of result based on context somehow?
if isinstance(obj.type, RVec):
return builder.builtin_len(obj, expr.line, use_pyssize_t=True)
else:
return builder.builtin_len(obj, expr.line)
return None
@specialize_function("builtins.list")
def dict_methods_fast_path(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
"""Specialize a common case when list() is called on a dictionary
view method call.
For example:
foo = list(bar.keys())
"""
if not (len(expr.args) == 1 and expr.arg_kinds == [ARG_POS]):
return None
arg = expr.args[0]
if not (isinstance(arg, CallExpr) and not arg.args and isinstance(arg.callee, MemberExpr)):
return None
base = arg.callee.expr
attr = arg.callee.name
rtype = builder.node_type(base)
if not (is_dict_rprimitive(rtype) and attr in ("keys", "values", "items")):
return None
obj = builder.accept(base)
# Note that it is not safe to use fast methods on dict subclasses,
# so the corresponding helpers in CPy.h fallback to (inlined)
# generic logic.
if attr == "keys":
return builder.call_c(dict_keys_op, [obj], expr.line)
elif attr == "values":
return builder.call_c(dict_values_op, [obj], expr.line)
else:
return builder.call_c(dict_items_op, [obj], expr.line)
@specialize_function("builtins.list")
def translate_list_from_generator_call(
builder: IRBuilder, expr: CallExpr, callee: RefExpr
) -> Value | None:
"""Special case for simplest list comprehension.
For example:
list(f(x) for x in some_list/some_tuple/some_str)
'translate_list_comprehension()' would take care of other cases
if this fails.
"""
if (
len(expr.args) == 1
and expr.arg_kinds[0] == ARG_POS
and isinstance(expr.args[0], GeneratorExpr)
):
def set_item(x: Value, y: Value, z: Value, line: int) -> None:
builder.call_c(new_list_set_item_op, [x, y, z], line)
return sequence_from_generator_preallocate_helper(
builder,
expr.args[0],
empty_op_llbuilder=builder.builder.new_list_op_with_length,
set_item_op=set_item,
)
return None
@specialize_function("builtins.tuple")
def translate_tuple_from_generator_call(
builder: IRBuilder, expr: CallExpr, callee: RefExpr
) -> Value | None:
"""Special case for simplest tuple creation from a generator.
For example:
tuple(f(x) for x in some_list/some_tuple/some_str/some_bytes)
'translate_safe_generator_call()' would take care of other cases
if this fails.
"""
if (
len(expr.args) == 1
and expr.arg_kinds[0] == ARG_POS
and isinstance(expr.args[0], GeneratorExpr)
):
def set_item(x: Value, y: Value, z: Value, line: int) -> None:
builder.call_c(new_tuple_set_item_op, [x, y, z], line)
return sequence_from_generator_preallocate_helper(
builder,
expr.args[0],
empty_op_llbuilder=builder.builder.new_tuple_with_length,
set_item_op=set_item,
)
return None
@specialize_function("builtins.set")
def translate_set_from_generator_call(
builder: IRBuilder, expr: CallExpr, callee: RefExpr
) -> Value | None:
"""Special case for set creation from a generator.
For example:
set(f(...) for ... in iterator/nested_generators...)
"""
if (
len(expr.args) == 1
and expr.arg_kinds[0] == ARG_POS
and isinstance(expr.args[0], GeneratorExpr)
):
return translate_set_comprehension(builder, expr.args[0])
return None
@specialize_function("builtins.min")
@specialize_function("builtins.max")
def faster_min_max(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
if expr.arg_kinds == [ARG_POS, ARG_POS]:
x, y = builder.accept(expr.args[0]), builder.accept(expr.args[1])
result = Register(builder.node_type(expr))
# CPython evaluates arguments reversely when calling min(...) or max(...)
if callee.fullname == "builtins.min":
comparison = builder.binary_op(y, x, "<", expr.line)
else:
comparison = builder.binary_op(y, x, ">", expr.line)
true_block, false_block, next_block = BasicBlock(), BasicBlock(), BasicBlock()
builder.add_bool_branch(comparison, true_block, false_block)
builder.activate_block(true_block)
builder.assign(result, builder.coerce(y, result.type, expr.line), expr.line)
builder.goto(next_block)
builder.activate_block(false_block)
builder.assign(result, builder.coerce(x, result.type, expr.line), expr.line)
builder.goto(next_block)
builder.activate_block(next_block)
return result
return None
@specialize_function("builtins.tuple")
@specialize_function("builtins.frozenset")
@specialize_function("builtins.dict")
@specialize_function("builtins.min")
@specialize_function("builtins.max")
@specialize_function("builtins.sorted")
@specialize_function("collections.OrderedDict")
@specialize_function("join", str_rprimitive)
@specialize_function("extend", list_rprimitive)
@specialize_function("update", dict_rprimitive)
@specialize_function("update", set_rprimitive)
def translate_safe_generator_call(
builder: IRBuilder, expr: CallExpr, callee: RefExpr
) -> Value | None:
"""Special cases for things that consume iterators where we know we
can safely compile a generator into a list.
"""
if (
len(expr.args) > 0
and expr.arg_kinds[0] == ARG_POS
and isinstance(expr.args[0], GeneratorExpr)
):
if isinstance(callee, MemberExpr):
return builder.gen_method_call(
builder.accept(callee.expr),
callee.name,
(
[translate_list_comprehension(builder, expr.args[0])]
+ [builder.accept(arg) for arg in expr.args[1:]]
),
builder.node_type(expr),
expr.line,
expr.arg_kinds,
expr.arg_names,
)
else:
return builder.call_refexpr_with_args(
expr,
callee,
(
[translate_list_comprehension(builder, expr.args[0])]
+ [builder.accept(arg) for arg in expr.args[1:]]
),
)
return None
@specialize_function("builtins.any")
def translate_any_call(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
if (
len(expr.args) == 1
and expr.arg_kinds == [ARG_POS]
and isinstance(expr.args[0], GeneratorExpr)
):
return any_all_helper(builder, expr.args[0], builder.false, lambda x: x, builder.true)
return None
@specialize_function("builtins.all")
def translate_all_call(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
if (
len(expr.args) == 1
and expr.arg_kinds == [ARG_POS]
and isinstance(expr.args[0], GeneratorExpr)
):
return any_all_helper(
builder,
expr.args[0],
builder.true,
lambda x: builder.unary_op(x, "not", expr.line),
builder.false,
)
return None
def any_all_helper(
builder: IRBuilder,
gen: GeneratorExpr,
initial_value: Callable[[], Value],
modify: Callable[[Value], Value],
new_value: Callable[[], Value],
) -> Value:
init_val = initial_value()
retval = Register(bool_rprimitive, line=init_val.line)
builder.assign(retval, init_val, init_val.line)
loop_params = list(zip(gen.indices, gen.sequences, gen.condlists, gen.is_async))
true_block, false_block, exit_block = BasicBlock(), BasicBlock(), BasicBlock()
def gen_inner_stmts() -> None:
comparison = modify(builder.accept(gen.left_expr))
builder.add_bool_branch(comparison, true_block, false_block)
builder.activate_block(true_block)
new_val = new_value()
builder.assign(retval, new_val, new_val.line)
builder.goto(exit_block)
builder.activate_block(false_block)
comprehension_helper(builder, loop_params, gen_inner_stmts, gen.line)
builder.goto_and_activate(exit_block)
return retval
@specialize_function("builtins.sum")
def translate_sum_call(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
# specialized implementation is used if:
# - only one or two arguments given (if not, sum() has been given invalid arguments)
# - first argument is a Generator (there is no benefit to optimizing the performance of eg.
# sum([1, 2, 3]), so non-Generator Iterables are not handled)
if not (
len(expr.args) in (1, 2)
and expr.arg_kinds[0] == ARG_POS
and isinstance(expr.args[0], GeneratorExpr)
):
return None
# handle 'start' argument, if given
if len(expr.args) == 2:
# ensure call to sum() was properly constructed
if expr.arg_kinds[1] not in (ARG_POS, ARG_NAMED):
return None
start_expr = expr.args[1]
else:
start_expr = IntExpr(0)
gen_expr = expr.args[0]
target_type = builder.node_type(expr)
retval = Register(target_type, line=expr.line)
builder.assign(
retval, builder.coerce(builder.accept(start_expr), target_type, expr.line), expr.line
)
def gen_inner_stmts() -> None:
call_expr = builder.accept(gen_expr.left_expr)
builder.assign(
retval, builder.binary_op(retval, call_expr, "+", call_expr.line), call_expr.line
)
loop_params = list(
zip(gen_expr.indices, gen_expr.sequences, gen_expr.condlists, gen_expr.is_async)
)
comprehension_helper(builder, loop_params, gen_inner_stmts, gen_expr.line)
return retval
@specialize_function("dataclasses.field")
@specialize_function("attr.ib")
@specialize_function("attr.attrib")
@specialize_function("attr.Factory")
def translate_dataclasses_field_call(
builder: IRBuilder, expr: CallExpr, callee: RefExpr
) -> Value | None:
"""Special case for 'dataclasses.field', 'attr.attrib', and 'attr.Factory'
function calls because the results of such calls are type-checked
by mypy using the types of the arguments to their respective
functions, resulting in attempted coercions by mypyc that throw a
runtime error.
"""
builder.types[expr] = AnyType(TypeOfAny.from_error)
return None
@specialize_function("builtins.next")
def translate_next_call(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
"""Special case for calling next() on a generator expression, an
idiom that shows up some in mypy.
For example, next(x for x in l if x.id == 12, None) will
generate code that searches l for an element where x.id == 12
and produce the first such object, or None if no such element
exists.
"""
if not (
expr.arg_kinds in ([ARG_POS], [ARG_POS, ARG_POS])
and isinstance(expr.args[0], GeneratorExpr)
):
return None
gen = expr.args[0]
retval = Register(builder.node_type(expr))
default_val = builder.accept(expr.args[1]) if len(expr.args) > 1 else None
exit_block = BasicBlock()
def gen_inner_stmts() -> None:
# next takes the first element of the generator, so if
# something gets produced, we are done.
builder.assign(retval, builder.accept(gen.left_expr), gen.left_expr.line)
builder.goto(exit_block)
loop_params = list(zip(gen.indices, gen.sequences, gen.condlists, gen.is_async))
comprehension_helper(builder, loop_params, gen_inner_stmts, gen.line)
# Now we need the case for when nothing got hit. If there was
# a default value, we produce it, and otherwise we raise
# StopIteration.
if default_val:
builder.assign(retval, default_val, gen.left_expr.line)
builder.goto(exit_block)
else:
builder.add(RaiseStandardError(RaiseStandardError.STOP_ITERATION, None, expr.line))
builder.add(Unreachable())
builder.activate_block(exit_block)
return retval
isinstance_primitives: Final = {
"builtins.bool": isinstance_bool,
"builtins.bytearray": isinstance_bytearray,
"builtins.bytes": isinstance_bytes,
"builtins.dict": isinstance_dict,
"builtins.float": isinstance_float,
"builtins.frozenset": isinstance_frozenset,
"builtins.int": isinstance_int,
"builtins.list": isinstance_list,
"builtins.set": isinstance_set,
"builtins.str": isinstance_str,
"builtins.tuple": isinstance_tuple,
"librt.vecs.vec": isinstance_vec,
}
@specialize_function("builtins.isinstance")
def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
"""Special case for builtins.isinstance.
Prevent coercions on the thing we are checking the instance of -
there is no need to coerce something to a new type before checking
what type it is, and the coercion could lead to bugs.
"""
if not (len(expr.args) == 2 and expr.arg_kinds == [ARG_POS, ARG_POS]):
return None
obj_expr = expr.args[0]
type_expr = expr.args[1]
if isinstance(type_expr, TupleExpr) and not type_expr.items:
# we can compile this case to a noop
return builder.false()
if isinstance(type_expr, (RefExpr, TupleExpr)):
builder.types[obj_expr] = AnyType(TypeOfAny.from_error)
irs = builder.flatten_classes(type_expr)
if irs is not None:
can_borrow = all(
ir.is_ext_class and not ir.inherits_python and not ir.allow_interpreted_subclasses
for ir in irs
)
obj = builder.accept(obj_expr, can_borrow=can_borrow)
return builder.builder.isinstance_helper(obj, irs, expr.line)
if isinstance(type_expr, RefExpr):
node = type_expr.node
if node:
desc = isinstance_primitives.get(node.fullname)
if desc:
obj = builder.accept(obj_expr)
return builder.primitive_op(desc, [obj], expr.line)
elif isinstance(type_expr, TupleExpr):
node_names: list[str] = []
for item in type_expr.items:
if not isinstance(item, RefExpr):
return None
if item.node is None:
return None
if item.node.fullname not in node_names:
node_names.append(item.node.fullname)
descs = [isinstance_primitives.get(fullname) for fullname in node_names]
if None in descs:
# not all types are primitive types, abort
return None
obj = builder.accept(obj_expr)
retval = Register(bool_rprimitive)
pass_block = BasicBlock()
fail_block = BasicBlock()
exit_block = BasicBlock()
# Chain the checks: if any succeed, jump to pass_block; else, continue
for i, desc in enumerate(descs):
is_last = i == len(descs) - 1
next_block = fail_block if is_last else BasicBlock()
builder.add_bool_branch(
builder.primitive_op(cast(PrimitiveDescription, desc), [obj], expr.line),
pass_block,
next_block,
)
if not is_last:
builder.activate_block(next_block)
# If any check passed
builder.activate_block(pass_block)
builder.assign(retval, builder.true(), expr.line)
builder.goto(exit_block)
# If all checks failed
builder.activate_block(fail_block)
builder.assign(retval, builder.false(), expr.line)
builder.goto(exit_block)
# Return the result
builder.activate_block(exit_block)
return retval
return None
@specialize_function("setdefault", dict_rprimitive)
def translate_dict_setdefault(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
"""Special case for 'dict.setdefault' which would only construct
default empty collection when needed.
The dict_setdefault_spec_init_op checks whether the dict contains
the key and would construct the empty collection only once.
For example, this specializer works for the following cases:
d.setdefault(key, set()).add(value)
d.setdefault(key, []).append(value)
d.setdefault(key, {})[inner_key] = inner_val
"""
if (
len(expr.args) == 2
and expr.arg_kinds == [ARG_POS, ARG_POS]
and isinstance(callee, MemberExpr)
):
arg = expr.args[1]
if isinstance(arg, ListExpr):
if len(arg.items):
return None
data_type = Integer(1, c_int_rprimitive, expr.line)
elif isinstance(arg, DictExpr):
if len(arg.items):
return None
data_type = Integer(2, c_int_rprimitive, expr.line)
elif (
isinstance(arg, CallExpr)
and isinstance(arg.callee, NameExpr)
and arg.callee.fullname == "builtins.set"
):
if len(arg.args):
return None
data_type = Integer(3, c_int_rprimitive, expr.line)
else:
return None
callee_dict = builder.accept(callee.expr)
key_val = builder.accept(expr.args[0])
return builder.call_c(
dict_setdefault_spec_init_op, [callee_dict, key_val, data_type], expr.line
)
return None
@specialize_function("format", str_rprimitive)
def translate_str_format(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
if isinstance(callee, MemberExpr):
folded_callee = constant_fold_expr(builder, callee.expr)
if isinstance(folded_callee, str) and expr.arg_kinds.count(ARG_POS) == len(expr.arg_kinds):
tokens = tokenizer_format_call(folded_callee)
if tokens is None:
return None
literals, format_ops = tokens
# Convert variables to strings
substitutions = convert_format_expr_to_str(builder, format_ops, expr.args, expr.line)
if substitutions is None:
return None
return join_formatted_strings(builder, literals, substitutions, expr.line)
return None
@specialize_function("join", str_rprimitive)
def translate_fstring(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
"""Special case for f-string, which is translated into str.join()
in mypy AST.
This specializer optimizes simplest f-strings which don't contain
any format operation.
"""
if (
isinstance(callee, MemberExpr)
and isinstance(callee.expr, StrExpr)
and callee.expr.value == ""
and expr.arg_kinds == [ARG_POS]
and isinstance(expr.args[0], ListExpr)
):
for item in expr.args[0].items:
if isinstance(item, StrExpr):
continue
elif isinstance(item, CallExpr):
if not isinstance(item.callee, MemberExpr) or item.callee.name != "format":
return None
elif (
not isinstance(item.callee.expr, StrExpr) or item.callee.expr.value != "{:{}}"
):
return None
if not isinstance(item.args[1], StrExpr) or item.args[1].value != "":
return None
else:
return None
format_ops = []
exprs: list[Expression] = []
for item in expr.args[0].items:
if isinstance(item, StrExpr) and item.value != "":
format_ops.append(FormatOp.STR)
exprs.append(item)
elif isinstance(item, CallExpr):
format_ops.append(FormatOp.STR)
exprs.append(item.args[0])
def get_literal_str(expr: Expression) -> str | None:
if isinstance(expr, StrExpr):
return expr.value
elif isinstance(expr, RefExpr) and isinstance(expr.node, Var) and expr.node.is_final:
final_value = expr.node.final_value
if final_value is not None:
return str(final_value)
return None
for i in range(len(exprs) - 1):
while (
len(exprs) >= i + 2
and (first := get_literal_str(exprs[i])) is not None
and (second := get_literal_str(exprs[i + 1])) is not None
):
exprs = [*exprs[:i], StrExpr(first + second), *exprs[i + 2 :]]
format_ops = [*format_ops[:i], FormatOp.STR, *format_ops[i + 2 :]]
substitutions = convert_format_expr_to_str(builder, format_ops, exprs, expr.line)
if substitutions is None:
return None
return join_formatted_strings(builder, None, substitutions, expr.line)
return None
@specialize_function("encode", str_rprimitive)
def str_encode_fast_path(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
"""Specialize common cases of str.encode for most used encodings and strict errors."""
if not isinstance(callee, MemberExpr):
return None
# We can only specialize if we have string literals as args
if len(expr.arg_kinds) > 0 and not isinstance(expr.args[0], StrExpr):
return None
if len(expr.arg_kinds) > 1 and not isinstance(expr.args[1], StrExpr):
return None
encoding = "utf8"
errors = "strict"
if len(expr.arg_kinds) > 0 and isinstance(expr.args[0], StrExpr):
if expr.arg_kinds[0] == ARG_NAMED:
if expr.arg_names[0] == "encoding":
encoding = expr.args[0].value
elif expr.arg_names[0] == "errors":
errors = expr.args[0].value
elif expr.arg_kinds[0] == ARG_POS:
encoding = expr.args[0].value
else:
return None
if len(expr.arg_kinds) > 1 and isinstance(expr.args[1], StrExpr):
if expr.arg_kinds[1] == ARG_NAMED:
if expr.arg_names[1] == "encoding":
encoding = expr.args[1].value
elif expr.arg_names[1] == "errors":
errors = expr.args[1].value
elif expr.arg_kinds[1] == ARG_POS:
errors = expr.args[1].value
else:
return None
if errors != "strict":
# We can only specialize strict errors
return None
encoding = encoding.lower().replace("-", "").replace("_", "") # normalize
# Specialized encodings and their accepted aliases
if encoding in ["u8", "utf", "utf8", "cp65001"]:
return builder.call_c(str_encode_utf8_strict, [builder.accept(callee.expr)], expr.line)
elif encoding in ["646", "ascii", "usascii"]:
return builder.call_c(str_encode_ascii_strict, [builder.accept(callee.expr)], expr.line)
elif encoding in ["iso88591", "8859", "cp819", "latin", "latin1", "l1"]:
return builder.call_c(str_encode_latin1_strict, [builder.accept(callee.expr)], expr.line)
return None
@specialize_function("decode", bytes_rprimitive)
def bytes_decode_fast_path(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
"""Specialize common cases of obj.decode for most used encodings and strict errors."""
if not isinstance(callee, MemberExpr):
return None
# We can only specialize if we have string literals as args
if len(expr.arg_kinds) > 0 and not isinstance(expr.args[0], StrExpr):
return None
if len(expr.arg_kinds) > 1 and not isinstance(expr.args[1], StrExpr):
return None
encoding = "utf8"
errors = "strict"
if len(expr.arg_kinds) > 0 and isinstance(expr.args[0], StrExpr):
if expr.arg_kinds[0] == ARG_NAMED:
if expr.arg_names[0] == "encoding":
encoding = expr.args[0].value
elif expr.arg_names[0] == "errors":
errors = expr.args[0].value
elif expr.arg_kinds[0] == ARG_POS:
encoding = expr.args[0].value
else:
return None
if len(expr.arg_kinds) > 1 and isinstance(expr.args[1], StrExpr):
if expr.arg_kinds[1] == ARG_NAMED:
if expr.arg_names[1] == "encoding":
encoding = expr.args[1].value
elif expr.arg_names[1] == "errors":
errors = expr.args[1].value
elif expr.arg_kinds[1] == ARG_POS:
errors = expr.args[1].value
else:
return None
if errors != "strict":
# We can only specialize strict errors
return None
encoding = encoding.lower().replace("_", "-") # normalize
# Specialized encodings and their accepted aliases
if encoding in ["u8", "utf", "utf8", "utf-8", "cp65001"]:
return builder.call_c(bytes_decode_utf8_strict, [builder.accept(callee.expr)], expr.line)
elif encoding in ["646", "ascii", "usascii", "us-ascii"]:
return builder.call_c(bytes_decode_ascii_strict, [builder.accept(callee.expr)], expr.line)
elif encoding in [
"iso8859-1",
"iso-8859-1",
"8859",
"cp819",
"latin",
"latin1",
"latin-1",
"l1",
]:
return builder.call_c(bytes_decode_latin1_strict, [builder.accept(callee.expr)], expr.line)
return None
@specialize_function("mypy_extensions.i64")
def translate_i64(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
if len(expr.args) != 1 or expr.arg_kinds[0] != ARG_POS:
return None
arg = expr.args[0]
arg_type = builder.node_type(arg)
if is_int64_rprimitive(arg_type):
return builder.accept(arg)
elif is_int32_rprimitive(arg_type) or is_int16_rprimitive(arg_type):
val = builder.accept(arg)
return builder.add(Extend(val, int64_rprimitive, signed=True, line=expr.line))
elif is_uint8_rprimitive(arg_type):
val = builder.accept(arg)
return builder.add(Extend(val, int64_rprimitive, signed=False, line=expr.line))
elif is_int_rprimitive(arg_type) or is_bool_rprimitive(arg_type):
val = builder.accept(arg)
return builder.coerce(val, int64_rprimitive, expr.line)
return None
@specialize_function("mypy_extensions.i32")
def translate_i32(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
if len(expr.args) != 1 or expr.arg_kinds[0] != ARG_POS:
return None
arg = expr.args[0]
arg_type = builder.node_type(arg)
if is_int32_rprimitive(arg_type):
return builder.accept(arg)
elif is_int64_rprimitive(arg_type):
val = builder.accept(arg)
return builder.add(Truncate(val, int32_rprimitive, line=expr.line))
elif is_int16_rprimitive(arg_type):
val = builder.accept(arg)
return builder.add(Extend(val, int32_rprimitive, signed=True, line=expr.line))
elif is_uint8_rprimitive(arg_type):
val = builder.accept(arg)
return builder.add(Extend(val, int32_rprimitive, signed=False, line=expr.line))
elif is_int_rprimitive(arg_type) or is_bool_rprimitive(arg_type):
val = builder.accept(arg)
val = truncate_literal(val, int32_rprimitive)
return builder.coerce(val, int32_rprimitive, expr.line)
return None
@specialize_function("mypy_extensions.i16")
def translate_i16(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
if len(expr.args) != 1 or expr.arg_kinds[0] != ARG_POS:
return None
arg = expr.args[0]
arg_type = builder.node_type(arg)
if is_int16_rprimitive(arg_type):
return builder.accept(arg)
elif is_int32_rprimitive(arg_type) or is_int64_rprimitive(arg_type):
val = builder.accept(arg)
return builder.add(Truncate(val, int16_rprimitive, line=expr.line))
elif is_uint8_rprimitive(arg_type):
val = builder.accept(arg)
return builder.add(Extend(val, int16_rprimitive, signed=False, line=expr.line))
elif is_int_rprimitive(arg_type) or is_bool_rprimitive(arg_type):
val = builder.accept(arg)
val = truncate_literal(val, int16_rprimitive)
return builder.coerce(val, int16_rprimitive, expr.line)
return None
@specialize_function("mypy_extensions.u8")
def translate_u8(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
if len(expr.args) != 1 or expr.arg_kinds[0] != ARG_POS:
return None
arg = expr.args[0]
arg_type = builder.node_type(arg)
if is_uint8_rprimitive(arg_type):
return builder.accept(arg)
elif (
is_int16_rprimitive(arg_type)
or is_int32_rprimitive(arg_type)
or is_int64_rprimitive(arg_type)
):
val = builder.accept(arg)
return builder.add(Truncate(val, uint8_rprimitive, line=expr.line))
elif is_int_rprimitive(arg_type) or is_bool_rprimitive(arg_type):
val = builder.accept(arg)
val = truncate_literal(val, uint8_rprimitive)
return builder.coerce(val, uint8_rprimitive, expr.line)
return None
def truncate_literal(value: Value, rtype: RPrimitive) -> Value:
"""If value is an integer literal value, truncate it to given native int rtype.
For example, truncate 256 into 0 if rtype is u8.
"""
if not isinstance(value, Integer):
return value # Not a literal, nothing to do
x = value.numeric_value()
max_unsigned = (1 << (rtype.size * 8)) - 1
x = x & max_unsigned
if rtype.is_signed and x >= (max_unsigned + 1) // 2:
# Adjust to make it a negative value
x -= max_unsigned + 1
return Integer(x, rtype)
@specialize_function("builtins.int")
def translate_int(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
if len(expr.args) != 1 or expr.arg_kinds[0] != ARG_POS:
return None
arg = expr.args[0]
arg_type = builder.node_type(arg)
if (
is_bool_rprimitive(arg_type)
or is_int_rprimitive(arg_type)
or is_fixed_width_rtype(arg_type)
):
src = builder.accept(arg)
return builder.coerce(src, int_rprimitive, expr.line)
return None
@specialize_function("builtins.bool")
def translate_bool(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
if len(expr.args) != 1 or expr.arg_kinds[0] != ARG_POS:
return None
arg = expr.args[0]
src = builder.accept(arg)
return builder.builder.bool_value(src)
@specialize_function("builtins.float")
def translate_float(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
if len(expr.args) != 1 or expr.arg_kinds[0] != ARG_POS:
return None
arg = expr.args[0]
arg_type = builder.node_type(arg)
if is_float_rprimitive(arg_type):
# No-op float conversion.
return builder.accept(arg)
return None
@specialize_function("builtins.ord")
def translate_ord(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
if len(expr.args) != 1 or expr.arg_kinds[0] != ARG_POS:
return None
arg_expr = expr.args[0]
arg = constant_fold_expr(builder, arg_expr)
if isinstance(arg, (str, bytes)) and len(arg) == 1:
return Integer(ord(arg))
# Check for ord(s[i]) where s is str and i is an integer
if isinstance(arg_expr, IndexExpr):
# Check base type
base_type = builder.node_type(arg_expr.base)
if is_str_rprimitive(base_type):
# Check index type
index_expr = arg_expr.index
index_type = builder.node_type(index_expr)
if is_tagged(index_type) or is_fixed_width_rtype(index_type):
# This is ord(s[i]) where s is str and i is an integer.
# Generate specialized inline code using the helper.
result = translate_getitem_with_bounds_check(
builder,
arg_expr.base,
[arg_expr.index],
expr,
str_adjust_index_op,
str_range_check_op,
str_get_item_unsafe_as_int_op,
)
return result
return None
def is_object(callee: RefExpr) -> bool:
"""Returns True for object.<name> calls."""
return (
isinstance(callee, MemberExpr)
and isinstance(callee.expr, NameExpr)
and callee.expr.fullname == "builtins.object"
)
def is_super_or_object(expr: CallExpr, callee: RefExpr) -> bool:
"""Returns True for super().<name> or object.<name> calls."""
return isinstance(expr.callee, SuperExpr) or is_object(callee)
@specialize_function("__new__", object_rprimitive)
def translate_object_new(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
fn = builder.fn_info
if fn.name != "__new__" or not is_super_or_object(expr, callee):
return None
ir = builder.get_current_class_ir()
if ir is None:
return None
call = '"object.__new__()"'
if not ir.is_ext_class:
builder.error(f"{call} not supported for non-extension classes", expr.line)
return None
if ir.inherits_python:
builder.error(
f"{call} not supported for classes inheriting from non-native classes", expr.line
)
return None
if len(expr.args) != 1:
builder.error(f"{call} supported only with 1 argument, got {len(expr.args)}", expr.line)
return None
typ_arg = expr.args[0]
method_args = fn.fitem.arg_names
if isinstance(typ_arg, NameExpr) and len(method_args) > 0 and method_args[0] == typ_arg.name:
subtype = builder.accept(expr.args[0])
subs = ir.subclasses()
if subs is not None and len(subs) == 0:
return builder.add(Call(ir.setup, [subtype], expr.line))
# Call a function that dynamically resolves the setup function of extension classes from the type object.
# This is necessary because the setup involves default attribute initialization and setting up
# the vtable which are specific to a given type and will not work if a subtype is created using
# the setup function of its base.
return builder.call_c(setup_object, [subtype], expr.line)
return None
@specialize_function("__setattr__", object_rprimitive)
def translate_object_setattr(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
is_super = isinstance(expr.callee, SuperExpr)
is_object_callee = is_object(callee)
if not ((is_super and len(expr.args) >= 2) or (is_object_callee and len(expr.args) >= 3)):
return None
self_reg = builder.accept(expr.args[0]) if is_object_callee else builder.self()
ir = builder.get_current_class_ir()
if ir and (not ir.is_ext_class or ir.builtin_base or ir.inherits_python):
return None
# Need to offset by 1 for super().__setattr__ calls because there is no self arg in this case.
name_idx = 0 if is_super else 1
value_idx = 1 if is_super else 2
attr_name = expr.args[name_idx]
attr_value = expr.args[value_idx]
value = builder.accept(attr_value)
if isinstance(attr_name, StrExpr) and ir and ir.has_attr(attr_name.value):
name = attr_name.value
value = builder.coerce(value, ir.attributes[name], expr.line)
return builder.add(SetAttr(self_reg, name, value, expr.line))
name_reg = builder.accept(attr_name)
return builder.call_c(generic_setattr, [self_reg, name_reg, value], expr.line)
@specialize_function("to_bytes", int_rprimitive)
def specialize_int_to_bytes(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
# int.to_bytes(length, byteorder, signed=False)
if any(kind not in (ARG_POS, ARG_NAMED) for kind in expr.arg_kinds):
return None
if not isinstance(callee, MemberExpr):
return None
length_expr: Expression | None = None
byteorder_expr: Expression | None = None
signed_expr: Expression | None = None
positional_index = 0
for name, arg in zip(expr.arg_names, expr.args):
if name is None:
if positional_index == 0:
length_expr = arg
elif positional_index == 1:
byteorder_expr = arg
elif positional_index == 2:
signed_expr = arg
else:
return None
positional_index += 1
elif name == "length":
if length_expr is not None:
return None
length_expr = arg
elif name == "byteorder":
if byteorder_expr is not None:
return None
byteorder_expr = arg
elif name == "signed":
if signed_expr is not None:
return None
signed_expr = arg
else:
return None
if length_expr is None or byteorder_expr is None:
return None
signed_is_bool = True
if signed_expr is not None:
signed_is_bool = is_bool_rprimitive(builder.node_type(signed_expr))
if not (
is_int_rprimitive(builder.node_type(length_expr))
and is_str_rprimitive(builder.node_type(byteorder_expr))
and signed_is_bool
):
return None
self_arg = builder.accept(callee.expr)
length_arg = builder.accept(length_expr)
if signed_expr is None:
signed_arg = builder.false()
else:
signed_arg = builder.accept(signed_expr)
if isinstance(byteorder_expr, StrExpr):
if byteorder_expr.value == "little":
return builder.call_c(
int_to_little_endian_op, [self_arg, length_arg, signed_arg], expr.line
)
elif byteorder_expr.value == "big":
return builder.call_c(
int_to_big_endian_op, [self_arg, length_arg, signed_arg], expr.line
)
# Fallback to generic primitive op
byteorder_arg = builder.accept(byteorder_expr)
return builder.call_c(
int_to_bytes_op, [self_arg, length_arg, byteorder_arg, signed_arg], expr.line
)
def translate_getitem_with_bounds_check(
builder: IRBuilder,
base_expr: Expression,
args: list[Expression],
ctx_expr: Expression,
adjust_index_op: PrimitiveDescription,
range_check_op: PrimitiveDescription,
get_item_unsafe_op: PrimitiveDescription,
) -> Value | None:
"""Shared helper for optimized __getitem__ with bounds checking.
This implements the common pattern of:
1. Adjusting negative indices
2. Checking if index is in valid range
3. Raising IndexError if out of range
4. Getting the item if in range
Args:
builder: The IR builder
base_expr: The base object expression
args: The arguments to __getitem__ (should be length 1)
ctx_expr: The context expression for line numbers
adjust_index_op: Primitive op to adjust negative indices
range_check_op: Primitive op to check if index is in valid range
get_item_unsafe_op: Primitive op to get item (no bounds checking)
Returns:
The result value, or None if optimization doesn't apply
"""
# Check that we have exactly one argument
if len(args) != 1:
return None
# Get the object
obj = builder.accept(base_expr)
# Get the index argument
index = builder.accept(args[0])
# Adjust the index (handle negative indices)
adjusted_index = builder.primitive_op(adjust_index_op, [obj, index], ctx_expr.line)
# Check if the adjusted index is in valid range
range_check = builder.primitive_op(range_check_op, [obj, adjusted_index], ctx_expr.line)
# Create blocks for branching
valid_block = BasicBlock()
invalid_block = BasicBlock()
builder.add_bool_branch(range_check, valid_block, invalid_block)
# Handle invalid index - raise IndexError
builder.activate_block(invalid_block)
builder.add(
RaiseStandardError(RaiseStandardError.INDEX_ERROR, "index out of range", ctx_expr.line)
)
builder.add(Unreachable())
# Handle valid index - get the item
builder.activate_block(valid_block)
result = builder.primitive_op(get_item_unsafe_op, [obj, adjusted_index], ctx_expr.line)
return result
@specialize_dunder("__getitem__", bytes_writer_rprimitive)
def translate_bytes_writer_get_item(
builder: IRBuilder, base_expr: Expression, args: list[Expression], ctx_expr: Expression
) -> Value | None:
"""Optimized BytesWriter.__getitem__ implementation with bounds checking."""
return translate_getitem_with_bounds_check(
builder,
base_expr,
args,
ctx_expr,
bytes_writer_adjust_index_op,
bytes_writer_range_check_op,
bytes_writer_get_item_unsafe_op,
)
@specialize_dunder("__setitem__", bytes_writer_rprimitive)
def translate_bytes_writer_set_item(
builder: IRBuilder, base_expr: Expression, args: list[Expression], ctx_expr: Expression
) -> Value | None:
"""Optimized BytesWriter.__setitem__ implementation with bounds checking."""
# Check that we have exactly two arguments (index and value)
if len(args) != 2:
return None
# Get the BytesWriter object
obj = builder.accept(base_expr)
# Get the index and value arguments
index = builder.accept(args[0])
value = builder.accept(args[1])
# Adjust the index (handle negative indices)
adjusted_index = builder.primitive_op(
bytes_writer_adjust_index_op, [obj, index], ctx_expr.line
)
# Check if the adjusted index is in valid range
range_check = builder.primitive_op(
bytes_writer_range_check_op, [obj, adjusted_index], ctx_expr.line
)
# Create blocks for branching
valid_block = BasicBlock()
invalid_block = BasicBlock()
builder.add_bool_branch(range_check, valid_block, invalid_block)
# Handle invalid index - raise IndexError
builder.activate_block(invalid_block)
builder.add(
RaiseStandardError(RaiseStandardError.INDEX_ERROR, "index out of range", ctx_expr.line)
)
builder.add(Unreachable())
# Handle valid index - set the item
builder.activate_block(valid_block)
builder.primitive_op(
bytes_writer_set_item_unsafe_op, [obj, adjusted_index, value], ctx_expr.line
)
return builder.none()
@specialize_dunder("__getitem__", string_writer_rprimitive)
def translate_string_writer_get_item(
builder: IRBuilder, base_expr: Expression, args: list[Expression], ctx_expr: Expression
) -> Value | None:
"""Optimized StringWriter.__getitem__ implementation with bounds checking."""
return translate_getitem_with_bounds_check(
builder,
base_expr,
args,
ctx_expr,
string_writer_adjust_index_op,
string_writer_range_check_op,
string_writer_get_item_unsafe_op,
)
@specialize_dunder("__getitem__", bytes_rprimitive)
def translate_bytes_get_item(
builder: IRBuilder, base_expr: Expression, args: list[Expression], ctx_expr: Expression
) -> Value | None:
"""Optimized bytes.__getitem__ implementation with bounds checking."""
return translate_getitem_with_bounds_check(
builder,
base_expr,
args,
ctx_expr,
bytes_adjust_index_op,
bytes_range_check_op,
bytes_get_item_unsafe_op,
)
@specialize_function("librt.vecs.append")
def translate_vec_append(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
if len(expr.args) == 2 and expr.arg_kinds == [ARG_POS, ARG_POS]:
vec_arg = expr.args[0]
item_arg = expr.args[1]
vec_type = builder.node_type(vec_arg)
if isinstance(vec_type, RVec):
vec_value = builder.accept(vec_arg)
arg_value = builder.accept(item_arg)
return vec_append(builder.builder, vec_value, arg_value, item_arg.line)
return None
@specialize_function("librt.vecs.remove")
def translate_vec_remove(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
if len(expr.args) == 2 and expr.arg_kinds == [ARG_POS, ARG_POS]:
vec_arg = expr.args[0]
item_arg = expr.args[1]
vec_type = builder.node_type(vec_arg)
if isinstance(vec_type, RVec):
vec_value = builder.accept(vec_arg)
arg_value = builder.accept(item_arg)
return vec_remove(builder.builder, vec_value, arg_value, item_arg.line)
return None
@specialize_function("librt.vecs.pop")
def translate_vec_pop(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
if 1 <= len(expr.args) <= 2 and all(kind == ARG_POS for kind in expr.arg_kinds):
vec_arg = expr.args[0]
vec_type = builder.node_type(vec_arg)
if isinstance(vec_type, RVec):
vec_value = builder.accept(vec_arg)
if len(expr.args) == 2:
index_value = builder.accept(expr.args[1])
else:
index_value = Integer(-1, int64_rprimitive)
return vec_pop(builder.builder, vec_value, index_value, vec_arg.line)
return None