• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OpShin / opshin / 696

pending completion
696

push

travis-ci-com

nielstron
Fix casting to anything in case of upcasting

21 of 21 new or added lines in 2 files covered. (100.0%)

3295 of 3568 relevant lines covered (92.35%)

3.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.67
/opshin/optimize/optimize_remove_deadvars.py
1
from ast import *
4✔
2
from copy import copy
4✔
3
from collections import defaultdict
4✔
4

5
from ..util import CompilingNodeVisitor, CompilingNodeTransformer
4✔
6
from ..type_inference import INITIAL_SCOPE
4✔
7

8
"""
2✔
9
Removes assignments to variables that are never read
10
"""
11

12

13
class NameLoadCollector(CompilingNodeVisitor):
4✔
14
    step = "Collecting used variables"
4✔
15

16
    def __init__(self):
4✔
17
        self.loaded = defaultdict(int)
4✔
18

19
    def visit_Name(self, node: Name) -> None:
4✔
20
        if isinstance(node.ctx, Load):
4✔
21
            self.loaded[node.id] += 1
4✔
22

23
    def visit_ClassDef(self, node: ClassDef):
4✔
24
        # ignore the content (i.e. attribute names) of class definitions
25
        pass
4✔
26

27
    def visit_FunctionDef(self, node: FunctionDef):
4✔
28
        # ignore the type hints of function arguments
29
        for s in node.body:
4✔
30
            self.visit(s)
4✔
31

32

33
class SafeOperationVisitor(CompilingNodeVisitor):
4✔
34
    step = "Collecting computations that can not throw errors"
4✔
35

36
    def __init__(self, guaranteed_names):
4✔
37
        self.guaranteed_names = guaranteed_names
4✔
38

39
    def generic_visit(self, node: AST) -> bool:
4✔
40
        # generally every operation is unsafe except we whitelist it
41
        return False
4✔
42

43
    def visit_Lambda(self, node: Lambda) -> bool:
4✔
44
        # lambda definition is fine as it actually doesn't compute anything
45
        return True
×
46

47
    def visit_Constant(self, node: Constant) -> bool:
4✔
48
        # Constants can not fail
49
        return True
×
50

51
    def visit_RawPlutoExpr(self, node) -> bool:
4✔
52
        # these expressions are not evaluated further
53
        return True
4✔
54

55
    def visit_Name(self, node: Name) -> bool:
4✔
56
        return node.id in self.guaranteed_names
4✔
57

58

59
class OptimizeRemoveDeadvars(CompilingNodeTransformer):
4✔
60
    step = "Removing unused variables"
4✔
61

62
    loaded_vars = None
4✔
63
    # names that are guaranteed to be available to the current node
64
    # this acts differently to the type inferencer! in particular, ite/while/for all produce their own scope
65
    guaranteed_avail_names = [list(INITIAL_SCOPE.keys())]
4✔
66

67
    def guaranteed(self, name: str) -> bool:
4✔
68
        name = name
4✔
69
        for scope in reversed(self.guaranteed_avail_names):
4✔
70
            if name in scope:
4✔
71
                return True
×
72
        return False
4✔
73

74
    def enter_scope(self):
4✔
75
        self.guaranteed_avail_names.append([])
4✔
76

77
    def exit_scope(self):
4✔
78
        self.guaranteed_avail_names.pop()
4✔
79

80
    def set_guaranteed(self, name: str):
4✔
81
        self.guaranteed_avail_names[-1].append(name)
4✔
82

83
    def visit_Module(self, node: Module) -> Module:
4✔
84
        # repeat until no more change due to removal
85
        # i.e. b = a; c = b needs 2 passes to remove c and b
86
        node_cp = copy(node)
4✔
87
        self.loaded_vars = None
4✔
88
        while True:
2✔
89
            self.enter_scope()
4✔
90
            # collect all variable names
91
            collector = NameLoadCollector()
4✔
92
            collector.visit(node_cp)
4✔
93
            loaded_vars = set(collector.loaded.keys()) | {"validator"}
4✔
94
            # break if the set of loaded vars did not change -> set of vars to remove does also not change
95
            if loaded_vars == self.loaded_vars:
4✔
96
                break
