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""" Built-in iterator nodes. 19 20These play a role in for loops, and in unpacking. They can something be 21predicted to succeed or fail, in which case, code can become less complex. 22 23The length of things is an important optimization issue for these to be 24good. 25""" 26 27from nuitka.PythonVersions import python_version 28 29from .ExpressionBases import ( 30 ExpressionBuiltinSingleArgBase, 31 ExpressionChildrenHavingBase, 32) 33from .NodeBases import StatementChildHavingBase 34from .NodeMakingHelpers import ( 35 makeRaiseExceptionReplacementStatement, 36 wrapExpressionWithSideEffects, 37) 38from .shapes.StandardShapes import tshape_iterator 39 40 41class ExpressionBuiltinIter1(ExpressionBuiltinSingleArgBase): 42 kind = "EXPRESSION_BUILTIN_ITER1" 43 44 simulator = iter 45 46 def computeExpression(self, trace_collection): 47 trace_collection.initIteratorValue(self) 48 49 value = self.subnode_value 50 return value.computeExpressionIter1( 51 iter_node=self, trace_collection=trace_collection 52 ) 53 54 def computeExpressionIter1(self, iter_node, trace_collection): 55 # Iteration over an iterator is that iterator. 56 57 return self, "new_builtin", "Eliminated useless iterator creation." 58 59 def getTypeShape(self): 60 return self.subnode_value.getTypeShape().getShapeIter() 61 62 def computeExpressionNext1(self, next_node, trace_collection): 63 value = self.subnode_value 64 65 if value.isKnownToBeIterableAtMin(1) and value.canPredictIterationValues(): 66 result = wrapExpressionWithSideEffects( 67 new_node=value.getIterationValue(0), 68 old_node=value, 69 side_effects=value.getIterationValueRange(1, None), 70 ) 71 72 return False, ( 73 result, 74 "new_expression", 75 "Predicted 'next' value from iteration.", 76 ) 77 78 # TODO: This is only true for a few value types, use type shape to tell if 79 # it might escape or raise. 80 self.onContentEscapes(trace_collection) 81 82 # Any code could be run, note that. 83 trace_collection.onControlFlowEscape(self) 84 85 # Any exception may be raised. 86 trace_collection.onExceptionRaiseExit(BaseException) 87 88 return True, (next_node, None, None) 89 90 def isKnownToBeIterable(self, count): 91 if count is None: 92 return True 93 94 iter_length = self.subnode_value.getIterationLength() 95 return iter_length == count 96 97 def isKnownToBeIterableAtMin(self, count): 98 assert type(count) is int 99 100 iter_length = self.subnode_value.getIterationMinLength() 101 return iter_length is not None and count <= iter_length 102 103 def getIterationLength(self): 104 return self.subnode_value.getIterationLength() 105 106 def canPredictIterationValues(self): 107 return self.subnode_value.canPredictIterationValues() 108 109 def getIterationValue(self, element_index): 110 return self.subnode_value.getIterationValue(element_index) 111 112 def getIterationHandle(self): 113 return self.subnode_value.getIterationHandle() 114 115 def extractSideEffects(self): 116 # Iterator making is the side effect itself. 117 value = self.subnode_value 118 119 if value.isCompileTimeConstant() and value.isKnownToBeIterable(None): 120 return () 121 else: 122 return (self,) 123 124 def mayHaveSideEffects(self): 125 value = self.subnode_value 126 127 if value.isCompileTimeConstant(): 128 return not value.isKnownToBeIterable(None) 129 130 return True 131 132 def mayRaiseException(self, exception_type): 133 value = self.subnode_value 134 135 if value.mayRaiseException(exception_type): 136 return True 137 138 if value.isKnownToBeIterable(None): 139 return False 140 141 return True 142 143 def mayRaiseExceptionOperation(self): 144 value = self.subnode_value 145 146 return value.isKnownToBeIterable(None) is not True 147 148 def onRelease(self, trace_collection): 149 # print "onRelease", self 150 pass 151 152 153class ExpressionBuiltinIterForUnpack(ExpressionBuiltinIter1): 154 kind = "EXPRESSION_BUILTIN_ITER_FOR_UNPACK" 155 156 @staticmethod 157 def simulator(value): 158 try: 159 return iter(value) 160 except TypeError: 161 raise TypeError( 162 "cannot unpack non-iterable %s object" % (type(value).__name__) 163 ) 164 165 166class StatementSpecialUnpackCheck(StatementChildHavingBase): 167 kind = "STATEMENT_SPECIAL_UNPACK_CHECK" 168 169 named_child = "iterator" 170 171 __slots__ = ("count",) 172 173 def __init__(self, iterator, count, source_ref): 174 StatementChildHavingBase.__init__(self, value=iterator, source_ref=source_ref) 175 176 self.count = int(count) 177 178 def getDetails(self): 179 return {"count": self.getCount()} 180 181 def getCount(self): 182 return self.count 183 184 def computeStatement(self, trace_collection): 185 iterator = trace_collection.onExpression(self.subnode_iterator) 186 187 if iterator.mayRaiseException(BaseException): 188 trace_collection.onExceptionRaiseExit(BaseException) 189 190 if iterator.willRaiseException(BaseException): 191 from .NodeMakingHelpers import ( 192 makeStatementExpressionOnlyReplacementNode, 193 ) 194 195 result = makeStatementExpressionOnlyReplacementNode( 196 expression=iterator, node=self 197 ) 198 199 return ( 200 result, 201 "new_raise", 202 """\ 203Explicit raise already raises implicitly building exception type.""", 204 ) 205 206 if ( 207 iterator.isExpressionTempVariableRef() 208 and iterator.variable_trace.isAssignTrace() 209 ): 210 211 iterator = iterator.variable_trace.getAssignNode().subnode_source 212 213 current_index = trace_collection.getIteratorNextCount(iterator) 214 else: 215 current_index = None 216 217 if current_index is not None: 218 iter_length = iterator.getIterationLength() 219 220 if iter_length is not None: 221 # Remove the check if it can be decided at compile time. 222 if current_index == iter_length: 223 return ( 224 None, 225 "new_statements", 226 """\ 227Determined iteration end check to be always true.""", 228 ) 229 else: 230 result = makeRaiseExceptionReplacementStatement( 231 statement=self, 232 exception_type="ValueError", 233 exception_value="too many values to unpack" 234 if python_version < 0x300 235 else "too many values to unpack (expected %d)" 236 % self.getCount(), 237 ) 238 239 trace_collection.onExceptionRaiseExit(TypeError) 240 241 return ( 242 result, 243 "new_raise", 244 """\ 245Determined iteration end check to always raise.""", 246 ) 247 248 trace_collection.onExceptionRaiseExit(BaseException) 249 250 return self, None, None 251 252 @staticmethod 253 def getStatementNiceName(): 254 return "iteration check statement" 255 256 257class ExpressionBuiltinIter2(ExpressionChildrenHavingBase): 258 kind = "EXPRESSION_BUILTIN_ITER2" 259 260 named_children = ("callable_arg", "sentinel") 261 262 def __init__(self, callable_arg, sentinel, source_ref): 263 ExpressionChildrenHavingBase.__init__( 264 self, 265 values={"callable_arg": callable_arg, "sentinel": sentinel}, 266 source_ref=source_ref, 267 ) 268 269 @staticmethod 270 def getTypeShape(): 271 # TODO: This could be more specific, this one is a fixed thing! 272 return tshape_iterator 273 274 def computeExpression(self, trace_collection): 275 # TODO: The "callable" should be investigated here, maybe it is not 276 # really callable, or raises an exception. 277 278 return self, None, None 279 280 def computeExpressionIter1(self, iter_node, trace_collection): 281 return self, "new_builtin", "Eliminated useless iterator creation." 282 283 284class ExpressionAsyncIter(ExpressionBuiltinSingleArgBase): 285 kind = "EXPRESSION_ASYNC_ITER" 286 287 def computeExpression(self, trace_collection): 288 value = self.subnode_value 289 290 return value.computeExpressionAsyncIter( 291 iter_node=self, trace_collection=trace_collection 292 ) 293 294 def isKnownToBeIterable(self, count): 295 if count is None: 296 return True 297 298 # TODO: Should ask value if it is. 299 return None 300 301 def getIterationLength(self): 302 return self.subnode_value.getIterationLength() 303 304 def extractSideEffects(self): 305 # Iterator making is the side effect itself. 306 if self.subnode_value.isCompileTimeConstant(): 307 return () 308 else: 309 return (self,) 310 311 def mayHaveSideEffects(self): 312 if self.subnode_value.isCompileTimeConstant(): 313 return self.subnode_value.isKnownToBeIterable(None) 314 315 return True 316 317 def mayRaiseException(self, exception_type): 318 value = self.subnode_value 319 320 if value.mayRaiseException(exception_type): 321 return True 322 323 if value.isKnownToBeIterable(None): 324 return False 325 326 return True 327 328 def isKnownToBeIterableAtMin(self, count): 329 assert type(count) is int 330 331 iter_length = self.subnode_value.getIterationMinLength() 332 return iter_length is not None and iter_length < count 333 334 def onRelease(self, trace_collection): 335 # print "onRelease", self 336 pass 337 338 339class ExpressionAsyncNext(ExpressionBuiltinSingleArgBase): 340 kind = "EXPRESSION_ASYNC_NEXT" 341 342 def __init__(self, value, source_ref): 343 ExpressionBuiltinSingleArgBase.__init__( 344 self, value=value, source_ref=source_ref 345 ) 346 347 def computeExpression(self, trace_collection): 348 # TODO: Predict iteration result if possible via SSA variable trace of 349 # the iterator state. 350 351 # Assume exception is possible. TODO: We might query the next from the 352 # source with a computeExpressionAsyncNext slot, but we delay that. 353 trace_collection.onExceptionRaiseExit(BaseException) 354 355 return self, None, None 356