Skip to content

Commit 04ca37e

Browse files
authored
migrate typechecker to mypy (#691)
1 parent a34aa90 commit 04ca37e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+553
-491
lines changed

.mypy.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[mypy]
2+
mypy_path = $MYPY_CONFIG_FILE_DIR/stubs

Makefile

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,12 @@ ci_housekeeping: sopretty check_check check doc
4141
ci_unit_tests: unit_tests
4242

4343
check:
44-
pyre \
45-
--search-path stubs \
46-
--typeshed `python3 -c 'import sys, site, os; print(next(p for p in (os.path.join(dir,"lib/pyre_check/typeshed") for dir in (sys.prefix,site.getuserbase())) if os.path.isdir(p)))'` \
47-
--show-parse-errors check
44+
mypy WDL
4845
pylint -j `python3 -c 'import multiprocessing as mp; print(mp.cpu_count())'` --errors-only WDL
4946
flake8 WDL
5047

5148
check_check:
52-
# regression test against pyre doing nothing (issue #100)
49+
# regression test against pyre/mypy doing nothing (issue #100)
5350
echo "check_check: str = 42" > WDL/DELETEME_check_check.py
5451
$(MAKE) check > /dev/null 2>&1 && exit 1 || exit 0
5552
rm WDL/DELETEME_check_check.py

WDL/CLI.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ def __call__(self, parser, namespace, values, option_string=None):
146146
# importlib_metadata doesn't seem to provide EntryPoint.dist to get from an entry point to
147147
# the metadata of the package providing it; continuing to use pkg_resources for this. Risk
148148
# that they give inconsistent results?
149-
import pkg_resources
149+
import pkg_resources # type: ignore
150150

151151
for group in runtime.config.default_plugins().keys():
152152
group = f"miniwdl.plugin.{group}"
@@ -1117,7 +1117,8 @@ def runner_input_json_file(available_inputs, namespace, input_file, downloadable
11171117
if input_file:
11181118
input_file = input_file.strip()
11191119
if input_file:
1120-
import yaml # delayed heavy import
1120+
# delayed heavy import:
1121+
import yaml # type: ignore
11211122

11221123
input_json = None
11231124
if input_file[0] == "{":
@@ -1157,7 +1158,7 @@ def bold(line):
11571158
ans = [
11581159
"",
11591160
bold(f"{target.name} ({target.pos.uri})"),
1160-
bold(f"{'-'*(len(target.name)+len(target.pos.uri)+3)}"),
1161+
bold(f"{'-' * (len(target.name) + len(target.pos.uri) + 3)}"),
11611162
]
11621163
required_inputs = target.required_inputs
11631164
ans.append(bold("\nrequired inputs:"))
@@ -1756,7 +1757,7 @@ def configure(cfg=None, show=False, force=False, **kwargs):
17561757
die("`miniwdl configure` is for interactive use")
17571758

17581759
from datetime import datetime
1759-
import bullet
1760+
import bullet # type: ignore
17601761
from xdg import XDG_CONFIG_HOME
17611762

17621763
miniwdl_version = pkg_version()
@@ -2159,6 +2160,7 @@ def _type_to_input_template(ty: Type.Base):
21592160
"""
21602161
if isinstance(ty, Type.StructInstance):
21612162
ans = {}
2163+
assert ty.members
21622164
for member_name, member_type in ty.members.items():
21632165
if not member_type.optional: # TODO: opt in to these
21642166
ans[member_name] = _type_to_input_template(member_type)

WDL/Env.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
# pyre-strict
21
"""
32
Environments, for identifier resolution during WDL typechecking and evaluation.
43
"""
4+
55
from typing import Optional, TypeVar, Generic, Any, Callable, Set, Iterator
66

77
T = TypeVar("T")
@@ -18,9 +18,9 @@ class Binding(Generic[T]):
1818

1919
_name: str
2020
_value: T
21-
_info: Any # pyre-ignore
21+
_info: Any
2222

23-
def __init__(self, name: str, value: T, info: Any = None) -> None: # pyre-ignore
23+
def __init__(self, name: str, value: T, info: Any = None) -> None:
2424
self._name = name
2525
self._value = value
2626
self._info = info
@@ -39,7 +39,7 @@ def value(self) -> T:
3939
return self._value
4040

4141
@property
42-
def info(self) -> Any: # pyre-ignore
42+
def info(self) -> Any:
4343
":type: Any"
4444
return self._info
4545

@@ -76,7 +76,7 @@ def __bool__(self) -> bool:
7676

7777
def __iter__(self) -> Iterator[Binding[T]]:
7878
mask = set()
79-
pos = self
79+
pos: Optional[Bindings[T]] = self
8080
while pos is not None:
8181
if isinstance(pos._binding, Binding) and pos._binding.name not in mask:
8282
mask.add(pos._binding.name)
@@ -86,7 +86,7 @@ def __iter__(self) -> Iterator[Binding[T]]:
8686
def __len__(self) -> int:
8787
return sum(1 for _ in self)
8888

89-
def bind(self, name: str, value: T, info: Any = None) -> "Bindings[T]": # pyre-ignore
89+
def bind(self, name: str, value: T, info: Any = None) -> "Bindings[T]":
9090
"""
9191
Return an environment with a new binding prepended. Any existing binding for the same name
9292
is shadowed by the new one. (This should not usually arise in view of the immutability of
@@ -146,7 +146,7 @@ def map(self, f: Callable[[Binding[T]], Optional[Binding[S]]]) -> "Bindings[S]":
146146
Copy the environment with each binding transformed by the given function. If the function
147147
returns ``None`` then the binding is excluded.
148148
"""
149-
ans = Bindings()
149+
ans: Bindings[S] = Bindings()
150150
for b in self:
151151
fb = f(b)
152152
if isinstance(fb, Binding):
@@ -214,14 +214,14 @@ def wrap_namespace(self, namespace: str) -> "Bindings[T]":
214214
assert namespace
215215
if not namespace.endswith("."):
216216
namespace += "."
217-
ans = Bindings()
217+
ans: Bindings[T] = Bindings()
218218
for b in self:
219219
ans = Bindings(Binding(namespace + b.name, b.value, b.info), ans)
220220
return _rev(ans)
221221

222222

223223
def _rev(env: Bindings[T]) -> Bindings[T]:
224-
ans = Bindings()
224+
ans: Bindings[T] = Bindings()
225225
for b in env:
226226
ans = Bindings(b, ans)
227227
return ans

WDL/Error.py

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
# pyre-strict
21
from typing import (
32
List,
43
Optional,
54
Union,
65
Iterable,
7-
TypeVar,
86
Generator,
97
Callable,
108
Any,
@@ -69,9 +67,6 @@ def __init__(self, pos: SourcePosition, import_uri: str, message: Optional[str]
6967
self.pos = pos
7068

7169

72-
TVSourceNode = TypeVar("TVSourceNode", bound="SourceNode")
73-
74-
7570
@total_ordering
7671
class SourceNode:
7772
"""Base class for an AST node, recording the source position"""
@@ -83,32 +78,36 @@ class SourceNode:
8378
Source position for this AST node
8479
"""
8580

81+
parent: Optional["SourceNode"] = None
82+
"""
83+
:type: Optional[SourceNode]
84+
85+
Parent node in the AST, if any
86+
"""
87+
8688
def __init__(self, pos: SourcePosition) -> None:
8789
self.pos = pos
8890

89-
def __lt__(self, rhs: TVSourceNode) -> bool:
90-
if isinstance(rhs, SourceNode):
91-
return (
92-
self.pos.abspath,
93-
self.pos.line,
94-
self.pos.column,
95-
self.pos.end_line,
96-
self.pos.end_column,
97-
) < (
98-
rhs.pos.abspath,
99-
rhs.pos.line,
100-
rhs.pos.column,
101-
rhs.pos.end_line,
102-
rhs.pos.end_column,
103-
)
104-
return False
91+
def __lt__(self, rhs: "SourceNode") -> bool:
92+
return isinstance(rhs, SourceNode) and (
93+
self.pos.abspath,
94+
self.pos.line,
95+
self.pos.column,
96+
self.pos.end_line,
97+
self.pos.end_column,
98+
) < (
99+
rhs.pos.abspath,
100+
rhs.pos.line,
101+
rhs.pos.column,
102+
rhs.pos.end_line,
103+
rhs.pos.end_column,
104+
)
105105

106-
def __eq__(self, rhs: TVSourceNode) -> bool:
107-
assert isinstance(rhs, SourceNode)
108-
return self.pos == rhs.pos
106+
def __eq__(self, rhs: Any) -> bool:
107+
return isinstance(rhs, SourceNode) and self.pos == rhs.pos
109108

110109
@property
111-
def children(self: TVSourceNode) -> Iterable[TVSourceNode]:
110+
def children(self) -> Iterable["SourceNode"]:
112111
"""
113112
:type: Iterable[SourceNode]
114113
@@ -134,6 +133,8 @@ class ValidationError(Exception):
134133
135134
The complete source text of the WDL document (if available)"""
136135

136+
declared_wdl_version: Optional[str] = None
137+
137138
def __init__(self, node: Union[SourceNode, SourcePosition], message: str) -> None:
138139
if isinstance(node, SourceNode):
139140
self.node = node
@@ -162,25 +163,25 @@ def __init__(self, node: Union[SourceNode, SourcePosition], name: str) -> None:
162163

163164

164165
class NoSuchFunction(ValidationError):
165-
def __init__(self, node: SourceNode, name: str) -> None:
166+
def __init__(self, node: Union[SourceNode, SourcePosition], name: str) -> None:
166167
super().__init__(node, "No such function: " + name)
167168

168169

169170
class WrongArity(ValidationError):
170-
def __init__(self, node: SourceNode, expected: int) -> None:
171+
def __init__(self, node: Union[SourceNode, SourcePosition], expected: int) -> None:
171172
# avoiding circular dep:
172173
# assert isinstance(node, WDL.Expr.Apply)
173174
msg = "{} expects {} argument(s)".format(getattr(node, "function_name"), expected)
174175
super().__init__(node, msg)
175176

176177

177178
class NotAnArray(ValidationError):
178-
def __init__(self, node: SourceNode) -> None:
179+
def __init__(self, node: Union[SourceNode, SourcePosition]) -> None:
179180
super().__init__(node, "Not an array")
180181

181182

182183
class NoSuchMember(ValidationError):
183-
def __init__(self, node: SourceNode, member: str) -> None:
184+
def __init__(self, node: Union[SourceNode, SourcePosition], member: str) -> None:
184185
super().__init__(node, "No such member '{}'".format(member))
185186

186187

@@ -266,6 +267,10 @@ class MultipleValidationErrors(Exception):
266267
exceptions: List[ValidationError]
267268
""":type: List[ValidationError]"""
268269

270+
source_text: Optional[str] = None
271+
272+
declared_wdl_version: Optional[str] = None
273+
269274
def __init__(
270275
self, *exceptions: List[Union[ValidationError, "MultipleValidationErrors"]]
271276
) -> None:
@@ -290,7 +295,7 @@ class _MultiContext:
290295
def __init__(self) -> None:
291296
self._exceptions = []
292297

293-
def try1(self, fn: Callable[[], Any]) -> Optional[Any]: # pyre-ignore
298+
def try1(self, fn: Callable[[], Any]) -> Optional[Any]:
294299
try:
295300
return fn()
296301
except (ValidationError, MultipleValidationErrors) as exn:
@@ -304,8 +309,7 @@ def maybe_raise(self) -> None:
304309
if len(self._exceptions) == 1:
305310
raise self._exceptions[0]
306311
if self._exceptions:
307-
# pyre-ignore
308-
raise MultipleValidationErrors(*self._exceptions) from self._exceptions[0]
312+
raise MultipleValidationErrors(*self._exceptions) from self._exceptions[0] # type: ignore
309313

310314

311315
@contextmanager
@@ -345,7 +349,6 @@ class RuntimeError(Exception):
345349
Backend-specific information about an error (for example, pointer to a centralized log system)
346350
"""
347351

348-
# pyre-ignore
349352
def __init__(self, *args, more_info: Optional[Dict[str, Any]] = None, **kwargs) -> None:
350353
super().__init__(*args, **kwargs)
351354
self.more_info = more_info if more_info else {}

0 commit comments

Comments
 (0)