#     Copyright 2025, Kay Hayen, mailto:kay.hayen@gmail.com find license text at end of file


"""Optimize calls to built-in references to specific built-in calls.

For built-in name references, we check if it's one of the supported built-in
types, and then specialize for the ones, where it makes sense.
"""

from nuitka.__past__ import unicode, xrange
from nuitka.Errors import NuitkaAssumptionError
from nuitka.nodes.AttributeNodes import (
    ExpressionBuiltinGetattr,
    ExpressionBuiltinHasattr,
    ExpressionBuiltinSetattr,
    makeExpressionAttributeLookup,
)
from nuitka.nodes.BuiltinAllNodes import ExpressionBuiltinAll
from nuitka.nodes.BuiltinAnyNodes import ExpressionBuiltinAny
from nuitka.nodes.BuiltinComplexNodes import (
    ExpressionBuiltinComplex1,
    ExpressionBuiltinComplex2,
)
from nuitka.nodes.BuiltinDecodingNodes import (
    ExpressionBuiltinChr,
    ExpressionBuiltinOrd,
)
from nuitka.nodes.BuiltinDecoratorNodes import (
    ExpressionBuiltinClassmethod,
    ExpressionBuiltinStaticmethod,
)
from nuitka.nodes.BuiltinDictNodes import ExpressionBuiltinDict
from nuitka.nodes.BuiltinFormatNodes import (
    ExpressionBuiltinAscii,
    ExpressionBuiltinBin,
    ExpressionBuiltinFormat,
    ExpressionBuiltinHex,
    ExpressionBuiltinId,
    ExpressionBuiltinOct,
)
from nuitka.nodes.BuiltinHashNodes import ExpressionBuiltinHash
from nuitka.nodes.BuiltinInputNodes import ExpressionBuiltinInput
from nuitka.nodes.BuiltinIntegerNodes import (
    ExpressionBuiltinInt1,
    ExpressionBuiltinInt2,
)
from nuitka.nodes.BuiltinIteratorNodes import (
    ExpressionBuiltinIter1,
    ExpressionBuiltinIter2,
)
from nuitka.nodes.BuiltinLenNodes import ExpressionBuiltinLen
from nuitka.nodes.BuiltinNextNodes import (
    ExpressionBuiltinNext1,
    ExpressionBuiltinNext2,
)
from nuitka.nodes.BuiltinOpenNodes import (
    ExpressionBuiltinOpenP2,
    ExpressionBuiltinOpenP3,
)
from nuitka.nodes.BuiltinRangeNodes import (
    ExpressionBuiltinRange1,
    ExpressionBuiltinRange2,
    ExpressionBuiltinRange3,
    ExpressionBuiltinXrange1,
    ExpressionBuiltinXrange2,
    ExpressionBuiltinXrange3,
)
from nuitka.nodes.BuiltinRefNodes import (
    ExpressionBuiltinAnonymousRef,
    makeExpressionBuiltinTypeRef,
)
from nuitka.nodes.BuiltinSumNodes import (
    ExpressionBuiltinSum1,
    ExpressionBuiltinSum2,
)
from nuitka.nodes.BuiltinTypeNodes import (
    ExpressionBuiltinBool,
    ExpressionBuiltinBytearray1,
    ExpressionBuiltinBytearray3,
    ExpressionBuiltinFloat,
    ExpressionBuiltinFrozenset,
    ExpressionBuiltinList,
    ExpressionBuiltinSet,
    ExpressionBuiltinStrP2,
    ExpressionBuiltinStrP3,
    ExpressionBuiltinTuple,
    ExpressionBuiltinUnicodeP2,
)
from nuitka.nodes.BuiltinVarsNodes import ExpressionBuiltinVars
from nuitka.nodes.CallNodes import makeExpressionCall
from nuitka.nodes.ClassNodes import ExpressionBuiltinType3
from nuitka.nodes.ComparisonNodes import ExpressionComparisonIs
from nuitka.nodes.ConditionalNodes import (
    ExpressionConditional,
    makeStatementConditional,
)
from nuitka.nodes.ConstantRefNodes import makeConstantRefNode
from nuitka.nodes.ContainerMakingNodes import makeExpressionMakeTupleOrConstant
from nuitka.nodes.ExecEvalNodes import (
    ExpressionBuiltinCompile,
    ExpressionBuiltinEval,
    ExpressionBuiltinExec,
    ExpressionBuiltinExecfile,
)
from nuitka.nodes.GlobalsLocalsNodes import (
    ExpressionBuiltinDir1,
    ExpressionBuiltinGlobals,
)
from nuitka.nodes.ImportNodes import ExpressionBuiltinImport
from nuitka.nodes.KeyValuePairNodes import (
    makeKeyValuePairExpressionsFromKwArgs,
)
from nuitka.nodes.NodeMakingHelpers import (
    makeConstantReplacementNode,
    makeExpressionBuiltinLocals,
    makeRaiseExceptionReplacementExpression,
    makeRaiseExceptionReplacementExpressionFromInstance,
)
from nuitka.nodes.OperatorNodes import ExpressionOperationBinaryDivmod
from nuitka.nodes.OperatorNodesUnary import (
    ExpressionOperationNot,
    ExpressionOperationUnaryAbs,
    ExpressionOperationUnaryRepr,
)
from nuitka.nodes.OutlineNodes import ExpressionOutlineBody
from nuitka.nodes.ReturnNodes import makeStatementReturn
from nuitka.nodes.SliceNodes import makeExpressionBuiltinSlice
from nuitka.nodes.TypeNodes import (
    ExpressionBuiltinIsinstance,
    ExpressionBuiltinIssubclass,
    ExpressionBuiltinSuper0,
    ExpressionBuiltinSuper1,
    ExpressionBuiltinSuper2,
    ExpressionBuiltinType1,
)
from nuitka.nodes.VariableAssignNodes import (
    makeStatementAssignmentVariable,
    makeStatementDelVariable,
)
from nuitka.nodes.VariableRefNodes import (
    ExpressionTempVariableRef,
    ExpressionVariableRef,
)
from nuitka.PythonVersions import python_version
from nuitka.specs import BuiltinParameterSpecs
from nuitka.tree.ReformulationExecStatements import wrapEvalGlobalsAndLocals
from nuitka.tree.ReformulationTryFinallyStatements import (
    makeTryFinallyStatement,
)
from nuitka.tree.TreeHelpers import (
    makeCallNode,
    makeStatementsSequence,
    makeStatementsSequenceFromStatement,
)


