1""" Aliases gather aliasing informations. """ 2 3from pythran.analyses.global_declarations import GlobalDeclarations 4from pythran.intrinsic import Intrinsic, Class, UnboundValue 5from pythran.passmanager import ModuleAnalysis 6from pythran.tables import functions, methods, MODULES 7from pythran.unparse import Unparser 8from pythran.conversion import demangle 9import pythran.metadata as md 10from pythran.utils import isnum 11 12import gast as ast 13from copy import deepcopy 14from itertools import product 15import io 16 17 18IntrinsicAliases = dict() 19 20class UnboundIdentifierError(RuntimeError): 21 pass 22 23 24class ContainerOf(object): 25 ''' 26 Represents a container of something 27 28 We just know that if indexed by the integer value `index', 29 we get `containee' 30 ''' 31 UnknownIndex = float('nan') 32 33 __slots__ = 'index', 'containee' 34 35 cache = {} 36 37 def __new__(cls, *args, **kwargs): 38 # cache the creation of new objects, so that same keys give same id 39 # thus great hashing 40 key = tuple(args), tuple(kwargs.items()) 41 if key not in ContainerOf.cache: 42 new_obj = super(ContainerOf, cls).__new__(cls) 43 ContainerOf.cache[key] = new_obj 44 return ContainerOf.cache[key] 45 46 def __init__(self, containee, index=UnknownIndex): 47 self.index = index 48 self.containee = containee 49 50 51def save_intrinsic_alias(module): 52 """ Recursively save default aliases for pythonic functions. """ 53 for v in module.values(): 54 if isinstance(v, dict): # Submodules case 55 save_intrinsic_alias(v) 56 else: 57 IntrinsicAliases[v] = frozenset((v,)) 58 if isinstance(v, Class): 59 save_intrinsic_alias(v.fields) 60 61 62for module in MODULES.values(): 63 save_intrinsic_alias(module) 64 65 66class Aliases(ModuleAnalysis): 67 ''' 68 Gather aliasing informations across nodes 69 70 As a result, each node from the module is associated to a set of node or 71 Intrinsic to which it *may* alias to. 72 ''' 73 74 RetId = '@' 75 76 def __init__(self): 77 self.result = dict() 78 self.aliases = None 79 ContainerOf.cache.clear() 80 super(Aliases, self).__init__(GlobalDeclarations) 81 82 @staticmethod 83 def dump(result, filter=None): 84 def pp(n): 85 output = io.StringIO() 86 Unparser(n, output) 87 return output.getvalue().strip() 88 89 if isinstance(result, dict): 90 for k, v in result.items(): 91 if (filter is None) or isinstance(k, filter): 92 print('{} => {}'.format(pp(k), sorted(map(pp, v)))) 93 elif isinstance(result, (frozenset, set)): 94 print(sorted(map(pp, result))) 95 96 def get_unbound_value_set(self): 97 return {UnboundValue} 98 99 @staticmethod 100 def access_path(node): 101 if isinstance(node, ast.Name): 102 return MODULES.get(demangle(node.id), node.id) 103 elif isinstance(node, ast.Attribute): 104 attr_key = demangle(node.attr) 105 value_dict = Aliases.access_path(node.value) 106 if attr_key not in value_dict: 107 raise PythranSyntaxError( 108 "Unsupported attribute '{}' for this object" 109 .format(attr_key), 110 node.value) 111 return value_dict[attr_key] 112 elif isinstance(node, ast.FunctionDef): 113 return node.name 114 else: 115 return node 116 117 # aliasing created by expressions 118 def add(self, node, values=None): 119 if values is None: # no given target for the alias 120 if isinstance(node, Intrinsic): 121 values = {node} # an Intrinsic always aliases to itself 122 else: 123 values = self.get_unbound_value_set() 124 self.result[node] = values 125 return values 126 127 def visit_BoolOp(self, node): 128 ''' 129 Resulting node may alias to either operands: 130 131 >>> from pythran import passmanager 132 >>> pm = passmanager.PassManager('demo') 133 >>> module = ast.parse('def foo(a, b): return a or b') 134 >>> result = pm.gather(Aliases, module) 135 >>> Aliases.dump(result, filter=ast.BoolOp) 136 (a or b) => ['a', 'b'] 137 138 Note that a literal does not create any alias 139 140 >>> module = ast.parse('def foo(a, b): return a or 0') 141 >>> result = pm.gather(Aliases, module) 142 >>> Aliases.dump(result, filter=ast.BoolOp) 143 (a or 0) => ['<unbound-value>', 'a'] 144 ''' 145 return self.add(node, set.union(*[self.visit(n) for n in node.values])) 146 147 def visit_UnaryOp(self, node): 148 ''' 149 Resulting node does not alias to anything 150 151 >>> from pythran import passmanager 152 >>> pm = passmanager.PassManager('demo') 153 >>> module = ast.parse('def foo(a): return -a') 154 >>> result = pm.gather(Aliases, module) 155 >>> Aliases.dump(result, filter=ast.UnaryOp) 156 (- a) => ['<unbound-value>'] 157 ''' 158 self.generic_visit(node) 159 return self.add(node) 160 161 visit_BinOp = visit_UnaryOp 162 visit_Compare = visit_UnaryOp 163 164 def visit_IfExp(self, node): 165 ''' 166 Resulting node alias to either branch 167 168 >>> from pythran import passmanager 169 >>> pm = passmanager.PassManager('demo') 170 >>> module = ast.parse('def foo(a, b, c): return a if c else b') 171 >>> result = pm.gather(Aliases, module) 172 >>> Aliases.dump(result, filter=ast.IfExp) 173 (a if c else b) => ['a', 'b'] 174 ''' 175 self.visit(node.test) 176 rec = [self.visit(n) for n in (node.body, node.orelse)] 177 return self.add(node, set.union(*rec)) 178 179 def visit_Dict(self, node): 180 ''' 181 A dict is abstracted as an unordered container of its values 182 183 >>> from pythran import passmanager 184 >>> pm = passmanager.PassManager('demo') 185 >>> module = ast.parse('def foo(a, b): return {0: a, 1: b}') 186 >>> result = pm.gather(Aliases, module) 187 >>> Aliases.dump(result, filter=ast.Dict) 188 {0: a, 1: b} => ['|a|', '|b|'] 189 190 where the |id| notation means something that may contain ``id``. 191 ''' 192 if node.keys: 193 elts_aliases = set() 194 for key, val in zip(node.keys, node.values): 195 self.visit(key) # res ignored, just to fill self.aliases 196 elt_aliases = self.visit(val) 197 elts_aliases.update(map(ContainerOf, elt_aliases)) 198 else: 199 elts_aliases = None 200 return self.add(node, elts_aliases) 201 202 def visit_Set(self, node): 203 ''' 204 A set is abstracted as an unordered container of its elements 205 206 >>> from pythran import passmanager 207 >>> pm = passmanager.PassManager('demo') 208 >>> module = ast.parse('def foo(a, b): return {a, b}') 209 >>> result = pm.gather(Aliases, module) 210 >>> Aliases.dump(result, filter=ast.Set) 211 {a, b} => ['|a|', '|b|'] 212 213 where the |id| notation means something that may contain ``id``. 214 ''' 215 if node.elts: 216 elts_aliases = {ContainerOf(alias) 217 for elt in node.elts 218 for alias in self.visit(elt)} 219 else: 220 elts_aliases = None 221 return self.add(node, elts_aliases) 222 223 def visit_Return(self, node): 224 ''' 225 A side effect of computing aliases on a Return is that it updates the 226 ``return_alias`` field of current function 227 228 >>> from pythran import passmanager 229 >>> pm = passmanager.PassManager('demo') 230 >>> module = ast.parse('def foo(a, b): return a') 231 >>> result = pm.gather(Aliases, module) 232 >>> module.body[0].return_alias # doctest: +ELLIPSIS 233 <function ...merge_return_aliases at...> 234 235 This field is a function that takes as many nodes as the function 236 argument count as input and returns an expression based on 237 these arguments if the function happens to create aliasing 238 between its input and output. In our case: 239 240 >>> f = module.body[0].return_alias 241 >>> Aliases.dump(f([ast.Name('A', ast.Load(), None, None), 242 ... ast.Constant(1, None)])) 243 ['A'] 244 245 This also works if the relationship between input and output 246 is more complex: 247 248 >>> module = ast.parse('def foo(a, b): return a or b[0]') 249 >>> result = pm.gather(Aliases, module) 250 >>> f = module.body[0].return_alias 251 >>> List = ast.List([ast.Name('L0', ast.Load(), None, None)], 252 ... ast.Load()) 253 >>> Aliases.dump(f([ast.Name('B', ast.Load(), None, None), List])) 254 ['B', '[L0][0]'] 255 256 Which actually means that when called with two arguments ``B`` and 257 the single-element list ``[L[0]]``, ``foo`` may returns either the 258 first argument, or the first element of the second argument. 259 ''' 260 if not node.value: 261 return 262 ret_aliases = self.visit(node.value) 263 if Aliases.RetId in self.aliases: 264 ret_aliases = ret_aliases.union(self.aliases[Aliases.RetId]) 265 self.aliases[Aliases.RetId] = ret_aliases 266 267 def call_return_alias(self, node): 268 269 def interprocedural_aliases(func, args): 270 arg_aliases = [self.result[arg] or {arg} for arg in args] 271 return_aliases = set() 272 for args_combination in product(*arg_aliases): 273 return_aliases.update( 274 func.return_alias(args_combination)) 275 return {expand_subscript(ra) for ra in return_aliases} 276 277 def expand_subscript(node): 278 if isinstance(node, ast.Subscript): 279 if isinstance(node.value, ContainerOf): 280 return node.value.containee 281 return node 282 283 def full_args(func, call): 284 args = call.args 285 if isinstance(func, ast.FunctionDef): 286 extra = len(func.args.args) - len(args) 287 if extra: 288 tail = [deepcopy(n) for n in func.args.defaults[extra:]] 289 for arg in tail: 290 self.visit(arg) 291 args = args + tail 292 return args 293 294 func = node.func 295 aliases = set() 296 297 if node.keywords: 298 # too soon, we don't support keywords in interprocedural_aliases 299 pass 300 elif isinstance(func, ast.Attribute): 301 _, signature = methods.get(func.attr, 302 functions.get(func.attr, 303 [(None, None)])[0]) 304 if signature: 305 args = full_args(signature, node) 306 aliases = interprocedural_aliases(signature, args) 307 308 elif isinstance(func, ast.Name): 309 func_aliases = self.result[func] 310 for func_alias in func_aliases: 311 if hasattr(func_alias, 'return_alias'): 312 args = full_args(func_alias, node) 313 aliases.update(interprocedural_aliases(func_alias, args)) 314 else: 315 pass # better thing to do ? 316 [self.add(a) for a in aliases if a not in self.result] 317 return aliases or self.get_unbound_value_set() 318 319 def visit_Call(self, node): 320 ''' 321 Resulting node alias to the return_alias of called function, 322 if the function is already known by Pythran (i.e. it's an Intrinsic) 323 or if Pythran already computed it's ``return_alias`` behavior. 324 325 >>> from pythran import passmanager 326 >>> pm = passmanager.PassManager('demo') 327 >>> fun = """ 328 ... def f(a): return a 329 ... def foo(b): c = f(b)""" 330 >>> module = ast.parse(fun) 331 332 The ``f`` function create aliasing between 333 the returned value and its first argument. 334 335 >>> result = pm.gather(Aliases, module) 336 >>> Aliases.dump(result, filter=ast.Call) 337 f(b) => ['b'] 338 339 This also works with intrinsics, e.g ``dict.setdefault`` which 340 may create alias between its third argument and the return value. 341 342 >>> fun = 'def foo(a, d): builtins.dict.setdefault(d, 0, a)' 343 >>> module = ast.parse(fun) 344 >>> result = pm.gather(Aliases, module) 345 >>> Aliases.dump(result, filter=ast.Call) 346 builtins.dict.setdefault(d, 0, a) => ['<unbound-value>', 'a'] 347 348 Note that complex cases can arise, when one of the formal parameter 349 is already known to alias to various values: 350 351 >>> fun = """ 352 ... def f(a, b): return a and b 353 ... def foo(A, B, C, D): return f(A or B, C or D)""" 354 >>> module = ast.parse(fun) 355 >>> result = pm.gather(Aliases, module) 356 >>> Aliases.dump(result, filter=ast.Call) 357 f((A or B), (C or D)) => ['A', 'B', 'C', 'D'] 358 ''' 359 self.generic_visit(node) 360 f = node.func 361 # special handler for bind functions 362 if isinstance(f, ast.Attribute) and f.attr == "partial": 363 return self.add(node, {node}) 364 else: 365 return_alias = self.call_return_alias(node) 366 # expand collected aliases 367 all_aliases = set() 368 for value in return_alias: 369 # no translation 370 if isinstance(value, (ContainerOf, ast.FunctionDef, 371 Intrinsic)): 372 all_aliases.add(value) 373 elif value in self.result: 374 all_aliases.update(self.result[value]) 375 else: 376 try: 377 ap = Aliases.access_path(value) 378 all_aliases.update(self.aliases.get(ap, ())) 379 except NotImplementedError: 380 # should we do something better here? 381 all_aliases.add(value) 382 return self.add(node, all_aliases) 383 384 visit_Constant = visit_UnaryOp 385 386 def visit_Attribute(self, node): 387 return self.add(node, {Aliases.access_path(node)}) 388 389 def visit_Subscript(self, node): 390 ''' 391 Resulting node alias stores the subscript relationship if we don't know 392 anything about the subscripted node. 393 394 >>> from pythran import passmanager 395 >>> pm = passmanager.PassManager('demo') 396 >>> module = ast.parse('def foo(a): return a[0]') 397 >>> result = pm.gather(Aliases, module) 398 >>> Aliases.dump(result, filter=ast.Subscript) 399 a[0] => ['a[0]'] 400 401 If we know something about the container, e.g. in case of a list, we 402 can use this information to get more accurate informations: 403 404 >>> module = ast.parse('def foo(a, b, c): return [a, b][c]') 405 >>> result = pm.gather(Aliases, module) 406 >>> Aliases.dump(result, filter=ast.Subscript) 407 [a, b][c] => ['a', 'b'] 408 409 Moreover, in case of a tuple indexed by a constant value, we can 410 further refine the aliasing information: 411 412 >>> fun = """ 413 ... def f(a, b): return a, b 414 ... def foo(a, b): return f(a, b)[0]""" 415 >>> module = ast.parse(fun) 416 >>> result = pm.gather(Aliases, module) 417 >>> Aliases.dump(result, filter=ast.Subscript) 418 f(a, b)[0] => ['a'] 419 420 Nothing is done for slices, even if the indices are known :-/ 421 422 >>> module = ast.parse('def foo(a, b, c): return [a, b, c][1:]') 423 >>> result = pm.gather(Aliases, module) 424 >>> Aliases.dump(result, filter=ast.Subscript) 425 [a, b, c][1:] => ['<unbound-value>'] 426 ''' 427 if isinstance(node.slice, ast.Tuple): 428 # could be enhanced through better handling of containers 429 self.visit(node.value) 430 for elt in node.slice.elts: 431 self.visit(elt) 432 aliases = None 433 else: 434 aliases = set() 435 self.visit(node.slice) 436 value_aliases = self.visit(node.value) 437 for alias in value_aliases: 438 if isinstance(alias, ContainerOf): 439 if isinstance(node.slice, ast.Slice): 440 continue 441 if isnum(node.slice): 442 if node.slice.value != alias.index: 443 continue 444 # FIXME: what if the index is a slice variable... 445 aliases.add(alias.containee) 446 elif isinstance(getattr(alias, 'ctx', None), (ast.Param, 447 ast.Store)): 448 aliases.add(ast.Subscript(alias, node.slice, node.ctx)) 449 if not aliases: 450 aliases = None 451 return self.add(node, aliases) 452 453 def visit_OMPDirective(self, node): 454 ''' 455 omp directive may introduce new variables, just register them 456 ''' 457 for dep in node.deps: 458 self.add(dep) 459 460 def visit_Name(self, node): 461 if node.id not in self.aliases: 462 raise UnboundIdentifierError 463 return self.add(node, self.aliases[node.id]) 464 465 def visit_Tuple(self, node): 466 ''' 467 A tuple is abstracted as an ordered container of its values 468 469 >>> from pythran import passmanager 470 >>> pm = passmanager.PassManager('demo') 471 >>> module = ast.parse('def foo(a, b): return a, b') 472 >>> result = pm.gather(Aliases, module) 473 >>> Aliases.dump(result, filter=ast.Tuple) 474 (a, b) => ['|[0]=a|', '|[1]=b|'] 475 476 where the |[i]=id| notation means something that 477 may contain ``id`` at index ``i``. 478 ''' 479 if node.elts: 480 elts_aliases = set() 481 for i, elt in enumerate(node.elts): 482 elt_aliases = self.visit(elt) 483 elts_aliases.update(ContainerOf(alias, i) 484 for alias in elt_aliases) 485 else: 486 elts_aliases = None 487 return self.add(node, elts_aliases) 488 489 visit_List = visit_Set 490 491 def visit_comprehension(self, node): 492 self.aliases[node.target.id] = {node.target} 493 self.generic_visit(node) 494 495 def visit_ListComp(self, node): 496 ''' 497 A comprehension is not abstracted in any way 498 499 >>> from pythran import passmanager 500 >>> pm = passmanager.PassManager('demo') 501 >>> module = ast.parse('def foo(a, b): return [a for i in b]') 502 >>> result = pm.gather(Aliases, module) 503 >>> Aliases.dump(result, filter=ast.ListComp) 504 [a for i in b] => ['<unbound-value>'] 505 ''' 506 for generator in node.generators: 507 self.visit_comprehension(generator) 508 self.visit(node.elt) 509 return self.add(node) 510 511 visit_SetComp = visit_ListComp 512 513 visit_GeneratorExp = visit_ListComp 514 515 def visit_DictComp(self, node): 516 ''' 517 A comprehension is not abstracted in any way 518 519 >>> from pythran import passmanager 520 >>> pm = passmanager.PassManager('demo') 521 >>> module = ast.parse('def foo(a, b): return {i: i for i in b}') 522 >>> result = pm.gather(Aliases, module) 523 >>> Aliases.dump(result, filter=ast.DictComp) 524 {i: i for i in b} => ['<unbound-value>'] 525 ''' 526 for generator in node.generators: 527 self.visit_comprehension(generator) 528 self.visit(node.key) 529 self.visit(node.value) 530 return self.add(node) 531 532 # aliasing created by statements 533 534 def visit_FunctionDef(self, node): 535 ''' 536 Initialise aliasing default value before visiting. 537 538 Add aliasing values for : 539 - Pythonic 540 - globals declarations 541 - current function arguments 542 ''' 543 self.aliases = IntrinsicAliases.copy() 544 545 self.aliases.update((k, {v}) 546 for k, v in self.global_declarations.items()) 547 548 self.aliases.update((arg.id, {arg}) 549 for arg in node.args.args) 550 551 self.generic_visit(node) 552 if Aliases.RetId in self.aliases: 553 # parametrize the expression 554 def parametrize(exp): 555 # constant or global -> no change 556 if isinstance(exp, (ast.Constant, Intrinsic, ast.FunctionDef)): 557 return lambda _: {exp} 558 elif isinstance(exp, ContainerOf): 559 pcontainee = parametrize(exp.containee) 560 index = exp.index 561 return lambda args: { 562 ContainerOf(pc, index) 563 for pc in pcontainee(args) 564 } 565 elif isinstance(exp, ast.Name): 566 try: 567 w = node.args.args.index(exp) 568 569 def return_alias(args): 570 if w < len(args): 571 return {args[w]} 572 else: 573 return {node.args.defaults[w - len(args)]} 574 return return_alias 575 except ValueError: 576 return lambda _: self.get_unbound_value_set() 577 elif isinstance(exp, ast.Subscript): 578 values = parametrize(exp.value) 579 slices = parametrize(exp.slice) 580 return lambda args: { 581 ast.Subscript(value, slice, ast.Load()) 582 for value in values(args) 583 for slice in slices(args)} 584 else: 585 return lambda _: self.get_unbound_value_set() 586 587 # this is a little tricky: for each returned alias, 588 # parametrize builds a function that, given a list of args, 589 # returns the alias 590 # then as we may have multiple returned alias, we compute the union 591 # of these returned aliases 592 return_aliases = [parametrize(ret_alias) 593 for ret_alias 594 in self.aliases[Aliases.RetId]] 595 596 def merge_return_aliases(args): 597 return {ra 598 for return_alias in return_aliases 599 for ra in return_alias(args)} 600 601 node.return_alias = merge_return_aliases 602 603 def visit_Assign(self, node): 604 r''' 605 Assignment creates aliasing between lhs and rhs 606 607 >>> from pythran import passmanager 608 >>> pm = passmanager.PassManager('demo') 609 >>> module = ast.parse('def foo(a): c = a ; d = e = c ; {c, d, e}') 610 >>> result = pm.gather(Aliases, module) 611 >>> Aliases.dump(result, filter=ast.Set) 612 {c, d, e} => ['|a|'] 613 614 Everyone points to the formal parameter 'a' \o/ 615 ''' 616 md.visit(self, node) 617 value_aliases = self.visit(node.value) 618 for t in node.targets: 619 if isinstance(t, ast.Name): 620 self.aliases[t.id] = set(value_aliases) or {t} 621 for alias in list(value_aliases): 622 if isinstance(alias, ast.Name): 623 a_id = alias.id 624 self.aliases[a_id] = self.aliases[a_id].union((t,)) 625 self.add(t, self.aliases[t.id]) 626 else: 627 self.visit(t) 628 629 def visit_For(self, node): 630 ''' 631 For loop creates aliasing between the target 632 and the content of the iterator 633 634 >>> from pythran import passmanager 635 >>> pm = passmanager.PassManager('demo') 636 >>> module = ast.parse(""" 637 ... def foo(a): 638 ... for i in a: 639 ... {i}""") 640 >>> result = pm.gather(Aliases, module) 641 >>> Aliases.dump(result, filter=ast.Set) 642 {i} => ['|i|'] 643 644 Not very useful, unless we know something about the iterated container 645 646 >>> module = ast.parse(""" 647 ... def foo(a, b): 648 ... for i in [a, b]: 649 ... {i}""") 650 >>> result = pm.gather(Aliases, module) 651 >>> Aliases.dump(result, filter=ast.Set) 652 {i} => ['|a|', '|b|'] 653 ''' 654 655 iter_aliases = self.visit(node.iter) 656 if all(isinstance(x, ContainerOf) for x in iter_aliases): 657 target_aliases = {iter_alias.containee for iter_alias in 658 iter_aliases} 659 else: 660 target_aliases = {node.target} 661 662 self.add(node.target, target_aliases) 663 self.aliases[node.target.id] = self.result[node.target] 664 665 self.generic_visit(node) 666 self.generic_visit(node) 667 668 def visit_While(self, node): 669 ''' 670 671 While statement evaluation is somehow equivalent to the evaluation of a 672 sequence, except the fact that in some subtle cases, the first rounds 673 of analyse fails because we do not follow the regular execution order 674 675 >>> from pythran import passmanager 676 >>> pm = passmanager.PassManager('demo') 677 >>> fun = """ 678 ... def foo(a): 679 ... while(a): 680 ... if a == 1: builtins.print(b) 681 ... else: b = a""" 682 >>> module = ast.parse(fun) 683 >>> result = pm.gather(Aliases, module) 684 ''' 685 self.generic_visit(node) 686 self.generic_visit(node) 687 688 def visit_If(self, node): 689 ''' 690 After an if statement, the values from both branches are merged, 691 potentially creating more aliasing: 692 693 >>> from pythran import passmanager 694 >>> pm = passmanager.PassManager('demo') 695 >>> fun = """ 696 ... def foo(a, b): 697 ... if a: c=a 698 ... else: c=b 699 ... return {c}""" 700 >>> module = ast.parse(fun) 701 >>> result = pm.gather(Aliases, module) 702 >>> Aliases.dump(result, filter=ast.Set) 703 {c} => ['|a|', '|b|'] 704 ''' 705 706 md.visit(self, node) 707 self.visit(node.test) 708 true_aliases = false_aliases = None 709 710 # first try the true branch 711 try: 712 tmp = self.aliases.copy() 713 for stmt in node.body: 714 self.visit(stmt) 715 true_aliases = self.aliases 716 self.aliases = tmp 717 except UnboundIdentifierError: 718 pass 719 720 # then try the false branch 721 try: 722 for stmt in node.orelse: 723 self.visit(stmt) 724 false_aliases = self.aliases 725 except UnboundIdentifierError: 726 pass 727 728 if true_aliases and not false_aliases: 729 self.aliases = true_aliases 730 for stmt in node.orelse: 731 self.visit(stmt) 732 false_aliases = self.aliases 733 734 if false_aliases and not true_aliases: 735 self.aliases = false_aliases 736 for stmt in node.body: 737 self.visit(stmt) 738 true_aliases = self.aliases 739 740 # merge the results from true and false branches 741 if false_aliases and true_aliases: 742 for k, v in true_aliases.items(): 743 if k in self.aliases: 744 self.aliases[k] = self.aliases[k].union(v) 745 else: 746 assert isinstance(v, (frozenset, set)) 747 self.aliases[k] = v 748 elif true_aliases: 749 self.aliases = true_aliases 750 751 def visit_ExceptHandler(self, node): 752 if node.name: 753 self.aliases[node.name.id] = {node.name} 754 self.generic_visit(node) 755 756 757class StrictAliases(Aliases): 758 """ 759 Gather aliasing informations across nodes, 760 without adding unsure aliases. 761 """ 762 763 def get_unbound_value_set(self): 764 return set() 765