1""" GlobalEffects computes function effect on global state. """
2
3from pythran.analyses.aliases import Aliases
4from pythran.analyses.global_declarations import GlobalDeclarations
5from pythran.passmanager import ModuleAnalysis
6from pythran.tables import MODULES
7from pythran.graph import DiGraph
8import pythran.intrinsic as intrinsic
9
10import gast as ast
11from functools import reduce
12
13
14class GlobalEffects(ModuleAnalysis):
15
16    """Add a flag on each function that updates a global variable."""
17
18    class FunctionEffect(object):
19        def __init__(self, node):
20            self.func = node
21            if isinstance(node, ast.FunctionDef):
22                self.global_effect = False
23            elif isinstance(node, intrinsic.Intrinsic):
24                self.global_effect = node.global_effects
25            elif isinstance(node, ast.alias):
26                self.global_effect = False
27            elif isinstance(node, str):
28                self.global_effect = False
29            elif isinstance(node, intrinsic.Class):
30                self.global_effect = False
31            elif isinstance(node, intrinsic.UnboundValueType):
32                self.global_effect = True  # conservative choice
33            else:
34                print(type(node), node)
35                raise NotImplementedError
36
37    def __init__(self):
38        self.result = DiGraph()
39        self.node_to_functioneffect = dict()
40        super(GlobalEffects, self).__init__(Aliases, GlobalDeclarations)
41
42    def prepare(self, node):
43        """
44        Initialise globals effects as this analyse is inter-procedural.
45
46        Initialisation done for Pythonic functions and default value set for
47        user defined functions.
48        """
49        super(GlobalEffects, self).prepare(node)
50
51        def register_node(module):
52            """ Recursively save globals effect for all functions. """
53            for v in module.values():
54                if isinstance(v, dict):  # Submodule case
55                    register_node(v)
56                else:
57                    fe = GlobalEffects.FunctionEffect(v)
58                    self.node_to_functioneffect[v] = fe
59                    self.result.add_node(fe)
60                    if isinstance(v, intrinsic.Class):
61                        register_node(v.fields)
62
63        register_node(self.global_declarations)
64        for module in MODULES.values():
65            register_node(module)
66        self.node_to_functioneffect[intrinsic.UnboundValue] = \
67            GlobalEffects.FunctionEffect(intrinsic.UnboundValue)
68
69    def run(self, node):
70        result = super(GlobalEffects, self).run(node)
71        keep_going = True
72        while keep_going:
73            keep_going = False
74            for function in result:
75                if function.global_effect:
76                    for pred in self.result.predecessors(function):
77                        if not pred.global_effect:
78                            keep_going = pred.global_effect = True
79        self.result = {f.func for f in result if f.global_effect}
80        return self.result
81
82    def visit_FunctionDef(self, node):
83        self.current_function = self.node_to_functioneffect[node]
84        assert self.current_function in self.result
85        self.generic_visit(node)
86
87    def visit_Print(self, _):
88        self.current_function.global_effect = True
89
90    def visit_Call(self, node):
91        # try to get all aliases of the function, if possible
92        # else use [] as a fallback
93        func_aliases = self.aliases[node.func]
94        # expand argument if any
95        func_aliases = reduce(
96            # all funcs
97            lambda x, y: x + (list(self.node_to_functioneffect.keys())
98                              if isinstance(y, ast.Name) else [y]),
99            func_aliases,
100            list())
101        for func_alias in func_aliases:
102            # special hook for bound functions
103            if isinstance(func_alias, ast.Call):
104                fake_call = ast.Call(func_alias.args[0],
105                                     func_alias.args[1:], [])
106                self.visit(fake_call)
107                continue
108
109            # conservative choice
110            if func_alias not in self.node_to_functioneffect:
111                func_alias = intrinsic.UnboundValue
112
113            func_alias = self.node_to_functioneffect[func_alias]
114            self.result.add_edge(self.current_function, func_alias)
115        self.generic_visit(node)
116