1#     Copyright 2021, Kay Hayen, mailto:kay.hayen@gmail.com
2#
3#     Part of "Nuitka", an optimizing Python compiler that is compatible and
4#     integrates with CPython, but also works on its own.
5#
6#     Licensed under the Apache License, Version 2.0 (the "License");
7#     you may not use this file except in compliance with the License.
8#     You may obtain a copy of the License at
9#
10#        http://www.apache.org/licenses/LICENSE-2.0
11#
12#     Unless required by applicable law or agreed to in writing, software
13#     distributed under the License is distributed on an "AS IS" BASIS,
14#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15#     See the License for the specific language governing permissions and
16#     limitations under the License.
17#
18""" Globals/locals/single arg dir nodes
19
20These nodes give access to variables, highly problematic, because using them,
21the code may change or access anything about them, so nothing can be trusted
22anymore, if we start to not know where their value goes.
23
24The "dir()" call without arguments is reformulated to locals or globals calls.
25"""
26
27from .ConstantRefNodes import makeConstantRefNode
28from .DictionaryNodes import ExpressionKeyValuePair, makeExpressionMakeDict
29from .ExpressionBases import ExpressionBase, ExpressionBuiltinSingleArgBase
30from .VariableRefNodes import ExpressionTempVariableRef, ExpressionVariableRef
31
32
33class ExpressionBuiltinGlobals(ExpressionBase):
34    kind = "EXPRESSION_BUILTIN_GLOBALS"
35
36    def __init__(self, source_ref):
37        ExpressionBase.__init__(self, source_ref=source_ref)
38
39    def finalize(self):
40        del self.parent
41
42    def computeExpressionRaw(self, trace_collection):
43        return self, None, None
44
45    @staticmethod
46    def mayHaveSideEffects():
47        return False
48
49    @staticmethod
50    def mayRaiseException(exception_type):
51        return False
52
53
54class ExpressionBuiltinLocalsBase(ExpressionBase):
55    # Base classes can be abstract, pylint: disable=abstract-method
56
57    __slots__ = ("variable_traces", "locals_scope")
58
59    def __init__(self, locals_scope, source_ref):
60        ExpressionBase.__init__(self, source_ref=source_ref)
61
62        self.variable_traces = None
63        self.locals_scope = locals_scope
64
65    def finalize(self):
66        del self.locals_scope
67        del self.variable_traces
68
69    def mayHaveSideEffects(self):
70        return False
71
72    def mayRaiseException(self, exception_type):
73        return False
74
75    def getVariableTraces(self):
76        return self.variable_traces
77
78    def getLocalsScope(self):
79        return self.locals_scope
80
81
82class ExpressionBuiltinLocalsUpdated(ExpressionBuiltinLocalsBase):
83    kind = "EXPRESSION_BUILTIN_LOCALS_UPDATED"
84
85    def __init__(self, locals_scope, source_ref):
86        ExpressionBuiltinLocalsBase.__init__(
87            self, locals_scope=locals_scope, source_ref=source_ref
88        )
89
90        assert locals_scope is not None
91
92    def computeExpressionRaw(self, trace_collection):
93        # Just inform the collection that all escaped.
94        self.variable_traces = trace_collection.onLocalsUsage(self.locals_scope)
95
96        trace_collection.onLocalsDictEscaped(self.locals_scope)
97
98        return self, None, None
99
100
101class ExpressionBuiltinLocalsRef(ExpressionBuiltinLocalsBase):
102    kind = "EXPRESSION_BUILTIN_LOCALS_REF"
103
104    def __init__(self, locals_scope, source_ref):
105        ExpressionBuiltinLocalsBase.__init__(
106            self, locals_scope=locals_scope, source_ref=source_ref
107        )
108
109    def getLocalsScope(self):
110        return self.locals_scope
111
112    def computeExpressionRaw(self, trace_collection):
113        if self.locals_scope.isMarkedForPropagation():
114            result = makeExpressionMakeDict(
115                pairs=(
116                    ExpressionKeyValuePair(
117                        key=makeConstantRefNode(
118                            constant=variable_name, source_ref=self.source_ref
119                        ),
120                        value=ExpressionTempVariableRef(
121                            variable=variable, source_ref=self.source_ref
122                        ),
123                        source_ref=self.source_ref,
124                    )
125                    for variable_name, variable in self.locals_scope.getPropagationVariables().items()
126                ),
127                source_ref=self.source_ref,
128            )
129
130            new_result = result.computeExpressionRaw(trace_collection)
131
132            assert new_result[0] is result
133
134            self.finalize()
135
136            return result, "new_expression", "Propagated locals dictionary reference."
137
138        # Just inform the collection that all escaped unless it is abortative.
139        if not self.getParent().isStatementReturn():
140            trace_collection.onLocalsUsage(locals_scope=self.locals_scope)
141
142        return self, None, None
143
144
145class ExpressionBuiltinLocalsCopy(ExpressionBuiltinLocalsBase):
146    kind = "EXPRESSION_BUILTIN_LOCALS_COPY"
147
148    def computeExpressionRaw(self, trace_collection):
149        # Just inform the collection that all escaped.
150
151        self.variable_traces = trace_collection.onLocalsUsage(
152            locals_scope=self.locals_scope
153        )
154
155        for variable, variable_trace in self.variable_traces:
156            if (
157                not variable_trace.mustHaveValue()
158                and not variable_trace.mustNotHaveValue()
159            ):
160                return self, None, None
161
162            # Other locals elsewhere.
163            if variable_trace.getNameUsageCount() > 1:
164                return self, None, None
165
166        pairs = []
167        for variable, variable_trace in self.variable_traces:
168            if variable_trace.mustHaveValue():
169                pairs.append(
170                    ExpressionKeyValuePair(
171                        key=makeConstantRefNode(
172                            constant=variable.getName(),
173                            user_provided=True,
174                            source_ref=self.source_ref,
175                        ),
176                        value=ExpressionVariableRef(
177                            variable=variable, source_ref=self.source_ref
178                        ),
179                        source_ref=self.source_ref,
180                    )
181                )
182
183        # Locals is sorted of course.
184        def _sorted(pairs):
185            names = [
186                variable.getName()
187                for variable in self.locals_scope.getProvidedVariables()
188            ]
189
190            return tuple(
191                sorted(
192                    pairs,
193                    key=lambda pair: names.index(
194                        pair.subnode_key.getCompileTimeConstant()
195                    ),
196                )
197            )
198
199        result = makeExpressionMakeDict(
200            pairs=_sorted(pairs), source_ref=self.source_ref
201        )
202
203        return result, "new_expression", "Statically predicted locals dictionary."
204
205
206class ExpressionBuiltinDir1(ExpressionBuiltinSingleArgBase):
207    kind = "EXPRESSION_BUILTIN_DIR1"
208
209    def computeExpression(self, trace_collection):
210        # TODO: Quite some cases should be possible to predict and this
211        # should be using a slot, with "__dir__" being overloaded or not.
212
213        # Any code could be run, note that.
214        trace_collection.onControlFlowEscape(self)
215
216        # Any exception may be raised.
217        trace_collection.onExceptionRaiseExit(BaseException)
218
219        return self, None, None
220