def mustBeDoneLater(extractor_function):
    """Decorator for extractor functions that need to be done later.

    Providing an outline body with temporary variables is not
    possible at the time of the call, so we need to do it later
    for these.
    """
    extractor_function.must_be_done_later = True
    return extractor_function


def dir_extractor(node):
    locals_scope = node.subnode_called.getLocalsScope()

    def buildDirEmptyCase(source_ref):
        source = makeExpressionBuiltinLocals(
            locals_scope=locals_scope, source_ref=source_ref
        )

        result = makeCallNode(
            makeExpressionAttributeLookup(
                expression=source, attribute_name="keys", source_ref=source_ref
            ),
            source_ref,
        )

        # For Python3, keys doesn't really return values, but instead a handle
        # only, but we want it to be a list.
        if python_version >= 0x300:
            result = ExpressionBuiltinList(value=result, source_ref=source_ref)

        return result

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        # TODO: Needs locals_scope attached.
        builtin_class=ExpressionBuiltinDir1,
        builtin_spec=BuiltinParameterSpecs.builtin_dir_spec,
        empty_special_class=buildDirEmptyCase,
    )


def vars_extractor(node):
    locals_scope = node.subnode_called.getLocalsScope()

    def selectVarsEmptyClass(source_ref):
        return makeExpressionBuiltinLocals(
            locals_scope=locals_scope, source_ref=source_ref
        )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        # TODO: Needs locals_cope attached
        builtin_class=ExpressionBuiltinVars,
        builtin_spec=BuiltinParameterSpecs.builtin_vars_spec,
        empty_special_class=selectVarsEmptyClass,
    )


def import_extractor(node):
    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinImport,
        builtin_spec=BuiltinParameterSpecs.builtin_import_spec,
    )


def type_extractor(node):
    args = node.subnode_args

    if args is None:
        iter_length = 0
    else:
        iter_length = args.getIterationLength()

    if iter_length == 1:
        return BuiltinParameterSpecs.extractBuiltinArgs(
            node=node,
            builtin_class=ExpressionBuiltinType1,
            builtin_spec=BuiltinParameterSpecs.builtin_type1_spec,
        )
    elif iter_length == 3:
        return BuiltinParameterSpecs.extractBuiltinArgs(
            node=node,
            builtin_class=ExpressionBuiltinType3,
            builtin_spec=BuiltinParameterSpecs.builtin_type3_spec,
        )
    else:
        return makeRaiseExceptionReplacementExpressionFromInstance(
            expression=node, exception=TypeError("type() takes 1 or 3 arguments")
        )


def iter_extractor(node):
    def wrapIterCreation(callable_arg, sentinel, source_ref):
        if sentinel is None:
            return ExpressionBuiltinIter1(value=callable_arg, source_ref=source_ref)
        else:
            return ExpressionBuiltinIter2(
                callable_arg=callable_arg, sentinel=sentinel, source_ref=source_ref
            )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=wrapIterCreation,
        builtin_spec=BuiltinParameterSpecs.builtin_iter_spec,
    )


def next_extractor(node):
    # Split up next with and without defaults, they are not going to behave
    # really very similar.
    def selectNextBuiltinClass(iterator, default, source_ref):
        if default is None:
            return ExpressionBuiltinNext1(value=iterator, source_ref=source_ref)
        else:
            return ExpressionBuiltinNext2(
                iterator=iterator, default=default, source_ref=source_ref
            )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=selectNextBuiltinClass,
        builtin_spec=BuiltinParameterSpecs.builtin_next_spec,
    )


def sum_extractor(node):
    # Split up sum with and without start value, one is much easier.
    def selectSumBuiltinClass(sequence, start, source_ref):
        if start is None:
            return ExpressionBuiltinSum1(sequence=sequence, source_ref=source_ref)
        else:
            return ExpressionBuiltinSum2(
                sequence=sequence, start=start, source_ref=source_ref
            )

    def makeSum0(source_ref):
        # pylint: disable=unused-argument

        return makeRaiseExceptionReplacementExpressionFromInstance(
            expression=node,
            exception=TypeError(
                "sum expected at least 1 arguments, got 0"
                if python_version < 0x380
                else "sum() takes at least 1 positional argument (0 given)"
            ),
        )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=selectSumBuiltinClass,
        builtin_spec=BuiltinParameterSpecs.builtin_sum_spec,
        empty_special_class=makeSum0,
    )


def dict_extractor(node):
    # The "dict" built-in is a bit strange in that it accepts a position
    # parameter, or not, but won't have a default value.
    def wrapExpressionBuiltinDictCreation(positional_args, dict_star_arg, source_ref):
        if positional_args:
            # Only one allowed, the spec converted too many into an exception.
            (pos_arg,) = positional_args
        else:
            pos_arg = None

        return ExpressionBuiltinDict(
            pos_arg=pos_arg,
            pairs=makeKeyValuePairExpressionsFromKwArgs(dict_star_arg),
            source_ref=source_ref,
        )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=wrapExpressionBuiltinDictCreation,
        builtin_spec=BuiltinParameterSpecs.builtin_dict_spec,
    )


