From c32783b3c52cd890ea91f9bcc892b78c74ed9749 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 1 Jul 2017 10:25:21 +0200 Subject: [PATCH 1/9] Start work on allowing strict-optional --- mypy/errors.py | 2 +- mypy_strict_optional.ini | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mypy/errors.py b/mypy/errors.py index 6648784be310..d5045689d603 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -278,7 +278,7 @@ def report(self, line: int, column: int, message: str, blocker: bool = False, self.add_error_info(info) def add_error_info(self, info: ErrorInfo) -> None: - (file, line) = info.origin + (file, line) = cast(Tuple[str, int], info.origin) # see issue 1855 if not info.blocker: # Blockers cannot be ignored if file in self.ignored_lines and line in self.ignored_lines[file]: # Annotation requests us to ignore all errors on this line. diff --git a/mypy_strict_optional.ini b/mypy_strict_optional.ini index 3b7d272291cb..2e04c01d73ee 100644 --- a/mypy_strict_optional.ini +++ b/mypy_strict_optional.ini @@ -2,4 +2,10 @@ ; This allows us to make mypy strict Optional compliant over time. [mypy] strict_optional = True -ignore_errors = True + +; temporary exceptions +[mypy-mypy.fastparse] +strict_optional = False + +[mypy-mypy.fastparse2] +strict_optional = False From 874d434034d71bf18706b8ed0b1567c5fe221d3d Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 1 Jul 2017 10:29:46 +0200 Subject: [PATCH 2/9] Fix import --- mypy/errors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/errors.py b/mypy/errors.py index d5045689d603..df1d7eead79e 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -4,7 +4,7 @@ from collections import OrderedDict, defaultdict from contextlib import contextmanager -from typing import Tuple, List, TypeVar, Set, Dict, Iterator, Optional +from typing import Tuple, List, TypeVar, Set, Dict, Iterator, Optional, cast from mypy.options import Options from mypy.version import __version__ as mypy_version From 7852b96448ccc79c4f7d31920e94582ef7b31992 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 1 Jul 2017 10:42:26 +0200 Subject: [PATCH 3/9] Add more exceptions --- mypy_strict_optional.ini | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/mypy_strict_optional.ini b/mypy_strict_optional.ini index 2e04c01d73ee..a8a897bb29ce 100644 --- a/mypy_strict_optional.ini +++ b/mypy_strict_optional.ini @@ -4,8 +4,20 @@ strict_optional = True ; temporary exceptions +[mypy-mypy.build] +strict_optional = False + [mypy-mypy.fastparse] strict_optional = False [mypy-mypy.fastparse2] strict_optional = False + +[mypy-mypy.checker] +strict_optional = False + +[mypy-mypy.checkexpr] +strict_optional = False + +[mypy-mypy.semanal] +strict_optional = False From e9b47a9e7ed308e5ee2873ec7ac883a80100b3df Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 1 Jul 2017 11:34:07 +0200 Subject: [PATCH 4/9] Fix previously clean files --- mypy/checkmember.py | 2 +- mypy/exprtotype.py | 1 + mypy/nodes.py | 6 +++--- mypy/typeanal.py | 3 ++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 84c453153be3..112efcf7359b 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -212,7 +212,7 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, not_ready_callback: Callable[[str, Context], None], msg: MessageBuilder, original_type: Type, - chk: 'mypy.checker.TypeChecker' = None) -> Type: + chk: 'mypy.checker.TypeChecker') -> Type: """Analyse attribute access that does not target a method. This is logically part of analyze_member_access and the arguments are similar. diff --git a/mypy/exprtotype.py b/mypy/exprtotype.py index db46cfd0f0c9..8325f6dccbd4 100644 --- a/mypy/exprtotype.py +++ b/mypy/exprtotype.py @@ -34,6 +34,7 @@ def expr_to_unanalyzed_type(expr: Expression, _parent: Optional[Expression] = No """ # The `parent` paremeter is used in recursive calls to provide context for # understanding whether an CallableArgument is ok. + name = None # type: Optional[str] if isinstance(expr, NameExpr): name = expr.name return UnboundType(name, line=expr.line, column=expr.column) diff --git a/mypy/nodes.py b/mypy/nodes.py index 8aec3d6bba3c..37383f36c18b 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1954,7 +1954,7 @@ class is generic then it will be a type constructor of higher kind. mro = None # type: List[TypeInfo] declared_metaclass = None # type: Optional[mypy.types.Instance] - metaclass_type = None # type: mypy.types.Instance + metaclass_type = None # type: Optional[mypy.types.Instance] subtypes = None # type: Set[TypeInfo] # Direct subclasses encountered so far 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 is_kw_arg = True -def check_arg_names(names: List[str], nodes: List[T], fail: Callable[[str, T], None], +def check_arg_names(names: List[Optional[str]], nodes: List[T], fail: Callable[[str, T], None], description: str = 'function definition') -> None: - seen_names = set() # type: Set[str] + seen_names = set() # type: Set[Optional[str]] for name, node in zip(names, nodes): if name is not None and name in seen_names: fail("Duplicate argument '{}' in {}".format(name, description), node) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 0ce0dca33f8f..9d12d912e480 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -438,7 +438,7 @@ def analyze_callable_args(self, arglist: TypeList) -> Optional[Tuple[List[Type], List[Optional[str]]]]: args = [] # type: List[Type] kinds = [] # type: List[int] - names = [] # type: List[str] + names = [] # type: List[Optional[str]] for arg in arglist.items: if isinstance(arg, CallableArgument): args.append(arg.typ) @@ -454,6 +454,7 @@ def analyze_callable_args(self, arglist: TypeList) -> Optional[Tuple[List[Type], found.fullname), arg) return None else: + assert found.fullname is not None kind = ARG_KINDS_BY_CONSTRUCTOR[found.fullname] kinds.append(kind) if arg.name is not None and kind in {ARG_STAR, ARG_STAR2}: From 56a85cf972488c58c4f9ba9029bf8e227b1cf581 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 1 Jul 2017 11:57:03 +0200 Subject: [PATCH 5/9] Few more fixes, leave out tests for now --- mypy/main.py | 2 +- mypy/stats.py | 14 +++++++++----- mypy_strict_optional.ini | 3 +++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/mypy/main.py b/mypy/main.py index 08fab04741c8..3b9667488728 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -28,7 +28,7 @@ class InvalidPackageName(Exception): """Exception indicating that a package name was invalid.""" -def main(script_path: str, args: List[str] = None) -> None: +def main(script_path: Optional[str], args: List[str] = None) -> None: """Main entry point to the type checker. Args: diff --git a/mypy/stats.py b/mypy/stats.py index ba7611821481..c536a3ecd5be 100644 --- a/mypy/stats.py +++ b/mypy/stats.py @@ -103,7 +103,10 @@ def visit_assignment_stmt(self, o: AssignmentStmt) -> None: items = [lvalue] for item in items: if isinstance(item, RefExpr) and item.is_def: - t = self.typemap.get(item) + if self.typemap is not None: + t = self.typemap.get(item) + else: + t = None if t: self.type(t) else: @@ -151,10 +154,11 @@ def visit_unary_expr(self, o: UnaryExpr) -> None: def process_node(self, node: Expression) -> None: if self.all_nodes: - typ = self.typemap.get(node) - if typ: - self.line = node.line - self.type(typ) + if self.typemap is not None: + typ = self.typemap.get(node) + if typ: + self.line = node.line + self.type(typ) def type(self, t: Type) -> None: if isinstance(t, AnyType): diff --git a/mypy_strict_optional.ini b/mypy_strict_optional.ini index a8a897bb29ce..643fb976f2ee 100644 --- a/mypy_strict_optional.ini +++ b/mypy_strict_optional.ini @@ -21,3 +21,6 @@ strict_optional = False [mypy-mypy.semanal] strict_optional = False + +[mypy-mypy.test] +strict_optional = False From 1fa649fdf9aaa1ba307d758401aa9e2374dbdeb9 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 1 Jul 2017 13:40:08 +0200 Subject: [PATCH 6/9] More fixes; started working on binder and tvar_scope --- mypy/binder.py | 24 +++++++++++++----------- mypy/messages.py | 16 ++++++++++------ mypy/traverser.py | 10 ++++++---- mypy/tvar_scope.py | 2 +- mypy/typeanal.py | 4 ++-- mypy_strict_optional.ini | 3 --- 6 files changed, 32 insertions(+), 27 deletions(-) diff --git a/mypy/binder.py b/mypy/binder.py index 3ae2952a5169..8e31ebb2ea5b 100644 --- a/mypy/binder.py +++ b/mypy/binder.py @@ -1,4 +1,4 @@ -from typing import (Dict, List, Set, Iterator, Union) +from typing import Dict, List, Set, Iterator, Union, Optional, cast from contextlib import contextmanager from mypy.types import Type, AnyType, PartialType, UnionType, NoneTyp @@ -15,7 +15,7 @@ BindableExpression = Union[IndexExpr, MemberExpr, NameExpr] -class Frame(Dict[Key, Type]): +class Frame(Dict[Key, Optional[Type]]): """A Frame represents a specific point in the execution of a program. It carries information about the current types of expressions at that point, arising either from assignments to those expressions @@ -68,7 +68,7 @@ def __init__(self) -> None: # has no corresponding element in this list. self.options_on_return = [] # type: List[List[Frame]] - # Maps expr.literal_hash] to get_declaration(expr) + # Maps expr.literal_hash to get_declaration(expr) # for every expr stored in the binder self.declarations = Frame() # Set of other keys to invalidate if a key is changed, e.g. x -> {x.a, x[0]} @@ -101,7 +101,7 @@ def push_frame(self) -> Frame: def _put(self, key: Key, type: Type, index: int=-1) -> None: self.frames[index][key] = type - def _get(self, key: Key, index: int=-1) -> Type: + def _get(self, key: Key, index: int=-1) -> Optional[Type]: if index < 0: index += len(self.frames) for i in range(index, -1, -1): @@ -124,7 +124,7 @@ def put(self, expr: Expression, typ: Type) -> None: def unreachable(self) -> None: self.frames[-1].unreachable = True - def get(self, expr: Expression) -> Type: + def get(self, expr: Expression) -> Optional[Type]: return self._get(expr.literal_hash) def is_unreachable(self) -> bool: @@ -163,15 +163,17 @@ def update_from_options(self, frames: List[Frame]) -> bool: # know anything about key in at least one possible frame. continue + type = resulting_values[0] + assert type is not None if isinstance(self.declarations.get(key), AnyType): - type = resulting_values[0] - if not all(is_same_type(type, t) for t in resulting_values[1:]): + # At this point resulting values can't contain None, see continue above + if not all(is_same_type(type, cast(Type, t)) for t in resulting_values[1:]): type = AnyType() else: - type = resulting_values[0] for other in resulting_values[1:]: + assert other is not None type = join_simple(self.declarations[key], type, other) - if not is_same_type(type, current_value): + if current_value is None or not is_same_type(type, current_value): self._put(key, type) changed = True @@ -252,7 +254,7 @@ def invalidate_dependencies(self, expr: BindableExpression) -> None: for dep in self.dependencies.get(expr.literal_hash, set()): self._cleanse_key(dep) - def most_recent_enclosing_type(self, expr: BindableExpression, type: Type) -> Type: + def most_recent_enclosing_type(self, expr: BindableExpression, type: Type) -> Optional[Type]: if isinstance(type, AnyType): return get_declaration(expr) key = expr.literal_hash @@ -342,7 +344,7 @@ def top_frame_context(self) -> Iterator[Frame]: self.pop_frame(True, 0) -def get_declaration(expr: BindableExpression) -> Type: +def get_declaration(expr: BindableExpression) -> Optional[Type]: if isinstance(expr, RefExpr) and isinstance(expr.node, Var): type = expr.node.type if not isinstance(type, PartialType): diff --git a/mypy/messages.py b/mypy/messages.py index 8d53d12ce651..803b70f11b69 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -6,7 +6,7 @@ import re import difflib -from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple +from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple, Optional from mypy.erasetype import erase_type from mypy.errors import Errors @@ -591,7 +591,8 @@ def too_few_arguments(self, callee: CallableType, context: Context, else: msg = 'Missing positional arguments' if callee.name and diff and all(d is not None for d in diff): - msg += ' "{}" in call to {}'.format('", "'.join(diff), callee.name) + msg += ' "{}" in call to {}'.format('", "'.join(cast(List[str], diff)), + callee.name) else: msg = 'Too few arguments' if callee.name: @@ -625,6 +626,7 @@ def unexpected_keyword_argument(self, callee: CallableType, name: str, self.fail(msg, context) module = find_defining_module(self.modules, callee) if module: + assert callee.definition is not None self.note('{} defined here'.format(callee.name), callee.definition, file=module.path, origin=context) @@ -636,9 +638,11 @@ def duplicate_argument_value(self, callee: CallableType, index: int, def does_not_return_value(self, callee_type: Type, context: Context) -> None: """Report an error about use of an unusable type.""" - if isinstance(callee_type, FunctionLike) and callee_type.get_name() is not None: - self.fail('{} does not return a value'.format( - capitalize(callee_type.get_name())), context) + name = None # type: Optional[str] + if isinstance(callee_type, FunctionLike): + name = callee_type.get_name() + if name is not None: + self.fail('{} does not return a value'.format(capitalize(name)), context) else: self.fail('Function does not return a value', context) @@ -1011,7 +1015,7 @@ def callable_name(type: CallableType) -> str: return 'function' -def find_defining_module(modules: Dict[str, MypyFile], typ: CallableType) -> MypyFile: +def find_defining_module(modules: Dict[str, MypyFile], typ: CallableType) -> Optional[MypyFile]: if not typ.definition: return None fullname = typ.definition.fullname() diff --git a/mypy/traverser.py b/mypy/traverser.py index d748b02cbd62..495bafd1d4e8 100644 --- a/mypy/traverser.py +++ b/mypy/traverser.py @@ -122,8 +122,9 @@ def visit_raise_stmt(self, o: RaiseStmt) -> None: def visit_try_stmt(self, o: TryStmt) -> None: o.body.accept(self) for i in range(len(o.types)): - if o.types[i]: - o.types[i].accept(self) + tp = o.types[i] + if tp is not None: + tp.accept(self) o.handlers[i].accept(self) if o.else_body is not None: o.else_body.accept(self) @@ -133,8 +134,9 @@ def visit_try_stmt(self, o: TryStmt) -> None: def visit_with_stmt(self, o: WithStmt) -> None: for i in range(len(o.expr)): o.expr[i].accept(self) - if o.target[i] is not None: - o.target[i].accept(self) + targ = o.target[i] + if targ is not None: + targ.accept(self) o.body.accept(self) def visit_member_expr(self, o: MemberExpr) -> None: diff --git a/mypy/tvar_scope.py b/mypy/tvar_scope.py index 3cdb67bbf992..54150896ccb7 100644 --- a/mypy/tvar_scope.py +++ b/mypy/tvar_scope.py @@ -30,7 +30,7 @@ def __init__(self, def get_function_scope(self) -> Optional['TypeVarScope']: """Get the nearest parent that's a function scope, not a class scope""" - it = self + it = self # type: Optional[TypeVarScope] while it is not None and it.is_class_scope: it = it.parent return it diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 9d12d912e480..ddb4264e2554 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -523,8 +523,8 @@ def bind_function_type_variables(self, self.fail("Type variable '{}' is bound by an outer class".format(name), defn) self.tvar_scope.bind(name, tvar) binding = self.tvar_scope.get_binding(tvar.fullname()) - assert binding is not None - defs.append(binding) + if binding is not None: + defs.append(binding) return defs diff --git a/mypy_strict_optional.ini b/mypy_strict_optional.ini index 643fb976f2ee..a8a897bb29ce 100644 --- a/mypy_strict_optional.ini +++ b/mypy_strict_optional.ini @@ -21,6 +21,3 @@ strict_optional = False [mypy-mypy.semanal] strict_optional = False - -[mypy-mypy.test] -strict_optional = False From 7d7134004232f1133707dadb65b9791a4a07b896 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 1 Jul 2017 22:51:20 +0200 Subject: [PATCH 7/9] Finish binder, few small fixes, add exception items --- mypy/binder.py | 11 +++++++++-- mypy/join.py | 2 +- mypy/test/data.py | 7 +++++-- mypy/test/testdeps.py | 8 +++++--- mypy/test/testdiff.py | 6 ++++-- mypy/test/testsemanal.py | 1 + mypy/test/testsolve.py | 6 +++--- mypy_strict_optional.ini | 34 ++++++++++++++++++++++++++++++++-- 8 files changed, 60 insertions(+), 15 deletions(-) diff --git a/mypy/binder.py b/mypy/binder.py index 8e31ebb2ea5b..c80aa1fda5c6 100644 --- a/mypy/binder.py +++ b/mypy/binder.py @@ -15,7 +15,7 @@ BindableExpression = Union[IndexExpr, MemberExpr, NameExpr] -class Frame(Dict[Key, Optional[Type]]): +class Frame(Dict[Key, Type]): """A Frame represents a specific point in the execution of a program. It carries information about the current types of expressions at that point, arising either from assignments to those expressions @@ -31,6 +31,13 @@ def __init__(self) -> None: self.unreachable = False +class DeclarationsFrame(Dict[Key, Optional[Type]]): + """Same as above, but allowed to have None values.""" + + def __init__(self) -> None: + self.unreachable = False + + class ConditionalTypeBinder: """Keep track of conditional types of variables. @@ -70,7 +77,7 @@ def __init__(self) -> None: # Maps expr.literal_hash to get_declaration(expr) # for every expr stored in the binder - self.declarations = Frame() + self.declarations = DeclarationsFrame() # Set of other keys to invalidate if a key is changed, e.g. x -> {x.a, x[0]} # Whenever a new key (e.g. x.a.b) is added, we update this self.dependencies = {} # type: Dict[Key, Set[Key]] diff --git a/mypy/join.py b/mypy/join.py index 0ae8c3ab4058..132017e5df67 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -15,7 +15,7 @@ from mypy import experiments -def join_simple(declaration: Type, s: Type, t: Type) -> Type: +def join_simple(declaration: Optional[Type], s: Type, t: Type) -> Type: """Return a simple least upper bound given the declared type.""" if (s.can_be_true, s.can_be_false) != (t.can_be_true, t.can_be_false): diff --git a/mypy/test/data.py b/mypy/test/data.py index 09fe931d0c62..38adf1835aef 100644 --- a/mypy/test/data.py +++ b/mypy/test/data.py @@ -164,7 +164,9 @@ def parse_test_cases( for file_path, contents in files: expand_errors(contents.split('\n'), tcout, file_path) lastline = p[i].line if i < len(p) else p[i - 1].line + 9999 - tc = DataDrivenTestCase(p[i0].arg, input, tcout, tcout2, path, + arg0 = p[i0].arg + assert arg0 is not None + tc = DataDrivenTestCase(arg0, input, tcout, tcout2, path, p[i0].line, lastline, perform, files, output_files, stale_modules, rechecked_modules, deleted_paths, native_sep) @@ -200,7 +202,7 @@ def __init__(self, file: str, line: int, lastline: int, - perform: Callable[['DataDrivenTestCase'], None], + perform: Optional[Callable[['DataDrivenTestCase'], None]], files: List[Tuple[str, str]], output_files: List[Tuple[str, str]], expected_stale_modules: Dict[int, Set[str]], @@ -270,6 +272,7 @@ def run(self) -> None: if self.name.endswith('-skip'): raise SkipTestCaseException() else: + assert self.perform is not None, 'Tests without `perform` should not be `run`' self.perform(self) def tear_down(self) -> None: diff --git a/mypy/test/testdeps.py b/mypy/test/testdeps.py index bfbf1db13391..a648782428a1 100644 --- a/mypy/test/testdeps.py +++ b/mypy/test/testdeps.py @@ -1,7 +1,7 @@ """Test cases for generating node-level dependencies (for fine-grained incremental checking)""" import os -from typing import List, Tuple, Dict +from typing import List, Tuple, Dict, Optional from mypy import build from mypy.build import BuildSource @@ -35,6 +35,8 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: src = '\n'.join(testcase.input) messages, files, type_map = self.build(src) a = messages + assert files is not None and type_map is not None, ('cases where CompileError' + ' occurred should not be run') deps = get_dependencies('__main__', files['__main__'], type_map) for source, targets in sorted(deps.items()): @@ -49,8 +51,8 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: testcase.line)) def build(self, source: str) -> Tuple[List[str], - Dict[str, MypyFile], - Dict[Expression, Type]]: + Optional[Dict[str, MypyFile]], + Optional[Dict[Expression, Type]]]: options = Options() options.use_builtins_fixtures = True options.show_traceback = True diff --git a/mypy/test/testdiff.py b/mypy/test/testdiff.py index e24575b33d68..84e5389ba4a2 100644 --- a/mypy/test/testdiff.py +++ b/mypy/test/testdiff.py @@ -1,7 +1,7 @@ """Test cases for AST diff (used for fine-grained incremental checking)""" import os -from typing import List, Tuple, Dict +from typing import List, Tuple, Dict, Optional from mypy import build from mypy.build import BuildSource @@ -46,6 +46,8 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: a.append('== next ==') a.extend(messages2) + assert files1 is not None and files2 is not None, ('cases where CompileError' + ' occurred should not be run') diff = compare_symbol_tables( '__main__', files1['__main__'].names, @@ -58,7 +60,7 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: 'Invalid output ({}, line {})'.format(testcase.file, testcase.line)) - def build(self, source: str) -> Tuple[List[str], Dict[str, MypyFile]]: + def build(self, source: str) -> Tuple[List[str], Optional[Dict[str, MypyFile]]]: options = Options() options.use_builtins_fixtures = True options.show_traceback = True diff --git a/mypy/test/testsemanal.py b/mypy/test/testsemanal.py index 6d7f2ddb24bb..d339a83b040d 100644 --- a/mypy/test/testsemanal.py +++ b/mypy/test/testsemanal.py @@ -201,6 +201,7 @@ def run_test(self, testcase: DataDrivenTestCase) -> None: for f in result.files.values(): for n in f.names.values(): if isinstance(n.node, TypeInfo): + assert n.fullname is not None typeinfos[n.fullname] = n.node # The output is the symbol table converted into a string. diff --git a/mypy/test/testsolve.py b/mypy/test/testsolve.py index fcf19273ebe0..1b8dc8374664 100644 --- a/mypy/test/testsolve.py +++ b/mypy/test/testsolve.py @@ -1,6 +1,6 @@ """Test cases for the constraint solver used in type inference.""" -from typing import List, Union, Tuple +from typing import List, Union, Tuple, Optional from mypy.myunit import Suite, assert_equal from mypy.constraints import SUPERTYPE_OF, SUBTYPE_OF, Constraint @@ -114,9 +114,9 @@ def test_both_normal_and_any_types_in_results(self) -> None: def assert_solve(self, vars: List[TypeVarId], constraints: List[Constraint], - results: List[Union[Type, Tuple[Type, Type]]], + results: List[Union[None, Type, Tuple[Type, Type]]], ) -> None: - res = [] + res = [] # type: List[Optional[Type]] for r in results: if isinstance(r, tuple): res.append(r[0]) diff --git a/mypy_strict_optional.ini b/mypy_strict_optional.ini index a8a897bb29ce..f1deaacca430 100644 --- a/mypy_strict_optional.ini +++ b/mypy_strict_optional.ini @@ -7,17 +7,47 @@ strict_optional = True [mypy-mypy.build] strict_optional = False +[mypy-mypy.checker] +strict_optional = False + +[mypy-mypy.checkexpr] +strict_optional = False + [mypy-mypy.fastparse] strict_optional = False [mypy-mypy.fastparse2] strict_optional = False -[mypy-mypy.checker] +[mypy-mypy.main] strict_optional = False -[mypy-mypy.checkexpr] +[mypy-mypy.myunit] strict_optional = False [mypy-mypy.semanal] strict_optional = False + +[mypy-mypy.server.astdiff] +strict_optional = False + +[mypy-mypy.server.astmerge] +strict_optional = False + +[mypy-mypy.server.aststrip] +strict_optional = False + +[mypy-mypy.server.update] +strict_optional = False + +[mypy-mypy.test.testinfer] +strict_optional = False + +[mypy-mypy.test.testmerge] +strict_optional = False + +[mypy-mypy.test.testtypes] +strict_optional = False + +[mypy-mypy.waiter] +strict_optional = False From 28a8b42af79317956e2ada0c4df3f9d9c6e97e2f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 2 Jul 2017 00:24:19 +0200 Subject: [PATCH 8/9] Fix a nasty bug in typevar_scope vs semanal first pass --- mypy/semanal.py | 1 + mypy/tvar_scope.py | 1 + test-data/unit/semanal-classes.test | 8 ++++---- test-data/unit/semanal-types.test | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index f3615c1a1e3c..ff0f29864fc6 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1713,6 +1713,7 @@ def analyze_lvalue(self, lval: Lvalue, nested: bool = False, v.info = self.type v.is_initialized_in_class = True v.set_line(lval) + v._fullname = self.qualified_name(lval.name) lval.node = v lval.is_def = True lval.kind = MDEF diff --git a/mypy/tvar_scope.py b/mypy/tvar_scope.py index 54150896ccb7..49f83591b5ca 100644 --- a/mypy/tvar_scope.py +++ b/mypy/tvar_scope.py @@ -68,6 +68,7 @@ def bind(self, name: str, tvar_expr: TypeVarExpr) -> TypeVarDef: def get_binding(self, item: Union[str, SymbolTableNode]) -> Optional[TypeVarDef]: fullname = item.fullname if isinstance(item, SymbolTableNode) else item + assert fullname is not None if fullname in self.scope: return self.scope[fullname] elif self.parent is not None: diff --git a/test-data/unit/semanal-classes.test b/test-data/unit/semanal-classes.test index 96c6ffe32523..22beb8d16f7a 100644 --- a/test-data/unit/semanal-classes.test +++ b/test-data/unit/semanal-classes.test @@ -252,7 +252,7 @@ MypyFile:1( IntExpr(1)) AssignmentStmt:3( NameExpr(y* [m]) - NameExpr(x [m])))) + NameExpr(x [__main__.A.x])))) [case testMethodRefInClassBody] class A: @@ -291,7 +291,7 @@ MypyFile:1( IntExpr(1))) Else( AssignmentStmt:5( - NameExpr(x [m]) + NameExpr(x [__main__.A.x]) IntExpr(2)))))) [case testForStatementInClassBody] @@ -310,7 +310,7 @@ MypyFile:1( Block:2( AssignmentStmt:3( NameExpr(y* [m]) - NameExpr(x [m])))))) + NameExpr(x [__main__.A.x])))))) [case testReferenceToClassWithinFunction] def f(): @@ -550,7 +550,7 @@ MypyFile:1( Init( AssignmentStmt:4( NameExpr(x [l]) - NameExpr(X [m]))) + NameExpr(X [__main__.A.X]))) Block:4( PassStmt:4())))) diff --git a/test-data/unit/semanal-types.test b/test-data/unit/semanal-types.test index d3db29acba65..ca005163cb05 100644 --- a/test-data/unit/semanal-types.test +++ b/test-data/unit/semanal-types.test @@ -92,7 +92,7 @@ MypyFile:1( NameExpr(None [builtins.None]) builtins.int) AssignmentStmt:4( - NameExpr(x [m]) + NameExpr(x [__main__.A.x]) IntExpr(1)))) [case testFunctionSig] From eef5ada33896bb7bec851c5511c6bb3dc54b9c7a Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 3 Jul 2017 21:30:22 +0200 Subject: [PATCH 9/9] Address CR --- mypy/typeanal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index ddb4264e2554..9d12d912e480 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -523,8 +523,8 @@ def bind_function_type_variables(self, self.fail("Type variable '{}' is bound by an outer class".format(name), defn) self.tvar_scope.bind(name, tvar) binding = self.tvar_scope.get_binding(tvar.fullname()) - if binding is not None: - defs.append(binding) + assert binding is not None + defs.append(binding) return defs