<commit_before>#     Copyright 2020, <NAME>, mailto:<EMAIL>
#
#     Part of "Nuitka", an optimizing Python compiler that is compatible and
#     integrates with CPython, but also works on its own.
#
#     Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
#
#     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.
#
""" Nodes that deal with locals, as dict or mapping.

The mapping types can be optimized into dict types, and the ones with
fallback can be optimized to no fallback variants.

"""

from nuitka import Variables
from nuitka.optimizations.TraceCollections import TraceCollectionBranch
from nuitka.PythonVersions import python_version
from nuitka.tree.TreeHelpers import makeStatementsSequence

from .AssignNodes import (
    StatementAssignmentVariable,
    StatementDelVariable,
    StatementReleaseVariable,
)
from .ConditionalNodes import ExpressionConditional
from .ConstantRefNodes import ExpressionConstantDictEmptyRef
from .ExpressionBases import ExpressionBase, ExpressionChildHavingBase
from .NodeBases import StatementBase, StatementChildHavingBase
from .VariableRefNodes import ExpressionTempVariableRef


class ExpressionLocalsVariableRefOrFallback(ExpressionChildHavingBase):
    kind = "EXPRESSION_LOCALS_VARIABLE_REF_OR_FALLBACK"

    named_child = "fallback"

    __slots__ = ("locals_scope", "variable", "variable_trace")

    def __init__(self, locals_scope, variable_name, fallback, source_ref):
        ExpressionChildHavingBase.__init__(self, value=fallback, source_ref=source_ref)

        assert locals_scope is not None

        self.locals_scope = locals_scope
        self.variable = locals_scope.getLocalsDictVariable(variable_name)

        self.variable_trace = None

    def getDetails(self):
        return {
            "locals_scope": self.locals_scope,
            "variable_name": self.getVariableName(),
        }

    def getVariableName(self):
        return self.variable.getName()

    def getLocalsDictScope(self):
        return self.locals_scope

    def computeExpressionRaw(self, trace_collection):
        self.variable_trace = trace_collection.getVariableCurrentTrace(
            variable=self.variable
        )

        replacement = self.variable_trace.getReplacementNode(self)

        if replacement is not None:
            trace_collection.signalChange(
                "new_expression",
                self.source_ref,
                "Value propagated for '%s' from '%s'."
                % (
                    self.variable.getName(),
                    replacement.getSourceReference().getAsString(),
                ),
            )

            # Need to compute the replacement still.
            return replacement.computeExpressionRaw(trace_collection)

        # If we cannot be sure if the value is set, then we need the fallback,
        # otherwise we could remove it simply.
        if self.variable_trace.mustHaveValue():
            trace_collection.signalChange(
                "new_expression",
                self.source_ref,
                "Name '%s' must be in locals dict." % self.variable.getName(),
            )

            result = ExpressionLocalsVariableRef(
                locals_scope=self.locals_scope,
                variable_name=self.variable.getName(),
                source_ref=self.source_ref,
            )

            # Need to compute the replacement still.
            return result.computeExpressionRaw(trace_collection)
        else:
            trace_collection.onExceptionRaiseExit(BaseException)

            branch_fallback = TraceCollectionBranch(
                parent=trace_collection, name="fallback node usage"
            )

            branch_fallback.computeBranch(self.subnode_fallback)

            trace_collection.mergeBranches(branch_fallback, None)

        # if we can be sure if doesn't have a value set, go to the fallback directly.
        if self.variable_trace.mustNotHaveValue():
            return (
                self.subnode_fallback,
                "new_expression",
                "Name '%s' cannot be in locals dict." % self.variable.getName(),
            )
        else:
            return self, None, None

    def computeExpressionCall(self, call_node, call_args, call_kw, trace_collection):
        trace_collection.onExceptionRaiseExit(BaseException)

        trace_collection.onControlFlowEscape(self)

        if (
            not Variables.complete
            and self.variable.getName()
            in ("dir", "eval", "exec", "execfile", "locals", "vars")
            and self.subnode_fallback.isExpressionVariableRef()
            and self.subnode_fallback.getVariable().isModuleVariable()
        ):
            # Just inform the collection that all escaped.
            trace_collection.onLocalsUsage(self.getLocalsDictScope())

        if (
            self.subnode_fallback.isExpressionBuiltinRef()
            or self.subnode_fallback.isExpressionConstantTypeRef()
        ):
            variable_name = self.variable.getName()

            # Create a cloned node with the locals variable.
            call_node_clone = call_node.makeClone()
            call_node_clone.setCalled(
                ExpressionLocalsVariableRef(
                    locals_scope=self.locals_scope,
                    variable_name=variable_name,
                    source_ref=self.source_ref,
                )
            )

            # Make the original one for the fallback
            call_node = call_node.makeCloneShallow()
            call_node.setCalled(self.subnode_fallback)

            result = ExpressionConditional(
                condition=ExpressionLocalsVariableCheck(
                    locals_scope=self.locals_scope,
                    variable_name=variable_name,
                    source_ref=self.source_ref,
                ),
                expression_yes=call_node_clone,
                expression_no=call_node,
                source_ref=self.source_ref,
            )

            return (
                result,
                "new_expression",
                "Moved call of uncertain dict variable '%s' to inside." % variable_name,
            )

        return call_node, None, None

    def mayRaiseException(self, exception_type):
        return python_version >= 300 or self.subnode_fallback.mayRaiseException(
            exception_type
        )


