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 for unary operations.
19
20Some of these come from built-ins, e.g. abs, some from syntax, and repr from both.
21"""
22from nuitka import PythonOperators
23
24from .ConstantRefNodes import makeConstantRefNode
25from .ExpressionBases import ExpressionChildHavingBase
26from .shapes.BuiltinTypeShapes import tshape_bool, tshape_str
27
28
29class ExpressionOperationUnaryBase(ExpressionChildHavingBase):
30    named_child = "operand"
31
32    __slots__ = ("operator", "simulator")
33
34    def __init__(self, operand, source_ref):
35        ExpressionChildHavingBase.__init__(self, value=operand, source_ref=source_ref)
36
37    def getOperator(self):
38        return self.operator
39
40    def computeExpression(self, trace_collection):
41        operator = self.getOperator()
42        operand = self.subnode_operand
43
44        if operand.isCompileTimeConstant():
45            operand_value = operand.getCompileTimeConstant()
46
47            return trace_collection.getCompileTimeComputationResult(
48                node=self,
49                computation=lambda: self.simulator(operand_value),
50                description="Operator '%s' with constant argument." % operator,
51            )
52        else:
53            # TODO: May go down to MemoryError for compile time constant overflow
54            # ones.
55            trace_collection.onExceptionRaiseExit(BaseException)
56
57            # The value of that node escapes and could change its contents.
58            # TODO: The unary operations don't do much to an operator, add
59            # methods, that don't do stuff on common types though.
60            # trace_collection.onValueEscapeSomeUnaryOperator(operand)
61
62            # Any code could be run, note that.
63            trace_collection.onControlFlowEscape(self)
64
65            return self, None, None
66
67    @staticmethod
68    def isExpressionOperationUnary():
69        return True
70
71
72class ExpressionOperationUnaryRepr(ExpressionOperationUnaryBase):
73    """Python unary operator `x` and repr built-in."""
74
75    kind = "EXPRESSION_OPERATION_UNARY_REPR"
76
77    operator = "Repr"
78
79    __slots__ = ("escape_desc",)
80
81    def __init__(self, operand, source_ref):
82        assert operand.isExpression(), operand
83
84        ExpressionOperationUnaryBase.__init__(
85            self, operand=operand, source_ref=source_ref
86        )
87
88        self.escape_desc = None
89
90    def computeExpression(self, trace_collection):
91        result, self.escape_desc = self.subnode_operand.computeExpressionOperationRepr(
92            repr_node=self, trace_collection=trace_collection
93        )
94
95        return result
96
97    def mayRaiseException(self, exception_type):
98        # TODO: Match getExceptionExit() more precisely against exception type given
99        return (
100            self.escape_desc is None
101            or self.escape_desc.getExceptionExit() is not None
102            or self.subnode_operand.mayRaiseException(exception_type)
103        )
104
105    def mayRaiseExceptionBool(self, exception_type):
106        return False
107
108    def mayHaveSideEffects(self):
109        operand = self.subnode_operand
110
111        if operand.mayHaveSideEffects():
112            return True
113
114        return self.escape_desc is None or self.escape_desc.isControlFlowEscape()
115
116    @staticmethod
117    def getTypeShape():
118        # Even unicode gets decoded in Python2
119        return tshape_str
120
121
122class ExpressionOperationUnarySub(ExpressionOperationUnaryBase):
123    """Python unary operator -"""
124
125    kind = "EXPRESSION_OPERATION_UNARY_SUB"
126
127    operator = "USub"
128    simulator = PythonOperators.unary_operator_functions[operator]
129
130    def __init__(self, operand, source_ref):
131        assert operand.isExpression(), operand
132
133        ExpressionOperationUnaryBase.__init__(
134            self, operand=operand, source_ref=source_ref
135        )
136
137
138class ExpressionOperationUnaryAdd(ExpressionOperationUnaryBase):
139    """Python unary operator +"""
140
141    kind = "EXPRESSION_OPERATION_UNARY_ADD"
142
143    operator = "UAdd"
144    simulator = PythonOperators.unary_operator_functions[operator]
145
146    def __init__(self, operand, source_ref):
147        assert operand.isExpression(), operand
148
149        ExpressionOperationUnaryBase.__init__(
150            self, operand=operand, source_ref=source_ref
151        )
152
153
154class ExpressionOperationUnaryInvert(ExpressionOperationUnaryBase):
155    """Python unary operator ~"""
156
157    kind = "EXPRESSION_OPERATION_UNARY_INVERT"
158
159    operator = "Invert"
160    simulator = PythonOperators.unary_operator_functions[operator]
161
162    def __init__(self, operand, source_ref):
163        assert operand.isExpression(), operand
164
165        ExpressionOperationUnaryBase.__init__(
166            self, operand=operand, source_ref=source_ref
167        )
168
169
170class ExpressionOperationNot(ExpressionOperationUnaryBase):
171    kind = "EXPRESSION_OPERATION_NOT"
172
173    operator = "Not"
174    simulator = PythonOperators.unary_operator_functions[operator]
175
176    def __init__(self, operand, source_ref):
177        ExpressionOperationUnaryBase.__init__(
178            self, operand=operand, source_ref=source_ref
179        )
180
181    @staticmethod
182    def getTypeShape():
183        return tshape_bool
184
185    def computeExpression(self, trace_collection):
186        return self.subnode_operand.computeExpressionOperationNot(
187            not_node=self, trace_collection=trace_collection
188        )
189
190    def mayRaiseException(self, exception_type):
191        return self.subnode_operand.mayRaiseException(
192            exception_type
193        ) or self.subnode_operand.mayRaiseExceptionBool(exception_type)
194
195    def mayRaiseExceptionBool(self, exception_type):
196        return self.subnode_operand.mayRaiseExceptionBool(exception_type)
197
198    def getTruthValue(self):
199        result = self.subnode_operand.getTruthValue()
200
201        # Need to invert the truth value of operand of course here.
202        return None if result is None else not result
203
204    def mayHaveSideEffects(self):
205        operand = self.subnode_operand
206
207        if operand.mayHaveSideEffects():
208            return True
209
210        return operand.mayHaveSideEffectsBool()
211
212    def mayHaveSideEffectsBool(self):
213        return self.subnode_operand.mayHaveSideEffectsBool()
214
215    def extractSideEffects(self):
216        operand = self.subnode_operand
217
218        # TODO: Find the common ground of these, and make it an expression
219        # method.
220        if operand.isExpressionMakeSequence():
221            return operand.extractSideEffects()
222
223        if operand.isExpressionMakeDict():
224            return operand.extractSideEffects()
225
226        return (self,)
227
228
229class ExpressionOperationUnaryAbs(ExpressionOperationUnaryBase):
230    kind = "EXPRESSION_OPERATION_UNARY_ABS"
231
232    operator = "Abs"
233    simulator = PythonOperators.unary_operator_functions[operator]
234
235    def __init__(self, operand, source_ref):
236        ExpressionOperationUnaryBase.__init__(
237            self, operand=operand, source_ref=source_ref
238        )
239
240    def computeExpression(self, trace_collection):
241        return self.subnode_operand.computeExpressionAbs(
242            abs_node=self, trace_collection=trace_collection
243        )
244
245    def mayRaiseException(self, exception_type):
246        operand = self.subnode_operand
247
248        if operand.mayRaiseException(exception_type):
249            return True
250
251        return operand.mayRaiseExceptionAbs(exception_type)
252
253    def mayHaveSideEffects(self):
254        operand = self.subnode_operand
255
256        if operand.mayHaveSideEffects():
257            return True
258
259        return operand.mayHaveSideEffectsAbs()
260
261
262def makeExpressionOperationUnary(operator, operand, source_ref):
263    if operator == "Repr":
264        unary_class = ExpressionOperationUnaryRepr
265    elif operator == "USub":
266        unary_class = ExpressionOperationUnarySub
267    elif operator == "UAdd":
268        unary_class = ExpressionOperationUnaryAdd
269    elif operator == "Invert":
270        unary_class = ExpressionOperationUnaryInvert
271    else:
272        assert False, operand
273
274    # Shortcut these unary operations, avoiding "-1", etc. to ever become one.
275    if operand.isCompileTimeConstant():
276        try:
277            constant = unary_class.simulator(operand.getCompileTimeConstant())
278        except Exception:  # Catch all the things, pylint: disable=broad-except
279            # Compile time detectable error, postpone these, so they get traced.
280            pass
281        else:
282            return makeConstantRefNode(
283                constant=constant,
284                source_ref=source_ref,
285                user_provided=getattr(operand, "user_provided", False),
286            )
287
288    return unary_class(operand=operand, source_ref=source_ref)
289