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""" Slice nodes.
19
20Slices are important when working with lists. Tracking them can allow to
21achieve more compact code, or predict results at compile time.
22
23There will be a method "computeExpressionSlice" to aid predicting them.
24"""
25
26from nuitka.PythonVersions import python_version
27from nuitka.specs import BuiltinParameterSpecs
28
29from .ConstantRefNodes import ExpressionConstantNoneRef, makeConstantRefNode
30from .ExpressionBases import (
31    ExpressionChildHavingBase,
32    ExpressionChildrenHavingBase,
33    ExpressionSpecBasedComputationNoRaiseMixin,
34)
35from .NodeBases import (
36    SideEffectsFromChildrenMixin,
37    StatementChildrenHavingBase,
38)
39from .NodeMakingHelpers import (
40    convertNoneConstantToNone,
41    makeStatementExpressionOnlyReplacementNode,
42    makeStatementOnlyNodesFromExpressions,
43    wrapExpressionWithSideEffects,
44)
45from .shapes.BuiltinTypeShapes import tshape_slice
46
47
48class StatementAssignmentSlice(StatementChildrenHavingBase):
49    kind = "STATEMENT_ASSIGNMENT_SLICE"
50
51    named_children = ("source", "expression", "lower", "upper")
52
53    def __init__(self, expression, lower, upper, source, source_ref):
54        assert python_version < 0x300
55
56        StatementChildrenHavingBase.__init__(
57            self,
58            values={
59                "source": source,
60                "expression": expression,
61                "lower": lower,
62                "upper": upper,
63            },
64            source_ref=source_ref,
65        )
66
67    def computeStatement(self, trace_collection):
68        source = trace_collection.onExpression(self.subnode_source)
69
70        # No assignment will occur, if the assignment source raises, so strip it
71        # away.
72        if source.willRaiseException(BaseException):
73            result = makeStatementExpressionOnlyReplacementNode(
74                expression=source, node=self
75            )
76
77            return (
78                result,
79                "new_raise",
80                """\
81Slice assignment raises exception in assigned value, removed assignment.""",
82            )
83
84        lookup_source = trace_collection.onExpression(self.subnode_expression)
85
86        if lookup_source.willRaiseException(BaseException):
87            result = makeStatementOnlyNodesFromExpressions(
88                expressions=(source, lookup_source)
89            )
90
91            return (
92                result,
93                "new_raise",
94                """\
95Slice assignment raises exception in sliced value, removed assignment.""",
96            )
97
98        lower = trace_collection.onExpression(self.subnode_lower, allow_none=True)
99
100        if lower is not None and lower.willRaiseException(BaseException):
101            result = makeStatementOnlyNodesFromExpressions(
102                expressions=(source, lookup_source, lower)
103            )
104
105            return (
106                result,
107                "new_raise",
108                """\
109Slice assignment raises exception in lower slice boundary value, removed \
110assignment.""",
111            )
112
113        upper = trace_collection.onExpression(self.subnode_upper, allow_none=True)
114
115        if upper is not None and upper.willRaiseException(BaseException):
116            result = makeStatementOnlyNodesFromExpressions(
117                expressions=(source, lookup_source, lower, upper)
118            )
119
120            return (
121                result,
122                "new_raise",
123                """\
124Slice assignment raises exception in upper slice boundary value, removed \
125assignment.""",
126            )
127
128        return lookup_source.computeExpressionSetSlice(
129            set_node=self,
130            lower=lower,
131            upper=upper,
132            value_node=source,
133            trace_collection=trace_collection,
134        )
135
136
137class StatementDelSlice(StatementChildrenHavingBase):
138    kind = "STATEMENT_DEL_SLICE"
139
140    named_children = ("expression", "lower", "upper")
141
142    def __init__(self, expression, lower, upper, source_ref):
143        StatementChildrenHavingBase.__init__(
144            self,
145            values={"expression": expression, "lower": lower, "upper": upper},
146            source_ref=source_ref,
147        )
148
149    def computeStatement(self, trace_collection):
150        lookup_source = trace_collection.onExpression(self.subnode_expression)
151
152        if lookup_source.willRaiseException(BaseException):
153            result = makeStatementExpressionOnlyReplacementNode(
154                expression=lookup_source, node=self
155            )
156
157            return (
158                result,
159                "new_raise",
160                """\
161Slice del raises exception in sliced value, removed del""",
162            )
163
164        lower = trace_collection.onExpression(self.subnode_lower, allow_none=True)
165
166        if lower is not None and lower.willRaiseException(BaseException):
167            result = makeStatementOnlyNodesFromExpressions(
168                expressions=(lookup_source, lower)
169            )
170
171            return (
172                result,
173                "new_raise",
174                """
175Slice del raises exception in lower slice boundary value, removed del""",
176            )
177
178        trace_collection.onExpression(self.subnode_upper, allow_none=True)
179        upper = self.subnode_upper
180
181        if upper is not None and upper.willRaiseException(BaseException):
182            result = makeStatementOnlyNodesFromExpressions(
183                expressions=(lookup_source, lower, upper)
184            )
185
186            return (
187                result,
188                "new_raise",
189                """
190Slice del raises exception in upper slice boundary value, removed del""",
191            )
192
193        return lookup_source.computeExpressionDelSlice(
194            set_node=self, lower=lower, upper=upper, trace_collection=trace_collection
195        )
196
197
198class ExpressionSliceLookup(ExpressionChildrenHavingBase):
199    kind = "EXPRESSION_SLICE_LOOKUP"
200
201    named_children = ("expression", "lower", "upper")
202
203    checkers = {"upper": convertNoneConstantToNone, "lower": convertNoneConstantToNone}
204
205    def __init__(self, expression, lower, upper, source_ref):
206        assert python_version < 0x300
207
208        ExpressionChildrenHavingBase.__init__(
209            self,
210            values={"expression": expression, "upper": upper, "lower": lower},
211            source_ref=source_ref,
212        )
213
214    def computeExpression(self, trace_collection):
215        lookup_source = self.subnode_expression
216
217        return lookup_source.computeExpressionSlice(
218            lookup_node=self,
219            lower=self.subnode_lower,
220            upper=self.subnode_upper,
221            trace_collection=trace_collection,
222        )
223
224    @staticmethod
225    def isKnownToBeIterable(count):
226        # TODO: Should ask SliceRegistry
227        return None
228
229
230def makeExpressionBuiltinSlice(start, stop, step, source_ref):
231    if (
232        (start is None or start.isCompileTimeConstant())
233        and (stop is None or stop.isCompileTimeConstant())
234        and (step is None or step.isCompileTimeConstant())
235    ):
236        # Avoid going slices for what is effectively constant.
237
238        start_value = None if start is None else start.getCompileTimeConstant()
239        stop_value = None if stop is None else stop.getCompileTimeConstant()
240        step_value = None if step is None else step.getCompileTimeConstant()
241
242        return makeConstantRefNode(
243            constant=slice(start_value, stop_value, step_value), source_ref=source_ref
244        )
245
246    if start is None and step is None:
247        return ExpressionBuiltinSlice1(stop=stop, source_ref=source_ref)
248
249    if start is None:
250        start = ExpressionConstantNoneRef(source_ref=source_ref)
251    if stop is None:
252        stop = ExpressionConstantNoneRef(source_ref=source_ref)
253
254    if step is None:
255        return ExpressionBuiltinSlice2(start=start, stop=stop, source_ref=source_ref)
256
257    return ExpressionBuiltinSlice3(
258        start=start, stop=stop, step=step, source_ref=source_ref
259    )
260
261
262class ExpressionBuiltinSliceMixin(SideEffectsFromChildrenMixin):
263    # Mixins are required to slots
264    __slots__ = ()
265
266    builtin_spec = BuiltinParameterSpecs.builtin_slice_spec
267
268    @staticmethod
269    def getTypeShape():
270        return tshape_slice
271
272    @staticmethod
273    def isKnownToBeIterable(count):
274        # Virtual method provided by mixin, pylint: disable=unused-argument
275
276        # Definitely not iterable at all
277        return False
278
279    def mayHaveSideEffects(self):
280        return self.mayRaiseException(BaseException)
281
282
283class ExpressionBuiltinSlice3(
284    ExpressionBuiltinSliceMixin,
285    ExpressionSpecBasedComputationNoRaiseMixin,
286    ExpressionChildrenHavingBase,
287):
288    kind = "EXPRESSION_BUILTIN_SLICE3"
289
290    named_children = ("start", "stop", "step")
291
292    def __init__(self, start, stop, step, source_ref):
293        ExpressionChildrenHavingBase.__init__(
294            self,
295            values={"start": start, "stop": stop, "step": step},
296            source_ref=source_ref,
297        )
298
299    def computeExpression(self, trace_collection):
300        if (
301            self.subnode_step.isExpressionConstantNoneRef()
302            or self.subnode_step.getIndexValue() == 1
303        ):
304            return trace_collection.computedExpressionResult(
305                wrapExpressionWithSideEffects(
306                    old_node=self,
307                    new_node=ExpressionBuiltinSlice2(
308                        start=self.subnode_start,
309                        stop=self.subnode_stop,
310                        source_ref=self.source_ref,
311                    ),
312                    side_effects=self.subnode_step.extractSideEffects(),
313                ),
314                "new_expression",
315                "Reduce 3 argument slice object creation to two argument form.",
316            )
317
318        return self.computeBuiltinSpec(
319            trace_collection=trace_collection,
320            given_values=(self.subnode_start, self.subnode_stop, self.subnode_step),
321        )
322
323    def mayRaiseException(self, exception_type):
324        return (
325            self.subnode_start.mayRaiseException(exception_type)
326            or self.subnode_stop.mayRaiseException(exception_type)
327            or self.subnode_step.mayRaiseException(exception_type)
328        )
329
330
331class ExpressionBuiltinSlice2(
332    ExpressionBuiltinSliceMixin,
333    ExpressionSpecBasedComputationNoRaiseMixin,
334    ExpressionChildrenHavingBase,
335):
336    kind = "EXPRESSION_BUILTIN_SLICE2"
337
338    named_children = ("start", "stop")
339
340    def __init__(self, start, stop, source_ref):
341        ExpressionChildrenHavingBase.__init__(
342            self,
343            values={"start": start, "stop": stop},
344            source_ref=source_ref,
345        )
346
347    def computeExpression(self, trace_collection):
348        if self.subnode_start.isExpressionConstantNoneRef():
349            return trace_collection.computedExpressionResult(
350                wrapExpressionWithSideEffects(
351                    old_node=self,
352                    new_node=ExpressionBuiltinSlice1(
353                        stop=self.subnode_stop, source_ref=self.source_ref
354                    ),
355                    side_effects=self.subnode_start.extractSideEffects(),
356                ),
357                "new_expression",
358                "Reduce 2 argument slice object creation to single argument form.",
359            )
360
361        return self.computeBuiltinSpec(
362            trace_collection=trace_collection,
363            given_values=(self.subnode_start, self.subnode_stop),
364        )
365
366    def mayRaiseException(self, exception_type):
367        return self.subnode_start.mayRaiseException(
368            exception_type
369        ) or self.subnode_stop.mayRaiseException(exception_type)
370
371
372class ExpressionBuiltinSlice1(
373    ExpressionBuiltinSliceMixin,
374    ExpressionSpecBasedComputationNoRaiseMixin,
375    ExpressionChildHavingBase,
376):
377    kind = "EXPRESSION_BUILTIN_SLICE1"
378
379    named_child = "stop"
380
381    def __init__(self, stop, source_ref):
382        ExpressionChildHavingBase.__init__(
383            self,
384            value=stop,
385            source_ref=source_ref,
386        )
387
388    def computeExpression(self, trace_collection):
389        return self.computeBuiltinSpec(
390            trace_collection=trace_collection,
391            given_values=(self.subnode_stop,),
392        )
393
394    def mayRaiseException(self, exception_type):
395        return self.subnode_stop.mayRaiseException(exception_type)
396