# TODO: Why is this unused.
class ExpressionLocalsMappingVariableRefOrFallback(
    ExpressionLocalsVariableRefOrFallback
):
    kind = "EXPRESSION_LOCALS_MAPPING_VARIABLE_REF_OR_FALLBACK"


class ExpressionLocalsVariableRef(ExpressionBase):
    kind = "EXPRESSION_LOCALS_VARIABLE_REF"

    __slots__ = "variable", "locals_scope", "variable_trace"

    def __init__(self, locals_scope, variable_name, source_ref):

        ExpressionBase.__init__(self, source_ref=source_ref)

        self.locals_scope = locals_scope
        self.variable = locals_scope.getLocalsDictVariable(variable_name)

        self.variable_trace = None

    def finalize(self):
        del self.parent
        del self.locals_scope
        del self.variable
<commit_msg>Optimization: Predict exceptions for locals variable fallback more precisely<commit_after>#     Copyright 2020, <NAME>, mailto:<EMAIL>
#
#     Part of "Nuitka", an optimizing Python compiler that is compatible and
#     integrates with CPython, but also works on its own.
#
#     Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
#
#     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.
#
""" Nodes that deal with locals, as dict or mapping.

The mapping types can be optimized into dict types, and the ones with
fallback can be optimized to no fallback variants.

"""

from nuitka import Variables
from nuitka.optimizations.TraceCollections import TraceCollectionBranch
from nuitka.PythonVersions import python_version
from nuitka.tree.TreeHelpers import makeStatementsSequence

from .AssignNodes import (
    StatementAssignmentVariable,
    StatementDelVariable,
    StatementReleaseVariable,
)
from .ConditionalNodes import ExpressionConditional
from .ConstantRefNodes import ExpressionConstantDictEmptyRef
from .ExpressionBases import ExpressionBase, ExpressionChildHavingBase
from .NodeBases import StatementBase, StatementChildHavingBase
from .shapes.BuiltinTypeShapes import tshape_dict
from .VariableRefNodes import ExpressionTempVariableRef