def chr_extractor(node):
    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinChr,
        builtin_spec=BuiltinParameterSpecs.builtin_chr_spec,
    )


def ord_extractor(node):
    def makeOrd0(source_ref):
        # pylint: disable=unused-argument

        return makeRaiseExceptionReplacementExpressionFromInstance(
            expression=node,
            exception=TypeError("ord() takes exactly one argument (0 given)"),
        )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinOrd,
        builtin_spec=BuiltinParameterSpecs.builtin_ord_spec,
        empty_special_class=makeOrd0,
    )


def bin_extractor(node):
    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinBin,
        builtin_spec=BuiltinParameterSpecs.builtin_bin_spec,
    )


def oct_extractor(node):
    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinOct,
        builtin_spec=BuiltinParameterSpecs.builtin_oct_spec,
    )


def hex_extractor(node):
    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinHex,
        builtin_spec=BuiltinParameterSpecs.builtin_hex_spec,
    )


def id_extractor(node):
    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinId,
        builtin_spec=BuiltinParameterSpecs.builtin_id_spec,
    )


def repr_extractor(node):
    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionOperationUnaryRepr,
        builtin_spec=BuiltinParameterSpecs.builtin_repr_spec,
    )


if python_version >= 0x300:

    def ascii_extractor(node):
        return BuiltinParameterSpecs.extractBuiltinArgs(
            node=node,
            builtin_class=ExpressionBuiltinAscii,
            builtin_spec=BuiltinParameterSpecs.builtin_repr_spec,
        )


def range_extractor(node):
    def selectRangeBuiltin(low, high, step, source_ref):
        if high is None:
            return ExpressionBuiltinRange1(low=low, source_ref=source_ref)
        elif step is None:
            return ExpressionBuiltinRange2(low=low, high=high, source_ref=source_ref)
        else:
            return ExpressionBuiltinRange3(
                low=low, high=high, step=step, source_ref=source_ref
            )

    def makeRange0(source_ref):
        # pylint: disable=unused-argument
        try:
            range()
        except Exception as e:  # We want to broad here, pylint: disable=broad-except
            return makeRaiseExceptionReplacementExpressionFromInstance(
                expression=node, exception=e
            )
        else:
            raise NuitkaAssumptionError("range without argument is expected to raise")

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=selectRangeBuiltin,
        builtin_spec=BuiltinParameterSpecs.builtin_range_spec,
        empty_special_class=makeRange0,
    )


def xrange_extractor(node):
    def selectXrangeBuiltin(low, high, step, source_ref):
        if high is None:
            return ExpressionBuiltinXrange1(low=low, source_ref=source_ref)
        elif step is None:
            return ExpressionBuiltinXrange2(low=low, high=high, source_ref=source_ref)
        else:
            return ExpressionBuiltinXrange3(
                low=low, high=high, step=step, source_ref=source_ref
            )

    def makeXrange0(source_ref):
        # pylint: disable=unused-argument
        try:
            xrange()
        except Exception as e:  # We want to broad here, pylint: disable=broad-except
            return makeRaiseExceptionReplacementExpressionFromInstance(
                expression=node, exception=e
            )
        else:
            raise NuitkaAssumptionError("range without argument is expected to raise")

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=selectXrangeBuiltin,
        builtin_spec=BuiltinParameterSpecs.builtin_xrange_spec,
        empty_special_class=makeXrange0,
    )


def len_extractor(node):
    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinLen,
        builtin_spec=BuiltinParameterSpecs.builtin_len_spec,
    )


def all_extractor(node):
    # pylint: disable=unused-argument
    def makeAll0(source_ref):
        exception_message = "all() takes exactly one argument (0 given)"

        return makeRaiseExceptionReplacementExpressionFromInstance(
            expression=node, exception=TypeError(exception_message)
        )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinAll,
        builtin_spec=BuiltinParameterSpecs.builtin_all_spec,
        empty_special_class=makeAll0,
    )


def abs_extractor(node):
    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionOperationUnaryAbs,
        builtin_spec=BuiltinParameterSpecs.builtin_abs_spec,
    )


def any_extractor(node):
    # pylint: disable=unused-argument
    def makeAny0(source_ref):
        exception_message = "any() takes exactly one argument (0 given)"

        return makeRaiseExceptionReplacementExpressionFromInstance(
            expression=node, exception=TypeError(exception_message)
        )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinAny,
        builtin_spec=BuiltinParameterSpecs.builtin_any_spec,
        empty_special_class=makeAny0,
    )


def tuple_extractor(node):
    def makeTuple0(source_ref):
        # pylint: disable=unused-argument

        return makeConstantReplacementNode(constant=(), node=node, user_provided=False)

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinTuple,
        builtin_spec=BuiltinParameterSpecs.builtin_tuple_spec,
        empty_special_class=makeTuple0,
    )


def list_extractor(node):
    def makeList0(source_ref):
        # pylint: disable=unused-argument

        return makeConstantReplacementNode(constant=[], node=node, user_provided=False)

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinList,
        builtin_spec=BuiltinParameterSpecs.builtin_list_spec,
        empty_special_class=makeList0,
    )


def set_extractor(node):
    def makeSet0(source_ref):
        # pylint: disable=unused-argument

        return makeConstantReplacementNode(
            constant=set(), node=node, user_provided=False
        )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinSet,
        builtin_spec=BuiltinParameterSpecs.builtin_set_spec,
        empty_special_class=makeSet0,
    )


