Skip to content

Commit 28abad1

Browse files
ilevkivskyiJukkaL
authored andcommitted
Turn on strict optional in CI (#3646)
Turn on strict optional for most files when self-checking (there are still 16 files that are skipped). Refactor more code to be strict-optional clean.
1 parent 88a0194 commit 28abad1

21 files changed

+131
-52
lines changed

mypy/binder.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import (Dict, List, Set, Iterator, Union)
1+
from typing import Dict, List, Set, Iterator, Union, Optional, cast
22
from contextlib import contextmanager
33

44
from mypy.types import Type, AnyType, PartialType, UnionType, NoneTyp
@@ -31,6 +31,13 @@ def __init__(self) -> None:
3131
self.unreachable = False
3232

3333

34+
class DeclarationsFrame(Dict[Key, Optional[Type]]):
35+
"""Same as above, but allowed to have None values."""
36+
37+
def __init__(self) -> None:
38+
self.unreachable = False
39+
40+
3441
class ConditionalTypeBinder:
3542
"""Keep track of conditional types of variables.
3643
@@ -68,9 +75,9 @@ def __init__(self) -> None:
6875
# has no corresponding element in this list.
6976
self.options_on_return = [] # type: List[List[Frame]]
7077

71-
# Maps expr.literal_hash] to get_declaration(expr)
78+
# Maps expr.literal_hash to get_declaration(expr)
7279
# for every expr stored in the binder
73-
self.declarations = Frame()
80+
self.declarations = DeclarationsFrame()
7481
# Set of other keys to invalidate if a key is changed, e.g. x -> {x.a, x[0]}
7582
# Whenever a new key (e.g. x.a.b) is added, we update this
7683
self.dependencies = {} # type: Dict[Key, Set[Key]]
@@ -101,7 +108,7 @@ def push_frame(self) -> Frame:
101108
def _put(self, key: Key, type: Type, index: int=-1) -> None:
102109
self.frames[index][key] = type
103110

104-
def _get(self, key: Key, index: int=-1) -> Type:
111+
def _get(self, key: Key, index: int=-1) -> Optional[Type]:
105112
if index < 0:
106113
index += len(self.frames)
107114
for i in range(index, -1, -1):
@@ -124,7 +131,7 @@ def put(self, expr: Expression, typ: Type) -> None:
124131
def unreachable(self) -> None:
125132
self.frames[-1].unreachable = True
126133

127-
def get(self, expr: Expression) -> Type:
134+
def get(self, expr: Expression) -> Optional[Type]:
128135
return self._get(expr.literal_hash)
129136

130137
def is_unreachable(self) -> bool:
@@ -163,15 +170,17 @@ def update_from_options(self, frames: List[Frame]) -> bool:
163170
# know anything about key in at least one possible frame.
164171
continue
165172

173+
type = resulting_values[0]
174+
assert type is not None
166175
if isinstance(self.declarations.get(key), AnyType):
167-
type = resulting_values[0]
168-
if not all(is_same_type(type, t) for t in resulting_values[1:]):
176+
# At this point resulting values can't contain None, see continue above
177+
if not all(is_same_type(type, cast(Type, t)) for t in resulting_values[1:]):
169178
type = AnyType()
170179
else:
171-
type = resulting_values[0]
172180
for other in resulting_values[1:]:
181+
assert other is not None
173182
type = join_simple(self.declarations[key], type, other)
174-
if not is_same_type(type, current_value):
183+
if current_value is None or not is_same_type(type, current_value):
175184
self._put(key, type)
176185
changed = True
177186

@@ -252,7 +261,7 @@ def invalidate_dependencies(self, expr: BindableExpression) -> None:
252261
for dep in self.dependencies.get(expr.literal_hash, set()):
253262
self._cleanse_key(dep)
254263

255-
def most_recent_enclosing_type(self, expr: BindableExpression, type: Type) -> Type:
264+
def most_recent_enclosing_type(self, expr: BindableExpression, type: Type) -> Optional[Type]:
256265
if isinstance(type, AnyType):
257266
return get_declaration(expr)
258267
key = expr.literal_hash
@@ -342,7 +351,7 @@ def top_frame_context(self) -> Iterator[Frame]:
342351
self.pop_frame(True, 0)
343352

344353

345-
def get_declaration(expr: BindableExpression) -> Type:
354+
def get_declaration(expr: BindableExpression) -> Optional[Type]:
346355
if isinstance(expr, RefExpr) and isinstance(expr.node, Var):
347356
type = expr.node.type
348357
if not isinstance(type, PartialType):

mypy/checkmember.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo,
212212
not_ready_callback: Callable[[str, Context], None],
213213
msg: MessageBuilder,
214214
original_type: Type,
215-
chk: 'mypy.checker.TypeChecker' = None) -> Type:
215+
chk: 'mypy.checker.TypeChecker') -> Type:
216216
"""Analyse attribute access that does not target a method.
217217
218218
This is logically part of analyze_member_access and the arguments are similar.

mypy/errors.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from collections import OrderedDict, defaultdict
55
from contextlib import contextmanager
66

7-
from typing import Tuple, List, TypeVar, Set, Dict, Iterator, Optional
7+
from typing import Tuple, List, TypeVar, Set, Dict, Iterator, Optional, cast
88

99
from mypy.options import Options
1010
from mypy.version import __version__ as mypy_version
@@ -278,7 +278,7 @@ def report(self, line: int, column: int, message: str, blocker: bool = False,
278278
self.add_error_info(info)
279279

280280
def add_error_info(self, info: ErrorInfo) -> None:
281-
(file, line) = info.origin
281+
(file, line) = cast(Tuple[str, int], info.origin) # see issue 1855
282282
if not info.blocker: # Blockers cannot be ignored
283283
if file in self.ignored_lines and line in self.ignored_lines[file]:
284284
# Annotation requests us to ignore all errors on this line.

mypy/exprtotype.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def expr_to_unanalyzed_type(expr: Expression, _parent: Optional[Expression] = No
3434
"""
3535
# The `parent` paremeter is used in recursive calls to provide context for
3636
# understanding whether an CallableArgument is ok.
37+
name = None # type: Optional[str]
3738
if isinstance(expr, NameExpr):
3839
name = expr.name
3940
return UnboundType(name, line=expr.line, column=expr.column)

mypy/join.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from mypy import experiments
1616

1717

18-
def join_simple(declaration: Type, s: Type, t: Type) -> Type:
18+
def join_simple(declaration: Optional[Type], s: Type, t: Type) -> Type:
1919
"""Return a simple least upper bound given the declared type."""
2020

2121
if (s.can_be_true, s.can_be_false) != (t.can_be_true, t.can_be_false):

mypy/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class InvalidPackageName(Exception):
2828
"""Exception indicating that a package name was invalid."""
2929

3030

31-
def main(script_path: str, args: List[str] = None) -> None:
31+
def main(script_path: Optional[str], args: List[str] = None) -> None:
3232
"""Main entry point to the type checker.
3333
3434
Args:

mypy/messages.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import re
77
import difflib
88

9-
from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple
9+
from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple, Optional
1010

1111
from mypy.erasetype import erase_type
1212
from mypy.errors import Errors
@@ -591,7 +591,8 @@ def too_few_arguments(self, callee: CallableType, context: Context,
591591
else:
592592
msg = 'Missing positional arguments'
593593
if callee.name and diff and all(d is not None for d in diff):
594-
msg += ' "{}" in call to {}'.format('", "'.join(diff), callee.name)
594+
msg += ' "{}" in call to {}'.format('", "'.join(cast(List[str], diff)),
595+
callee.name)
595596
else:
596597
msg = 'Too few arguments'
597598
if callee.name:
@@ -625,6 +626,7 @@ def unexpected_keyword_argument(self, callee: CallableType, name: str,
625626
self.fail(msg, context)
626627
module = find_defining_module(self.modules, callee)
627628
if module:
629+
assert callee.definition is not None
628630
self.note('{} defined here'.format(callee.name), callee.definition,
629631
file=module.path, origin=context)
630632

@@ -636,9 +638,11 @@ def duplicate_argument_value(self, callee: CallableType, index: int,
636638

637639
def does_not_return_value(self, callee_type: Type, context: Context) -> None:
638640
"""Report an error about use of an unusable type."""
639-
if isinstance(callee_type, FunctionLike) and callee_type.get_name() is not None:
640-
self.fail('{} does not return a value'.format(
641-
capitalize(callee_type.get_name())), context)
641+
name = None # type: Optional[str]
642+
if isinstance(callee_type, FunctionLike):
643+
name = callee_type.get_name()
644+
if name is not None:
645+
self.fail('{} does not return a value'.format(capitalize(name)), context)
642646
else:
643647
self.fail('Function does not return a value', context)
644648

@@ -1011,7 +1015,7 @@ def callable_name(type: CallableType) -> str:
10111015
return 'function'
10121016

10131017

1014-
def find_defining_module(modules: Dict[str, MypyFile], typ: CallableType) -> MypyFile:
1018+
def find_defining_module(modules: Dict[str, MypyFile], typ: CallableType) -> Optional[MypyFile]:
10151019
if not typ.definition:
10161020
return None
10171021
fullname = typ.definition.fullname()

mypy/nodes.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1954,7 +1954,7 @@ class is generic then it will be a type constructor of higher kind.
19541954
mro = None # type: List[TypeInfo]
19551955

19561956
declared_metaclass = None # type: Optional[mypy.types.Instance]
1957-
metaclass_type = None # type: mypy.types.Instance
1957+
metaclass_type = None # type: Optional[mypy.types.Instance]
19581958

19591959
subtypes = None # type: Set[TypeInfo] # Direct subclasses encountered so far
19601960
names = None # type: SymbolTable # Names defined directly in this type
@@ -2506,9 +2506,9 @@ def check_arg_kinds(arg_kinds: List[int], nodes: List[T], fail: Callable[[str, T
25062506
is_kw_arg = True
25072507

25082508

2509-
def check_arg_names(names: List[str], nodes: List[T], fail: Callable[[str, T], None],
2509+
def check_arg_names(names: List[Optional[str]], nodes: List[T], fail: Callable[[str, T], None],
25102510
description: str = 'function definition') -> None:
2511-
seen_names = set() # type: Set[str]
2511+
seen_names = set() # type: Set[Optional[str]]
25122512
for name, node in zip(names, nodes):
25132513
if name is not None and name in seen_names:
25142514
fail("Duplicate argument '{}' in {}".format(name, description), node)

mypy/semanal.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,6 +1713,7 @@ def analyze_lvalue(self, lval: Lvalue, nested: bool = False,
17131713
v.info = self.type
17141714
v.is_initialized_in_class = True
17151715
v.set_line(lval)
1716+
v._fullname = self.qualified_name(lval.name)
17161717
lval.node = v
17171718
lval.is_def = True
17181719
lval.kind = MDEF

mypy/stats.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,10 @@ def visit_assignment_stmt(self, o: AssignmentStmt) -> None:
103103
items = [lvalue]
104104
for item in items:
105105
if isinstance(item, RefExpr) and item.is_def:
106-
t = self.typemap.get(item)
106+
if self.typemap is not None:
107+
t = self.typemap.get(item)
108+
else:
109+
t = None
107110
if t:
108111
self.type(t)
109112
else:
@@ -151,10 +154,11 @@ def visit_unary_expr(self, o: UnaryExpr) -> None:
151154

152155
def process_node(self, node: Expression) -> None:
153156
if self.all_nodes:
154-
typ = self.typemap.get(node)
155-
if typ:
156-
self.line = node.line
157-
self.type(typ)
157+
if self.typemap is not None:
158+
typ = self.typemap.get(node)
159+
if typ:
160+
self.line = node.line
161+
self.type(typ)
158162

159163
def type(self, t: Type) -> None:
160164
if isinstance(t, AnyType):

mypy/test/data.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,9 @@ def parse_test_cases(
164164
for file_path, contents in files:
165165
expand_errors(contents.split('\n'), tcout, file_path)
166166
lastline = p[i].line if i < len(p) else p[i - 1].line + 9999
167-
tc = DataDrivenTestCase(p[i0].arg, input, tcout, tcout2, path,
167+
arg0 = p[i0].arg
168+
assert arg0 is not None
169+
tc = DataDrivenTestCase(arg0, input, tcout, tcout2, path,
168170
p[i0].line, lastline, perform,
169171
files, output_files, stale_modules,
170172
rechecked_modules, deleted_paths, native_sep)
@@ -200,7 +202,7 @@ def __init__(self,
200202
file: str,
201203
line: int,
202204
lastline: int,
203-
perform: Callable[['DataDrivenTestCase'], None],
205+
perform: Optional[Callable[['DataDrivenTestCase'], None]],
204206
files: List[Tuple[str, str]],
205207
output_files: List[Tuple[str, str]],
206208
expected_stale_modules: Dict[int, Set[str]],
@@ -270,6 +272,7 @@ def run(self) -> None:
270272
if self.name.endswith('-skip'):
271273
raise SkipTestCaseException()
272274
else:
275+
assert self.perform is not None, 'Tests without `perform` should not be `run`'
273276
self.perform(self)
274277

275278
def tear_down(self) -> None:

mypy/test/testdeps.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Test cases for generating node-level dependencies (for fine-grained incremental checking)"""
22

33
import os
4-
from typing import List, Tuple, Dict
4+
from typing import List, Tuple, Dict, Optional
55

66
from mypy import build
77
from mypy.build import BuildSource
@@ -35,6 +35,8 @@ def run_case(self, testcase: DataDrivenTestCase) -> None:
3535
src = '\n'.join(testcase.input)
3636
messages, files, type_map = self.build(src)
3737
a = messages
38+
assert files is not None and type_map is not None, ('cases where CompileError'
39+
' occurred should not be run')
3840
deps = get_dependencies('__main__', files['__main__'], type_map)
3941

4042
for source, targets in sorted(deps.items()):
@@ -49,8 +51,8 @@ def run_case(self, testcase: DataDrivenTestCase) -> None:
4951
testcase.line))
5052

5153
def build(self, source: str) -> Tuple[List[str],
52-
Dict[str, MypyFile],
53-
Dict[Expression, Type]]:
54+
Optional[Dict[str, MypyFile]],
55+
Optional[Dict[Expression, Type]]]:
5456
options = Options()
5557
options.use_builtins_fixtures = True
5658
options.show_traceback = True

mypy/test/testdiff.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Test cases for AST diff (used for fine-grained incremental checking)"""
22

33
import os
4-
from typing import List, Tuple, Dict
4+
from typing import List, Tuple, Dict, Optional
55

66
from mypy import build
77
from mypy.build import BuildSource
@@ -46,6 +46,8 @@ def run_case(self, testcase: DataDrivenTestCase) -> None:
4646
a.append('== next ==')
4747
a.extend(messages2)
4848

49+
assert files1 is not None and files2 is not None, ('cases where CompileError'
50+
' occurred should not be run')
4951
diff = compare_symbol_tables(
5052
'__main__',
5153
files1['__main__'].names,
@@ -58,7 +60,7 @@ def run_case(self, testcase: DataDrivenTestCase) -> None:
5860
'Invalid output ({}, line {})'.format(testcase.file,
5961
testcase.line))
6062

61-
def build(self, source: str) -> Tuple[List[str], Dict[str, MypyFile]]:
63+
def build(self, source: str) -> Tuple[List[str], Optional[Dict[str, MypyFile]]]:
6264
options = Options()
6365
options.use_builtins_fixtures = True
6466
options.show_traceback = True

mypy/test/testsemanal.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ def run_test(self, testcase: DataDrivenTestCase) -> None:
201201
for f in result.files.values():
202202
for n in f.names.values():
203203
if isinstance(n.node, TypeInfo):
204+
assert n.fullname is not None
204205
typeinfos[n.fullname] = n.node
205206

206207
# The output is the symbol table converted into a string.

mypy/test/testsolve.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Test cases for the constraint solver used in type inference."""
22

3-
from typing import List, Union, Tuple
3+
from typing import List, Union, Tuple, Optional
44

55
from mypy.myunit import Suite, assert_equal
66
from mypy.constraints import SUPERTYPE_OF, SUBTYPE_OF, Constraint
@@ -114,9 +114,9 @@ def test_both_normal_and_any_types_in_results(self) -> None:
114114
def assert_solve(self,
115115
vars: List[TypeVarId],
116116
constraints: List[Constraint],
117-
results: List[Union[Type, Tuple[Type, Type]]],
117+
results: List[Union[None, Type, Tuple[Type, Type]]],
118118
) -> None:
119-
res = []
119+
res = [] # type: List[Optional[Type]]
120120
for r in results:
121121
if isinstance(r, tuple):
122122
res.append(r[0])

mypy/traverser.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,9 @@ def visit_raise_stmt(self, o: RaiseStmt) -> None:
122122
def visit_try_stmt(self, o: TryStmt) -> None:
123123
o.body.accept(self)
124124
for i in range(len(o.types)):
125-
if o.types[i]:
126-
o.types[i].accept(self)
125+
tp = o.types[i]
126+
if tp is not None:
127+
tp.accept(self)
127128
o.handlers[i].accept(self)
128129
if o.else_body is not None:
129130
o.else_body.accept(self)
@@ -133,8 +134,9 @@ def visit_try_stmt(self, o: TryStmt) -> None:
133134
def visit_with_stmt(self, o: WithStmt) -> None:
134135
for i in range(len(o.expr)):
135136
o.expr[i].accept(self)
136-
if o.target[i] is not None:
137-
o.target[i].accept(self)
137+
targ = o.target[i]
138+
if targ is not None:
139+
targ.accept(self)
138140
o.body.accept(self)
139141

140142
def visit_member_expr(self, o: MemberExpr) -> None:

0 commit comments

Comments
 (0)