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""" Nodes that build containers.
19
20"""
21
22import functools
23from abc import abstractmethod
24
25from nuitka.PythonVersions import needsSetLiteralReverseInsertion
26
27from .ConstantRefNodes import (
28    ExpressionConstantListEmptyRef,
29    ExpressionConstantSetEmptyRef,
30    ExpressionConstantTupleEmptyRef,
31    makeConstantRefNode,
32)
33from .ExpressionBases import ExpressionChildHavingBase
34from .IterationHandles import ListAndTupleContainerMakingIterationHandle
35from .NodeBases import SideEffectsFromChildrenMixin
36from .NodeMakingHelpers import (
37    getComputationResult,
38    makeStatementOnlyNodesFromExpressions,
39    wrapExpressionWithSideEffects,
40)
41from .shapes.BuiltinTypeShapes import tshape_list, tshape_set, tshape_tuple
42
43
44class ExpressionMakeSequenceBase(
45    SideEffectsFromChildrenMixin, ExpressionChildHavingBase
46):
47    __slots__ = ("sequence_kind",)
48
49    named_child = "elements"
50
51    def __init__(self, sequence_kind, elements, source_ref):
52        assert elements
53
54        for element in elements:
55            assert element.isExpression(), element
56
57        self.sequence_kind = sequence_kind.lower()
58
59        ExpressionChildHavingBase.__init__(
60            self, value=tuple(elements), source_ref=source_ref
61        )
62
63    @staticmethod
64    def isExpressionMakeSequence():
65        return True
66
67    @abstractmethod
68    def getSimulator(self):
69        """The simulator for the container making, for overload."""
70
71    def computeExpression(self, trace_collection):
72        elements = self.subnode_elements
73
74        for count, element in enumerate(elements):
75            if element.willRaiseException(BaseException):
76                result = wrapExpressionWithSideEffects(
77                    side_effects=elements[:count], new_node=element, old_node=self
78                )
79
80                return result, "new_raise", "Sequence creation raises exception"
81
82        for element in elements:
83            if not element.isCompileTimeConstant():
84                return self, None, None
85
86        simulator = self.getSimulator()
87        assert simulator is not None
88
89        return getComputationResult(
90            node=self,
91            computation=lambda: simulator(
92                element.getCompileTimeConstant() for element in elements
93            ),
94            description="%s with constant arguments." % simulator.__name__.title(),
95            user_provided=True,
96        )
97
98    @staticmethod
99    def mayHaveSideEffectsBool():
100        return False
101
102    def isKnownToBeIterable(self, count):
103        return count is None or count == len(self.subnode_elements)
104
105    def isKnownToBeIterableAtMin(self, count):
106        return count <= len(self.subnode_elements)
107
108    def getIterationValue(self, count):
109        return self.subnode_elements[count]
110
111    def getIterationValueRange(self, start, stop):
112        return self.subnode_elements[start:stop]
113
114    @staticmethod
115    def canPredictIterationValues():
116        return True
117
118    def getIterationValues(self):
119        return self.subnode_elements
120
121    def getIterationHandle(self):
122        return ListAndTupleContainerMakingIterationHandle(self.subnode_elements)
123
124    @staticmethod
125    def getTruthValue():
126        return True
127
128    def mayRaiseException(self, exception_type):
129        for element in self.subnode_elements:
130            if element.mayRaiseException(exception_type):
131                return True
132
133        return False
134
135    def computeExpressionDrop(self, statement, trace_collection):
136        result = makeStatementOnlyNodesFromExpressions(
137            expressions=self.subnode_elements
138        )
139
140        del self.parent
141
142        return (
143            result,
144            "new_statements",
145            """\
146Removed sequence creation for unused sequence.""",
147        )
148
149    def onContentEscapes(self, trace_collection):
150        for element in self.subnode_elements:
151            element.onContentEscapes(trace_collection)
152
153
154def makeExpressionMakeTuple(elements, source_ref):
155    if elements:
156        return ExpressionMakeTuple(elements, source_ref)
157    else:
158        # TODO: Get rid of user provided for empty tuple refs, makes no sense.
159        return ExpressionConstantTupleEmptyRef(
160            user_provided=False, source_ref=source_ref
161        )
162
163
164def makeExpressionMakeTupleOrConstant(elements, user_provided, source_ref):
165    for element in elements:
166        # TODO: Compile time constant ought to be the criterion.
167        if not element.isExpressionConstantRef():
168            result = makeExpressionMakeTuple(elements, source_ref)
169            break
170    else:
171        result = makeConstantRefNode(
172            constant=tuple(element.getCompileTimeConstant() for element in elements),
173            user_provided=user_provided,
174            source_ref=source_ref,
175        )
176
177    if elements:
178        result.setCompatibleSourceReference(
179            source_ref=elements[-1].getCompatibleSourceReference()
180        )
181
182    return result
183
184
185class ExpressionMakeTuple(ExpressionMakeSequenceBase):
186    kind = "EXPRESSION_MAKE_TUPLE"
187
188    def __init__(self, elements, source_ref):
189        ExpressionMakeSequenceBase.__init__(
190            self, sequence_kind="TUPLE", elements=elements, source_ref=source_ref
191        )
192
193    @staticmethod
194    def getTypeShape():
195        return tshape_tuple
196
197    @staticmethod
198    def getSimulator():
199        return tuple
200
201    def getIterationLength(self):
202        return len(self.subnode_elements)
203
204
205def makeExpressionMakeList(elements, source_ref):
206    if elements:
207        return ExpressionMakeList(elements, source_ref)
208    else:
209        # TODO: Get rid of user provided for empty list refs, makes no sense.
210        return ExpressionConstantListEmptyRef(
211            user_provided=False, source_ref=source_ref
212        )
213
214
215def makeExpressionMakeListOrConstant(elements, user_provided, source_ref):
216    assert type(elements) is list
217
218    for element in elements:
219        # TODO: Compile time constant ought to be the criterion.
220        if not element.isExpressionConstantRef():
221            result = makeExpressionMakeList(elements, source_ref)
222            break
223    else:
224        result = makeConstantRefNode(
225            constant=[element.getCompileTimeConstant() for element in elements],
226            user_provided=user_provided,
227            source_ref=source_ref,
228        )
229
230    if elements:
231        result.setCompatibleSourceReference(
232            source_ref=elements[-1].getCompatibleSourceReference()
233        )
234
235    return result
236
237
238class ExpressionMakeList(ExpressionMakeSequenceBase):
239    kind = "EXPRESSION_MAKE_LIST"
240
241    def __init__(self, elements, source_ref):
242        ExpressionMakeSequenceBase.__init__(
243            self, sequence_kind="LIST", elements=elements, source_ref=source_ref
244        )
245
246    @staticmethod
247    def getTypeShape():
248        return tshape_list
249
250    @staticmethod
251    def getSimulator():
252        return list
253
254    def getIterationLength(self):
255        return len(self.subnode_elements)
256
257    def computeExpressionIter1(self, iter_node, trace_collection):
258        result = ExpressionMakeTuple(
259            elements=self.subnode_elements, source_ref=self.source_ref
260        )
261
262        self.parent.replaceChild(self, result)
263        del self.parent
264
265        return (
266            iter_node,
267            "new_expression",
268            """\
269Iteration over list lowered to iteration over tuple.""",
270        )
271
272
273class ExpressionMakeSet(ExpressionMakeSequenceBase):
274    kind = "EXPRESSION_MAKE_SET"
275
276    def __init__(self, elements, source_ref):
277        ExpressionMakeSequenceBase.__init__(
278            self, sequence_kind="SET", elements=elements, source_ref=source_ref
279        )
280
281    @staticmethod
282    def getTypeShape():
283        return tshape_set
284
285    @staticmethod
286    def getSimulator():
287        return set
288
289    def getIterationLength(self):
290        element_count = len(self.subnode_elements)
291
292        # Hashing and equality may consume elements of the produced set.
293        if element_count >= 2:
294            return None
295        else:
296            return element_count
297
298    @staticmethod
299    def getIterationMinLength():
300        # Hashing and equality may consume elements of the produced set.
301        return 1
302
303    def mayRaiseException(self, exception_type):
304        for element in self.subnode_elements:
305            if not element.isKnownToBeHashable():
306                return True
307
308            if element.mayRaiseException(exception_type):
309                return True
310
311        return False
312
313    def computeExpressionIter1(self, iter_node, trace_collection):
314        result = ExpressionMakeTuple(
315            elements=self.subnode_elements, source_ref=self.source_ref
316        )
317
318        self.parent.replaceChild(self, result)
319        del self.parent
320
321        return (
322            iter_node,
323            "new_expression",
324            """\
325Iteration over set lowered to iteration over tuple.""",
326        )
327
328
329needs_set_literal_reverse = needsSetLiteralReverseInsertion()
330
331
332def makeExpressionMakeSetLiteral(elements, source_ref):
333    if elements:
334        if needs_set_literal_reverse:
335            return ExpressionMakeSetLiteral(elements, source_ref)
336        else:
337            return ExpressionMakeSet(elements, source_ref)
338    else:
339        # TODO: Get rid of user provided for empty set refs, makes no sense.
340        return ExpressionConstantSetEmptyRef(user_provided=False, source_ref=source_ref)
341
342
343@functools.wraps(set)
344def reversed_set(value):
345    return set(reversed(tuple(value)))
346
347
348def makeExpressionMakeSetLiteralOrConstant(elements, user_provided, source_ref):
349    for element in elements:
350        # TODO: Compile time constant ought to be the criterion.
351        if not element.isExpressionConstantRef():
352            result = makeExpressionMakeSetLiteral(elements, source_ref)
353            break
354    else:
355        # Need to reverse now if needed.
356        if needs_set_literal_reverse:
357            elements = tuple(reversed(elements))
358
359        result = makeConstantRefNode(
360            constant=set(element.getCompileTimeConstant() for element in elements),
361            user_provided=user_provided,
362            source_ref=source_ref,
363        )
364
365    if elements:
366        result.setCompatibleSourceReference(
367            source_ref=elements[-1].getCompatibleSourceReference()
368        )
369
370    return result
371
372
373class ExpressionMakeSetLiteral(ExpressionMakeSet):
374    kind = "EXPRESSION_MAKE_SET_LITERAL"
375
376    @staticmethod
377    def getSimulator():
378        return reversed_set
379