def frozenset_extractor(node):
    def makeFrozenset0(source_ref):
        # pylint: disable=unused-argument

        return makeConstantReplacementNode(
            constant=frozenset(), node=node, user_provided=False
        )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinFrozenset,
        builtin_spec=BuiltinParameterSpecs.builtin_frozenset_spec,
        empty_special_class=makeFrozenset0,
    )


def float_extractor(node):
    def makeFloat0(source_ref):
        # pylint: disable=unused-argument

        return makeConstantReplacementNode(
            constant=float(), node=node, user_provided=False
        )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinFloat,
        builtin_spec=BuiltinParameterSpecs.builtin_float_spec,
        empty_special_class=makeFloat0,
    )


def complex_extractor(node):
    def makeComplex0(source_ref):
        # pylint: disable=unused-argument

        return makeConstantReplacementNode(
            constant=complex(), node=node, user_provided=False
        )

    def selectComplexBuiltin(real, imag, source_ref):
        if imag is None:
            return ExpressionBuiltinComplex1(value=real, source_ref=source_ref)
        else:
            return ExpressionBuiltinComplex2(
                real=real, imag=imag, source_ref=source_ref
            )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=selectComplexBuiltin,
        builtin_spec=BuiltinParameterSpecs.builtin_complex_spec,
        empty_special_class=makeComplex0,
    )


def str_extractor(node):
    def makeStr0(source_ref):
        # pylint: disable=unused-argument

        return makeConstantReplacementNode(
            constant=str(), node=node, user_provided=False
        )

    builtin_class = ExpressionBuiltinStrP2 if str is bytes else ExpressionBuiltinStrP3

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=builtin_class,
        builtin_spec=builtin_class.builtin_spec,
        empty_special_class=makeStr0,
    )


if python_version < 0x300:

    def unicode_extractor(node):
        def makeUnicode0(source_ref):
            # pylint: disable=unused-argument

            return makeConstantReplacementNode(
                constant=unicode(), node=node, user_provided=False
            )

        return BuiltinParameterSpecs.extractBuiltinArgs(
            node=node,
            builtin_class=ExpressionBuiltinUnicodeP2,
            builtin_spec=ExpressionBuiltinUnicodeP2.builtin_spec,
            empty_special_class=makeUnicode0,
        )

else:
    from nuitka.nodes.BuiltinTypeNodes import (
        ExpressionBuiltinBytes1,
        ExpressionBuiltinBytes3,
    )

    def bytes_extractor(node):
        def makeBytes0(source_ref):
            # pylint: disable=unused-argument

            return makeConstantReplacementNode(
                constant=bytes(), node=node, user_provided=False
            )

        def selectBytesBuiltin(string, encoding, errors, source_ref):
            if encoding is None and errors is None:
                return ExpressionBuiltinBytes1(value=string, source_ref=source_ref)
            else:
                return ExpressionBuiltinBytes3(
                    value=string,
                    encoding=encoding,
                    errors=errors,
                    source_ref=source_ref,
                )

        return BuiltinParameterSpecs.extractBuiltinArgs(
            node=node,
            builtin_class=selectBytesBuiltin,
            builtin_spec=BuiltinParameterSpecs.builtin_bytes_p3_spec,
            empty_special_class=makeBytes0,
        )


def bool_extractor(node):
    def makeBool0(source_ref):
        # pylint: disable=unused-argument

        return makeConstantReplacementNode(
            constant=bool(), node=node, user_provided=False
        )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinBool,
        builtin_spec=BuiltinParameterSpecs.builtin_bool_spec,
        empty_special_class=makeBool0,
    )


def int_extractor(node):
    def makeInt0(source_ref):
        # pylint: disable=unused-argument

        return makeConstantReplacementNode(
            constant=int(), node=node, user_provided=False
        )

    def selectIntBuiltin(value, base, source_ref):
        if base is None:
            return ExpressionBuiltinInt1(value=value, source_ref=source_ref)
        else:
            return ExpressionBuiltinInt2(value=value, base=base, source_ref=source_ref)

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=selectIntBuiltin,
        builtin_spec=BuiltinParameterSpecs.builtin_int_spec,
        empty_special_class=makeInt0,
    )


if python_version < 0x300:
    from nuitka.nodes.BuiltinIntegerNodes import (
        ExpressionBuiltinLong1,
        ExpressionBuiltinLong2,
    )

    def long_extractor(node):
        def makeLong0(source_ref):
            # pylint: disable=unused-argument

            return makeConstantReplacementNode(
                constant=int(), node=node, user_provided=False
            )

        def selectIntBuiltin(value, base, source_ref):
            if base is None:
                return ExpressionBuiltinLong1(value=value, source_ref=source_ref)
            else:
                return ExpressionBuiltinLong2(
                    value=value, base=base, source_ref=source_ref
                )

        return BuiltinParameterSpecs.extractBuiltinArgs(
            node=node,
            builtin_class=selectIntBuiltin,
            builtin_spec=BuiltinParameterSpecs.builtin_int_spec,
            empty_special_class=makeLong0,
        )


def globals_extractor(node):
    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinGlobals,
        builtin_spec=BuiltinParameterSpecs.builtin_globals_spec,
    )


def locals_extractor(node):
    locals_scope = node.subnode_called.getLocalsScope()

    def makeLocalsNode(source_ref):
        return makeExpressionBuiltinLocals(
            locals_scope=locals_scope, source_ref=source_ref
        )

    # Note: Locals on the module level is really globals.
    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=makeLocalsNode,
        builtin_spec=BuiltinParameterSpecs.builtin_locals_spec,
    )