4✔
97
            # remove unloaded ones
98
            self.loaded_vars = loaded_vars
4✔
99
            node_cp.body = [self.visit(s) for s in node_cp.body]
4✔
100
            self.exit_scope()
4✔
101
        return node_cp
4✔
102

103
    def visit_If(self, node: If):
4✔
104
        node_cp = copy(node)
4✔
105
        node_cp.test = self.visit(node.test)
4✔
106
        self.enter_scope()
4✔
107
        node_cp.body = [self.visit(s) for s in node.body]
4✔
108
        scope_body_cp = self.guaranteed_avail_names[-1].copy()
4✔
109
        self.exit_scope()
4✔
110
        self.enter_scope()
4✔
111
        node_cp.orelse = [self.visit(s) for s in node.orelse]
4✔
112
        scope_orelse_cp = self.guaranteed_avail_names[-1].copy()
4✔
113
        self.exit_scope()
4✔
114
        # what remains after this in the scope is the intersection of both
115
        for var in set(scope_body_cp).intersection(scope_orelse_cp):
4✔
116
            self.set_guaranteed(var)
4✔
117
        return node_cp
4✔
118

119
    def visit_While(self, node: While):
4✔
120
        node_cp = copy(node)
4✔
121
        node_cp.test = self.visit(node.test)
4✔
122
        self.enter_scope()
4✔
123
        node_cp.body = [self.visit(s) for s in node.body]
4✔
124
        node_cp.orelse = [self.visit(s) for s in node.orelse]
4✔
125
        self.exit_scope()
4✔
126
        return node_cp
4✔
127

128
    def visit_For(self, node: For):
4✔
129
        node_cp = copy(node)
4✔
130
        assert isinstance(node.target, Name), "Can only assign to singleton name"
4✔
131
        self.enter_scope()
4✔
132
        self.guaranteed(node.target.id)
4✔
133
        node_cp.body = [self.visit(s) for s in node.body]
4✔
134
        node_cp.orelse = [self.visit(s) for s in node.orelse]
4✔
135
        self.exit_scope()
4✔
136
        return node_cp
4✔
137

138
    def visit_Assign(self, node: Assign):
4✔
139
        if (
4✔
140
            len(node.targets) != 1
141
            or not isinstance(node.targets[0], Name)
142
            or node.targets[0].id in self.loaded_vars
143
            or not SafeOperationVisitor(sum(self.guaranteed_avail_names, [])).visit(
144
                node.value
145
            )
146
        ):
147
            for t in node.targets:
4✔
148
                assert isinstance(
4✔
149
                    t, Name
150
                ), "Need to have name for dead var remover to work"
151
                self.set_guaranteed(t.id)
4✔
152
            return self.generic_visit(node)
4✔
153
        return Pass()
4✔
154

155
    def visit_AnnAssign(self, node: AnnAssign):
4✔
156
        if (
4✔
157
            not isinstance(node.target, Name)
158
            or node.target.id in self.loaded_vars
159
            or not SafeOperationVisitor(sum(self.guaranteed_avail_names, [])).visit(
160
                node.value
161
            )
162
        ):
163
            assert isinstance(
4✔
164
                node.target, Name
165
            ), "Need to have assignments to name for dead var remover to work"
166
            self.set_guaranteed(node.target.id)
4✔
167
            return self.generic_visit(node)
4✔
168
        return Pass()
×
169

170
    def visit_ClassDef(self, node: ClassDef):
4✔
171
        if node.name in self.loaded_vars:
4✔
172
            self.set_guaranteed(node.name)
4✔
173
            return node
4✔
174
        return Pass()
4✔
175

176
    def visit_FunctionDef(self, node: FunctionDef):
4✔
177
        node_cp = copy(node)
4✔
178
        if node.name in self.loaded_vars:
4✔
179
            self.set_guaranteed(node.name)
4✔
180
            self.enter_scope()
4✔
181
            # variable names are available here
182
            for a in node.args.args:
4✔
183
                self.set_guaranteed(a.arg)
4✔
184
            node_cp.body = [self.visit(s) for s in node.body]
4✔
185
            self.exit_scope()
4✔
186
            return node_cp
4✔
187
        return Pass()
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc