| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067 |
- """Transform mypy expression ASTs to mypyc IR (Intermediate Representation).
- The top-level AST transformation logic is implemented in mypyc.irbuild.visitor
- and mypyc.irbuild.builder.
- """
- from __future__ import annotations
- import math
- from typing import Callable, Sequence
- from mypy.nodes import (
- ARG_POS,
- LDEF,
- AssertTypeExpr,
- AssignmentExpr,
- BytesExpr,
- CallExpr,
- CastExpr,
- ComparisonExpr,
- ComplexExpr,
- ConditionalExpr,
- DictExpr,
- DictionaryComprehension,
- EllipsisExpr,
- Expression,
- FloatExpr,
- GeneratorExpr,
- IndexExpr,
- IntExpr,
- ListComprehension,
- ListExpr,
- MemberExpr,
- MypyFile,
- NameExpr,
- OpExpr,
- RefExpr,
- SetComprehension,
- SetExpr,
- SliceExpr,
- StarExpr,
- StrExpr,
- SuperExpr,
- TupleExpr,
- TypeApplication,
- TypeInfo,
- UnaryExpr,
- Var,
- )
- from mypy.types import Instance, ProperType, TupleType, TypeType, get_proper_type
- from mypyc.common import MAX_SHORT_INT
- from mypyc.ir.class_ir import ClassIR
- from mypyc.ir.func_ir import FUNC_CLASSMETHOD, FUNC_STATICMETHOD
- from mypyc.ir.ops import (
- Assign,
- BasicBlock,
- ComparisonOp,
- Integer,
- LoadAddress,
- LoadLiteral,
- RaiseStandardError,
- Register,
- TupleGet,
- TupleSet,
- Value,
- )
- from mypyc.ir.rtypes import (
- RTuple,
- bool_rprimitive,
- int_rprimitive,
- is_fixed_width_rtype,
- is_int_rprimitive,
- is_list_rprimitive,
- is_none_rprimitive,
- object_rprimitive,
- set_rprimitive,
- )
- from mypyc.irbuild.ast_helpers import is_borrow_friendly_expr, process_conditional
- from mypyc.irbuild.builder import IRBuilder, int_borrow_friendly_op
- from mypyc.irbuild.constant_fold import constant_fold_expr
- from mypyc.irbuild.for_helpers import (
- comprehension_helper,
- translate_list_comprehension,
- translate_set_comprehension,
- )
- from mypyc.irbuild.format_str_tokenizer import (
- convert_format_expr_to_bytes,
- convert_format_expr_to_str,
- join_formatted_bytes,
- join_formatted_strings,
- tokenizer_printf_style,
- )
- from mypyc.irbuild.specialize import apply_function_specialization, apply_method_specialization
- from mypyc.primitives.bytes_ops import bytes_slice_op
- from mypyc.primitives.dict_ops import dict_get_item_op, dict_new_op, dict_set_item_op
- from mypyc.primitives.generic_ops import iter_op
- from mypyc.primitives.int_ops import int_comparison_op_mapping
- from mypyc.primitives.list_ops import list_append_op, list_extend_op, list_slice_op
- from mypyc.primitives.misc_ops import ellipsis_op, get_module_dict_op, new_slice_op, type_op
- from mypyc.primitives.registry import CFunctionDescription, builtin_names
- from mypyc.primitives.set_ops import set_add_op, set_in_op, set_update_op
- from mypyc.primitives.str_ops import str_slice_op
- from mypyc.primitives.tuple_ops import list_tuple_op, tuple_slice_op
- # Name and attribute references
- def transform_name_expr(builder: IRBuilder, expr: NameExpr) -> Value:
- if expr.node is None:
- builder.add(
- RaiseStandardError(
- RaiseStandardError.RUNTIME_ERROR,
- "mypyc internal error: should be unreachable",
- expr.line,
- )
- )
- return builder.none()
- fullname = expr.node.fullname
- if fullname in builtin_names:
- typ, src = builtin_names[fullname]
- return builder.add(LoadAddress(typ, src, expr.line))
- # special cases
- if fullname == "builtins.None":
- return builder.none()
- if fullname == "builtins.True":
- return builder.true()
- if fullname == "builtins.False":
- return builder.false()
- math_literal = transform_math_literal(builder, fullname)
- if math_literal is not None:
- return math_literal
- if isinstance(expr.node, Var) and expr.node.is_final:
- value = builder.emit_load_final(
- expr.node,
- fullname,
- expr.name,
- builder.is_native_ref_expr(expr),
- builder.types[expr],
- expr.line,
- )
- if value is not None:
- return value
- if isinstance(expr.node, MypyFile) and expr.node.fullname in builder.imports:
- return builder.load_module(expr.node.fullname)
- # If the expression is locally defined, then read the result from the corresponding
- # assignment target and return it. Otherwise if the expression is a global, load it from
- # the globals dictionary.
- # Except for imports, that currently always happens in the global namespace.
- if expr.kind == LDEF and not (isinstance(expr.node, Var) and expr.node.is_suppressed_import):
- # Try to detect and error when we hit the irritating mypy bug
- # where a local variable is cast to None. (#5423)
- if (
- isinstance(expr.node, Var)
- and is_none_rprimitive(builder.node_type(expr))
- and expr.node.is_inferred
- ):
- builder.error(
- 'Local variable "{}" has inferred type None; add an annotation'.format(
- expr.node.name
- ),
- expr.node.line,
- )
- # TODO: Behavior currently only defined for Var, FuncDef and MypyFile node types.
- if isinstance(expr.node, MypyFile):
- # Load reference to a module imported inside function from
- # the modules dictionary. It would be closer to Python
- # semantics to access modules imported inside functions
- # via local variables, but this is tricky since the mypy
- # AST doesn't include a Var node for the module. We
- # instead load the module separately on each access.
- mod_dict = builder.call_c(get_module_dict_op, [], expr.line)
- obj = builder.call_c(
- dict_get_item_op, [mod_dict, builder.load_str(expr.node.fullname)], expr.line
- )
- return obj
- else:
- return builder.read(builder.get_assignment_target(expr, for_read=True), expr.line)
- return builder.load_global(expr)
- def transform_member_expr(builder: IRBuilder, expr: MemberExpr) -> Value:
- # First check if this is maybe a final attribute.
- final = builder.get_final_ref(expr)
- if final is not None:
- fullname, final_var, native = final
- value = builder.emit_load_final(
- final_var, fullname, final_var.name, native, builder.types[expr], expr.line
- )
- if value is not None:
- return value
- math_literal = transform_math_literal(builder, expr.fullname)
- if math_literal is not None:
- return math_literal
- if isinstance(expr.node, MypyFile) and expr.node.fullname in builder.imports:
- return builder.load_module(expr.node.fullname)
- can_borrow = builder.is_native_attr_ref(expr)
- obj = builder.accept(expr.expr, can_borrow=can_borrow)
- rtype = builder.node_type(expr)
- # Special case: for named tuples transform attribute access to faster index access.
- typ = get_proper_type(builder.types.get(expr.expr))
- if isinstance(typ, TupleType) and typ.partial_fallback.type.is_named_tuple:
- fields = typ.partial_fallback.type.metadata["namedtuple"]["fields"]
- if expr.name in fields:
- index = builder.builder.load_int(fields.index(expr.name))
- return builder.gen_method_call(obj, "__getitem__", [index], rtype, expr.line)
- check_instance_attribute_access_through_class(builder, expr, typ)
- borrow = can_borrow and builder.can_borrow
- return builder.builder.get_attr(obj, expr.name, rtype, expr.line, borrow=borrow)
- def check_instance_attribute_access_through_class(
- builder: IRBuilder, expr: MemberExpr, typ: ProperType | None
- ) -> None:
- """Report error if accessing an instance attribute through class object."""
- if isinstance(expr.expr, RefExpr):
- node = expr.expr.node
- if isinstance(typ, TypeType) and isinstance(typ.item, Instance):
- # TODO: Handle other item types
- node = typ.item.type
- if isinstance(node, TypeInfo):
- class_ir = builder.mapper.type_to_ir.get(node)
- if class_ir is not None and class_ir.is_ext_class:
- sym = node.get(expr.name)
- if (
- sym is not None
- and isinstance(sym.node, Var)
- and not sym.node.is_classvar
- and not sym.node.is_final
- ):
- builder.error(
- 'Cannot access instance attribute "{}" through class object'.format(
- expr.name
- ),
- expr.line,
- )
- builder.note(
- '(Hint: Use "x: Final = ..." or "x: ClassVar = ..." to define '
- "a class attribute)",
- expr.line,
- )
- def transform_super_expr(builder: IRBuilder, o: SuperExpr) -> Value:
- # warning(builder, 'can not optimize super() expression', o.line)
- sup_val = builder.load_module_attr_by_fullname("builtins.super", o.line)
- if o.call.args:
- args = [builder.accept(arg) for arg in o.call.args]
- else:
- assert o.info is not None
- typ = builder.load_native_type_object(o.info.fullname)
- ir = builder.mapper.type_to_ir[o.info]
- iter_env = iter(builder.builder.args)
- # Grab first argument
- vself: Value = next(iter_env)
- if builder.fn_info.is_generator:
- # grab sixth argument (see comment in translate_super_method_call)
- self_targ = list(builder.symtables[-1].values())[6]
- vself = builder.read(self_targ, builder.fn_info.fitem.line)
- elif not ir.is_ext_class:
- vself = next(iter_env) # second argument is self if non_extension class
- args = [typ, vself]
- res = builder.py_call(sup_val, args, o.line)
- return builder.py_get_attr(res, o.name, o.line)
- # Calls
- def transform_call_expr(builder: IRBuilder, expr: CallExpr) -> Value:
- callee = expr.callee
- if isinstance(expr.analyzed, CastExpr):
- return translate_cast_expr(builder, expr.analyzed)
- elif isinstance(expr.analyzed, AssertTypeExpr):
- # Compile to a no-op.
- return builder.accept(expr.analyzed.expr)
- elif (
- isinstance(callee, (NameExpr, MemberExpr))
- and isinstance(callee.node, TypeInfo)
- and callee.node.is_newtype
- ):
- # A call to a NewType type is a no-op at runtime.
- return builder.accept(expr.args[0])
- if isinstance(callee, IndexExpr) and isinstance(callee.analyzed, TypeApplication):
- callee = callee.analyzed.expr # Unwrap type application
- if isinstance(callee, MemberExpr):
- if isinstance(callee.expr, RefExpr) and isinstance(callee.expr.node, MypyFile):
- # Call a module-level function, not a method.
- return translate_call(builder, expr, callee)
- return apply_method_specialization(builder, expr, callee) or translate_method_call(
- builder, expr, callee
- )
- elif isinstance(callee, SuperExpr):
- return translate_super_method_call(builder, expr, callee)
- else:
- return translate_call(builder, expr, callee)
- def translate_call(builder: IRBuilder, expr: CallExpr, callee: Expression) -> Value:
- # The common case of calls is refexprs
- if isinstance(callee, RefExpr):
- return apply_function_specialization(builder, expr, callee) or translate_refexpr_call(
- builder, expr, callee
- )
- function = builder.accept(callee)
- args = [builder.accept(arg) for arg in expr.args]
- return builder.py_call(
- function, args, expr.line, arg_kinds=expr.arg_kinds, arg_names=expr.arg_names
- )
- def translate_refexpr_call(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value:
- """Translate a non-method call."""
- # Gen the argument values
- arg_values = [builder.accept(arg) for arg in expr.args]
- return builder.call_refexpr_with_args(expr, callee, arg_values)
- def translate_method_call(builder: IRBuilder, expr: CallExpr, callee: MemberExpr) -> Value:
- """Generate IR for an arbitrary call of form e.m(...).
- This can also deal with calls to module-level functions.
- """
- if builder.is_native_ref_expr(callee):
- # Call to module-level native function or such
- return translate_call(builder, expr, callee)
- elif (
- isinstance(callee.expr, RefExpr)
- and isinstance(callee.expr.node, TypeInfo)
- and callee.expr.node in builder.mapper.type_to_ir
- and builder.mapper.type_to_ir[callee.expr.node].has_method(callee.name)
- ):
- # Call a method via the *class*
- assert isinstance(callee.expr.node, TypeInfo)
- ir = builder.mapper.type_to_ir[callee.expr.node]
- return call_classmethod(builder, ir, expr, callee)
- elif builder.is_module_member_expr(callee):
- # Fall back to a PyCall for non-native module calls
- function = builder.accept(callee)
- args = [builder.accept(arg) for arg in expr.args]
- return builder.py_call(
- function, args, expr.line, arg_kinds=expr.arg_kinds, arg_names=expr.arg_names
- )
- else:
- if isinstance(callee.expr, RefExpr):
- node = callee.expr.node
- if isinstance(node, Var) and node.is_cls:
- typ = get_proper_type(node.type)
- if isinstance(typ, TypeType) and isinstance(typ.item, Instance):
- class_ir = builder.mapper.type_to_ir.get(typ.item.type)
- if class_ir and class_ir.is_ext_class and class_ir.has_no_subclasses():
- # Call a native classmethod via cls that can be statically bound,
- # since the class has no subclasses.
- return call_classmethod(builder, class_ir, expr, callee)
- receiver_typ = builder.node_type(callee.expr)
- # If there is a specializer for this method name/type, try calling it.
- # We would return the first successful one.
- val = apply_method_specialization(builder, expr, callee, receiver_typ)
- if val is not None:
- return val
- obj = builder.accept(callee.expr)
- args = [builder.accept(arg) for arg in expr.args]
- return builder.gen_method_call(
- obj,
- callee.name,
- args,
- builder.node_type(expr),
- expr.line,
- expr.arg_kinds,
- expr.arg_names,
- )
- def call_classmethod(builder: IRBuilder, ir: ClassIR, expr: CallExpr, callee: MemberExpr) -> Value:
- decl = ir.method_decl(callee.name)
- args = []
- arg_kinds, arg_names = expr.arg_kinds.copy(), expr.arg_names.copy()
- # Add the class argument for class methods in extension classes
- if decl.kind == FUNC_CLASSMETHOD and ir.is_ext_class:
- args.append(builder.load_native_type_object(ir.fullname))
- arg_kinds.insert(0, ARG_POS)
- arg_names.insert(0, None)
- args += [builder.accept(arg) for arg in expr.args]
- if ir.is_ext_class:
- return builder.builder.call(decl, args, arg_kinds, arg_names, expr.line)
- else:
- obj = builder.accept(callee.expr)
- return builder.gen_method_call(
- obj,
- callee.name,
- args,
- builder.node_type(expr),
- expr.line,
- expr.arg_kinds,
- expr.arg_names,
- )
- def translate_super_method_call(builder: IRBuilder, expr: CallExpr, callee: SuperExpr) -> Value:
- if callee.info is None or (len(callee.call.args) != 0 and len(callee.call.args) != 2):
- return translate_call(builder, expr, callee)
- # We support two-argument super but only when it is super(CurrentClass, self)
- # TODO: We could support it when it is a parent class in many cases?
- if len(callee.call.args) == 2:
- self_arg = callee.call.args[1]
- if (
- not isinstance(self_arg, NameExpr)
- or not isinstance(self_arg.node, Var)
- or not self_arg.node.is_self
- ):
- return translate_call(builder, expr, callee)
- typ_arg = callee.call.args[0]
- if (
- not isinstance(typ_arg, NameExpr)
- or not isinstance(typ_arg.node, TypeInfo)
- or callee.info is not typ_arg.node
- ):
- return translate_call(builder, expr, callee)
- ir = builder.mapper.type_to_ir[callee.info]
- # Search for the method in the mro, skipping ourselves. We
- # determine targets of super calls to native methods statically.
- for base in ir.mro[1:]:
- if callee.name in base.method_decls:
- break
- else:
- if (
- ir.is_ext_class
- and ir.builtin_base is None
- and not ir.inherits_python
- and callee.name == "__init__"
- and len(expr.args) == 0
- ):
- # Call translates to object.__init__(self), which is a
- # no-op, so omit the call.
- return builder.none()
- return translate_call(builder, expr, callee)
- decl = base.method_decl(callee.name)
- arg_values = [builder.accept(arg) for arg in expr.args]
- arg_kinds, arg_names = expr.arg_kinds.copy(), expr.arg_names.copy()
- if decl.kind != FUNC_STATICMETHOD:
- # Grab first argument
- vself: Value = builder.self()
- if decl.kind == FUNC_CLASSMETHOD:
- vself = builder.call_c(type_op, [vself], expr.line)
- elif builder.fn_info.is_generator:
- # For generator classes, the self target is the 6th value
- # in the symbol table (which is an ordered dict). This is sort
- # of ugly, but we can't search by name since the 'self' parameter
- # could be named anything, and it doesn't get added to the
- # environment indexes.
- self_targ = list(builder.symtables[-1].values())[6]
- vself = builder.read(self_targ, builder.fn_info.fitem.line)
- arg_values.insert(0, vself)
- arg_kinds.insert(0, ARG_POS)
- arg_names.insert(0, None)
- return builder.builder.call(decl, arg_values, arg_kinds, arg_names, expr.line)
- def translate_cast_expr(builder: IRBuilder, expr: CastExpr) -> Value:
- src = builder.accept(expr.expr)
- target_type = builder.type_to_rtype(expr.type)
- return builder.coerce(src, target_type, expr.line)
- # Operators
- def transform_unary_expr(builder: IRBuilder, expr: UnaryExpr) -> Value:
- folded = try_constant_fold(builder, expr)
- if folded:
- return folded
- return builder.unary_op(builder.accept(expr.expr), expr.op, expr.line)
- def transform_op_expr(builder: IRBuilder, expr: OpExpr) -> Value:
- if expr.op in ("and", "or"):
- return builder.shortcircuit_expr(expr)
- # Special case for string formatting
- if expr.op == "%" and isinstance(expr.left, (StrExpr, BytesExpr)):
- ret = translate_printf_style_formatting(builder, expr.left, expr.right)
- if ret is not None:
- return ret
- folded = try_constant_fold(builder, expr)
- if folded:
- return folded
- borrow_left = False
- borrow_right = False
- ltype = builder.node_type(expr.left)
- rtype = builder.node_type(expr.right)
- # Special case some int ops to allow borrowing operands.
- if is_int_rprimitive(ltype) and is_int_rprimitive(rtype):
- if expr.op == "//":
- expr = try_optimize_int_floor_divide(expr)
- if expr.op in int_borrow_friendly_op:
- borrow_left = is_borrow_friendly_expr(builder, expr.right)
- borrow_right = True
- elif is_fixed_width_rtype(ltype) and is_fixed_width_rtype(rtype):
- borrow_left = is_borrow_friendly_expr(builder, expr.right)
- borrow_right = True
- left = builder.accept(expr.left, can_borrow=borrow_left)
- right = builder.accept(expr.right, can_borrow=borrow_right)
- return builder.binary_op(left, right, expr.op, expr.line)
- def try_optimize_int_floor_divide(expr: OpExpr) -> OpExpr:
- """Replace // with a power of two with a right shift, if possible."""
- if not isinstance(expr.right, IntExpr):
- return expr
- divisor = expr.right.value
- shift = divisor.bit_length() - 1
- if 0 < shift < 28 and divisor == (1 << shift):
- return OpExpr(">>", expr.left, IntExpr(shift))
- return expr
- def transform_index_expr(builder: IRBuilder, expr: IndexExpr) -> Value:
- index = expr.index
- base_type = builder.node_type(expr.base)
- is_list = is_list_rprimitive(base_type)
- can_borrow_base = is_list and is_borrow_friendly_expr(builder, index)
- base = builder.accept(expr.base, can_borrow=can_borrow_base)
- if isinstance(base.type, RTuple) and isinstance(index, IntExpr):
- return builder.add(TupleGet(base, index.value, expr.line))
- if isinstance(index, SliceExpr):
- value = try_gen_slice_op(builder, base, index)
- if value:
- return value
- index_reg = builder.accept(expr.index, can_borrow=is_list)
- return builder.gen_method_call(
- base, "__getitem__", [index_reg], builder.node_type(expr), expr.line
- )
- def try_constant_fold(builder: IRBuilder, expr: Expression) -> Value | None:
- """Return the constant value of an expression if possible.
- Return None otherwise.
- """
- value = constant_fold_expr(builder, expr)
- if value is not None:
- return builder.load_literal_value(value)
- return None
- def try_gen_slice_op(builder: IRBuilder, base: Value, index: SliceExpr) -> Value | None:
- """Generate specialized slice op for some index expressions.
- Return None if a specialized op isn't available.
- This supports obj[x:y], obj[:x], and obj[x:] for a few types.
- """
- if index.stride:
- # We can only handle the default stride of 1.
- return None
- if index.begin_index:
- begin_type = builder.node_type(index.begin_index)
- else:
- begin_type = int_rprimitive
- if index.end_index:
- end_type = builder.node_type(index.end_index)
- else:
- end_type = int_rprimitive
- # Both begin and end index must be int (or missing).
- if is_int_rprimitive(begin_type) and is_int_rprimitive(end_type):
- if index.begin_index:
- begin = builder.accept(index.begin_index)
- else:
- begin = builder.load_int(0)
- if index.end_index:
- end = builder.accept(index.end_index)
- else:
- # Replace missing end index with the largest short integer
- # (a sequence can't be longer).
- end = builder.load_int(MAX_SHORT_INT)
- candidates = [list_slice_op, tuple_slice_op, str_slice_op, bytes_slice_op]
- return builder.builder.matching_call_c(candidates, [base, begin, end], index.line)
- return None
- def transform_conditional_expr(builder: IRBuilder, expr: ConditionalExpr) -> Value:
- if_body, else_body, next_block = BasicBlock(), BasicBlock(), BasicBlock()
- process_conditional(builder, expr.cond, if_body, else_body)
- expr_type = builder.node_type(expr)
- # Having actual Phi nodes would be really nice here!
- target = Register(expr_type)
- builder.activate_block(if_body)
- true_value = builder.accept(expr.if_expr)
- true_value = builder.coerce(true_value, expr_type, expr.line)
- builder.add(Assign(target, true_value))
- builder.goto(next_block)
- builder.activate_block(else_body)
- false_value = builder.accept(expr.else_expr)
- false_value = builder.coerce(false_value, expr_type, expr.line)
- builder.add(Assign(target, false_value))
- builder.goto(next_block)
- builder.activate_block(next_block)
- return target
- def set_literal_values(builder: IRBuilder, items: Sequence[Expression]) -> list[object] | None:
- values: list[object] = []
- for item in items:
- const_value = constant_fold_expr(builder, item)
- if const_value is not None:
- values.append(const_value)
- continue
- if isinstance(item, RefExpr):
- if item.fullname == "builtins.None":
- values.append(None)
- elif item.fullname == "builtins.True":
- values.append(True)
- elif item.fullname == "builtins.False":
- values.append(False)
- elif isinstance(item, TupleExpr):
- tuple_values = set_literal_values(builder, item.items)
- if tuple_values is not None:
- values.append(tuple(tuple_values))
- if len(values) != len(items):
- # Bail if not all items can be converted into values.
- return None
- return values
- def precompute_set_literal(builder: IRBuilder, s: SetExpr) -> Value | None:
- """Try to pre-compute a frozenset literal during module initialization.
- Return None if it's not possible.
- Supported items:
- - Anything supported by irbuild.constant_fold.constant_fold_expr()
- - None, True, and False
- - Tuple literals with only items listed above
- """
- values = set_literal_values(builder, s.items)
- if values is not None:
- return builder.add(LoadLiteral(frozenset(values), set_rprimitive))
- return None
- def transform_comparison_expr(builder: IRBuilder, e: ComparisonExpr) -> Value:
- # x in (...)/[...]
- # x not in (...)/[...]
- first_op = e.operators[0]
- if (
- first_op in ["in", "not in"]
- and len(e.operators) == 1
- and isinstance(e.operands[1], (TupleExpr, ListExpr))
- ):
- items = e.operands[1].items
- n_items = len(items)
- # x in y -> x == y[0] or ... or x == y[n]
- # x not in y -> x != y[0] and ... and x != y[n]
- # 16 is arbitrarily chosen to limit code size
- if 1 < n_items < 16:
- if e.operators[0] == "in":
- bin_op = "or"
- cmp_op = "=="
- else:
- bin_op = "and"
- cmp_op = "!="
- lhs = e.operands[0]
- mypy_file = builder.graph["builtins"].tree
- assert mypy_file is not None
- info = mypy_file.names["bool"].node
- assert isinstance(info, TypeInfo)
- bool_type = Instance(info, [])
- exprs = []
- for item in items:
- expr = ComparisonExpr([cmp_op], [lhs, item])
- builder.types[expr] = bool_type
- exprs.append(expr)
- or_expr: Expression = exprs.pop(0)
- for expr in exprs:
- or_expr = OpExpr(bin_op, or_expr, expr)
- builder.types[or_expr] = bool_type
- return builder.accept(or_expr)
- # x in [y]/(y) -> x == y
- # x not in [y]/(y) -> x != y
- elif n_items == 1:
- if e.operators[0] == "in":
- cmp_op = "=="
- else:
- cmp_op = "!="
- e.operators = [cmp_op]
- e.operands[1] = items[0]
- # x in []/() -> False
- # x not in []/() -> True
- elif n_items == 0:
- if e.operators[0] == "in":
- return builder.false()
- else:
- return builder.true()
- # x in {...}
- # x not in {...}
- if (
- first_op in ("in", "not in")
- and len(e.operators) == 1
- and isinstance(e.operands[1], SetExpr)
- ):
- set_literal = precompute_set_literal(builder, e.operands[1])
- if set_literal is not None:
- lhs = e.operands[0]
- result = builder.builder.call_c(
- set_in_op, [builder.accept(lhs), set_literal], e.line, bool_rprimitive
- )
- if first_op == "not in":
- return builder.unary_op(result, "not", e.line)
- return result
- if len(e.operators) == 1:
- # Special some common simple cases
- if first_op in ("is", "is not"):
- right_expr = e.operands[1]
- if isinstance(right_expr, NameExpr) and right_expr.fullname == "builtins.None":
- # Special case 'is None' / 'is not None'.
- return translate_is_none(builder, e.operands[0], negated=first_op != "is")
- left_expr = e.operands[0]
- if is_int_rprimitive(builder.node_type(left_expr)):
- right_expr = e.operands[1]
- if is_int_rprimitive(builder.node_type(right_expr)):
- if first_op in int_borrow_friendly_op:
- borrow_left = is_borrow_friendly_expr(builder, right_expr)
- left = builder.accept(left_expr, can_borrow=borrow_left)
- right = builder.accept(right_expr, can_borrow=True)
- return builder.compare_tagged(left, right, first_op, e.line)
- # TODO: Don't produce an expression when used in conditional context
- # All of the trickiness here is due to support for chained conditionals
- # (`e1 < e2 > e3`, etc). `e1 < e2 > e3` is approximately equivalent to
- # `e1 < e2 and e2 > e3` except that `e2` is only evaluated once.
- expr_type = builder.node_type(e)
- # go(i, prev) generates code for `ei opi e{i+1} op{i+1} ... en`,
- # assuming that prev contains the value of `ei`.
- def go(i: int, prev: Value) -> Value:
- if i == len(e.operators) - 1:
- return transform_basic_comparison(
- builder, e.operators[i], prev, builder.accept(e.operands[i + 1]), e.line
- )
- next = builder.accept(e.operands[i + 1])
- return builder.builder.shortcircuit_helper(
- "and",
- expr_type,
- lambda: transform_basic_comparison(builder, e.operators[i], prev, next, e.line),
- lambda: go(i + 1, next),
- e.line,
- )
- return go(0, builder.accept(e.operands[0]))
- def translate_is_none(builder: IRBuilder, expr: Expression, negated: bool) -> Value:
- v = builder.accept(expr, can_borrow=True)
- return builder.binary_op(v, builder.none_object(), "is not" if negated else "is", expr.line)
- def transform_basic_comparison(
- builder: IRBuilder, op: str, left: Value, right: Value, line: int
- ) -> Value:
- if (
- is_int_rprimitive(left.type)
- and is_int_rprimitive(right.type)
- and op in int_comparison_op_mapping
- ):
- return builder.compare_tagged(left, right, op, line)
- if is_fixed_width_rtype(left.type) and op in int_comparison_op_mapping:
- if right.type == left.type:
- if left.type.is_signed:
- op_id = ComparisonOp.signed_ops[op]
- else:
- op_id = ComparisonOp.unsigned_ops[op]
- return builder.builder.comparison_op(left, right, op_id, line)
- elif isinstance(right, Integer):
- if left.type.is_signed:
- op_id = ComparisonOp.signed_ops[op]
- else:
- op_id = ComparisonOp.unsigned_ops[op]
- return builder.builder.comparison_op(
- left, builder.coerce(right, left.type, line), op_id, line
- )
- elif (
- is_fixed_width_rtype(right.type)
- and op in int_comparison_op_mapping
- and isinstance(left, Integer)
- ):
- if right.type.is_signed:
- op_id = ComparisonOp.signed_ops[op]
- else:
- op_id = ComparisonOp.unsigned_ops[op]
- return builder.builder.comparison_op(
- builder.coerce(left, right.type, line), right, op_id, line
- )
- negate = False
- if op == "is not":
- op, negate = "is", True
- elif op == "not in":
- op, negate = "in", True
- target = builder.binary_op(left, right, op, line)
- if negate:
- target = builder.unary_op(target, "not", line)
- return target
- def translate_printf_style_formatting(
- builder: IRBuilder, format_expr: StrExpr | BytesExpr, rhs: Expression
- ) -> Value | None:
- tokens = tokenizer_printf_style(format_expr.value)
- if tokens is not None:
- literals, format_ops = tokens
- exprs = []
- if isinstance(rhs, TupleExpr):
- exprs = rhs.items
- elif isinstance(rhs, Expression):
- exprs.append(rhs)
- if isinstance(format_expr, BytesExpr):
- substitutions = convert_format_expr_to_bytes(
- builder, format_ops, exprs, format_expr.line
- )
- if substitutions is not None:
- return join_formatted_bytes(builder, literals, substitutions, format_expr.line)
- else:
- substitutions = convert_format_expr_to_str(
- builder, format_ops, exprs, format_expr.line
- )
- if substitutions is not None:
- return join_formatted_strings(builder, literals, substitutions, format_expr.line)
- return None
- # Literals
- def transform_int_expr(builder: IRBuilder, expr: IntExpr) -> Value:
- return builder.builder.load_int(expr.value)
- def transform_float_expr(builder: IRBuilder, expr: FloatExpr) -> Value:
- return builder.builder.load_float(expr.value)
- def transform_complex_expr(builder: IRBuilder, expr: ComplexExpr) -> Value:
- return builder.builder.load_complex(expr.value)
- def transform_str_expr(builder: IRBuilder, expr: StrExpr) -> Value:
- return builder.load_str(expr.value)
- def transform_bytes_expr(builder: IRBuilder, expr: BytesExpr) -> Value:
- return builder.load_bytes_from_str_literal(expr.value)
- def transform_ellipsis(builder: IRBuilder, o: EllipsisExpr) -> Value:
- return builder.add(LoadAddress(ellipsis_op.type, ellipsis_op.src, o.line))
- # Display expressions
- def transform_list_expr(builder: IRBuilder, expr: ListExpr) -> Value:
- return _visit_list_display(builder, expr.items, expr.line)
- def _visit_list_display(builder: IRBuilder, items: list[Expression], line: int) -> Value:
- return _visit_display(
- builder, items, builder.new_list_op, list_append_op, list_extend_op, line, True
- )
- def transform_tuple_expr(builder: IRBuilder, expr: TupleExpr) -> Value:
- if any(isinstance(item, StarExpr) for item in expr.items):
- # create a tuple of unknown length
- return _visit_tuple_display(builder, expr)
- # create a tuple of fixed length (RTuple)
- tuple_type = builder.node_type(expr)
- # When handling NamedTuple et. al we might not have proper type info,
- # so make some up if we need it.
- types = (
- tuple_type.types
- if isinstance(tuple_type, RTuple)
- else [object_rprimitive] * len(expr.items)
- )
- items = []
- for item_expr, item_type in zip(expr.items, types):
- reg = builder.accept(item_expr)
- items.append(builder.coerce(reg, item_type, item_expr.line))
- return builder.add(TupleSet(items, expr.line))
- def _visit_tuple_display(builder: IRBuilder, expr: TupleExpr) -> Value:
- """Create a list, then turn it into a tuple."""
- val_as_list = _visit_list_display(builder, expr.items, expr.line)
- return builder.call_c(list_tuple_op, [val_as_list], expr.line)
- def transform_dict_expr(builder: IRBuilder, expr: DictExpr) -> Value:
- """First accepts all keys and values, then makes a dict out of them."""
- key_value_pairs = []
- for key_expr, value_expr in expr.items:
- key = builder.accept(key_expr) if key_expr is not None else None
- value = builder.accept(value_expr)
- key_value_pairs.append((key, value))
- return builder.builder.make_dict(key_value_pairs, expr.line)
- def transform_set_expr(builder: IRBuilder, expr: SetExpr) -> Value:
- return _visit_display(
- builder, expr.items, builder.new_set_op, set_add_op, set_update_op, expr.line, False
- )
- def _visit_display(
- builder: IRBuilder,
- items: list[Expression],
- constructor_op: Callable[[list[Value], int], Value],
- append_op: CFunctionDescription,
- extend_op: CFunctionDescription,
- line: int,
- is_list: bool,
- ) -> Value:
- accepted_items = []
- for item in items:
- if isinstance(item, StarExpr):
- accepted_items.append((True, builder.accept(item.expr)))
- else:
- accepted_items.append((False, builder.accept(item)))
- result: Value | None = None
- initial_items = []
- for starred, value in accepted_items:
- if result is None and not starred and is_list:
- initial_items.append(value)
- continue
- if result is None:
- result = constructor_op(initial_items, line)
- builder.call_c(extend_op if starred else append_op, [result, value], line)
- if result is None:
- result = constructor_op(initial_items, line)
- return result
- # Comprehensions
- def transform_list_comprehension(builder: IRBuilder, o: ListComprehension) -> Value:
- return translate_list_comprehension(builder, o.generator)
- def transform_set_comprehension(builder: IRBuilder, o: SetComprehension) -> Value:
- return translate_set_comprehension(builder, o.generator)
- def transform_dictionary_comprehension(builder: IRBuilder, o: DictionaryComprehension) -> Value:
- d = builder.maybe_spill(builder.call_c(dict_new_op, [], o.line))
- loop_params = list(zip(o.indices, o.sequences, o.condlists, o.is_async))
- def gen_inner_stmts() -> None:
- k = builder.accept(o.key)
- v = builder.accept(o.value)
- builder.call_c(dict_set_item_op, [builder.read(d), k, v], o.line)
- comprehension_helper(builder, loop_params, gen_inner_stmts, o.line)
- return builder.read(d)
- # Misc
- def transform_slice_expr(builder: IRBuilder, expr: SliceExpr) -> Value:
- def get_arg(arg: Expression | None) -> Value:
- if arg is None:
- return builder.none_object()
- else:
- return builder.accept(arg)
- args = [get_arg(expr.begin_index), get_arg(expr.end_index), get_arg(expr.stride)]
- return builder.call_c(new_slice_op, args, expr.line)
- def transform_generator_expr(builder: IRBuilder, o: GeneratorExpr) -> Value:
- builder.warning("Treating generator comprehension as list", o.line)
- return builder.call_c(iter_op, [translate_list_comprehension(builder, o)], o.line)
- def transform_assignment_expr(builder: IRBuilder, o: AssignmentExpr) -> Value:
- value = builder.accept(o.value)
- target = builder.get_assignment_target(o.target)
- builder.assign(target, value, o.line)
- return value
- def transform_math_literal(builder: IRBuilder, fullname: str) -> Value | None:
- if fullname == "math.e":
- return builder.load_float(math.e)
- if fullname == "math.pi":
- return builder.load_float(math.pi)
- if fullname == "math.inf":
- return builder.load_float(math.inf)
- if fullname == "math.nan":
- return builder.load_float(math.nan)
- if fullname == "math.tau":
- return builder.load_float(math.tau)
- return None
|