if python_version < 0x300:

    @mustBeDoneLater
    def execfile_extractor(node):
        def wrapExpressionBuiltinExecfileCreation(
            filename, globals_arg, locals_arg, source_ref
        ):
            outline_body = ExpressionOutlineBody(
                provider=node.getParentVariableProvider(),
                name="execfile_call",
                source_ref=node.source_ref,
            )

            globals_ref, locals_ref, tried, final = wrapEvalGlobalsAndLocals(
                provider=outline_body,
                locals_scope=node.getParentVariableProvider().getLocalsScope(),
                globals_node=globals_arg,
                locals_node=locals_arg,
                temp_scope=outline_body.getOutlineTempScope(),
                source_ref=source_ref,
            )

            tried = makeStatementsSequence(
                statements=(
                    tried,
                    makeStatementReturn(
                        expression=ExpressionBuiltinExecfile(
                            in_class_body=node.getParentVariableProvider().isExpressionClassBodyBase(),
                            source_code=makeCallNode(
                                makeExpressionAttributeLookup(
                                    expression=ExpressionBuiltinOpenP2(
                                        filename=filename,
                                        mode=makeConstantRefNode(
                                            constant="rU", source_ref=source_ref
                                        ),
                                        buffering=None,
                                        source_ref=source_ref,
                                    ),
                                    attribute_name="read",
                                    source_ref=source_ref,
                                ),
                                source_ref,
                            ),
                            globals_arg=globals_ref,
                            locals_arg=locals_ref,
                            source_ref=source_ref,
                        ),
                        source_ref=source_ref,
                    ),
                ),
                allow_none=False,
                source_ref=source_ref,
            )

            outline_body.setChildBody(
                makeStatementsSequenceFromStatement(
                    statement=makeTryFinallyStatement(
                        provider=outline_body,
                        tried=tried,
                        final=final,
                        source_ref=source_ref,
                    )
                )
            )

            return outline_body

        return BuiltinParameterSpecs.extractBuiltinArgs(
            node=node,
            builtin_class=wrapExpressionBuiltinExecfileCreation,
            builtin_spec=BuiltinParameterSpecs.builtin_execfile_spec,
        )


@mustBeDoneLater
def eval_extractor(node):
    def wrapEvalBuiltin(source, globals_arg, locals_arg, source_ref):
        provider = node.getParentVariableProvider()

        outline_body = ExpressionOutlineBody(
            provider=node.getParentVariableProvider(),
            name="eval_call",
            source_ref=source_ref,
        )

        globals_ref, locals_ref, tried, final = wrapEvalGlobalsAndLocals(
            provider=outline_body,
            locals_scope=provider.getLocalsScope(),
            globals_node=globals_arg,
            locals_node=locals_arg,
            temp_scope=outline_body.getOutlineTempScope(),
            source_ref=source_ref,
        )

        # The wrapping should not relocate to the "source_ref".
        assert (
            globals_arg is None
            or globals_ref.getSourceReference() == globals_arg.getSourceReference()
        )
        assert (
            locals_arg is None
            or locals_ref.getSourceReference() == locals_arg.getSourceReference()
        )

        source_variable = outline_body.allocateTempVariable(
            temp_scope=None, name="source", temp_type="object"
        )

        final.setChildStatements(
            final.subnode_statements
            + (
                makeStatementDelVariable(
                    variable=source_variable, tolerant=True, source_ref=source_ref
                ),
            )
        )

        strip_choice = makeConstantRefNode(constant=(" \t",), source_ref=source_ref)

        if python_version >= 0x300:
            strip_choice = ExpressionConditional(
                condition=ExpressionComparisonIs(
                    left=ExpressionBuiltinType1(
                        value=ExpressionTempVariableRef(
                            variable=source_variable, source_ref=source_ref
                        ),
                        source_ref=source_ref,
                    ),
                    right=makeExpressionBuiltinTypeRef(
                        builtin_name="bytes", source_ref=source_ref
                    ),
                    source_ref=source_ref,
                ),
                expression_yes=makeConstantRefNode(
                    constant=(b" \t",), source_ref=source_ref
                ),
                expression_no=strip_choice,
                source_ref=source_ref,
            )

        # Source needs some special treatment for eval, if it's a string, it
        # must be stripped.
        string_fixup = makeStatementAssignmentVariable(
            variable=source_variable,
            source=makeExpressionCall(
                called=makeExpressionAttributeLookup(
                    expression=ExpressionTempVariableRef(
                        variable=source_variable, source_ref=source_ref
                    ),
                    attribute_name="strip",
                    source_ref=source_ref,
                ),
                args=strip_choice,  # This is a tuple
                kw=None,
                source_ref=source_ref,
            ),
            source_ref=source_ref,
        )

        acceptable_builtin_types = [
            ExpressionBuiltinAnonymousRef(builtin_name="code", source_ref=source_ref)
        ]

        if python_version >= 0x270:
            acceptable_builtin_types.append(
                makeExpressionBuiltinTypeRef(
                    builtin_name="memoryview", source_ref=source_ref
                )
            )

        statements = (
            makeStatementAssignmentVariable(
                variable=source_variable, source=source, source_ref=source_ref
            ),
            makeStatementConditional(
                condition=ExpressionOperationNot(
                    operand=ExpressionBuiltinIsinstance(
                        instance=ExpressionTempVariableRef(
                            variable=source_variable, source_ref=source_ref
                        ),
                        classes=makeExpressionMakeTupleOrConstant(
                            elements=tuple(acceptable_builtin_types),
                            user_provided=True,
                            source_ref=source_ref,
                        ),
                        source_ref=source_ref,
                    ),
                    source_ref=source_ref,
                ),
                yes_branch=string_fixup,
                no_branch=None,
                source_ref=source_ref,
            ),
            makeStatementReturn(
                expression=ExpressionBuiltinEval(
                    source_code=ExpressionTempVariableRef(
                        variable=source_variable, source_ref=source_ref
                    ),
                    globals_arg=globals_ref,
                    locals_arg=locals_ref,
                    source_ref=source_ref,
                ),
                source_ref=source_ref,
            ),
        )

        tried = makeStatementsSequence(
            statements=(tried,) + statements, allow_none=False, source_ref=source_ref
        )

        outline_body.setChildBody(
            makeStatementsSequenceFromStatement(
                statement=makeTryFinallyStatement(
                    provider=outline_body,
                    tried=tried,
                    final=final,
                    source_ref=source_ref,
                )
            )
        )

        return outline_body

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=wrapEvalBuiltin,
        builtin_spec=BuiltinParameterSpecs.builtin_eval_spec,
    )


