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""" Attribute nodes
19
20Knowing attributes of an object is very important, esp. when it comes to 'self'
21and objects and classes.
22
23There will be a methods "computeExpression*Attribute" to aid predicting them,
24with many variants for setting, deleting, and accessing. Also there is some
25complication in the form of special lookups, that won't go through the normal
26path, but just check slots.
27
28Due to ``getattr`` and ``setattr`` built-ins, there is also a different in the
29computations for objects and for compile time known strings. This reflects what
30CPython also does with "tp_getattr" and "tp_getattro".
31
32These nodes are therefore mostly delegating the work to expressions they
33work on, and let them decide and do the heavy lifting of optimization
34and annotation is happening in the nodes that implement these compute slots.
35"""
36
37from .ExpressionBases import (
38    ExpressionChildHavingBase,
39    ExpressionChildrenHavingBase,
40)
41from .NodeBases import StatementChildHavingBase, StatementChildrenHavingBase
42from .NodeMakingHelpers import wrapExpressionWithNodeSideEffects
43
44
45class StatementAssignmentAttribute(StatementChildrenHavingBase):
46    """Assignment to an attribute.
47
48    Typically from code like: source.attribute_name = expression
49
50    Both source and expression may be complex expressions, the source
51    is evaluated first. Assigning to an attribute has its on slot on
52    the source, which gets to decide if it knows it will work or not,
53    and what value it will be.
54    """
55
56    __slots__ = ("attribute_name",)
57
58    kind = "STATEMENT_ASSIGNMENT_ATTRIBUTE"
59
60    named_children = ("source", "expression")
61
62    def __init__(self, expression, attribute_name, source, source_ref):
63        StatementChildrenHavingBase.__init__(
64            self,
65            values={"expression": expression, "source": source},
66            source_ref=source_ref,
67        )
68
69        self.attribute_name = attribute_name
70
71    def getDetails(self):
72        return {"attribute_name": self.attribute_name}
73
74    def getAttributeName(self):
75        return self.attribute_name
76
77    def computeStatement(self, trace_collection):
78        result, change_tags, change_desc = self.computeStatementSubExpressions(
79            trace_collection=trace_collection
80        )
81
82        if result is not self:
83            return result, change_tags, change_desc
84
85        return self.subnode_expression.computeExpressionSetAttribute(
86            set_node=self,
87            attribute_name=self.attribute_name,
88            value_node=self.subnode_source,
89            trace_collection=trace_collection,
90        )
91
92    @staticmethod
93    def getStatementNiceName():
94        return "attribute assignment statement"
95
96
97class StatementDelAttribute(StatementChildHavingBase):
98    """Deletion of an attribute.
99
100    Typically from code like: del source.attribute_name
101
102    The source may be complex expression. Deleting an attribute has its on
103    slot on the source, which gets to decide if it knows it will work or
104    not, and what value it will be.
105    """
106
107    kind = "STATEMENT_DEL_ATTRIBUTE"
108
109    named_child = "expression"
110
111    __slots__ = ("attribute_name",)
112
113    def __init__(self, expression, attribute_name, source_ref):
114        StatementChildHavingBase.__init__(self, value=expression, source_ref=source_ref)
115
116        self.attribute_name = attribute_name
117
118    def getDetails(self):
119        return {"attribute_name": self.attribute_name}
120
121    def getAttributeName(self):
122        return self.attribute_name
123
124    def computeStatement(self, trace_collection):
125        result, change_tags, change_desc = self.computeStatementSubExpressions(
126            trace_collection=trace_collection
127        )
128
129        if result is not self:
130            return result, change_tags, change_desc
131
132        return self.subnode_expression.computeExpressionDelAttribute(
133            set_node=self,
134            attribute_name=self.attribute_name,
135            trace_collection=trace_collection,
136        )
137
138    @staticmethod
139    def getStatementNiceName():
140        return "attribute del statement"
141
142
143class ExpressionAttributeLookup(ExpressionChildHavingBase):
144    """Looking up an attribute of an object.
145
146    Typically code like: source.attribute_name
147    """
148
149    kind = "EXPRESSION_ATTRIBUTE_LOOKUP"
150
151    named_child = "expression"
152    __slots__ = ("attribute_name",)
153
154    def __init__(self, expression, attribute_name, source_ref):
155        ExpressionChildHavingBase.__init__(
156            self, value=expression, source_ref=source_ref
157        )
158
159        self.attribute_name = attribute_name
160
161    def getAttributeName(self):
162        return self.attribute_name
163
164    def getDetails(self):
165        return {"attribute_name": self.attribute_name}
166
167    def computeExpression(self, trace_collection):
168        return self.subnode_expression.computeExpressionAttribute(
169            lookup_node=self,
170            attribute_name=self.attribute_name,
171            trace_collection=trace_collection,
172        )
173
174    def mayRaiseException(self, exception_type):
175        return self.subnode_expression.mayRaiseExceptionAttributeLookup(
176            exception_type=exception_type, attribute_name=self.attribute_name
177        )
178
179    @staticmethod
180    def isKnownToBeIterable(count):
181        # TODO: Could be known. We would need for computeExpressionAttribute to
182        # either return a new node, or a decision maker.
183        return None
184
185
186class ExpressionAttributeLookupSpecial(ExpressionAttributeLookup):
187    """Special lookup up an attribute of an object.
188
189    Typically from code like this: with source: pass
190
191    These directly go to slots, and are performed for with statements
192    of Python2.7 or higher.
193    """
194
195    kind = "EXPRESSION_ATTRIBUTE_LOOKUP_SPECIAL"
196
197    def computeExpression(self, trace_collection):
198        return self.subnode_expression.computeExpressionAttributeSpecial(
199            lookup_node=self,
200            attribute_name=self.attribute_name,
201            trace_collection=trace_collection,
202        )
203
204
205class ExpressionBuiltinGetattr(ExpressionChildrenHavingBase):
206    """Built-in "getattr".
207
208    Typical code like this: getattr(object_arg, name, default)
209
210    The default is optional, but computed before the lookup is done.
211    """
212
213    kind = "EXPRESSION_BUILTIN_GETATTR"
214
215    named_children = ("expression", "name", "default")
216
217    def __init__(self, expression, name, default, source_ref):
218        ExpressionChildrenHavingBase.__init__(
219            self,
220            values={"expression": expression, "name": name, "default": default},
221            source_ref=source_ref,
222        )
223
224    def computeExpression(self, trace_collection):
225        trace_collection.onExceptionRaiseExit(BaseException)
226
227        default = self.subnode_default
228
229        if default is None or not default.mayHaveSideEffects():
230            attribute = self.subnode_name
231
232            attribute_name = attribute.getStringValue()
233
234            if attribute_name is not None:
235                source = self.subnode_expression
236                if source.isKnownToHaveAttribute(attribute_name):
237                    # If source has side effects, they must be evaluated, before
238                    # the lookup, meaning, a temporary variable should be assigned.
239                    # For now, we give up in this case.
240
241                    side_effects = source.extractSideEffects()
242
243                    if not side_effects:
244                        result = ExpressionAttributeLookup(
245                            expression=source,
246                            attribute_name=attribute_name,
247                            source_ref=self.source_ref,
248                        )
249
250                        result = wrapExpressionWithNodeSideEffects(
251                            new_node=result, old_node=attribute
252                        )
253
254                        return (
255                            result,
256                            "new_expression",
257                            """Replaced call to built-in 'getattr' with constant \
258attribute '%s' to mere attribute lookup"""
259                            % attribute_name,
260                        )
261
262        return self, None, None
263
264
265class ExpressionBuiltinSetattr(ExpressionChildrenHavingBase):
266    """Built-in "setattr".
267
268    Typical code like this: setattr(source, attribute, value)
269    """
270
271    kind = "EXPRESSION_BUILTIN_SETATTR"
272
273    named_children = ("expression", "attribute", "value")
274
275    def __init__(self, expression, name, value, source_ref):
276        ExpressionChildrenHavingBase.__init__(
277            self,
278            values={"expression": expression, "attribute": name, "value": value},
279            source_ref=source_ref,
280        )
281
282    def computeExpression(self, trace_collection):
283        trace_collection.onExceptionRaiseExit(BaseException)
284
285        # Note: Might be possible to predict or downgrade to mere attribute set.
286        return self, None, None
287
288
289class ExpressionBuiltinHasattr(ExpressionChildrenHavingBase):
290    kind = "EXPRESSION_BUILTIN_HASATTR"
291
292    named_children = ("expression", "attribute")
293
294    def __init__(self, expression, name, source_ref):
295        ExpressionChildrenHavingBase.__init__(
296            self,
297            values={"expression": expression, "attribute": name},
298            source_ref=source_ref,
299        )
300
301    def computeExpression(self, trace_collection):
302        # We do at least for compile time constants optimization here, but more
303        # could be done, were we to know shapes.
304        source = self.subnode_expression
305
306        if source.isCompileTimeConstant():
307            attribute = self.subnode_attribute
308
309            attribute_name = attribute.getStringValue()
310
311            if attribute_name is not None:
312
313                # If source or attribute have side effects, they must be
314                # evaluated, before the lookup.
315                (
316                    result,
317                    tags,
318                    change_desc,
319                ) = trace_collection.getCompileTimeComputationResult(
320                    node=self,
321                    computation=lambda: hasattr(
322                        source.getCompileTimeConstant(), attribute_name
323                    ),
324                    description="Call to 'hasattr' pre-computed.",
325                )
326
327                result = wrapExpressionWithNodeSideEffects(
328                    new_node=result, old_node=attribute
329                )
330                result = wrapExpressionWithNodeSideEffects(
331                    new_node=result, old_node=source
332                )
333
334                return result, tags, change_desc
335
336        trace_collection.onExceptionRaiseExit(BaseException)
337
338        return self, None, None
339
340
341class ExpressionAttributeCheck(ExpressionChildHavingBase):
342    kind = "EXPRESSION_ATTRIBUTE_CHECK"
343
344    named_child = "expression"
345
346    __slots__ = ("attribute_name",)
347
348    def __init__(self, expression, attribute_name, source_ref):
349        ExpressionChildHavingBase.__init__(
350            self, value=expression, source_ref=source_ref
351        )
352
353        self.attribute_name = attribute_name
354
355    def getDetails(self):
356        return {"attribute_name": self.attribute_name}
357
358    def computeExpression(self, trace_collection):
359        # We do at least for compile time constants optimization here, but more
360        # could be done, were we to know shapes.
361        source = self.subnode_expression
362
363        if source.isCompileTimeConstant():
364            (
365                result,
366                tags,
367                change_desc,
368            ) = trace_collection.getCompileTimeComputationResult(
369                node=self,
370                computation=lambda: hasattr(
371                    source.getCompileTimeConstant(), self.attribute_name
372                ),
373                description="Attribute check has been pre-computed.",
374            )
375
376            # If source has has side effects, they must be evaluated.
377            result = wrapExpressionWithNodeSideEffects(new_node=result, old_node=source)
378
379            return result, tags, change_desc
380
381        trace_collection.onExceptionRaiseExit(BaseException)
382
383        return self, None, None
384
385    @staticmethod
386    def mayRaiseException(exception_type):
387        return False
388
389    def getAttributeName(self):
390        return self.attribute_name
391