class ExpressionLocalsVariableRefOrFallback(ExpressionChildHavingBase):
    kind = "EXPRESSION_LOCALS_VARIABLE_REF_OR_FALLBACK"

    named_child = "fallback"

    __slots__ = ("locals_scope", "variable", "variable_trace")

    def __init__(self, locals_scope, variable_name, fallback, source_ref):
        ExpressionChildHavingBase.__init__(self, value=fallback, source_ref=source_ref)

        assert locals_scope is not None

        self.locals_scope = locals_scope
        self.variable = locals_scope.getLocalsDictVariable(variable_name)

        self.variable_trace = None

    def getDetails(self):
        return {
            "locals_scope": self.locals_scope,
            "variable_name": self.getVariableName(),
        }

    def getVariableName(self):
        return self.variable.getName()

    def getLocalsDictScope(self):
        return self.locals_scope

    def computeExpressionRaw(self, trace_collection):
        self.variable_trace = trace_collection.getVariableCurrentTrace(
            variable=self.variable
        )

        replacement = self.variable_trace.getReplacementNode(self)

        if replacement is not None:
            trace_collection.signalChange(
                "new_expression",
                self.source_ref,
                "Value propagated for '%s' from '%s'."
                % (
                    self.variable.getName(),
                    replacement.getSourceReference().getAsString(),
                ),
            )

            # Need to compute the replacement still.
            return replacement.computeExpressionRaw(trace_collection)

        # If we cannot be sure if the value is set, then we need the fallback,
        # otherwise we could remove it simply.
        if self.variable_trace.mustHaveValue():
            trace_collection.signalChange(
                "new_expression",
                self.source_ref,
                "Name '%s' must be in locals dict." % self.variable.getName(),
            )

            result = ExpressionLocalsVariableRef(
                locals_scope=self.locals_scope,
                variable_name=self.variable.getName(),
                source_ref=self.source_ref,
            )

            # Need to compute the replacement still.
            return result.computeExpressionRaw(trace_collection)
        else:
            trace_collection.onExceptionRaiseExit(BaseException)

            branch_fallback = TraceCollectionBranch(
                parent=trace_collection, name="fallback node usage"
            )

            branch_fallback.computeBranch(self.subnode_fallback)

            trace_collection.mergeBranches(branch_fallback, None)

        # if we can be sure if doesn't have a value set, go to the fallback directly.
        if self.variable_trace.mustNotHaveValue():
            return (
                self.subnode_fallback,
                "new_expression",
                "Name '%s' cannot be in locals dict." % self.variable.getName(),
            )
        else:
            return self, None, None

    def computeExpressionCall(self, call_node, call_args, call_kw, trace_collection):
        trace_collection.onExceptionRaiseExit(BaseException)

        trace_collection.onControlFlowEscape(self)

        if (
            not Variables.complete
            and self.variable.getName()
            in ("dir", "eval", "exec", "execfile", "locals", "vars")
            and self.subnode_fallback.isExpressionVariableRef()
            and self.subnode_fallback.getVariable().isModuleVariable()
        ):
            # Just inform the collection that all escaped.
            trace_collection.onLocalsUsage(self.getLocalsDictScope())

        if (
            self.subnode_fallback.isExpressionBuiltinRef()
            or self.subnode_fallback.isExpressionConstantTypeRef()
        ):
            variable_name = self.variable.getName()

            # Create a cloned node with the locals variable.
            call_node_clone = call_node.makeClone()
            call_node_clone.setCalled(
                ExpressionLocalsVariableRef(
                    locals_scope=self.locals_scope,
                    variable_name=variable_name,
                    source_ref=self.source_ref,
                )
            )

            # Make the original one for the fallback
            call_node = call_node.makeCloneShallow()
            call_node.setCalled(self.subnode_fallback)

            result = ExpressionConditional(
                condition=ExpressionLocalsVariableCheck(
                    locals_scope=self.locals_scope,
                    variable_name=variable_name,
                    source_ref=self.source_ref,
                ),
                expression_yes=call_node_clone,
                expression_no=call_node,
                source_ref=self.source_ref,
            )

            return (
                result,
                "new_expression",
                "Moved call of uncertain dict variable '%s' to inside." % variable_name,
            )

        return call_node, None, None

    def mayRaiseException(self, exception_type):
        if python_version < 300 or self.locals_scope.getTypeShape() is tshape_dict:
            return False

        return self.subnode_fallback.mayRaiseException(exception_type)


# TODO: Why is this unused.
class ExpressionLocalsMappingVariableRefOrFallback(
    ExpressionLocalsVariableRefOrFallback
):
    kind = "EXPRESSION_LOCALS_MAPPING_VARIABLE_REF_OR_FALLBACK"


class ExpressionLocalsVariableRef(ExpressionBase):
    kind = "EXPRESSION_LOCALS_VARIABLE_REF"

    __slots__ = "variable", "locals_scope", "variable_trace"

    def __init__(self, locals_scope, variable_name, source_ref):

        ExpressionBase.__init__(self, source_ref=source_ref)

        self.locals_scope = locals_scope
        self.variable = locals_scope.getLocalsDictVariable(variable_name)

        self.variable_trace = None

    def finalize(self):
        del self.parent
        del self.locals_scope
        del self.variable

{'fraction_non_alphanumeric': 0.06738562091503268, 'fraction_numerical': 0.001437908496732026, 'mean_word_length': 2.438426966292135, 'pattern_counts': {'":': 4, '<': 8, '<?xml version=': 0, '>': 8, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5272610', 'n_tokens_mistral': 4006, 'n_tokens_neox': 3845, 'n_words': 987}