if python_version >= 0x300:

    @mustBeDoneLater
    def exec_extractor(node):
        def wrapExpressionBuiltinExecCreation(
            source, globals_arg, locals_arg, closure=None, source_ref=None
        ):
            provider = node.getParentVariableProvider()

            outline_body = ExpressionOutlineBody(
                provider=provider, name="exec_call", source_ref=source_ref
            )

            globals_ref, locals_ref, tried, final = wrapEvalGlobalsAndLocals(
                provider=outline_body,
                locals_scope=provider.getLocalsScope(),
                globals_node=globals_arg,
                locals_node=locals_arg,
                temp_scope=outline_body.getOutlineTempScope(),
                source_ref=source_ref,
            )

            tried = makeStatementsSequence(
                statements=(
                    tried,
                    makeStatementReturn(
                        expression=ExpressionBuiltinExec(
                            source_code=source,
                            globals_arg=globals_ref,
                            locals_arg=locals_ref,
                            closure=closure,
                            source_ref=source_ref,
                        ),
                        source_ref=source_ref,
                    ),
                ),
                allow_none=False,
                source_ref=source_ref,
            )

            # Hack: Allow some APIs to work already
            tried.parent = outline_body

            outline_body.setChildBody(
                makeStatementsSequenceFromStatement(
                    statement=makeTryFinallyStatement(
                        provider=provider,
                        tried=tried,
                        final=final,
                        source_ref=source_ref,
                    )
                )
            )

            return outline_body

        return BuiltinParameterSpecs.extractBuiltinArgs(
            node=node,
            builtin_class=wrapExpressionBuiltinExecCreation,
            builtin_spec=BuiltinParameterSpecs.builtin_exec_spec,
        )


def compile_extractor(node):
    def wrapExpressionBuiltinCompileCreation(
        source_code, filename, mode, flags, dont_inherit, optimize=None, source_ref=None
    ):
        return ExpressionBuiltinCompile(
            source_code=source_code,
            filename=filename,
            mode=mode,
            flags=flags,
            dont_inherit=dont_inherit,
            optimize=optimize,
            source_ref=source_ref,
        )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=wrapExpressionBuiltinCompileCreation,
        builtin_spec=BuiltinParameterSpecs.builtin_compile_spec,
    )


def open_extractor(node):
    def makeOpen0(source_ref):
        # pylint: disable=unused-argument
        try:
            # Not giving arguments or context on purpose
            # pylint: disable=consider-using-with,unspecified-encoding
            open()
        except Exception as e:  # We want to broad here, pylint: disable=broad-except
            return makeRaiseExceptionReplacementExpressionFromInstance(
                expression=node, exception=e
            )
        else:
            raise NuitkaAssumptionError("open without argument is expected to raise")

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=(
            ExpressionBuiltinOpenP3 if str is not bytes else ExpressionBuiltinOpenP2
        ),
        builtin_spec=BuiltinParameterSpecs.builtin_open_spec,
        empty_special_class=makeOpen0,
    )


def super_extractor(node):
    def wrapSuperBuiltin(type_arg, object_arg, source_ref):
        # Many cases due to complex defaulting behavior, pylint: disable=too-many-return-statements

        if type_arg is None and python_version >= 0x300:
            if provider.isCompiledPythonModule():
                return makeRaiseExceptionReplacementExpression(
                    expression=node,
                    exception_type="RuntimeError",
                    exception_value="super(): no arguments",
                )

            class_variable = provider.getVariableForReference(variable_name="__class__")

            provider.trace_collection.getVariableCurrentTrace(class_variable).addUsage()

            type_arg = ExpressionVariableRef(
                # Ought to be already closure taken due to "super" flag in
                # tree building.
                variable=class_variable,
                source_ref=source_ref,
            )

            # If we already have this as a local variable, then use that
            # instead.
            type_arg_owner = class_variable.getOwner()
            if type_arg_owner is provider or not (
                type_arg_owner.isExpressionFunctionBody()
                or type_arg_owner.isExpressionClassBodyBase()
            ):
                return makeRaiseExceptionReplacementExpression(
                    expression=node,
                    exception_type=(
                        "SystemError" if python_version < 0x331 else "RuntimeError"
                    ),
                    exception_value="super(): __class__ cell not found",
                )

            if object_arg is None:
                if (
                    provider.isExpressionGeneratorObjectBody()
                    or provider.isExpressionCoroutineObjectBody()
                    or provider.isExpressionAsyncgenObjectBody()
                ):
                    parameter_provider = provider.getParentVariableProvider()
                else:
                    parameter_provider = provider

                if parameter_provider.getParameters().getArgumentCount() == 0:
                    return makeRaiseExceptionReplacementExpression(
                        expression=node,
                        exception_type="RuntimeError",
                        exception_value="super(): no arguments",
                    )
                else:
                    par1_name = parameter_provider.getParameters().getArgumentNames()[0]

                    object_variable = provider.getVariableForReference(
                        variable_name=par1_name
                    )

                    provider.trace_collection.getVariableCurrentTrace(
                        object_variable
                    ).addUsage()

                    object_arg = ExpressionVariableRef(
                        variable=object_variable, source_ref=source_ref
                    )

                    if not object_arg.getVariable().isParameterVariable():
                        return makeRaiseExceptionReplacementExpression(
                            expression=node,
                            exception_type=(
                                "SystemError"
                                if python_version < 0x300
                                else "RuntimeError"
                            ),
                            exception_value="super(): __class__ cell not found",
                        )

            return ExpressionBuiltinSuper0(
                type_arg=type_arg,
                object_arg=object_arg,
                source_ref=source_ref,
            )

        if object_arg is None:
            return ExpressionBuiltinSuper1(
                type_arg=type_arg,
                source_ref=source_ref,
            )
        else:
            return ExpressionBuiltinSuper2(
                type_arg=type_arg,
                object_arg=object_arg,
                source_ref=source_ref,
            )

    provider = node.getParentVariableProvider().getEntryPoint()

    if not provider.isCompiledPythonModule():
        provider.discardFlag("has_super")

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=wrapSuperBuiltin,
        builtin_spec=BuiltinParameterSpecs.builtin_super_spec,
    )


