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""" Built-in iterator nodes.
19
20These play a role in for loops, and in unpacking. They can something be
21predicted to succeed or fail, in which case, code can become less complex.
22
23The length of things is an important optimization issue for these to be
24good.
25"""
26
27from nuitka.PythonVersions import python_version
28
29from .ExpressionBases import (
30    ExpressionBuiltinSingleArgBase,
31    ExpressionChildrenHavingBase,
32)
33from .NodeBases import StatementChildHavingBase
34from .NodeMakingHelpers import (
35    makeRaiseExceptionReplacementStatement,
36    wrapExpressionWithSideEffects,
37)
38from .shapes.StandardShapes import tshape_iterator
39
40
41class ExpressionBuiltinIter1(ExpressionBuiltinSingleArgBase):
42    kind = "EXPRESSION_BUILTIN_ITER1"
43
44    simulator = iter
45
46    def computeExpression(self, trace_collection):
47        trace_collection.initIteratorValue(self)
48
49        value = self.subnode_value
50        return value.computeExpressionIter1(
51            iter_node=self, trace_collection=trace_collection
52        )
53
54    def computeExpressionIter1(self, iter_node, trace_collection):
55        # Iteration over an iterator is that iterator.
56
57        return self, "new_builtin", "Eliminated useless iterator creation."
58
59    def getTypeShape(self):
60        return self.subnode_value.getTypeShape().getShapeIter()
61
62    def computeExpressionNext1(self, next_node, trace_collection):
63        value = self.subnode_value
64
65        if value.isKnownToBeIterableAtMin(1) and value.canPredictIterationValues():
66            result = wrapExpressionWithSideEffects(
67                new_node=value.getIterationValue(0),
68                old_node=value,
69                side_effects=value.getIterationValueRange(1, None),
70            )
71
72            return False, (
73                result,
74                "new_expression",
75                "Predicted 'next' value from iteration.",
76            )
77
78        # TODO: This is only true for a few value types, use type shape to tell if
79        # it might escape or raise.
80        self.onContentEscapes(trace_collection)
81
82        # Any code could be run, note that.
83        trace_collection.onControlFlowEscape(self)
84
85        # Any exception may be raised.
86        trace_collection.onExceptionRaiseExit(BaseException)
87
88        return True, (next_node, None, None)
89
90    def isKnownToBeIterable(self, count):
91        if count is None:
92            return True
93
94        iter_length = self.subnode_value.getIterationLength()
95        return iter_length == count
96
97    def isKnownToBeIterableAtMin(self, count):
98        assert type(count) is int
99
100        iter_length = self.subnode_value.getIterationMinLength()
101        return iter_length is not None and count <= iter_length
102
103    def getIterationLength(self):
104        return self.subnode_value.getIterationLength()
105
106    def canPredictIterationValues(self):
107        return self.subnode_value.canPredictIterationValues()
108
109    def getIterationValue(self, element_index):
110        return self.subnode_value.getIterationValue(element_index)
111
112    def getIterationHandle(self):
113        return self.subnode_value.getIterationHandle()
114
115    def extractSideEffects(self):
116        # Iterator making is the side effect itself.
117        value = self.subnode_value
118
119        if value.isCompileTimeConstant() and value.isKnownToBeIterable(None):
120            return ()
121        else:
122            return (self,)
123
124    def mayHaveSideEffects(self):
125        value = self.subnode_value
126
127        if value.isCompileTimeConstant():
128            return not value.isKnownToBeIterable(None)
129
130        return True
131
132    def mayRaiseException(self, exception_type):
133        value = self.subnode_value
134
135        if value.mayRaiseException(exception_type):
136            return True
137
138        if value.isKnownToBeIterable(None):
139            return False
140
141        return True
142
143    def mayRaiseExceptionOperation(self):
144        value = self.subnode_value
145
146        return value.isKnownToBeIterable(None) is not True
147
148    def onRelease(self, trace_collection):
149        # print "onRelease", self
150        pass
151
152
153class ExpressionBuiltinIterForUnpack(ExpressionBuiltinIter1):
154    kind = "EXPRESSION_BUILTIN_ITER_FOR_UNPACK"
155
156    @staticmethod
157    def simulator(value):
158        try:
159            return iter(value)
160        except TypeError:
161            raise TypeError(
162                "cannot unpack non-iterable %s object" % (type(value).__name__)
163            )
164
165
166class StatementSpecialUnpackCheck(StatementChildHavingBase):
167    kind = "STATEMENT_SPECIAL_UNPACK_CHECK"
168
169    named_child = "iterator"
170
171    __slots__ = ("count",)
172
173    def __init__(self, iterator, count, source_ref):
174        StatementChildHavingBase.__init__(self, value=iterator, source_ref=source_ref)
175
176        self.count = int(count)
177
178    def getDetails(self):
179        return {"count": self.getCount()}
180
181    def getCount(self):
182        return self.count
183
184    def computeStatement(self, trace_collection):
185        iterator = trace_collection.onExpression(self.subnode_iterator)
186
187        if iterator.mayRaiseException(BaseException):
188            trace_collection.onExceptionRaiseExit(BaseException)
189
190        if iterator.willRaiseException(BaseException):
191            from .NodeMakingHelpers import (
192                makeStatementExpressionOnlyReplacementNode,
193            )
194
195            result = makeStatementExpressionOnlyReplacementNode(
196                expression=iterator, node=self
197            )
198
199            return (
200                result,
201                "new_raise",
202                """\
203Explicit raise already raises implicitly building exception type.""",
204            )
205
206        if (
207            iterator.isExpressionTempVariableRef()
208            and iterator.variable_trace.isAssignTrace()
209        ):
210
211            iterator = iterator.variable_trace.getAssignNode().subnode_source
212
213            current_index = trace_collection.getIteratorNextCount(iterator)
214        else:
215            current_index = None
216
217        if current_index is not None:
218            iter_length = iterator.getIterationLength()
219
220            if iter_length is not None:
221                # Remove the check if it can be decided at compile time.
222                if current_index == iter_length:
223                    return (
224                        None,
225                        "new_statements",
226                        """\
227Determined iteration end check to be always true.""",
228                    )
229                else:
230                    result = makeRaiseExceptionReplacementStatement(
231                        statement=self,
232                        exception_type="ValueError",
233                        exception_value="too many values to unpack"
234                        if python_version < 0x300
235                        else "too many values to unpack (expected %d)"
236                        % self.getCount(),
237                    )
238
239                    trace_collection.onExceptionRaiseExit(TypeError)
240
241                    return (
242                        result,
243                        "new_raise",
244                        """\
245Determined iteration end check to always raise.""",
246                    )
247
248        trace_collection.onExceptionRaiseExit(BaseException)
249
250        return self, None, None
251
252    @staticmethod
253    def getStatementNiceName():
254        return "iteration check statement"
255
256
257class ExpressionBuiltinIter2(ExpressionChildrenHavingBase):
258    kind = "EXPRESSION_BUILTIN_ITER2"
259
260    named_children = ("callable_arg", "sentinel")
261
262    def __init__(self, callable_arg, sentinel, source_ref):
263        ExpressionChildrenHavingBase.__init__(
264            self,
265            values={"callable_arg": callable_arg, "sentinel": sentinel},
266            source_ref=source_ref,
267        )
268
269    @staticmethod
270    def getTypeShape():
271        # TODO: This could be more specific, this one is a fixed thing!
272        return tshape_iterator
273
274    def computeExpression(self, trace_collection):
275        # TODO: The "callable" should be investigated here, maybe it is not
276        # really callable, or raises an exception.
277
278        return self, None, None
279
280    def computeExpressionIter1(self, iter_node, trace_collection):
281        return self, "new_builtin", "Eliminated useless iterator creation."
282
283
284class ExpressionAsyncIter(ExpressionBuiltinSingleArgBase):
285    kind = "EXPRESSION_ASYNC_ITER"
286
287    def computeExpression(self, trace_collection):
288        value = self.subnode_value
289
290        return value.computeExpressionAsyncIter(
291            iter_node=self, trace_collection=trace_collection
292        )
293
294    def isKnownToBeIterable(self, count):
295        if count is None:
296            return True
297
298        # TODO: Should ask value if it is.
299        return None
300
301    def getIterationLength(self):
302        return self.subnode_value.getIterationLength()
303
304    def extractSideEffects(self):
305        # Iterator making is the side effect itself.
306        if self.subnode_value.isCompileTimeConstant():
307            return ()
308        else:
309            return (self,)
310
311    def mayHaveSideEffects(self):
312        if self.subnode_value.isCompileTimeConstant():
313            return self.subnode_value.isKnownToBeIterable(None)
314
315        return True
316
317    def mayRaiseException(self, exception_type):
318        value = self.subnode_value
319
320        if value.mayRaiseException(exception_type):
321            return True
322
323        if value.isKnownToBeIterable(None):
324            return False
325
326        return True
327
328    def isKnownToBeIterableAtMin(self, count):
329        assert type(count) is int
330
331        iter_length = self.subnode_value.getIterationMinLength()
332        return iter_length is not None and iter_length < count
333
334    def onRelease(self, trace_collection):
335        # print "onRelease", self
336        pass
337
338
339class ExpressionAsyncNext(ExpressionBuiltinSingleArgBase):
340    kind = "EXPRESSION_ASYNC_NEXT"
341
342    def __init__(self, value, source_ref):
343        ExpressionBuiltinSingleArgBase.__init__(
344            self, value=value, source_ref=source_ref
345        )
346
347    def computeExpression(self, trace_collection):
348        # TODO: Predict iteration result if possible via SSA variable trace of
349        # the iterator state.
350
351        # Assume exception is possible. TODO: We might query the next from the
352        # source with a computeExpressionAsyncNext slot, but we delay that.
353        trace_collection.onExceptionRaiseExit(BaseException)
354
355        return self, None, None
356