OILS / mycpp / util.py View on Github | oilshell.org

200 lines, 99 significant
1"""
2util.py
3"""
4from __future__ import print_function
5
6import sys
7from mypy.nodes import (CallExpr, IfStmt, Block, Expression, MypyFile,
8 MemberExpr, IntExpr, NameExpr, ComparisonExpr)
9from mypy.types import Instance, Type
10
11from typing import Any, Optional, List, Tuple, Union
12
13# Used by cppgen_pass and const_pass
14
15# mycpp/examples/small_str.py sorta works with this!
16#SMALL_STR = True
17
18SMALL_STR = False
19
20SymbolPath = Tuple[str, ...]
21
22
23def log(msg: str, *args: Any) -> None:
24 if args:
25 msg = msg % args
26 print(msg, file=sys.stderr)
27
28
29def SymbolToString(parts: SymbolPath,
30 strip_package: bool = False,
31 delim: str = '::') -> str:
32 """
33 Join the given name path into a string with the given delimiter.
34 Use strip_package to remove the top-level directory (e.g. `core`, `ysh`)
35 when dealing with C++ namespaces.
36 """
37 if not strip_package:
38 return delim.join(parts)
39
40 if len(parts) > 1:
41 return delim.join(('', ) + parts[1:])
42
43 return parts[0]
44
45
46def SplitPyName(name: str) -> SymbolPath:
47 ret = tuple(name.split('.'))
48 if len(ret) and ret[0] == 'mycpp':
49 # Drop the prefix 'mycpp.' if present. This makes names compatible with
50 # the examples that use testpkg.
51 return ret[1:]
52
53 return ret
54
55
56CaseList = List[Tuple[Expression, Block]]
57
58CaseError = Tuple[str, int, str]
59
60
61def CollectSwitchCases(
62 module_path: str,
63 if_node: IfStmt,
64 out: CaseList,
65 errors: Optional[List[CaseError]] = None) -> Union[Block, int]:
66 """
67 The MyPy AST has a recursive structure for if-elif-elif rather than a
68 flat one. It's a bit confusing.
69
70 Appends (expr, block) cases to out param, and returns the default
71 block, which has no expression.
72
73 default block may be None.
74
75 Returns False if there is no default block.
76 """
77 assert isinstance(if_node, IfStmt), if_node
78 assert len(if_node.expr) == 1, if_node.expr
79 assert len(if_node.body) == 1, if_node.body
80
81 expr = if_node.expr[0]
82 body = if_node.body[0]
83
84 if not isinstance(expr, CallExpr):
85 if errors is not None:
86 errors.append((module_path, expr.line,
87 'Expected call like case(x), got %s' % expr))
88 return -1 # error code
89
90 out.append((expr, body))
91
92 if if_node.else_body:
93 first_of_block = if_node.else_body.body[0]
94 # BUG: this is meant for 'elif' only. But it also triggers for
95 #
96 # else:
97 # if 0:
98
99 if isinstance(first_of_block, IfStmt):
100 return CollectSwitchCases(module_path, first_of_block, out, errors)
101 else:
102 # default case - no expression
103 return if_node.else_body
104
105 return -2 # NO DEFAULT BLOCK
106
107
108def ShouldSkipPyFile(node: MypyFile) -> bool:
109 # Skip some stdlib stuff. A lot of it is brought in by 'import
110 # typing'. These module are special; their contents are currently all
111 # built-in primitives.
112 return node.fullname in ('__future__', 'sys', 'types', 'typing', 'abc',
113 '_ast', 'ast', '_weakrefset', 'collections',
114 'cStringIO', 're', 'builtins')
115
116
117def IsStr(t: Type) -> bool:
118 """Helper to check if a type is a string."""
119 return isinstance(t, Instance) and t.type.fullname == 'builtins.str'
120
121
122def _ShouldSkipIf(stmt: IfStmt) -> bool:
123 cond = stmt.expr[0]
124
125 # Omit anything that looks like if __name__ == ...
126 if (isinstance(cond, ComparisonExpr) and
127 isinstance(cond.operands[0], NameExpr) and
128 cond.operands[0].name == '__name__'):
129 return True
130
131 if isinstance(cond, NameExpr) and cond.name == 'TYPE_CHECKING':
132 # Omit if TYPE_CHECKING blocks. They contain type expressions that
133 # don't type check!
134 return True
135
136 return False
137
138
139def GetSpecialIfCondition(stmt: IfStmt) -> Optional[str]:
140 cond = stmt.expr[0]
141 if isinstance(cond, NameExpr) and cond.name == 'TYPE_CHECKING':
142 return cond.name
143
144 if isinstance(cond, MemberExpr) and cond.name in ('PYTHON', 'CPP'):
145 return cond.name
146
147 return None
148
149
150def ShouldVisitIfExpr(stmt: IfStmt) -> bool:
151 if _ShouldSkipIf(stmt) or GetSpecialIfCondition(stmt) in ('PYTHON', 'CPP'):
152 return False
153
154 cond = stmt.expr[0]
155 if isinstance(cond, IntExpr) and cond.value == 0:
156 return False
157
158 return True
159
160
161def ShouldVisitIfBody(stmt: IfStmt) -> bool:
162 if _ShouldSkipIf(stmt):
163 return False
164
165 cond = stmt.expr[0]
166 if isinstance(cond, MemberExpr) and cond.name == 'PYTHON':
167 return False
168
169 if isinstance(cond, IntExpr) and cond.value == 0:
170 return False
171
172 return True
173
174
175def ShouldVisitElseBody(stmt: IfStmt) -> bool:
176 if _ShouldSkipIf(stmt):
177 return False
178
179 cond = stmt.expr[0]
180 if isinstance(cond, MemberExpr) and cond.name == 'CPP':
181 return False
182
183 return stmt.else_body is not None
184
185
186def IsUnusedVar(var_name: str) -> bool:
187 return var_name == '_' or var_name.startswith('unused')
188
189
190def SkipAssignment(var_name: str) -> bool:
191 """
192 Skip at the top level:
193 _ = log
194 unused1 = log
195
196 Always skip:
197 x, _ = mytuple # no second var
198 """
199 # __all__ should be excluded
200 return IsUnusedVar(var_name)