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