def hasattr_extractor(node):
    # We need to have to builtin arguments, pylint: disable=redefined-builtin
    def makeExpressionBuiltinHasattr(object, name, source_ref):
        return ExpressionBuiltinHasattr(
            expression=object, name=name, source_ref=source_ref
        )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=makeExpressionBuiltinHasattr,
        builtin_spec=BuiltinParameterSpecs.builtin_hasattr_spec,
    )


def getattr_extractor(node):
    # We need to have to builtin arguments, pylint: disable=redefined-builtin
    def makeExpressionBuiltinGetattr(object, name, default, source_ref):
        return ExpressionBuiltinGetattr(
            expression=object, name=name, default=default, source_ref=source_ref
        )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=makeExpressionBuiltinGetattr,
        builtin_spec=BuiltinParameterSpecs.builtin_getattr_spec,
    )


def setattr_extractor(node):
    # We need to have to builtin arguments, pylint: disable=redefined-builtin
    def makeExpressionBuiltinSetattr(object, name, value, source_ref):
        return ExpressionBuiltinSetattr(
            expression=object, name=name, value=value, source_ref=source_ref
        )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=makeExpressionBuiltinSetattr,
        builtin_spec=BuiltinParameterSpecs.builtin_setattr_spec,
    )


def isinstance_extractor(node):
    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinIsinstance,
        builtin_spec=BuiltinParameterSpecs.builtin_isinstance_spec,
    )


def issubclass_extractor(node):
    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinIssubclass,
        builtin_spec=BuiltinParameterSpecs.builtin_isinstance_spec,
    )


def bytearray_extractor(node):
    def makeBytearray0(source_ref):
        return makeConstantRefNode(constant=bytearray(), source_ref=source_ref)

    def selectBytearrayBuiltinClass(string, encoding, errors, source_ref):
        # TODO: Highly suspicious that "errors"
        if encoding is None:
            return ExpressionBuiltinBytearray1(value=string, source_ref=source_ref)
        else:
            return ExpressionBuiltinBytearray3(
                string=string, encoding=encoding, errors=errors, source_ref=source_ref
            )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=selectBytearrayBuiltinClass,
        builtin_spec=BuiltinParameterSpecs.builtin_bytearray_spec,
        empty_special_class=makeBytearray0,
    )


def slice_extractor(node):
    def wrapSlice(start, stop, step, source_ref):
        if start is not None and stop is None:
            # Default rules are strange. If one argument is given, it's the
            # second one then.
            stop = start
            start = None

        return makeExpressionBuiltinSlice(
            start=start, stop=stop, step=step, source_ref=source_ref
        )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=wrapSlice,
        builtin_spec=BuiltinParameterSpecs.builtin_slice_spec,
    )


def hash_extractor(node):
    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinHash,
        builtin_spec=BuiltinParameterSpecs.builtin_hash_spec,
    )


def input_extractor(node):
    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinInput,
        builtin_spec=BuiltinParameterSpecs.builtin_input_spec,
    )


def format_extractor(node):
    def makeFormat0(source_ref):
        # pylint: disable=unused-argument

        return makeRaiseExceptionReplacementExpressionFromInstance(
            expression=node,
            exception=TypeError("format() takes at least 1 argument (0 given)"),
        )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinFormat,
        builtin_spec=BuiltinParameterSpecs.builtin_format_spec,
        empty_special_class=makeFormat0,
    )


def staticmethod_extractor(node):
    def makeStaticmethod0(source_ref):
        # pylint: disable=unused-argument

        return makeRaiseExceptionReplacementExpressionFromInstance(
            expression=node,
            exception=TypeError("staticmethod expected 1 arguments, got 0"),
        )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinStaticmethod,
        builtin_spec=BuiltinParameterSpecs.builtin_staticmethod_spec,
        empty_special_class=makeStaticmethod0,
    )


def classmethod_extractor(node):
    def makeStaticmethod0(source_ref):
        # pylint: disable=unused-argument

        return makeRaiseExceptionReplacementExpressionFromInstance(
            expression=node,
            exception=TypeError("classmethod expected 1 arguments, got 0"),
        )

    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionBuiltinClassmethod,
        builtin_spec=BuiltinParameterSpecs.builtin_classmethod_spec,
        empty_special_class=makeStaticmethod0,
    )


def divmod_extractor(node):
    return BuiltinParameterSpecs.extractBuiltinArgs(
        node=node,
        builtin_class=ExpressionOperationBinaryDivmod,
        builtin_spec=BuiltinParameterSpecs.builtin_divmod_spec,
    )


