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