_dispatch_dict = {
    "compile": compile_extractor,
    "globals": globals_extractor,
    "locals": locals_extractor,
    "eval": eval_extractor,
    "dir": dir_extractor,
    "vars": vars_extractor,
    "__import__": import_extractor,
    "chr": chr_extractor,
    "ord": ord_extractor,
    "bin": bin_extractor,
    "oct": oct_extractor,
    "hex": hex_extractor,
    "id": id_extractor,
    "type": type_extractor,
    "iter": iter_extractor,
    "next": next_extractor,
    "sum": sum_extractor,
    "tuple": tuple_extractor,
    "list": list_extractor,
    "dict": dict_extractor,
    "set": set_extractor,
    "frozenset": frozenset_extractor,
    "float": float_extractor,
    "complex": complex_extractor,
    "str": str_extractor,
    "bool": bool_extractor,
    "int": int_extractor,
    "repr": repr_extractor,
    "len": len_extractor,
    "any": any_extractor,
    "abs": abs_extractor,
    "all": all_extractor,
    "super": super_extractor,
    "hasattr": hasattr_extractor,
    "getattr": getattr_extractor,
    "setattr": setattr_extractor,
    "isinstance": isinstance_extractor,
    "issubclass": issubclass_extractor,
    "bytearray": bytearray_extractor,
    "slice": slice_extractor,
    "hash": hash_extractor,
    "format": format_extractor,
    "open": open_extractor,
    "staticmethod": staticmethod_extractor,
    "classmethod": classmethod_extractor,
    "divmod": divmod_extractor,
    "input": input_extractor,
}

if python_version < 0x300:
    # These are not in Python3
    _dispatch_dict["long"] = long_extractor
    _dispatch_dict["unicode"] = unicode_extractor
    _dispatch_dict["execfile"] = execfile_extractor
    _dispatch_dict["xrange"] = xrange_extractor

    _dispatch_dict["range"] = range_extractor
else:
    # This one is not in Python2
    _dispatch_dict["bytes"] = bytes_extractor
    _dispatch_dict["ascii"] = (
        ascii_extractor  # pylint: disable=possibly-used-before-assignment
    )
    _dispatch_dict["exec"] = (
        exec_extractor  # pylint: disable=possibly-used-before-assignment
    )

    # The Python3 range is really an xrange, use that.
    _dispatch_dict["range"] = xrange_extractor


def check():
    from nuitka.Builtins import builtin_names

    for builtin_name in _dispatch_dict:
        assert builtin_name in builtin_names, builtin_name


check()

_builtin_ignore_list = (
    # Not supporting 'print', because it could be replaced, and is not
    # worth the effort yet.
    "print",
    # TODO: This could, and should be supported, as we could e.g. lower
    # types easily for it.
    "sorted",
    # TODO: This would be very worthwhile, as it could easily optimize
    # its iteration away.
    "zip",
    # TODO: This would be most precious due to the type hint it gives
    "enumerate",
    # TODO: Also worthwhile for known values.
    "reversed",
    # TODO: Not sure what this really is about.
    "memoryview",
)


def _describeNewNode(new_node):
    """Describe the change for better understanding."""

    # Don't mention side effects, that's not what we care about.
    if new_node.isExpressionSideEffects():
        new_node = new_node.subnode_expression

    if new_node.isExpressionBuiltinImport():
        tags = "new_import"
        message = "built-in __import__ call"
    elif new_node.isExpressionBuiltin() or new_node.isStatementExec():
        tags = "new_builtin"
        message = "built-in call '%s'" % new_node.kind
    elif new_node.isExpressionRaiseException():
        tags = "new_raise"
        message = "exception raise"
    elif new_node.isExpressionOperationBinary():
        tags = "new_expression"
        message = "binary operation '%s'" % new_node.getOperator()
    elif new_node.isExpressionOperationUnary():
        tags = "new_expression"
        message = "unary operation '%s'" % new_node.getOperator()
    elif new_node.isExpressionCall():
        tags = "new_expression"
        message = "call"
    elif new_node.isExpressionOutlineBody():
        tags = "new_expression"
        message = "outlined function"
    elif new_node.isExpressionConstantRef():
        tags = "new_expression"
        message = "constant value"
    else:
        assert False, new_node

    return tags, message


def computeBuiltinCall(builtin_name, call_node, trace_collection):
    # There is some dispatching for how to output various types of changes,
    # with lots of cases.
    if builtin_name in _dispatch_dict:
        extractor_function = _dispatch_dict[builtin_name]

        if getattr(extractor_function, "must_be_done_later", False):
            trace_collection.onDelayedWork(
                call_node,
                extractor_function,
                "call to built-in '%s'" % builtin_name,
                _describeNewNode,
            )
            return call_node, None, None

        new_node = _dispatch_dict[builtin_name](call_node)

        assert new_node is not call_node, builtin_name
        assert new_node is not None, builtin_name

        tags, new_node_description = _describeNewNode(new_node)
        message = "Replaced builtin '%s' with %s." % (
            builtin_name,
            new_node_description,
        )

        return new_node, tags, message
    else:
        # TODO: Achieve coverage of all built-ins in at least the ignore list.
        # if False and builtin_name not in _builtin_ignore_list:
        #     optimization_logger.warning(
        #         "Not handling built-in %r, consider support." % builtin_name
        #     )

        return call_node, None, None


#     Part of "Nuitka", an optimizing Python compiler that is compatible and
#     integrates with CPython, but also works on its own.
#
#     Licensed under the GNU Affero General Public License, Version 3 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#        http://www.gnu.org/licenses/agpl.txt
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.
