From 736f034d81b7354332103c21c404bdf3ca2bfb0e Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 8 Dec 2019 18:46:55 +0000 Subject: [PATCH 1/9] Some basic implementation --- mypy/checker.py | 73 ++++++++---------------------- mypy/checkmember.py | 15 +++++- mypy/main.py | 2 + mypy/nodes.py | 52 ++++++++++++++++++++- mypy/options.py | 2 + mypy/semanal.py | 23 +++++++++- mypy/test/testcheck.py | 2 + mypy/test/testcmdline.py | 2 + mypy/test/testdeps.py | 1 + mypy/test/testdiff.py | 1 + mypy/test/testfinegrained.py | 2 + mypy/test/testmerge.py | 1 + mypy/test/testpythoneval.py | 1 + mypy/test/testtypegen.py | 1 + mypyc/test-data/commandline.test | 2 +- mypyc/test/test_run.py | 1 + mypyc/test/testutil.py | 1 + test-data/unit/check-abstract.test | 73 ++++++++++++++++++++++-------- 18 files changed, 178 insertions(+), 77 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 9a826cd41496..62a91fb21438 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -24,8 +24,8 @@ Import, ImportFrom, ImportAll, ImportBase, TypeAlias, ARG_POS, ARG_STAR, LITERAL_TYPE, MDEF, GDEF, CONTRAVARIANT, COVARIANT, INVARIANT, TypeVarExpr, AssignmentExpr, - is_final_node, - ARG_NAMED) + is_final_node, is_trivial_body, ARG_NAMED +) from mypy import nodes from mypy.literals import literal, literal_hash from mypy.typeanal import has_any_from_unimported_type, check_for_explicit_any @@ -445,7 +445,9 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: self.visit_decorator(cast(Decorator, defn.items[0])) for fdef in defn.items: assert isinstance(fdef, Decorator) - self.check_func_item(fdef.func, name=fdef.func.name) + self.check_func_item(fdef.func, + name=fdef.func.name, + allow_empty=True) if fdef.func.is_abstract: num_abstract += 1 if num_abstract not in (0, len(defn.items)): @@ -774,7 +776,8 @@ def _visit_func_def(self, defn: FuncDef) -> None: def check_func_item(self, defn: FuncItem, type_override: Optional[CallableType] = None, - name: Optional[str] = None) -> None: + name: Optional[str] = None, + allow_empty: bool = False) -> None: """Type check a function. If type_override is provided, use it as the function type. @@ -787,7 +790,7 @@ def check_func_item(self, defn: FuncItem, typ = type_override.copy_modified(line=typ.line, column=typ.column) if isinstance(typ, CallableType): with self.enter_attribute_inference_context(): - self.check_func_def(defn, typ, name) + self.check_func_def(defn, typ, name, allow_empty) else: raise RuntimeError('Not supported') @@ -804,7 +807,8 @@ def enter_attribute_inference_context(self) -> Iterator[None]: yield None self.inferred_attribute_types = old_types - def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) -> None: + def check_func_def(self, defn: FuncItem, typ: CallableType, + name: Optional[str], allow_empty: bool = False) -> None: """Type check a function definition.""" # Expand type variables with value restrictions to ordinary types. expanded = self.expand_typevars(defn, typ) @@ -956,7 +960,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) item.arguments[i].variable.type = arg_type # Type check initialization expressions. - body_is_trivial = self.is_trivial_body(defn.body) + body_is_trivial = is_trivial_body(defn.body) self.check_default_args(item, body_is_trivial) # Type check body in a new scope. @@ -973,7 +977,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) self.accept(item.body) unreachable = self.binder.is_unreachable() - if (self.options.warn_no_return and not unreachable): + if self.options.warn_no_return and not unreachable: if (defn.is_generator or is_named_instance(self.return_types[-1], 'typing.AwaitableGenerator')): return_type = self.get_generator_return_type(self.return_types[-1], @@ -984,7 +988,13 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) return_type = self.return_types[-1] return_type = get_proper_type(return_type) - if not isinstance(return_type, (NoneType, AnyType)) and not body_is_trivial: + allow_empty = allow_empty or self.options.allow_empty_bodies + if (not isinstance(return_type, (NoneType, AnyType)) and + (not body_is_trivial or + # Allow empty bodies for abstract methods, overloads, in tests and stubs. + not allow_empty + and not (isinstance(defn, FuncDef) and defn.is_abstract) + and not self.is_stub)): # Control flow fell off the end of a function that was # declared to return a non-None type and is not # entirely pass/Ellipsis/raise NotImplementedError. @@ -1097,51 +1107,6 @@ def check___new___signature(self, fdef: FuncDef, typ: CallableType) -> None: 'but must return a subtype of' ) - def is_trivial_body(self, block: Block) -> bool: - """Returns 'true' if the given body is "trivial" -- if it contains just a "pass", - "..." (ellipsis), or "raise NotImplementedError()". A trivial body may also - start with a statement containing just a string (e.g. a docstring). - - Note: functions that raise other kinds of exceptions do not count as - "trivial". We use this function to help us determine when it's ok to - relax certain checks on body, but functions that raise arbitrary exceptions - are more likely to do non-trivial work. For example: - - def halt(self, reason: str = ...) -> NoReturn: - raise MyCustomError("Fatal error: " + reason, self.line, self.context) - - A function that raises just NotImplementedError is much less likely to be - this complex. - """ - body = block.body - - # Skip a docstring - if (body and isinstance(body[0], ExpressionStmt) and - isinstance(body[0].expr, (StrExpr, UnicodeExpr))): - body = block.body[1:] - - if len(body) == 0: - # There's only a docstring (or no body at all). - return True - elif len(body) > 1: - return False - - stmt = body[0] - - if isinstance(stmt, RaiseStmt): - expr = stmt.expr - if expr is None: - return False - if isinstance(expr, CallExpr): - expr = expr.callee - - return (isinstance(expr, NameExpr) - and expr.fullname == 'builtins.NotImplementedError') - - return (isinstance(stmt, PassStmt) or - (isinstance(stmt, ExpressionStmt) and - isinstance(stmt.expr, EllipsisExpr))) - def check_reverse_op_method(self, defn: FuncItem, reverse_type: CallableType, reverse_name: str, context: Context) -> None: diff --git a/mypy/checkmember.py b/mypy/checkmember.py index c0f6ce2081f2..bcdbd0c51bcc 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -196,6 +196,16 @@ def analyze_instance_member_access(name: str, # Look up the member. First look up the method dictionary. method = info.get_method(name) if method: + if mx.is_super: + if isinstance(method, FuncDef) and method.is_abstract and method.is_trivial_body: + mx.msg.fail('Call to an abstract method with trivial body via super() is unsafe', + mx.context) + if isinstance(method, OverloadedFuncDef): + if method.impl: + impl = method.impl if isinstance(method.impl, FuncDef) else method.impl.func + if impl.is_trivial_body and impl.is_abstract: + mx.msg.fail('Call to an abstract method with trivial body via super() is unsafe', + mx.context) if method.is_property: assert isinstance(method, OverloadedFuncDef) first_item = cast(Decorator, method.items[0]) @@ -346,6 +356,9 @@ def analyze_member_var_access(name: str, if isinstance(vv, Decorator): # The associated Var node of a decorator contains the type. v = vv.var + if mx.is_super and vv.func.is_abstract and vv.func.is_trivial_body: + mx.msg.fail('Call to an abstract method with trivial body via super() is unsafe', + mx.context) if isinstance(vv, TypeInfo): # If the associated variable is a TypeInfo synthesize a Var node for @@ -565,7 +578,7 @@ def analyze_var(name: str, # * B.f: Callable[[B1], None] where B1 <: B (maybe B1 == B) # * x: Union[A1, B1] # In `x.f`, when checking `x` against A1 we assume x is compatible with A - # and similarly for B1 when checking agains B + # and similarly for B1 when checking against B dispatched_type = meet.meet_types(mx.original_type, itype) signature = freshen_function_type_vars(functype) signature = check_self_arg(signature, dispatched_type, var.is_classmethod, diff --git a/mypy/main.py b/mypy/main.py index 61f069a79950..2daf327d6239 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -699,6 +699,8 @@ def add_invertible_flag(flag: str, "the contents of SHADOW_FILE instead.") add_invertible_flag('--fast-exit', default=False, help=argparse.SUPPRESS, group=internals_group) + add_invertible_flag('--allow-empty-bodies', default=False, help=argparse.SUPPRESS, + group=internals_group) report_group = parser.add_argument_group( title='Report generation', diff --git a/mypy/nodes.py b/mypy/nodes.py index 4ee3948fedd3..580c6490ce9a 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -637,7 +637,7 @@ def is_dynamic(self) -> bool: FUNCDEF_FLAGS = FUNCITEM_FLAGS + [ - 'is_decorated', 'is_conditional', 'is_abstract', + 'is_decorated', 'is_conditional', 'is_abstract', 'is_trivial_body', ] # type: Final @@ -651,6 +651,7 @@ class FuncDef(FuncItem, SymbolNode, Statement): 'is_decorated', 'is_conditional', 'is_abstract', + 'is_trivial_body', 'original_def', ) @@ -664,6 +665,9 @@ def __init__(self, self.is_decorated = False self.is_conditional = False # Defined conditionally (within block)? self.is_abstract = False + # Is this an abstract method with trivial body? + # Such methods can't be called via super(). + self.is_trivial_body = False self.is_final = False # Original conditional definition self.original_def = None # type: Union[None, FuncDef, Var, Decorator] @@ -3203,3 +3207,49 @@ def local_definitions(names: SymbolTable, yield fullname, symnode, info if isinstance(node, TypeInfo): yield from local_definitions(node.names, fullname, node) + + +def is_trivial_body(block: Block) -> bool: + """Returns 'true' if the given body is "trivial" -- if it contains just a "pass", + "..." (ellipsis), or "raise NotImplementedError()". A trivial body may also + start with a statement containing just a string (e.g. a docstring). + + Note: functions that raise other kinds of exceptions do not count as + "trivial". We use this function to help us determine when it's ok to + relax certain checks on body, but functions that raise arbitrary exceptions + are more likely to do non-trivial work. For example: + + def halt(self, reason: str = ...) -> NoReturn: + raise MyCustomError("Fatal error: " + reason, self.line, self.context) + + A function that raises just NotImplementedError is much less likely to be + this complex. + """ + body = block.body + + # Skip a docstring + if (body and isinstance(body[0], ExpressionStmt) and + isinstance(body[0].expr, (StrExpr, UnicodeExpr))): + body = block.body[1:] + + if len(body) == 0: + # There's only a docstring (or no body at all). + return True + elif len(body) > 1: + return False + + stmt = body[0] + + if isinstance(stmt, RaiseStmt): + expr = stmt.expr + if expr is None: + return False + if isinstance(expr, CallExpr): + expr = expr.callee + + return (isinstance(expr, NameExpr) + and expr.fullname == 'builtins.NotImplementedError') + + return (isinstance(stmt, PassStmt) or + (isinstance(stmt, ExpressionStmt) and + isinstance(stmt.expr, EllipsisExpr))) diff --git a/mypy/options.py b/mypy/options.py index 38072b821d15..c5488fc7af83 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -262,6 +262,8 @@ def __init__(self) -> None: self.cache_map = {} # type: Dict[str, Tuple[str, str]] # Don't properly free objects on exit, just kill the current process. self.fast_exit = False + # Allow empty function bodies even if it is not safe, used for testing only. + self.allow_empty_bodies = False # Used to transform source code before parsing if not None # TODO: Make the type precise (AnyStr -> AnyStr) self.transform_source = None # type: Optional[Callable[[Any], Any]] diff --git a/mypy/semanal.py b/mypy/semanal.py index f1fb8ff8be93..6e32a1a25af9 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -76,6 +76,7 @@ nongen_builtins, get_member_expr_fullname, REVEAL_TYPE, REVEAL_LOCALS, is_final_node, TypedDictExpr, type_aliases_target_versions, EnumCallExpr, RUNTIME_PROTOCOL_DECOS, FakeExpression, Statement, AssignmentExpr, + is_trivial_body ) from mypy.tvar_scope import TypeVarScope from mypy.typevars import fill_typevars @@ -573,6 +574,13 @@ def analyze_func_def(self, defn: FuncDef) -> None: if isinstance(get_proper_type(defn.type.ret_type), AnyType): defn.type = defn.type.copy_modified(ret_type=NoneType()) self.prepare_method_signature(defn, self.type) + if is_trivial_body(defn.body) and not self.is_stub_file: + defn.is_trivial_body = True + if (self.type.is_protocol and + not isinstance(self.scope.function, OverloadedFuncDef)): + # Mark protocol methods with empty bodies as implicitly abstract. + # This makes explicit protocol subclassing type-safe. + defn.is_abstract = True # Analyze function signature with self.tvar_scope_frame(self.tvar_scope.method_frame()): @@ -723,6 +731,17 @@ def analyze_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: # We know this is an overload def. Infer properties and perform some checks. self.process_final_in_overload(defn) self.process_static_or_class_method_in_overload(defn) + if defn.impl: + self.process_overload_impl(defn) + + def process_overload_impl(self, defn: OverloadedFuncDef) -> None: + assert defn.impl is not None + impl = defn.impl if isinstance(defn.impl, FuncDef) else defn.impl.func + if is_trivial_body(impl.body) and self.is_class_scope() and not self.is_stub_file: + impl.is_trivial_body = True + assert self.type is not None + if not self.is_stub_file and self.type.is_protocol: + impl.is_abstract = True def analyze_overload_sigs_and_impl( self, @@ -796,13 +815,15 @@ def handle_missing_overload_implementation(self, defn: OverloadedFuncDef) -> Non """Generate error about missing overload implementation (only if needed).""" if not self.is_stub_file: if self.type and self.type.is_protocol and not self.is_func_scope(): - # An overloded protocol method doesn't need an implementation. + # An overloaded protocol method doesn't need an implementation, + # but if it doesn't have one, then it is considered implicitly abstract. for item in defn.items: if isinstance(item, Decorator): item.func.is_abstract = True else: item.is_abstract = True else: + # TODO: also allow omitting an implementation for abstract methods in normal ABCs? self.fail( "An overloaded function outside a stub file must have an implementation", defn) diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 2747d1c034d1..fcfc39ca27ae 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -169,6 +169,8 @@ def run_case_once(self, testcase: DataDrivenTestCase, options.show_column_numbers = True if 'errorcodes' in testcase.file: options.show_error_codes = True + if 'abstract' not in testcase.file: + options.allow_empty_bodies = True if incremental_step and options.incremental: # Don't overwrite # flags: --no-incremental in incremental test cases diff --git a/mypy/test/testcmdline.py b/mypy/test/testcmdline.py index dbefd2893b57..5a18ce370552 100644 --- a/mypy/test/testcmdline.py +++ b/mypy/test/testcmdline.py @@ -47,6 +47,8 @@ def test_python_cmdline(testcase: DataDrivenTestCase, step: int) -> None: args = parse_args(testcase.input[0]) args.append('--show-traceback') args.append('--no-site-packages') + if '--disallow-empty-bodies' not in args: + args.append('--allow-empty-bodies') if '--error-summary' not in args: args.append('--no-error-summary') # Type check the program. diff --git a/mypy/test/testdeps.py b/mypy/test/testdeps.py index 3b1cddf00756..aa36642afcf5 100644 --- a/mypy/test/testdeps.py +++ b/mypy/test/testdeps.py @@ -46,6 +46,7 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: options.python_version = python_version options.export_types = True options.preserve_asts = True + options.allow_empty_bodies = True messages, files, type_map = self.build(src, options) a = messages if files is None or type_map is None: diff --git a/mypy/test/testdiff.py b/mypy/test/testdiff.py index d4617c299b86..2f5595e0034d 100644 --- a/mypy/test/testdiff.py +++ b/mypy/test/testdiff.py @@ -54,6 +54,7 @@ def build(self, source: str, options.show_traceback = True options.cache_dir = os.devnull options.python_version = PYTHON3_VERSION + options.allow_empty_bodies = True try: result = build.build(sources=[BuildSource('main', None, source)], options=options, diff --git a/mypy/test/testfinegrained.py b/mypy/test/testfinegrained.py index 8939e5ff9fa2..036037763026 100644 --- a/mypy/test/testfinegrained.py +++ b/mypy/test/testfinegrained.py @@ -188,6 +188,8 @@ def get_options(self, options.use_fine_grained_cache = self.use_cache and not build_cache options.cache_fine_grained = self.use_cache options.local_partial_types = True + # Treat empty bodies safely for these test cases. + options.allow_empty_bodies = not testcase.name.endswith('_no_empty') if options.follow_imports == 'normal': options.follow_imports = 'error' diff --git a/mypy/test/testmerge.py b/mypy/test/testmerge.py index c9f04c2abef6..a2cc39ca4dba 100644 --- a/mypy/test/testmerge.py +++ b/mypy/test/testmerge.py @@ -109,6 +109,7 @@ def build(self, source: str, testcase: DataDrivenTestCase) -> Optional[BuildResu options.export_types = True options.show_traceback = True options.python_version = PYTHON3_VERSION + options.allow_empty_bodies = True main_path = os.path.join(test_temp_dir, 'main') with open(main_path, 'w', encoding='utf8') as f: f.write(source) diff --git a/mypy/test/testpythoneval.py b/mypy/test/testpythoneval.py index 298269b9a71b..9fec3e16195b 100644 --- a/mypy/test/testpythoneval.py +++ b/mypy/test/testpythoneval.py @@ -58,6 +58,7 @@ def test_python_evaluation(testcase: DataDrivenTestCase, cache_dir: str) -> None '--no-strict-optional', '--no-silence-site-packages', '--no-error-summary', + '--allow-empty-bodies', ] py2 = testcase.name.lower().endswith('python2') if py2: diff --git a/mypy/test/testtypegen.py b/mypy/test/testtypegen.py index a10035a8eab5..f6d03813120e 100644 --- a/mypy/test/testtypegen.py +++ b/mypy/test/testtypegen.py @@ -32,6 +32,7 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: options.show_traceback = True options.export_types = True options.preserve_asts = True + options.allow_empty_bodies = True result = build.build(sources=[BuildSource('main', None, src)], options=options, alt_lib_path=test_temp_dir) diff --git a/mypyc/test-data/commandline.test b/mypyc/test-data/commandline.test index b77c3dd9ffd5..5e02b416af29 100644 --- a/mypyc/test-data/commandline.test +++ b/mypyc/test-data/commandline.test @@ -163,7 +163,7 @@ class Nope(Trait1, Concrete2): # E: Non-trait bases must appear first in parent class NonExt2: @property # E: Property setters not supported in non-extension classes def test(self) -> int: - pass + return 0 @test.setter def test(self, x: int) -> None: diff --git a/mypyc/test/test_run.py b/mypyc/test/test_run.py index a0d4812296ae..c6a18f875e4e 100644 --- a/mypyc/test/test_run.py +++ b/mypyc/test/test_run.py @@ -167,6 +167,7 @@ def run_case_step(self, testcase: DataDrivenTestCase, incremental_step: int) -> options.python_version = max(sys.version_info[:2], (3, 6)) options.export_types = True options.preserve_asts = True + options.allow_empty_bodies = True options.incremental = self.separate # Avoid checking modules/packages named 'unchecked', to provide a way diff --git a/mypyc/test/testutil.py b/mypyc/test/testutil.py index 4bc5cbe6a04d..e2f3d134978e 100644 --- a/mypyc/test/testutil.py +++ b/mypyc/test/testutil.py @@ -93,6 +93,7 @@ def build_ir_for_single_file(input_lines: List[str], options.python_version = (3, 6) options.export_types = True options.preserve_asts = True + options.allow_empty_bodies = True options.per_module_options['__main__'] = {'mypyc': True} source = build.BuildSource('main', '__main__', program_text) diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test index 1fcbf8bf9f4e..a1e5f12a56ad 100644 --- a/test-data/unit/check-abstract.test +++ b/test-data/unit/check-abstract.test @@ -309,8 +309,8 @@ class A(metaclass=ABCMeta): class B(A): def f(self, x: str) -> int: \ # E: Argument 1 of "f" is incompatible with supertype "A"; supertype defines the argument type as "int" - pass - def g(self, x: int) -> int: pass + return 0 + def g(self, x: int) -> int: return 0 [out] [case testImplementingAbstractMethodWithMultipleBaseClasses] @@ -323,11 +323,11 @@ class J(metaclass=ABCMeta): @abstractmethod def g(self, x: str) -> str: pass class A(I, J): - def f(self, x: str) -> int: pass \ + def f(self, x: str) -> int: return 0 \ # E: Argument 1 of "f" is incompatible with supertype "I"; supertype defines the argument type as "int" - def g(self, x: str) -> int: pass \ + def g(self, x: str) -> int: return 0 \ # E: Return type "int" of "g" incompatible with return type "str" in supertype "J" - def h(self) -> int: pass # Not related to any base class + def h(self) -> int: return 0 # Not related to any base class [out] [case testImplementingAbstractMethodWithExtension] @@ -338,7 +338,7 @@ class J(metaclass=ABCMeta): def f(self, x: int) -> int: pass class I(J): pass class A(I): - def f(self, x: str) -> int: pass \ + def f(self, x: str) -> int: return 0 \ # E: Argument 1 of "f" is incompatible with supertype "J"; supertype defines the argument type as "int" [out] @@ -365,11 +365,11 @@ class I(metaclass=ABCMeta): def h(self, a: 'I') -> A: pass class A(I): def h(self, a: 'A') -> 'I': # Fail - pass + return A() def f(self, a: 'I') -> 'I': - pass + return A() def g(self, a: 'A') -> 'A': - pass + return A() [out] main:11: error: Argument 1 of "h" is incompatible with supertype "I"; supertype defines the argument type as "I" main:11: error: Return type "I" of "h" incompatible with return type "A" in supertype "I" @@ -653,7 +653,7 @@ class A(metaclass=ABCMeta): def __gt__(self, other: 'A') -> int: pass [case testAbstractOperatorMethods2] -import typing +from typing import cast, Any from abc import abstractmethod, ABCMeta class A(metaclass=ABCMeta): @abstractmethod @@ -662,7 +662,8 @@ class B: @abstractmethod def __add__(self, other: 'A') -> int: pass class C: - def __add__(self, other: int) -> B: pass + def __add__(self, other: int) -> B: + return cast(Any, None) [out] [case testAbstractClassWithAnyBase] @@ -742,7 +743,7 @@ class A(metaclass=ABCMeta): def x(self) -> int: pass class B(A): @property - def x(self) -> int: pass + def x(self) -> int: return 0 b = B() b.x() # E: "int" not callable [builtins fixtures/property.pyi] @@ -756,7 +757,7 @@ class A(metaclass=ABCMeta): def x(self, v: int) -> None: pass class B(A): @property - def x(self) -> int: pass + def x(self) -> int: return 0 @x.setter def x(self, v: int) -> None: pass b = B() @@ -770,7 +771,7 @@ class A(metaclass=ABCMeta): def x(self) -> int: pass class B(A): @property - def x(self) -> str: pass # E: Return type "str" of "x" incompatible with return type "int" in supertype "A" + def x(self) -> str: return 'yes' # E: Return type "str" of "x" incompatible with return type "int" in supertype "A" b = B() b.x() # E: "str" not callable [builtins fixtures/property.pyi] @@ -795,7 +796,7 @@ main:9: error: "int" has no attribute "y" from abc import abstractproperty, ABCMeta class A(metaclass=ABCMeta): @abstractproperty - def x(self) -> int: pass + def x(self) -> int: return 0 class B(A): @property def x(self) -> int: @@ -831,7 +832,7 @@ class A(metaclass=ABCMeta): def x(self, v: int) -> None: pass class B(A): @property # E - def x(self) -> int: pass + def x(self) -> int: return 0 b = B() b.x.y # E [builtins fixtures/property.pyi] @@ -887,7 +888,7 @@ class C(Mixin, A): class A: @property def foo(cls) -> str: - pass + return 'yes' class Mixin: foo = "foo" class C(Mixin, A): @@ -903,7 +904,7 @@ class Y(X): class A: @property def foo(cls) -> X: - pass + return X() class Mixin: foo = Y() class C(Mixin, A): @@ -915,7 +916,7 @@ class C(Mixin, A): class A: @property def foo(cls) -> str: - pass + return 'yes' class Mixin: foo = "foo" class C(A, Mixin): # E: Definition of "foo" in base class "A" is incompatible with definition in base class "Mixin" @@ -999,3 +1000,37 @@ d = my_abstract_types['B']() # E: Cannot instantiate abstract class 'MyAbstract d.do() [builtins fixtures/dict.pyi] + + +-- Treatment of empty bodies in ABCs and protocols +-- ----------------------------------------------- + +[case testEmptyBodyProhibitedFunction] +from typing import overload, Union + +def func1(x: str) -> int: pass # E: Missing return statement +def func2(x: str) -> int: ... # E: Missing return statement +def func3(x: str) -> int: # E: Missing return statement + """Some function.""" + +@overload +def func4(x: int) -> int: ... +@overload +def func4(x: str) -> str: ... +def func4(x: Union[int, str]) -> Union[int, str]: # E: Missing return statement + pass + +@overload +def func5(x: int) -> int: ... +@overload +def func5(x: str) -> str: ... +def func5(x: Union[int, str]) -> Union[int, str]: # E: Missing return statement + """Some function.""" + +[case testEmptyBodyProhibitedMethod] + +[builtins fixtures/classmethod.pyi] + +[case testEmptyBodyProhibitedProperty] + +[builtins fixtures/property.pyi] From ba1a94ff780c54d88c9829c62cbf148ac16fc5f9 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 8 Dec 2019 22:26:08 +0000 Subject: [PATCH 2/9] Add some tests --- mypy/checkmember.py | 9 +- mypy/errorcodes.py | 3 + mypy/messages.py | 4 + mypy/semanal.py | 29 +- mypy/semanal_classprop.py | 6 +- mypy/server/astdiff.py | 8 +- mypy/test/testcheck.py | 2 +- mypy/visitor.py | 155 ++++----- test-data/unit/check-abstract.test | 479 ++++++++++++++++++++++++++- test-data/unit/check-errorcodes.test | 12 + test-data/unit/diff.test | 13 + test-data/unit/fine-grained.test | 22 ++ 12 files changed, 645 insertions(+), 97 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index bcdbd0c51bcc..3340bd342063 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -198,14 +198,12 @@ def analyze_instance_member_access(name: str, if method: if mx.is_super: if isinstance(method, FuncDef) and method.is_abstract and method.is_trivial_body: - mx.msg.fail('Call to an abstract method with trivial body via super() is unsafe', - mx.context) + mx.msg.unsafe_super(method.name, method.info.name, mx.context) if isinstance(method, OverloadedFuncDef): if method.impl: impl = method.impl if isinstance(method.impl, FuncDef) else method.impl.func if impl.is_trivial_body and impl.is_abstract: - mx.msg.fail('Call to an abstract method with trivial body via super() is unsafe', - mx.context) + mx.msg.unsafe_super(method.name, method.info.name, mx.context) if method.is_property: assert isinstance(method, OverloadedFuncDef) first_item = cast(Decorator, method.items[0]) @@ -357,8 +355,7 @@ def analyze_member_var_access(name: str, # The associated Var node of a decorator contains the type. v = vv.var if mx.is_super and vv.func.is_abstract and vv.func.is_trivial_body: - mx.msg.fail('Call to an abstract method with trivial body via super() is unsafe', - mx.context) + mx.msg.unsafe_super(vv.func.name, vv.func.info.name, mx.context) if isinstance(vv, TypeInfo): # If the associated variable is a TypeInfo synthesize a Var node for diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index 7749db5e8008..458760287dd6 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -85,6 +85,9 @@ def __str__(self) -> str: 'General') # type: Final EXIT_RETURN = ErrorCode( 'exit-return', "Warn about too general return type for '__exit__'", 'General') # type: Final +SAFE_SUPER = ErrorCode( + 'safe-super', "Warn about calls to abstract methods with empty/trivial bodies", + 'General') # type: Final # These error codes aren't enabled by default. NO_UNTYPED_DEF = ErrorCode( diff --git a/mypy/messages.py b/mypy/messages.py index 2c4ebdadedab..37de6276564f 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -818,6 +818,10 @@ def first_argument_for_super_must_be_type(self, actual: Type, context: Context) type_str = format_type(actual) self.fail('Argument 1 for "super" must be a type object; got {}'.format(type_str), context) + def unsafe_super(self, method: str, cls: str, ctx: Context) -> None: + self.fail('Call to abstract method "{}" of "{}" with trivial body' + ' via super() is unsafe'.format(method, cls), ctx, code=codes.SAFE_SUPER) + def too_few_string_formatting_arguments(self, context: Context) -> None: self.fail('Not enough arguments for format string', context, code=codes.STRING_FORMATTING) diff --git a/mypy/semanal.py b/mypy/semanal.py index 6e32a1a25af9..f140ef623f8c 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -574,13 +574,6 @@ def analyze_func_def(self, defn: FuncDef) -> None: if isinstance(get_proper_type(defn.type.ret_type), AnyType): defn.type = defn.type.copy_modified(ret_type=NoneType()) self.prepare_method_signature(defn, self.type) - if is_trivial_body(defn.body) and not self.is_stub_file: - defn.is_trivial_body = True - if (self.type.is_protocol and - not isinstance(self.scope.function, OverloadedFuncDef)): - # Mark protocol methods with empty bodies as implicitly abstract. - # This makes explicit protocol subclassing type-safe. - defn.is_abstract = True # Analyze function signature with self.tvar_scope_frame(self.tvar_scope.method_frame()): @@ -606,6 +599,19 @@ def analyze_func_def(self, defn: FuncDef) -> None: self.analyze_arg_initializers(defn) self.analyze_function_body(defn) + + # Check and set trivial body flags. + if self.is_class_scope(): + assert self.type is not None + if is_trivial_body(defn.body) and not self.is_stub_file: + defn.is_trivial_body = True + if (self.type.is_protocol and + (not isinstance(self.scope.function, OverloadedFuncDef) + or defn.is_property)): + # Mark protocol methods with empty bodies as implicitly abstract. + # This makes explicit protocol subclassing type-safe. + defn.is_abstract = True + if defn.is_coroutine and isinstance(defn.type, CallableType) and not self.deferred: if defn.is_async_generator: # Async generator types are handled elsewhere @@ -889,6 +895,15 @@ def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) - deleted_items = [] for i, item in enumerate(items[1:]): if isinstance(item, Decorator): + # TODO: avoid code duplication with visit_decorator(). + removed = [] + for i, dec in enumerate(item.decorators): + dec.accept(self) + if refers_to_fullname(dec, 'abc.abstractmethod'): + item.func.is_abstract = True + removed.append(i) + for i in reversed(removed): + del item.decorators[i] if len(item.decorators) == 1: node = item.decorators[0] if isinstance(node, MemberExpr): diff --git a/mypy/semanal_classprop.py b/mypy/semanal_classprop.py index 7052a1197d04..6c76bf02b83d 100644 --- a/mypy/semanal_classprop.py +++ b/mypy/semanal_classprop.py @@ -8,6 +8,7 @@ from mypy.nodes import ( Node, TypeInfo, Var, Decorator, OverloadedFuncDef, SymbolTable, CallExpr, PromoteExpr, + FuncDef ) from mypy.types import Instance, Type from mypy.errors import Errors @@ -79,8 +80,9 @@ def calculate_class_abstract_status(typ: TypeInfo, is_stub_file: bool, errors: E else: func = node if isinstance(func, Decorator): - fdef = func.func - if fdef.is_abstract and name not in concrete: + func = func.func + if isinstance(func, FuncDef): + if func.is_abstract and name not in concrete: typ.is_abstract = True abstract.append(name) if base is typ: diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index 9893092882b5..accd9015b021 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -54,7 +54,7 @@ class level -- these are handled at attribute level (say, 'mod.Cls.method' from mypy.nodes import ( SymbolTable, TypeInfo, Var, SymbolNode, Decorator, TypeVarExpr, TypeAlias, - FuncBase, OverloadedFuncDef, FuncItem, MypyFile, UNBOUND_IMPORTED + FuncBase, OverloadedFuncDef, FuncItem, MypyFile, UNBOUND_IMPORTED, FuncDef ) from mypy.types import ( Type, TypeVisitor, UnboundType, AnyType, NoneType, UninhabitedType, @@ -174,10 +174,14 @@ def snapshot_definition(node: Optional[SymbolNode], signature = snapshot_type(node.type) else: signature = snapshot_untyped_signature(node) + if isinstance(node, FuncDef): + is_trivial_body = node.is_trivial_body + else: + is_trivial_body = False return ('Func', common, node.is_property, node.is_final, node.is_class, node.is_static, - signature) + is_trivial_body, signature) elif isinstance(node, Var): return ('Var', common, snapshot_optional_type(node.type), diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index fcfc39ca27ae..52492ea879f1 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -170,7 +170,7 @@ def run_case_once(self, testcase: DataDrivenTestCase, if 'errorcodes' in testcase.file: options.show_error_codes = True if 'abstract' not in testcase.file: - options.allow_empty_bodies = True + options.allow_empty_bodies = not testcase.name.endswith('_no_empty') if incremental_step and options.incremental: # Don't overwrite # flags: --no-incremental in incremental test cases diff --git a/mypy/visitor.py b/mypy/visitor.py index d692142e6bcc..d4b34dd2e5aa 100644 --- a/mypy/visitor.py +++ b/mypy/visitor.py @@ -1,7 +1,7 @@ """Generic abstract syntax tree node visitor""" from abc import abstractmethod -from typing import TypeVar, Generic +from typing import TypeVar, Generic, Optional from typing_extensions import TYPE_CHECKING from mypy_extensions import trait @@ -16,15 +16,15 @@ @trait class ExpressionVisitor(Generic[T]): @abstractmethod - def visit_int_expr(self, o: 'mypy.nodes.IntExpr') -> T: + def visit_int_expr(self, o: 'mypy.nodes.IntExpr') -> Optional[T]: pass @abstractmethod - def visit_str_expr(self, o: 'mypy.nodes.StrExpr') -> T: + def visit_str_expr(self, o: 'mypy.nodes.StrExpr') -> Optional[T]: pass @abstractmethod - def visit_bytes_expr(self, o: 'mypy.nodes.BytesExpr') -> T: + def visit_bytes_expr(self, o: 'mypy.nodes.BytesExpr') -> Optional[T]: pass @abstractmethod @@ -318,7 +318,7 @@ class NodeVisitor(Generic[T], ExpressionVisitor[T], StatementVisitor[T]): # Not in superclasses: - def visit_mypy_file(self, o: 'mypy.nodes.MypyFile') -> T: + def visit_mypy_file(self, o: 'mypy.nodes.MypyFile') -> Optional[T]: pass # TODO: We have a visit_var method, but no visit_typeinfo or any @@ -329,226 +329,227 @@ def visit_var(self, o: 'mypy.nodes.Var') -> T: # Module structure - def visit_import(self, o: 'mypy.nodes.Import') -> T: + def visit_import(self, o: 'mypy.nodes.Import') -> Optional[T]: pass - def visit_import_from(self, o: 'mypy.nodes.ImportFrom') -> T: + def visit_import_from(self, o: 'mypy.nodes.ImportFrom') -> Optional[T]: pass - def visit_import_all(self, o: 'mypy.nodes.ImportAll') -> T: + def visit_import_all(self, o: 'mypy.nodes.ImportAll') -> Optional[T]: pass # Definitions - def visit_func_def(self, o: 'mypy.nodes.FuncDef') -> T: + def visit_func_def(self, o: 'mypy.nodes.FuncDef') -> Optional[T]: pass def visit_overloaded_func_def(self, - o: 'mypy.nodes.OverloadedFuncDef') -> T: + o: 'mypy.nodes.OverloadedFuncDef') -> Optional[T]: pass - def visit_class_def(self, o: 'mypy.nodes.ClassDef') -> T: + def visit_class_def(self, o: 'mypy.nodes.ClassDef') -> Optional[T]: pass - def visit_global_decl(self, o: 'mypy.nodes.GlobalDecl') -> T: + def visit_global_decl(self, o: 'mypy.nodes.GlobalDecl') -> Optional[T]: pass - def visit_nonlocal_decl(self, o: 'mypy.nodes.NonlocalDecl') -> T: + def visit_nonlocal_decl(self, o: 'mypy.nodes.NonlocalDecl') -> Optional[T]: pass - def visit_decorator(self, o: 'mypy.nodes.Decorator') -> T: + def visit_decorator(self, o: 'mypy.nodes.Decorator') -> Optional[T]: pass - def visit_type_alias(self, o: 'mypy.nodes.TypeAlias') -> T: + def visit_type_alias(self, o: 'mypy.nodes.TypeAlias') -> Optional[T]: pass - def visit_placeholder_node(self, o: 'mypy.nodes.PlaceholderNode') -> T: + def visit_placeholder_node(self, o: 'mypy.nodes.PlaceholderNode') -> Optional[T]: pass # Statements - def visit_block(self, o: 'mypy.nodes.Block') -> T: + def visit_block(self, o: 'mypy.nodes.Block') -> Optional[T]: pass - def visit_expression_stmt(self, o: 'mypy.nodes.ExpressionStmt') -> T: + def visit_expression_stmt(self, o: 'mypy.nodes.ExpressionStmt') -> Optional[T]: pass - def visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt') -> T: + def visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt') -> Optional[T]: pass def visit_operator_assignment_stmt(self, - o: 'mypy.nodes.OperatorAssignmentStmt') -> T: + o: 'mypy.nodes.OperatorAssignmentStmt') -> Optional[T]: pass - def visit_while_stmt(self, o: 'mypy.nodes.WhileStmt') -> T: + def visit_while_stmt(self, o: 'mypy.nodes.WhileStmt') -> Optional[T]: pass - def visit_for_stmt(self, o: 'mypy.nodes.ForStmt') -> T: + def visit_for_stmt(self, o: 'mypy.nodes.ForStmt') -> Optional[T]: pass - def visit_return_stmt(self, o: 'mypy.nodes.ReturnStmt') -> T: + def visit_return_stmt(self, o: 'mypy.nodes.ReturnStmt') -> Optional[T]: pass - def visit_assert_stmt(self, o: 'mypy.nodes.AssertStmt') -> T: + def visit_assert_stmt(self, o: 'mypy.nodes.AssertStmt') -> Optional[T]: pass - def visit_del_stmt(self, o: 'mypy.nodes.DelStmt') -> T: + def visit_del_stmt(self, o: 'mypy.nodes.DelStmt') -> Optional[T]: pass - def visit_if_stmt(self, o: 'mypy.nodes.IfStmt') -> T: + def visit_if_stmt(self, o: 'mypy.nodes.IfStmt') -> Optional[T]: pass - def visit_break_stmt(self, o: 'mypy.nodes.BreakStmt') -> T: + def visit_break_stmt(self, o: 'mypy.nodes.BreakStmt') -> Optional[T]: pass - def visit_continue_stmt(self, o: 'mypy.nodes.ContinueStmt') -> T: + def visit_continue_stmt(self, o: 'mypy.nodes.ContinueStmt') -> Optional[T]: pass - def visit_pass_stmt(self, o: 'mypy.nodes.PassStmt') -> T: + def visit_pass_stmt(self, o: 'mypy.nodes.PassStmt') -> Optional[T]: pass - def visit_raise_stmt(self, o: 'mypy.nodes.RaiseStmt') -> T: + def visit_raise_stmt(self, o: 'mypy.nodes.RaiseStmt') -> Optional[T]: pass - def visit_try_stmt(self, o: 'mypy.nodes.TryStmt') -> T: + def visit_try_stmt(self, o: 'mypy.nodes.TryStmt') -> Optional[T]: pass - def visit_with_stmt(self, o: 'mypy.nodes.WithStmt') -> T: + def visit_with_stmt(self, o: 'mypy.nodes.WithStmt') -> Optional[T]: pass - def visit_print_stmt(self, o: 'mypy.nodes.PrintStmt') -> T: + def visit_print_stmt(self, o: 'mypy.nodes.PrintStmt') -> Optional[T]: pass - def visit_exec_stmt(self, o: 'mypy.nodes.ExecStmt') -> T: + def visit_exec_stmt(self, o: 'mypy.nodes.ExecStmt') -> Optional[T]: pass # Expressions (default no-op implementation) - def visit_int_expr(self, o: 'mypy.nodes.IntExpr') -> T: + def visit_int_expr(self, o: 'mypy.nodes.IntExpr') -> Optional[T]: pass - def visit_str_expr(self, o: 'mypy.nodes.StrExpr') -> T: + def visit_str_expr(self, o: 'mypy.nodes.StrExpr') -> Optional[T]: pass - def visit_bytes_expr(self, o: 'mypy.nodes.BytesExpr') -> T: + def visit_bytes_expr(self, o: 'mypy.nodes.BytesExpr') -> Optional[T]: pass - def visit_unicode_expr(self, o: 'mypy.nodes.UnicodeExpr') -> T: + def visit_unicode_expr(self, o: 'mypy.nodes.UnicodeExpr') -> Optional[T]: pass - def visit_float_expr(self, o: 'mypy.nodes.FloatExpr') -> T: + def visit_float_expr(self, o: 'mypy.nodes.FloatExpr') -> Optional[T]: pass - def visit_complex_expr(self, o: 'mypy.nodes.ComplexExpr') -> T: + def visit_complex_expr(self, o: 'mypy.nodes.ComplexExpr') -> Optional[T]: pass - def visit_ellipsis(self, o: 'mypy.nodes.EllipsisExpr') -> T: + def visit_ellipsis(self, o: 'mypy.nodes.EllipsisExpr') -> Optional[T]: pass - def visit_star_expr(self, o: 'mypy.nodes.StarExpr') -> T: + def visit_star_expr(self, o: 'mypy.nodes.StarExpr') -> Optional[T]: pass - def visit_name_expr(self, o: 'mypy.nodes.NameExpr') -> T: + def visit_name_expr(self, o: 'mypy.nodes.NameExpr') -> Optional[T]: pass - def visit_member_expr(self, o: 'mypy.nodes.MemberExpr') -> T: + def visit_member_expr(self, o: 'mypy.nodes.MemberExpr') -> Optional[T]: pass - def visit_yield_from_expr(self, o: 'mypy.nodes.YieldFromExpr') -> T: + def visit_yield_from_expr(self, o: 'mypy.nodes.YieldFromExpr') -> Optional[T]: pass - def visit_yield_expr(self, o: 'mypy.nodes.YieldExpr') -> T: + def visit_yield_expr(self, o: 'mypy.nodes.YieldExpr') -> Optional[T]: pass - def visit_call_expr(self, o: 'mypy.nodes.CallExpr') -> T: + def visit_call_expr(self, o: 'mypy.nodes.CallExpr') -> Optional[T]: pass - def visit_op_expr(self, o: 'mypy.nodes.OpExpr') -> T: + def visit_op_expr(self, o: 'mypy.nodes.OpExpr') -> Optional[T]: pass - def visit_comparison_expr(self, o: 'mypy.nodes.ComparisonExpr') -> T: + def visit_comparison_expr(self, o: 'mypy.nodes.ComparisonExpr') -> Optional[T]: pass - def visit_cast_expr(self, o: 'mypy.nodes.CastExpr') -> T: + def visit_cast_expr(self, o: 'mypy.nodes.CastExpr') -> Optional[T]: pass - def visit_reveal_expr(self, o: 'mypy.nodes.RevealExpr') -> T: + def visit_reveal_expr(self, o: 'mypy.nodes.RevealExpr') -> Optional[T]: pass - def visit_super_expr(self, o: 'mypy.nodes.SuperExpr') -> T: + def visit_super_expr(self, o: 'mypy.nodes.SuperExpr') -> Optional[T]: pass - def visit_assignment_expr(self, o: 'mypy.nodes.AssignmentExpr') -> T: + def visit_assignment_expr(self, o: 'mypy.nodes.AssignmentExpr') -> Optional[T]: pass - def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> T: + def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> Optional[T]: pass - def visit_list_expr(self, o: 'mypy.nodes.ListExpr') -> T: + def visit_list_expr(self, o: 'mypy.nodes.ListExpr') -> Optional[T]: pass - def visit_dict_expr(self, o: 'mypy.nodes.DictExpr') -> T: + def visit_dict_expr(self, o: 'mypy.nodes.DictExpr') -> Optional[T]: pass - def visit_tuple_expr(self, o: 'mypy.nodes.TupleExpr') -> T: + def visit_tuple_expr(self, o: 'mypy.nodes.TupleExpr') -> Optional[T]: pass - def visit_set_expr(self, o: 'mypy.nodes.SetExpr') -> T: + def visit_set_expr(self, o: 'mypy.nodes.SetExpr') -> Optional[T]: pass - def visit_index_expr(self, o: 'mypy.nodes.IndexExpr') -> T: + def visit_index_expr(self, o: 'mypy.nodes.IndexExpr') -> Optional[T]: pass - def visit_type_application(self, o: 'mypy.nodes.TypeApplication') -> T: + def visit_type_application(self, o: 'mypy.nodes.TypeApplication') -> Optional[T]: pass - def visit_lambda_expr(self, o: 'mypy.nodes.LambdaExpr') -> T: + def visit_lambda_expr(self, o: 'mypy.nodes.LambdaExpr') -> Optional[T]: pass - def visit_list_comprehension(self, o: 'mypy.nodes.ListComprehension') -> T: + def visit_list_comprehension(self, o: 'mypy.nodes.ListComprehension') -> Optional[T]: pass - def visit_set_comprehension(self, o: 'mypy.nodes.SetComprehension') -> T: + def visit_set_comprehension(self, o: 'mypy.nodes.SetComprehension') -> Optional[T]: pass - def visit_dictionary_comprehension(self, o: 'mypy.nodes.DictionaryComprehension') -> T: + def visit_dictionary_comprehension(self, o: 'mypy.nodes.DictionaryComprehension' + ) -> Optional[T]: pass - def visit_generator_expr(self, o: 'mypy.nodes.GeneratorExpr') -> T: + def visit_generator_expr(self, o: 'mypy.nodes.GeneratorExpr') -> Optional[T]: pass - def visit_slice_expr(self, o: 'mypy.nodes.SliceExpr') -> T: + def visit_slice_expr(self, o: 'mypy.nodes.SliceExpr') -> Optional[T]: pass - def visit_conditional_expr(self, o: 'mypy.nodes.ConditionalExpr') -> T: + def visit_conditional_expr(self, o: 'mypy.nodes.ConditionalExpr') -> Optional[T]: pass - def visit_backquote_expr(self, o: 'mypy.nodes.BackquoteExpr') -> T: + def visit_backquote_expr(self, o: 'mypy.nodes.BackquoteExpr') -> Optional[T]: pass - def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> T: + def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> Optional[T]: pass - def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> T: + def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> Optional[T]: pass - def visit_namedtuple_expr(self, o: 'mypy.nodes.NamedTupleExpr') -> T: + def visit_namedtuple_expr(self, o: 'mypy.nodes.NamedTupleExpr') -> Optional[T]: pass - def visit_enum_call_expr(self, o: 'mypy.nodes.EnumCallExpr') -> T: + def visit_enum_call_expr(self, o: 'mypy.nodes.EnumCallExpr') -> Optional[T]: pass - def visit_typeddict_expr(self, o: 'mypy.nodes.TypedDictExpr') -> T: + def visit_typeddict_expr(self, o: 'mypy.nodes.TypedDictExpr') -> Optional[T]: pass - def visit_newtype_expr(self, o: 'mypy.nodes.NewTypeExpr') -> T: + def visit_newtype_expr(self, o: 'mypy.nodes.NewTypeExpr') -> Optional[T]: pass - def visit__promote_expr(self, o: 'mypy.nodes.PromoteExpr') -> T: + def visit__promote_expr(self, o: 'mypy.nodes.PromoteExpr') -> Optional[T]: pass - def visit_await_expr(self, o: 'mypy.nodes.AwaitExpr') -> T: + def visit_await_expr(self, o: 'mypy.nodes.AwaitExpr') -> Optional[T]: pass - def visit_temp_node(self, o: 'mypy.nodes.TempNode') -> T: + def visit_temp_node(self, o: 'mypy.nodes.TempNode') -> Optional[T]: pass diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test index a1e5f12a56ad..ee7b02c718a2 100644 --- a/test-data/unit/check-abstract.test +++ b/test-data/unit/check-abstract.test @@ -1027,10 +1027,485 @@ def func5(x: str) -> str: ... def func5(x: Union[int, str]) -> Union[int, str]: # E: Missing return statement """Some function.""" -[case testEmptyBodyProhibitedMethod] +[case testEmptyBodyProhibitedMethodNonAbstract] +from typing import overload, Union + +class A: + def func1(self, x: str) -> int: pass # E: Missing return statement + def func2(self, x: str) -> int: ... # E: Missing return statement + def func3(self, x: str) -> int: # E: Missing return statement + """Some function.""" + +class B: + @classmethod + def func1(cls, x: str) -> int: pass # E: Missing return statement + @classmethod + def func2(cls, x: str) -> int: ... # E: Missing return statement + @classmethod + def func3(cls, x: str) -> int: # E: Missing return statement + """Some function.""" + +class C: + @overload + def func4(self, x: int) -> int: ... + @overload + def func4(self, x: str) -> str: ... + def func4(self, x: Union[int, str]) -> Union[int, str]: # E: Missing return statement + pass + @overload + def func5(self, x: int) -> int: ... + @overload + def func5(self, x: str) -> str: ... + def func5(self, x: Union[int, str]) -> Union[int, str]: # E: Missing return statement + """Some function.""" [builtins fixtures/classmethod.pyi] -[case testEmptyBodyProhibitedProperty] +[case testEmptyBodyProhibitedPropertyNonAbstract] +class A: + @property + def x(self) -> int: ... # E: Missing return statement + @property + def y(self) -> int: ... # E: Missing return statement + @y.setter + def y(self, value: int) -> None: ... + +class B: + @property + def x(self) -> int: pass # E: Missing return statement + @property + def y(self) -> int: pass # E: Missing return statement + @y.setter + def y(self, value: int) -> None: pass + +class C: + @property + def x(self) -> int: # E: Missing return statement + """Some property.""" + @property + def y(self) -> int: # E: Missing return statement + """Some property.""" + @y.setter + def y(self, value: int) -> None: pass +[builtins fixtures/property.pyi] + +[case testEmptyBodyAllowedFunctionStub] +import stub +[file stub.pyi] +from typing import overload, Union + +def func1(x: str) -> int: pass +def func2(x: str) -> int: ... +def func3(x: str) -> int: + """Some function.""" + +[case testEmptyBodyAllowedMethodNonAbstractStub] +import stub +[file stub.pyi] +from typing import overload, Union + +class A: + def func1(self, x: str) -> int: pass + def func2(self, x: str) -> int: ... + def func3(self, x: str) -> int: + """Some function.""" + +class B: + @classmethod + def func1(cls, x: str) -> int: pass + @classmethod + def func2(cls, x: str) -> int: ... + @classmethod + def func3(cls, x: str) -> int: + """Some function.""" +[builtins fixtures/classmethod.pyi] + +[case testEmptyBodyAllowedPropertyNonAbstractStub] +import stub +[file stub.pyi] +class A: + @property + def x(self) -> int: ... + @property + def y(self) -> int: ... + @y.setter + def y(self, value: int) -> None: ... + +class B: + @property + def x(self) -> int: pass + @property + def y(self) -> int: pass + @y.setter + def y(self, value: int) -> None: pass +class C: + @property + def x(self) -> int: + """Some property.""" + @property + def y(self) -> int: + """Some property.""" + @y.setter + def y(self, value: int) -> None: pass [builtins fixtures/property.pyi] + +[case testEmptyBodyAllowedMethodAbstract] +from typing import overload, Union +from abc import abstractmethod + +class A: + @abstractmethod + def func1(self, x: str) -> int: pass + @abstractmethod + def func2(self, x: str) -> int: ... + @abstractmethod + def func3(self, x: str) -> int: + """Some function.""" + +class B: + @classmethod + @abstractmethod + def func1(cls, x: str) -> int: pass + @classmethod + @abstractmethod + def func2(cls, x: str) -> int: ... + @classmethod + @abstractmethod + def func3(cls, x: str) -> int: + """Some function.""" + +class C: + @overload + @abstractmethod + def func4(self, x: int) -> int: ... + @overload + @abstractmethod + def func4(self, x: str) -> str: ... + @abstractmethod + def func4(self, x: Union[int, str]) -> Union[int, str]: + pass + + @overload + @abstractmethod + def func5(self, x: int) -> int: ... + @overload + @abstractmethod + def func5(self, x: str) -> str: ... + @abstractmethod + def func5(self, x: Union[int, str]) -> Union[int, str]: + """Some function.""" +[builtins fixtures/classmethod.pyi] + +[case testEmptyBodyProhibitedPropertyNonAbstract] +from abc import abstractmethod +class A: + @property + @abstractmethod + def x(self) -> int: ... + @property + @abstractmethod + def y(self) -> int: ... + @y.setter + @abstractmethod + def y(self, value: int) -> None: ... + +class B: + @property + @abstractmethod + def x(self) -> int: pass + @property + @abstractmethod + def y(self) -> int: pass + @y.setter + @abstractmethod + def y(self, value: int) -> None: pass + +class C: + @property + @abstractmethod + def x(self) -> int: + """Some property.""" + @property + @abstractmethod + def y(self) -> int: + """Some property.""" + @y.setter + @abstractmethod + def y(self, value: int) -> None: pass +[builtins fixtures/property.pyi] + +[case testEmptyBodyImplicitlyAbstractProtocol] +from typing import Protocol, overload, Union + +class P1(Protocol): + def meth(self) -> int: ... +class B1(P1): ... +class C1(P1): + def meth(self) -> int: + return 0 +B1() # E: Cannot instantiate abstract class 'B1' with abstract attribute 'meth' +C1() + +class P2(Protocol): + @classmethod + def meth(cls) -> int: ... +class B2(P2): ... +class C2(P2): + @classmethod + def meth(cls) -> int: + return 0 +B2() # E: Cannot instantiate abstract class 'B2' with abstract attribute 'meth' +C2() + +class P3(Protocol): + @overload + def meth(self, x: int) -> int: ... + @overload + def meth(self, x: str) -> str: ... +class B3(P3): ... +class C3(P3): + @overload + def meth(self, x: int) -> int: ... + @overload + def meth(self, x: str) -> str: ... + def meth(self, x: Union[int, str]) -> Union[int, str]: + return 0 +B3() # E: Cannot instantiate abstract class 'B3' with abstract attribute 'meth' +C3() +[builtins fixtures/classmethod.pyi] + +[case testEmptyBodyImplicitlyAbstractProtocolProperty] +from typing import Protocol + +class P1(Protocol): + @property + def attr(self) -> int: ... +class B1(P1): ... +class C1(P1): + @property + def attr(self) -> int: + return 0 +B1() # E: Cannot instantiate abstract class 'B1' with abstract attribute 'attr' +C1() + +class P2(Protocol): + @property + def attr(self) -> int: ... + @attr.setter + def attr(self, value: int) -> None: ... +class B2(P2): ... +class C2(P2): + @property + def attr(self) -> int: return 0 + @attr.setter + def attr(self, value: int) -> None: pass +B2() # E: Cannot instantiate abstract class 'B2' with abstract attribute 'attr' +C2() +[builtins fixtures/property.pyi] + +[case testEmptyBodyImplicitlyAbstractProtocolStub] +from stub import P1, P2, P3, P4 + +class B1(P1): ... +class B2(P2): ... +class B3(P3): ... +class B4(P4): ... + +B1() +B2() +B3() +B4() # E: Cannot instantiate abstract class 'B4' with abstract attribute 'meth' + +[file stub.pyi] +from typing import Protocol, overload, Union +from abc import abstractmethod + +class P1(Protocol): + def meth(self) -> int: ... + +class P2(Protocol): + @classmethod + def meth(cls) -> int: ... + +class P3(Protocol): + @overload + def meth(self, x: int) -> int: ... + @overload + def meth(self, x: str) -> str: ... + +class P4(Protocol): + @abstractmethod + def meth(self) -> int: ... +[builtins fixtures/classmethod.pyi] + +[case testEmptyBodyUnsafeAbstractSuper] +from stub import StubProto, StubAbstract +from typing import Protocol +from abc import abstractmethod + +class Proto(Protocol): + def meth(self) -> int: ... +class ProtoDef(Protocol): + def meth(self) -> int: return 0 + +class Abstract: + @abstractmethod + def meth(self) -> int: ... +class AbstractDef: + @abstractmethod + def meth(self) -> int: return 0 + +class SubProto(Proto): + def meth(self) -> int: + return super().meth() # E: Call to abstract method "meth" of "Proto" with trivial body via super() is unsafe +class SubProtoDef(ProtoDef): + def meth(self) -> int: + return super().meth() + +class SubAbstract(Abstract): + def meth(self) -> int: + return super().meth() # E: Call to abstract method "meth" of "Abstract" with trivial body via super() is unsafe +class SubAbstractDef(AbstractDef): + def meth(self) -> int: + return super().meth() + +class SubStubProto(StubProto): + def meth(self) -> int: + return super().meth() +class SubStubAbstract(StubAbstract): + def meth(self) -> int: + return super().meth() + +[file stub.pyi] +from typing import Protocol +from abc import abstractmethod + +class StubProto(Protocol): + def meth(self) -> int: ... +class StubAbstract: + @abstractmethod + def meth(self) -> int: ... + +[case testEmptyBodyUnsafeAbstractSuperProperty] +from stub import StubProto, StubAbstract +from typing import Protocol +from abc import abstractmethod + +class Proto(Protocol): + @property + def attr(self) -> int: ... +class SubProto(Proto): + @property + def attr(self) -> int: return super().attr # E: Call to abstract method "attr" of "Proto" with trivial body via super() is unsafe + +class ProtoDef(Protocol): + @property + def attr(self) -> int: return 0 +class SubProtoDef(ProtoDef): + @property + def attr(self) -> int: return super().attr + +class Abstract: + @property + @abstractmethod + def attr(self) -> int: ... +class SubAbstract(Abstract): + @property + @abstractmethod + def attr(self) -> int: return super().attr # E: Call to abstract method "attr" of "Abstract" with trivial body via super() is unsafe + +class AbstractDef: + @property + @abstractmethod + def attr(self) -> int: return 0 +class SubAbstractDef(AbstractDef): + @property + @abstractmethod + def attr(self) -> int: return super().attr + +class SubStubProto(StubProto): + @property + def attr(self) -> int: return super().attr +class SubStubAbstract(StubAbstract): + @property + def attr(self) -> int: return super().attr + +[file stub.pyi] +from typing import Protocol +from abc import abstractmethod + +class StubProto(Protocol): + @property + def attr(self) -> int: ... +class StubAbstract: + @property + @abstractmethod + def attr(self) -> int: ... +[builtins fixtures/property.pyi] + +[case testEmptyBodyUnsafeAbstractSuperOverloads] +from stub import StubProto +from typing import Protocol, overload, Union + +class ProtoEmptyImpl(Protocol): + @overload + def meth(self, x: str) -> str: ... + @overload + def meth(self, x: int) -> int: ... + def meth(self, x: Union[int, str]) -> Union[int, str]: + raise NotImplementedError +class ProtoDefImpl(Protocol): + @overload + def meth(self, x: str) -> str: ... + @overload + def meth(self, x: int) -> int: ... + def meth(self, x: Union[int, str]) -> Union[int, str]: + return 0 +class ProtoNoImpl(Protocol): + @overload + def meth(self, x: str) -> str: ... + @overload + def meth(self, x: int) -> int: ... + +class SubProtoEmptyImpl(ProtoEmptyImpl): + @overload + def meth(self, x: str) -> str: ... + @overload + def meth(self, x: int) -> int: ... + def meth(self, x: Union[int, str]) -> Union[int, str]: + return super().meth(0) # E: Call to abstract method "meth" of "ProtoEmptyImpl" with trivial body via super() is unsafe +class SubProtoDefImpl(ProtoDefImpl): + @overload + def meth(self, x: str) -> str: ... + @overload + def meth(self, x: int) -> int: ... + def meth(self, x: Union[int, str]) -> Union[int, str]: + return super().meth(0) +class SubStubProto(StubProto): + @overload + def meth(self, x: str) -> str: ... + @overload + def meth(self, x: int) -> int: ... + def meth(self, x: Union[int, str]) -> Union[int, str]: + return super().meth(0) + +# TODO: it would be good to also give an error in this case. +class SubProtoNoImpl(ProtoNoImpl): + @overload + def meth(self, x: str) -> str: ... + @overload + def meth(self, x: int) -> int: ... + def meth(self, x: Union[int, str]) -> Union[int, str]: + return super().meth(0) + +[file stub.pyi] +from typing import Protocol, overload + +class StubProto(Protocol): + @overload + def meth(self, x: str) -> str: ... + @overload + def meth(self, x: int) -> int: ... + +[builtins fixtures/exception.pyi] diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 28322eea2001..5a4cad935abd 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -703,3 +703,15 @@ class InvalidReturn: # N: If return type of "__exit__" implies that it may return True, the context manager may swallow exceptions return False [builtins fixtures/bool.pyi] + +[case testErrorCodeUnsafeSuper_no_empty] +from abc import abstractmethod + +class Base: + @abstractmethod + def meth(self) -> int: + raise NotImplementedError() +class Sub(Base): + def meth(self) -> int: + return super().meth() # E: Call to abstract method "meth" of "Base" with trivial body via super() is unsafe [safe-super] +[builtins fixtures/exception.pyi] diff --git a/test-data/unit/diff.test b/test-data/unit/diff.test index 34a5ef263cdd..3a203618c9ed 100644 --- a/test-data/unit/diff.test +++ b/test-data/unit/diff.test @@ -1465,3 +1465,16 @@ x: Union[Callable[[Arg(int, 'y')], None], Callable[[int], None]] [out] __main__.x + +[case testEmptyBodySuper] +from abc import abstractmethod +class C: + @abstractmethod + def meth(self) -> int: ... +[file next.py] +from abc import abstractmethod +class C: + @abstractmethod + def meth(self) -> int: return 0 +[out] +__main__.C.meth diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 11e83f560eee..0b8581851580 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -9288,3 +9288,25 @@ class B: self.x = 0 [out] == + +[case testAbstractBodyTurnsEmpty] +from b import Base + +class Sub(Base): + def meth(self) -> int: + return super().meth() + +[file b.py] +from abc import abstractmethod +class Base: + @abstractmethod + def meth(self) -> int: return 0 + +[file b.py.2] +from abc import abstractmethod +class Base: + @abstractmethod + def meth(self) -> int: ... +[out] +== +main:5: error: Call to abstract method "meth" of "Base" with trivial body via super() is unsafe From f197b05a5e5e9b296d0b2401b972f88fce5a34d7 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 8 Dec 2019 22:29:06 +0000 Subject: [PATCH 3/9] Add some tests --- mypy/visitor.py | 136 ++++++++++++++++++++++++------------------------ 1 file changed, 69 insertions(+), 67 deletions(-) diff --git a/mypy/visitor.py b/mypy/visitor.py index d4b34dd2e5aa..d3c2d5977cfe 100644 --- a/mypy/visitor.py +++ b/mypy/visitor.py @@ -28,163 +28,164 @@ def visit_bytes_expr(self, o: 'mypy.nodes.BytesExpr') -> Optional[T]: pass @abstractmethod - def visit_unicode_expr(self, o: 'mypy.nodes.UnicodeExpr') -> T: + def visit_unicode_expr(self, o: 'mypy.nodes.UnicodeExpr') -> Optional[T]: pass @abstractmethod - def visit_float_expr(self, o: 'mypy.nodes.FloatExpr') -> T: + def visit_float_expr(self, o: 'mypy.nodes.FloatExpr') -> Optional[T]: pass @abstractmethod - def visit_complex_expr(self, o: 'mypy.nodes.ComplexExpr') -> T: + def visit_complex_expr(self, o: 'mypy.nodes.ComplexExpr') -> Optional[T]: pass @abstractmethod - def visit_ellipsis(self, o: 'mypy.nodes.EllipsisExpr') -> T: + def visit_ellipsis(self, o: 'mypy.nodes.EllipsisExpr') -> Optional[T]: pass @abstractmethod - def visit_star_expr(self, o: 'mypy.nodes.StarExpr') -> T: + def visit_star_expr(self, o: 'mypy.nodes.StarExpr') -> Optional[T]: pass @abstractmethod - def visit_name_expr(self, o: 'mypy.nodes.NameExpr') -> T: + def visit_name_expr(self, o: 'mypy.nodes.NameExpr') -> Optional[T]: pass @abstractmethod - def visit_member_expr(self, o: 'mypy.nodes.MemberExpr') -> T: + def visit_member_expr(self, o: 'mypy.nodes.MemberExpr') -> Optional[T]: pass @abstractmethod - def visit_yield_from_expr(self, o: 'mypy.nodes.YieldFromExpr') -> T: + def visit_yield_from_expr(self, o: 'mypy.nodes.YieldFromExpr') -> Optional[T]: pass @abstractmethod - def visit_yield_expr(self, o: 'mypy.nodes.YieldExpr') -> T: + def visit_yield_expr(self, o: 'mypy.nodes.YieldExpr') -> Optional[T]: pass @abstractmethod - def visit_call_expr(self, o: 'mypy.nodes.CallExpr') -> T: + def visit_call_expr(self, o: 'mypy.nodes.CallExpr') -> Optional[T]: pass @abstractmethod - def visit_op_expr(self, o: 'mypy.nodes.OpExpr') -> T: + def visit_op_expr(self, o: 'mypy.nodes.OpExpr') -> Optional[T]: pass @abstractmethod - def visit_comparison_expr(self, o: 'mypy.nodes.ComparisonExpr') -> T: + def visit_comparison_expr(self, o: 'mypy.nodes.ComparisonExpr') -> Optional[T]: pass @abstractmethod - def visit_cast_expr(self, o: 'mypy.nodes.CastExpr') -> T: + def visit_cast_expr(self, o: 'mypy.nodes.CastExpr') -> Optional[T]: pass @abstractmethod - def visit_reveal_expr(self, o: 'mypy.nodes.RevealExpr') -> T: + def visit_reveal_expr(self, o: 'mypy.nodes.RevealExpr') -> Optional[T]: pass @abstractmethod - def visit_super_expr(self, o: 'mypy.nodes.SuperExpr') -> T: + def visit_super_expr(self, o: 'mypy.nodes.SuperExpr') -> Optional[T]: pass @abstractmethod - def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> T: + def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> Optional[T]: pass @abstractmethod - def visit_assignment_expr(self, o: 'mypy.nodes.AssignmentExpr') -> T: + def visit_assignment_expr(self, o: 'mypy.nodes.AssignmentExpr') -> Optional[T]: pass @abstractmethod - def visit_list_expr(self, o: 'mypy.nodes.ListExpr') -> T: + def visit_list_expr(self, o: 'mypy.nodes.ListExpr') -> Optional[T]: pass @abstractmethod - def visit_dict_expr(self, o: 'mypy.nodes.DictExpr') -> T: + def visit_dict_expr(self, o: 'mypy.nodes.DictExpr') -> Optional[T]: pass @abstractmethod - def visit_tuple_expr(self, o: 'mypy.nodes.TupleExpr') -> T: + def visit_tuple_expr(self, o: 'mypy.nodes.TupleExpr') -> Optional[T]: pass @abstractmethod - def visit_set_expr(self, o: 'mypy.nodes.SetExpr') -> T: + def visit_set_expr(self, o: 'mypy.nodes.SetExpr') -> Optional[T]: pass @abstractmethod - def visit_index_expr(self, o: 'mypy.nodes.IndexExpr') -> T: + def visit_index_expr(self, o: 'mypy.nodes.IndexExpr') -> Optional[T]: pass @abstractmethod - def visit_type_application(self, o: 'mypy.nodes.TypeApplication') -> T: + def visit_type_application(self, o: 'mypy.nodes.TypeApplication') -> Optional[T]: pass @abstractmethod - def visit_lambda_expr(self, o: 'mypy.nodes.LambdaExpr') -> T: + def visit_lambda_expr(self, o: 'mypy.nodes.LambdaExpr') -> Optional[T]: pass @abstractmethod - def visit_list_comprehension(self, o: 'mypy.nodes.ListComprehension') -> T: + def visit_list_comprehension(self, o: 'mypy.nodes.ListComprehension') -> Optional[T]: pass @abstractmethod - def visit_set_comprehension(self, o: 'mypy.nodes.SetComprehension') -> T: + def visit_set_comprehension(self, o: 'mypy.nodes.SetComprehension') -> Optional[T]: pass @abstractmethod - def visit_dictionary_comprehension(self, o: 'mypy.nodes.DictionaryComprehension') -> T: + def visit_dictionary_comprehension(self, o: 'mypy.nodes.DictionaryComprehension' + ) -> Optional[T]: pass @abstractmethod - def visit_generator_expr(self, o: 'mypy.nodes.GeneratorExpr') -> T: + def visit_generator_expr(self, o: 'mypy.nodes.GeneratorExpr') -> Optional[T]: pass @abstractmethod - def visit_slice_expr(self, o: 'mypy.nodes.SliceExpr') -> T: + def visit_slice_expr(self, o: 'mypy.nodes.SliceExpr') -> Optional[T]: pass @abstractmethod - def visit_conditional_expr(self, o: 'mypy.nodes.ConditionalExpr') -> T: + def visit_conditional_expr(self, o: 'mypy.nodes.ConditionalExpr') -> Optional[T]: pass @abstractmethod - def visit_backquote_expr(self, o: 'mypy.nodes.BackquoteExpr') -> T: + def visit_backquote_expr(self, o: 'mypy.nodes.BackquoteExpr') -> Optional[T]: pass @abstractmethod - def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> T: + def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> Optional[T]: pass @abstractmethod - def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> T: + def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> Optional[T]: pass @abstractmethod - def visit_namedtuple_expr(self, o: 'mypy.nodes.NamedTupleExpr') -> T: + def visit_namedtuple_expr(self, o: 'mypy.nodes.NamedTupleExpr') -> Optional[T]: pass @abstractmethod - def visit_enum_call_expr(self, o: 'mypy.nodes.EnumCallExpr') -> T: + def visit_enum_call_expr(self, o: 'mypy.nodes.EnumCallExpr') -> Optional[T]: pass @abstractmethod - def visit_typeddict_expr(self, o: 'mypy.nodes.TypedDictExpr') -> T: + def visit_typeddict_expr(self, o: 'mypy.nodes.TypedDictExpr') -> Optional[T]: pass @abstractmethod - def visit_newtype_expr(self, o: 'mypy.nodes.NewTypeExpr') -> T: + def visit_newtype_expr(self, o: 'mypy.nodes.NewTypeExpr') -> Optional[T]: pass @abstractmethod - def visit__promote_expr(self, o: 'mypy.nodes.PromoteExpr') -> T: + def visit__promote_expr(self, o: 'mypy.nodes.PromoteExpr') -> Optional[T]: pass @abstractmethod - def visit_await_expr(self, o: 'mypy.nodes.AwaitExpr') -> T: + def visit_await_expr(self, o: 'mypy.nodes.AwaitExpr') -> Optional[T]: pass @abstractmethod - def visit_temp_node(self, o: 'mypy.nodes.TempNode') -> T: + def visit_temp_node(self, o: 'mypy.nodes.TempNode') -> Optional[T]: pass @@ -193,115 +194,116 @@ class StatementVisitor(Generic[T]): # Definitions @abstractmethod - def visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt') -> T: + def visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt') -> Optional[T]: pass @abstractmethod - def visit_for_stmt(self, o: 'mypy.nodes.ForStmt') -> T: + def visit_for_stmt(self, o: 'mypy.nodes.ForStmt') -> Optional[T]: pass @abstractmethod - def visit_with_stmt(self, o: 'mypy.nodes.WithStmt') -> T: + def visit_with_stmt(self, o: 'mypy.nodes.WithStmt') -> Optional[T]: pass @abstractmethod - def visit_del_stmt(self, o: 'mypy.nodes.DelStmt') -> T: + def visit_del_stmt(self, o: 'mypy.nodes.DelStmt') -> Optional[T]: pass @abstractmethod - def visit_func_def(self, o: 'mypy.nodes.FuncDef') -> T: + def visit_func_def(self, o: 'mypy.nodes.FuncDef') -> Optional[T]: pass @abstractmethod - def visit_overloaded_func_def(self, o: 'mypy.nodes.OverloadedFuncDef') -> T: + def visit_overloaded_func_def(self, o: 'mypy.nodes.OverloadedFuncDef') -> Optional[T]: pass @abstractmethod - def visit_class_def(self, o: 'mypy.nodes.ClassDef') -> T: + def visit_class_def(self, o: 'mypy.nodes.ClassDef') -> Optional[T]: pass @abstractmethod - def visit_global_decl(self, o: 'mypy.nodes.GlobalDecl') -> T: + def visit_global_decl(self, o: 'mypy.nodes.GlobalDecl') -> Optional[T]: pass @abstractmethod - def visit_nonlocal_decl(self, o: 'mypy.nodes.NonlocalDecl') -> T: + def visit_nonlocal_decl(self, o: 'mypy.nodes.NonlocalDecl') -> Optional[T]: pass @abstractmethod - def visit_decorator(self, o: 'mypy.nodes.Decorator') -> T: + def visit_decorator(self, o: 'mypy.nodes.Decorator') -> Optional[T]: pass # Module structure @abstractmethod - def visit_import(self, o: 'mypy.nodes.Import') -> T: + def visit_import(self, o: 'mypy.nodes.Import') -> Optional[T]: pass @abstractmethod - def visit_import_from(self, o: 'mypy.nodes.ImportFrom') -> T: + def visit_import_from(self, o: 'mypy.nodes.ImportFrom') -> Optional[T]: pass @abstractmethod - def visit_import_all(self, o: 'mypy.nodes.ImportAll') -> T: + def visit_import_all(self, o: 'mypy.nodes.ImportAll') -> Optional[T]: pass # Statements @abstractmethod - def visit_block(self, o: 'mypy.nodes.Block') -> T: + def visit_block(self, o: 'mypy.nodes.Block') -> Optional[T]: pass @abstractmethod - def visit_expression_stmt(self, o: 'mypy.nodes.ExpressionStmt') -> T: + def visit_expression_stmt(self, o: 'mypy.nodes.ExpressionStmt') -> Optional[T]: pass @abstractmethod - def visit_operator_assignment_stmt(self, o: 'mypy.nodes.OperatorAssignmentStmt') -> T: + def visit_operator_assignment_stmt(self, o: 'mypy.nodes.OperatorAssignmentStmt' + ) -> Optional[T]: pass @abstractmethod - def visit_while_stmt(self, o: 'mypy.nodes.WhileStmt') -> T: + def visit_while_stmt(self, o: 'mypy.nodes.WhileStmt') -> Optional[T]: pass @abstractmethod - def visit_return_stmt(self, o: 'mypy.nodes.ReturnStmt') -> T: + def visit_return_stmt(self, o: 'mypy.nodes.ReturnStmt') -> Optional[T]: pass @abstractmethod - def visit_assert_stmt(self, o: 'mypy.nodes.AssertStmt') -> T: + def visit_assert_stmt(self, o: 'mypy.nodes.AssertStmt') -> Optional[T]: pass @abstractmethod - def visit_if_stmt(self, o: 'mypy.nodes.IfStmt') -> T: + def visit_if_stmt(self, o: 'mypy.nodes.IfStmt') -> Optional[T]: pass @abstractmethod - def visit_break_stmt(self, o: 'mypy.nodes.BreakStmt') -> T: + def visit_break_stmt(self, o: 'mypy.nodes.BreakStmt') -> Optional[T]: pass @abstractmethod - def visit_continue_stmt(self, o: 'mypy.nodes.ContinueStmt') -> T: + def visit_continue_stmt(self, o: 'mypy.nodes.ContinueStmt') -> Optional[T]: pass @abstractmethod - def visit_pass_stmt(self, o: 'mypy.nodes.PassStmt') -> T: + def visit_pass_stmt(self, o: 'mypy.nodes.PassStmt') -> Optional[T]: pass @abstractmethod - def visit_raise_stmt(self, o: 'mypy.nodes.RaiseStmt') -> T: + def visit_raise_stmt(self, o: 'mypy.nodes.RaiseStmt') -> Optional[T]: pass @abstractmethod - def visit_try_stmt(self, o: 'mypy.nodes.TryStmt') -> T: + def visit_try_stmt(self, o: 'mypy.nodes.TryStmt') -> Optional[T]: pass @abstractmethod - def visit_print_stmt(self, o: 'mypy.nodes.PrintStmt') -> T: + def visit_print_stmt(self, o: 'mypy.nodes.PrintStmt') -> Optional[T]: pass @abstractmethod - def visit_exec_stmt(self, o: 'mypy.nodes.ExecStmt') -> T: + def visit_exec_stmt(self, o: 'mypy.nodes.ExecStmt') -> Optional[T]: pass From 8a3b8c57034686acc721725907e4108c2d2890c0 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 8 Dec 2019 22:50:17 +0000 Subject: [PATCH 4/9] Fix lint and self-check --- mypy/checker.py | 4 +- mypy/visitor.py | 292 ++++++++++++++++++++++---------------------- mypy_self_check.ini | 4 + 3 files changed, 151 insertions(+), 149 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 62a91fb21438..fe83be6ee739 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -17,8 +17,8 @@ ClassDef, Block, AssignmentStmt, NameExpr, MemberExpr, IndexExpr, TupleExpr, ListExpr, ExpressionStmt, ReturnStmt, IfStmt, WhileStmt, OperatorAssignmentStmt, WithStmt, AssertStmt, - RaiseStmt, TryStmt, ForStmt, DelStmt, CallExpr, IntExpr, StrExpr, - UnicodeExpr, OpExpr, UnaryExpr, LambdaExpr, TempNode, SymbolTableNode, + RaiseStmt, TryStmt, ForStmt, DelStmt, CallExpr, IntExpr, + OpExpr, UnaryExpr, LambdaExpr, TempNode, SymbolTableNode, Context, Decorator, PrintStmt, BreakStmt, PassStmt, ContinueStmt, ComparisonExpr, StarExpr, EllipsisExpr, RefExpr, PromoteExpr, Import, ImportFrom, ImportAll, ImportBase, TypeAlias, diff --git a/mypy/visitor.py b/mypy/visitor.py index d3c2d5977cfe..64d7f119e668 100644 --- a/mypy/visitor.py +++ b/mypy/visitor.py @@ -1,7 +1,7 @@ """Generic abstract syntax tree node visitor""" from abc import abstractmethod -from typing import TypeVar, Generic, Optional +from typing import TypeVar, Generic from typing_extensions import TYPE_CHECKING from mypy_extensions import trait @@ -16,176 +16,175 @@ @trait class ExpressionVisitor(Generic[T]): @abstractmethod - def visit_int_expr(self, o: 'mypy.nodes.IntExpr') -> Optional[T]: + def visit_int_expr(self, o: 'mypy.nodes.IntExpr') -> T: pass @abstractmethod - def visit_str_expr(self, o: 'mypy.nodes.StrExpr') -> Optional[T]: + def visit_str_expr(self, o: 'mypy.nodes.StrExpr') -> T: pass @abstractmethod - def visit_bytes_expr(self, o: 'mypy.nodes.BytesExpr') -> Optional[T]: + def visit_bytes_expr(self, o: 'mypy.nodes.BytesExpr') -> T: pass @abstractmethod - def visit_unicode_expr(self, o: 'mypy.nodes.UnicodeExpr') -> Optional[T]: + def visit_unicode_expr(self, o: 'mypy.nodes.UnicodeExpr') -> T: pass @abstractmethod - def visit_float_expr(self, o: 'mypy.nodes.FloatExpr') -> Optional[T]: + def visit_float_expr(self, o: 'mypy.nodes.FloatExpr') -> T: pass @abstractmethod - def visit_complex_expr(self, o: 'mypy.nodes.ComplexExpr') -> Optional[T]: + def visit_complex_expr(self, o: 'mypy.nodes.ComplexExpr') -> T: pass @abstractmethod - def visit_ellipsis(self, o: 'mypy.nodes.EllipsisExpr') -> Optional[T]: + def visit_ellipsis(self, o: 'mypy.nodes.EllipsisExpr') -> T: pass @abstractmethod - def visit_star_expr(self, o: 'mypy.nodes.StarExpr') -> Optional[T]: + def visit_star_expr(self, o: 'mypy.nodes.StarExpr') -> T: pass @abstractmethod - def visit_name_expr(self, o: 'mypy.nodes.NameExpr') -> Optional[T]: + def visit_name_expr(self, o: 'mypy.nodes.NameExpr') -> T: pass @abstractmethod - def visit_member_expr(self, o: 'mypy.nodes.MemberExpr') -> Optional[T]: + def visit_member_expr(self, o: 'mypy.nodes.MemberExpr') -> T: pass @abstractmethod - def visit_yield_from_expr(self, o: 'mypy.nodes.YieldFromExpr') -> Optional[T]: + def visit_yield_from_expr(self, o: 'mypy.nodes.YieldFromExpr') -> T: pass @abstractmethod - def visit_yield_expr(self, o: 'mypy.nodes.YieldExpr') -> Optional[T]: + def visit_yield_expr(self, o: 'mypy.nodes.YieldExpr') -> T: pass @abstractmethod - def visit_call_expr(self, o: 'mypy.nodes.CallExpr') -> Optional[T]: + def visit_call_expr(self, o: 'mypy.nodes.CallExpr') -> T: pass @abstractmethod - def visit_op_expr(self, o: 'mypy.nodes.OpExpr') -> Optional[T]: + def visit_op_expr(self, o: 'mypy.nodes.OpExpr') -> T: pass @abstractmethod - def visit_comparison_expr(self, o: 'mypy.nodes.ComparisonExpr') -> Optional[T]: + def visit_comparison_expr(self, o: 'mypy.nodes.ComparisonExpr') -> T: pass @abstractmethod - def visit_cast_expr(self, o: 'mypy.nodes.CastExpr') -> Optional[T]: + def visit_cast_expr(self, o: 'mypy.nodes.CastExpr') -> T: pass @abstractmethod - def visit_reveal_expr(self, o: 'mypy.nodes.RevealExpr') -> Optional[T]: + def visit_reveal_expr(self, o: 'mypy.nodes.RevealExpr') -> T: pass @abstractmethod - def visit_super_expr(self, o: 'mypy.nodes.SuperExpr') -> Optional[T]: + def visit_super_expr(self, o: 'mypy.nodes.SuperExpr') -> T: pass @abstractmethod - def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> Optional[T]: + def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> T: pass @abstractmethod - def visit_assignment_expr(self, o: 'mypy.nodes.AssignmentExpr') -> Optional[T]: + def visit_assignment_expr(self, o: 'mypy.nodes.AssignmentExpr') -> T: pass @abstractmethod - def visit_list_expr(self, o: 'mypy.nodes.ListExpr') -> Optional[T]: + def visit_list_expr(self, o: 'mypy.nodes.ListExpr') -> T: pass @abstractmethod - def visit_dict_expr(self, o: 'mypy.nodes.DictExpr') -> Optional[T]: + def visit_dict_expr(self, o: 'mypy.nodes.DictExpr') -> T: pass @abstractmethod - def visit_tuple_expr(self, o: 'mypy.nodes.TupleExpr') -> Optional[T]: + def visit_tuple_expr(self, o: 'mypy.nodes.TupleExpr') -> T: pass @abstractmethod - def visit_set_expr(self, o: 'mypy.nodes.SetExpr') -> Optional[T]: + def visit_set_expr(self, o: 'mypy.nodes.SetExpr') -> T: pass @abstractmethod - def visit_index_expr(self, o: 'mypy.nodes.IndexExpr') -> Optional[T]: + def visit_index_expr(self, o: 'mypy.nodes.IndexExpr') -> T: pass @abstractmethod - def visit_type_application(self, o: 'mypy.nodes.TypeApplication') -> Optional[T]: + def visit_type_application(self, o: 'mypy.nodes.TypeApplication') -> T: pass @abstractmethod - def visit_lambda_expr(self, o: 'mypy.nodes.LambdaExpr') -> Optional[T]: + def visit_lambda_expr(self, o: 'mypy.nodes.LambdaExpr') -> T: pass @abstractmethod - def visit_list_comprehension(self, o: 'mypy.nodes.ListComprehension') -> Optional[T]: + def visit_list_comprehension(self, o: 'mypy.nodes.ListComprehension') -> T: pass @abstractmethod - def visit_set_comprehension(self, o: 'mypy.nodes.SetComprehension') -> Optional[T]: + def visit_set_comprehension(self, o: 'mypy.nodes.SetComprehension') -> T: pass @abstractmethod - def visit_dictionary_comprehension(self, o: 'mypy.nodes.DictionaryComprehension' - ) -> Optional[T]: + def visit_dictionary_comprehension(self, o: 'mypy.nodes.DictionaryComprehension') -> T: pass @abstractmethod - def visit_generator_expr(self, o: 'mypy.nodes.GeneratorExpr') -> Optional[T]: + def visit_generator_expr(self, o: 'mypy.nodes.GeneratorExpr') -> T: pass @abstractmethod - def visit_slice_expr(self, o: 'mypy.nodes.SliceExpr') -> Optional[T]: + def visit_slice_expr(self, o: 'mypy.nodes.SliceExpr') -> T: pass @abstractmethod - def visit_conditional_expr(self, o: 'mypy.nodes.ConditionalExpr') -> Optional[T]: + def visit_conditional_expr(self, o: 'mypy.nodes.ConditionalExpr') -> T: pass @abstractmethod - def visit_backquote_expr(self, o: 'mypy.nodes.BackquoteExpr') -> Optional[T]: + def visit_backquote_expr(self, o: 'mypy.nodes.BackquoteExpr') -> T: pass @abstractmethod - def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> Optional[T]: + def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> T: pass @abstractmethod - def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> Optional[T]: + def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> T: pass @abstractmethod - def visit_namedtuple_expr(self, o: 'mypy.nodes.NamedTupleExpr') -> Optional[T]: + def visit_namedtuple_expr(self, o: 'mypy.nodes.NamedTupleExpr') -> T: pass @abstractmethod - def visit_enum_call_expr(self, o: 'mypy.nodes.EnumCallExpr') -> Optional[T]: + def visit_enum_call_expr(self, o: 'mypy.nodes.EnumCallExpr') -> T: pass @abstractmethod - def visit_typeddict_expr(self, o: 'mypy.nodes.TypedDictExpr') -> Optional[T]: + def visit_typeddict_expr(self, o: 'mypy.nodes.TypedDictExpr') -> T: pass @abstractmethod - def visit_newtype_expr(self, o: 'mypy.nodes.NewTypeExpr') -> Optional[T]: + def visit_newtype_expr(self, o: 'mypy.nodes.NewTypeExpr') -> T: pass @abstractmethod - def visit__promote_expr(self, o: 'mypy.nodes.PromoteExpr') -> Optional[T]: + def visit__promote_expr(self, o: 'mypy.nodes.PromoteExpr') -> T: pass @abstractmethod - def visit_await_expr(self, o: 'mypy.nodes.AwaitExpr') -> Optional[T]: + def visit_await_expr(self, o: 'mypy.nodes.AwaitExpr') -> T: pass @abstractmethod - def visit_temp_node(self, o: 'mypy.nodes.TempNode') -> Optional[T]: + def visit_temp_node(self, o: 'mypy.nodes.TempNode') -> T: pass @@ -194,116 +193,115 @@ class StatementVisitor(Generic[T]): # Definitions @abstractmethod - def visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt') -> Optional[T]: + def visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt') -> T: pass @abstractmethod - def visit_for_stmt(self, o: 'mypy.nodes.ForStmt') -> Optional[T]: + def visit_for_stmt(self, o: 'mypy.nodes.ForStmt') -> T: pass @abstractmethod - def visit_with_stmt(self, o: 'mypy.nodes.WithStmt') -> Optional[T]: + def visit_with_stmt(self, o: 'mypy.nodes.WithStmt') -> T: pass @abstractmethod - def visit_del_stmt(self, o: 'mypy.nodes.DelStmt') -> Optional[T]: + def visit_del_stmt(self, o: 'mypy.nodes.DelStmt') -> T: pass @abstractmethod - def visit_func_def(self, o: 'mypy.nodes.FuncDef') -> Optional[T]: + def visit_func_def(self, o: 'mypy.nodes.FuncDef') -> T: pass @abstractmethod - def visit_overloaded_func_def(self, o: 'mypy.nodes.OverloadedFuncDef') -> Optional[T]: + def visit_overloaded_func_def(self, o: 'mypy.nodes.OverloadedFuncDef') -> T: pass @abstractmethod - def visit_class_def(self, o: 'mypy.nodes.ClassDef') -> Optional[T]: + def visit_class_def(self, o: 'mypy.nodes.ClassDef') -> T: pass @abstractmethod - def visit_global_decl(self, o: 'mypy.nodes.GlobalDecl') -> Optional[T]: + def visit_global_decl(self, o: 'mypy.nodes.GlobalDecl') -> T: pass @abstractmethod - def visit_nonlocal_decl(self, o: 'mypy.nodes.NonlocalDecl') -> Optional[T]: + def visit_nonlocal_decl(self, o: 'mypy.nodes.NonlocalDecl') -> T: pass @abstractmethod - def visit_decorator(self, o: 'mypy.nodes.Decorator') -> Optional[T]: + def visit_decorator(self, o: 'mypy.nodes.Decorator') -> T: pass # Module structure @abstractmethod - def visit_import(self, o: 'mypy.nodes.Import') -> Optional[T]: + def visit_import(self, o: 'mypy.nodes.Import') -> T: pass @abstractmethod - def visit_import_from(self, o: 'mypy.nodes.ImportFrom') -> Optional[T]: + def visit_import_from(self, o: 'mypy.nodes.ImportFrom') -> T: pass @abstractmethod - def visit_import_all(self, o: 'mypy.nodes.ImportAll') -> Optional[T]: + def visit_import_all(self, o: 'mypy.nodes.ImportAll') -> T: pass # Statements @abstractmethod - def visit_block(self, o: 'mypy.nodes.Block') -> Optional[T]: + def visit_block(self, o: 'mypy.nodes.Block') -> T: pass @abstractmethod - def visit_expression_stmt(self, o: 'mypy.nodes.ExpressionStmt') -> Optional[T]: + def visit_expression_stmt(self, o: 'mypy.nodes.ExpressionStmt') -> T: pass @abstractmethod - def visit_operator_assignment_stmt(self, o: 'mypy.nodes.OperatorAssignmentStmt' - ) -> Optional[T]: + def visit_operator_assignment_stmt(self, o: 'mypy.nodes.OperatorAssignmentStmt') -> T: pass @abstractmethod - def visit_while_stmt(self, o: 'mypy.nodes.WhileStmt') -> Optional[T]: + def visit_while_stmt(self, o: 'mypy.nodes.WhileStmt') -> T: pass @abstractmethod - def visit_return_stmt(self, o: 'mypy.nodes.ReturnStmt') -> Optional[T]: + def visit_return_stmt(self, o: 'mypy.nodes.ReturnStmt') -> T: pass @abstractmethod - def visit_assert_stmt(self, o: 'mypy.nodes.AssertStmt') -> Optional[T]: + def visit_assert_stmt(self, o: 'mypy.nodes.AssertStmt') -> T: pass @abstractmethod - def visit_if_stmt(self, o: 'mypy.nodes.IfStmt') -> Optional[T]: + def visit_if_stmt(self, o: 'mypy.nodes.IfStmt') -> T: pass @abstractmethod - def visit_break_stmt(self, o: 'mypy.nodes.BreakStmt') -> Optional[T]: + def visit_break_stmt(self, o: 'mypy.nodes.BreakStmt') -> T: pass @abstractmethod - def visit_continue_stmt(self, o: 'mypy.nodes.ContinueStmt') -> Optional[T]: + def visit_continue_stmt(self, o: 'mypy.nodes.ContinueStmt') -> T: pass @abstractmethod - def visit_pass_stmt(self, o: 'mypy.nodes.PassStmt') -> Optional[T]: + def visit_pass_stmt(self, o: 'mypy.nodes.PassStmt') -> T: pass @abstractmethod - def visit_raise_stmt(self, o: 'mypy.nodes.RaiseStmt') -> Optional[T]: + def visit_raise_stmt(self, o: 'mypy.nodes.RaiseStmt') -> T: pass @abstractmethod - def visit_try_stmt(self, o: 'mypy.nodes.TryStmt') -> Optional[T]: + def visit_try_stmt(self, o: 'mypy.nodes.TryStmt') -> T: pass @abstractmethod - def visit_print_stmt(self, o: 'mypy.nodes.PrintStmt') -> Optional[T]: + def visit_print_stmt(self, o: 'mypy.nodes.PrintStmt') -> T: pass @abstractmethod - def visit_exec_stmt(self, o: 'mypy.nodes.ExecStmt') -> Optional[T]: + def visit_exec_stmt(self, o: 'mypy.nodes.ExecStmt') -> T: pass @@ -315,12 +313,12 @@ class NodeVisitor(Generic[T], ExpressionVisitor[T], StatementVisitor[T]): methods. As all methods defined here return None by default, subclasses do not always need to override all the methods. - TODO make the default return value explicit + TODO: make the default return value explicit, then turn off the flag. """ # Not in superclasses: - def visit_mypy_file(self, o: 'mypy.nodes.MypyFile') -> Optional[T]: + def visit_mypy_file(self, o: 'mypy.nodes.MypyFile') -> T: pass # TODO: We have a visit_var method, but no visit_typeinfo or any @@ -331,227 +329,227 @@ def visit_var(self, o: 'mypy.nodes.Var') -> T: # Module structure - def visit_import(self, o: 'mypy.nodes.Import') -> Optional[T]: + def visit_import(self, o: 'mypy.nodes.Import') -> T: pass - def visit_import_from(self, o: 'mypy.nodes.ImportFrom') -> Optional[T]: + def visit_import_from(self, o: 'mypy.nodes.ImportFrom') -> T: pass - def visit_import_all(self, o: 'mypy.nodes.ImportAll') -> Optional[T]: + def visit_import_all(self, o: 'mypy.nodes.ImportAll') -> T: pass # Definitions - def visit_func_def(self, o: 'mypy.nodes.FuncDef') -> Optional[T]: + def visit_func_def(self, o: 'mypy.nodes.FuncDef') -> T: pass def visit_overloaded_func_def(self, - o: 'mypy.nodes.OverloadedFuncDef') -> Optional[T]: + o: 'mypy.nodes.OverloadedFuncDef') -> T: pass - def visit_class_def(self, o: 'mypy.nodes.ClassDef') -> Optional[T]: + def visit_class_def(self, o: 'mypy.nodes.ClassDef') -> T: pass - def visit_global_decl(self, o: 'mypy.nodes.GlobalDecl') -> Optional[T]: + def visit_global_decl(self, o: 'mypy.nodes.GlobalDecl') -> T: pass - def visit_nonlocal_decl(self, o: 'mypy.nodes.NonlocalDecl') -> Optional[T]: + def visit_nonlocal_decl(self, o: 'mypy.nodes.NonlocalDecl') -> T: pass - def visit_decorator(self, o: 'mypy.nodes.Decorator') -> Optional[T]: + def visit_decorator(self, o: 'mypy.nodes.Decorator') -> T: pass - def visit_type_alias(self, o: 'mypy.nodes.TypeAlias') -> Optional[T]: + def visit_type_alias(self, o: 'mypy.nodes.TypeAlias') -> T: pass - def visit_placeholder_node(self, o: 'mypy.nodes.PlaceholderNode') -> Optional[T]: + def visit_placeholder_node(self, o: 'mypy.nodes.PlaceholderNode') -> T: pass # Statements - def visit_block(self, o: 'mypy.nodes.Block') -> Optional[T]: + def visit_block(self, o: 'mypy.nodes.Block') -> T: pass - def visit_expression_stmt(self, o: 'mypy.nodes.ExpressionStmt') -> Optional[T]: + def visit_expression_stmt(self, o: 'mypy.nodes.ExpressionStmt') -> T: pass - def visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt') -> Optional[T]: + def visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt') -> T: pass def visit_operator_assignment_stmt(self, - o: 'mypy.nodes.OperatorAssignmentStmt') -> Optional[T]: + o: 'mypy.nodes.OperatorAssignmentStmt') -> T: pass - def visit_while_stmt(self, o: 'mypy.nodes.WhileStmt') -> Optional[T]: + def visit_while_stmt(self, o: 'mypy.nodes.WhileStmt') -> T: pass - def visit_for_stmt(self, o: 'mypy.nodes.ForStmt') -> Optional[T]: + def visit_for_stmt(self, o: 'mypy.nodes.ForStmt') -> T: pass - def visit_return_stmt(self, o: 'mypy.nodes.ReturnStmt') -> Optional[T]: + def visit_return_stmt(self, o: 'mypy.nodes.ReturnStmt') -> T: pass - def visit_assert_stmt(self, o: 'mypy.nodes.AssertStmt') -> Optional[T]: + def visit_assert_stmt(self, o: 'mypy.nodes.AssertStmt') -> T: pass - def visit_del_stmt(self, o: 'mypy.nodes.DelStmt') -> Optional[T]: + def visit_del_stmt(self, o: 'mypy.nodes.DelStmt') -> T: pass - def visit_if_stmt(self, o: 'mypy.nodes.IfStmt') -> Optional[T]: + def visit_if_stmt(self, o: 'mypy.nodes.IfStmt') -> T: pass - def visit_break_stmt(self, o: 'mypy.nodes.BreakStmt') -> Optional[T]: + def visit_break_stmt(self, o: 'mypy.nodes.BreakStmt') -> T: pass - def visit_continue_stmt(self, o: 'mypy.nodes.ContinueStmt') -> Optional[T]: + def visit_continue_stmt(self, o: 'mypy.nodes.ContinueStmt') -> T: pass - def visit_pass_stmt(self, o: 'mypy.nodes.PassStmt') -> Optional[T]: + def visit_pass_stmt(self, o: 'mypy.nodes.PassStmt') -> T: pass - def visit_raise_stmt(self, o: 'mypy.nodes.RaiseStmt') -> Optional[T]: + def visit_raise_stmt(self, o: 'mypy.nodes.RaiseStmt') -> T: pass - def visit_try_stmt(self, o: 'mypy.nodes.TryStmt') -> Optional[T]: + def visit_try_stmt(self, o: 'mypy.nodes.TryStmt') -> T: pass - def visit_with_stmt(self, o: 'mypy.nodes.WithStmt') -> Optional[T]: + def visit_with_stmt(self, o: 'mypy.nodes.WithStmt') -> T: pass - def visit_print_stmt(self, o: 'mypy.nodes.PrintStmt') -> Optional[T]: + def visit_print_stmt(self, o: 'mypy.nodes.PrintStmt') -> T: pass - def visit_exec_stmt(self, o: 'mypy.nodes.ExecStmt') -> Optional[T]: + def visit_exec_stmt(self, o: 'mypy.nodes.ExecStmt') -> T: pass # Expressions (default no-op implementation) - def visit_int_expr(self, o: 'mypy.nodes.IntExpr') -> Optional[T]: + def visit_int_expr(self, o: 'mypy.nodes.IntExpr') -> T: pass - def visit_str_expr(self, o: 'mypy.nodes.StrExpr') -> Optional[T]: + def visit_str_expr(self, o: 'mypy.nodes.StrExpr') -> T: pass - def visit_bytes_expr(self, o: 'mypy.nodes.BytesExpr') -> Optional[T]: + def visit_bytes_expr(self, o: 'mypy.nodes.BytesExpr') -> T: pass - def visit_unicode_expr(self, o: 'mypy.nodes.UnicodeExpr') -> Optional[T]: + def visit_unicode_expr(self, o: 'mypy.nodes.UnicodeExpr') -> T: pass - def visit_float_expr(self, o: 'mypy.nodes.FloatExpr') -> Optional[T]: + def visit_float_expr(self, o: 'mypy.nodes.FloatExpr') -> T: pass - def visit_complex_expr(self, o: 'mypy.nodes.ComplexExpr') -> Optional[T]: + def visit_complex_expr(self, o: 'mypy.nodes.ComplexExpr') -> T: pass - def visit_ellipsis(self, o: 'mypy.nodes.EllipsisExpr') -> Optional[T]: + def visit_ellipsis(self, o: 'mypy.nodes.EllipsisExpr') -> T: pass - def visit_star_expr(self, o: 'mypy.nodes.StarExpr') -> Optional[T]: + def visit_star_expr(self, o: 'mypy.nodes.StarExpr') -> T: pass - def visit_name_expr(self, o: 'mypy.nodes.NameExpr') -> Optional[T]: + def visit_name_expr(self, o: 'mypy.nodes.NameExpr') -> T: pass - def visit_member_expr(self, o: 'mypy.nodes.MemberExpr') -> Optional[T]: + def visit_member_expr(self, o: 'mypy.nodes.MemberExpr') -> T: pass - def visit_yield_from_expr(self, o: 'mypy.nodes.YieldFromExpr') -> Optional[T]: + def visit_yield_from_expr(self, o: 'mypy.nodes.YieldFromExpr') -> T: pass - def visit_yield_expr(self, o: 'mypy.nodes.YieldExpr') -> Optional[T]: + def visit_yield_expr(self, o: 'mypy.nodes.YieldExpr') -> T: pass - def visit_call_expr(self, o: 'mypy.nodes.CallExpr') -> Optional[T]: + def visit_call_expr(self, o: 'mypy.nodes.CallExpr') -> T: pass - def visit_op_expr(self, o: 'mypy.nodes.OpExpr') -> Optional[T]: + def visit_op_expr(self, o: 'mypy.nodes.OpExpr') -> T: pass - def visit_comparison_expr(self, o: 'mypy.nodes.ComparisonExpr') -> Optional[T]: + def visit_comparison_expr(self, o: 'mypy.nodes.ComparisonExpr') -> T: pass - def visit_cast_expr(self, o: 'mypy.nodes.CastExpr') -> Optional[T]: + def visit_cast_expr(self, o: 'mypy.nodes.CastExpr') -> T: pass - def visit_reveal_expr(self, o: 'mypy.nodes.RevealExpr') -> Optional[T]: + def visit_reveal_expr(self, o: 'mypy.nodes.RevealExpr') -> T: pass - def visit_super_expr(self, o: 'mypy.nodes.SuperExpr') -> Optional[T]: + def visit_super_expr(self, o: 'mypy.nodes.SuperExpr') -> T: pass - def visit_assignment_expr(self, o: 'mypy.nodes.AssignmentExpr') -> Optional[T]: + def visit_assignment_expr(self, o: 'mypy.nodes.AssignmentExpr') -> T: pass - def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> Optional[T]: + def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> T: pass - def visit_list_expr(self, o: 'mypy.nodes.ListExpr') -> Optional[T]: + def visit_list_expr(self, o: 'mypy.nodes.ListExpr') -> T: pass - def visit_dict_expr(self, o: 'mypy.nodes.DictExpr') -> Optional[T]: + def visit_dict_expr(self, o: 'mypy.nodes.DictExpr') -> T: pass - def visit_tuple_expr(self, o: 'mypy.nodes.TupleExpr') -> Optional[T]: + def visit_tuple_expr(self, o: 'mypy.nodes.TupleExpr') -> T: pass - def visit_set_expr(self, o: 'mypy.nodes.SetExpr') -> Optional[T]: + def visit_set_expr(self, o: 'mypy.nodes.SetExpr') -> T: pass - def visit_index_expr(self, o: 'mypy.nodes.IndexExpr') -> Optional[T]: + def visit_index_expr(self, o: 'mypy.nodes.IndexExpr') -> T: pass - def visit_type_application(self, o: 'mypy.nodes.TypeApplication') -> Optional[T]: + def visit_type_application(self, o: 'mypy.nodes.TypeApplication') -> T: pass - def visit_lambda_expr(self, o: 'mypy.nodes.LambdaExpr') -> Optional[T]: + def visit_lambda_expr(self, o: 'mypy.nodes.LambdaExpr') -> T: pass - def visit_list_comprehension(self, o: 'mypy.nodes.ListComprehension') -> Optional[T]: + def visit_list_comprehension(self, o: 'mypy.nodes.ListComprehension') -> T: pass - def visit_set_comprehension(self, o: 'mypy.nodes.SetComprehension') -> Optional[T]: + def visit_set_comprehension(self, o: 'mypy.nodes.SetComprehension') -> T: pass def visit_dictionary_comprehension(self, o: 'mypy.nodes.DictionaryComprehension' - ) -> Optional[T]: + ) -> T: pass - def visit_generator_expr(self, o: 'mypy.nodes.GeneratorExpr') -> Optional[T]: + def visit_generator_expr(self, o: 'mypy.nodes.GeneratorExpr') -> T: pass - def visit_slice_expr(self, o: 'mypy.nodes.SliceExpr') -> Optional[T]: + def visit_slice_expr(self, o: 'mypy.nodes.SliceExpr') -> T: pass - def visit_conditional_expr(self, o: 'mypy.nodes.ConditionalExpr') -> Optional[T]: + def visit_conditional_expr(self, o: 'mypy.nodes.ConditionalExpr') -> T: pass - def visit_backquote_expr(self, o: 'mypy.nodes.BackquoteExpr') -> Optional[T]: + def visit_backquote_expr(self, o: 'mypy.nodes.BackquoteExpr') -> T: pass - def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> Optional[T]: + def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> T: pass - def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> Optional[T]: + def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> T: pass - def visit_namedtuple_expr(self, o: 'mypy.nodes.NamedTupleExpr') -> Optional[T]: + def visit_namedtuple_expr(self, o: 'mypy.nodes.NamedTupleExpr') -> T: pass - def visit_enum_call_expr(self, o: 'mypy.nodes.EnumCallExpr') -> Optional[T]: + def visit_enum_call_expr(self, o: 'mypy.nodes.EnumCallExpr') -> T: pass - def visit_typeddict_expr(self, o: 'mypy.nodes.TypedDictExpr') -> Optional[T]: + def visit_typeddict_expr(self, o: 'mypy.nodes.TypedDictExpr') -> T: pass - def visit_newtype_expr(self, o: 'mypy.nodes.NewTypeExpr') -> Optional[T]: + def visit_newtype_expr(self, o: 'mypy.nodes.NewTypeExpr') -> T: pass - def visit__promote_expr(self, o: 'mypy.nodes.PromoteExpr') -> Optional[T]: + def visit__promote_expr(self, o: 'mypy.nodes.PromoteExpr') -> T: pass - def visit_await_expr(self, o: 'mypy.nodes.AwaitExpr') -> Optional[T]: + def visit_await_expr(self, o: 'mypy.nodes.AwaitExpr') -> T: pass - def visit_temp_node(self, o: 'mypy.nodes.TempNode') -> Optional[T]: + def visit_temp_node(self, o: 'mypy.nodes.TempNode') -> T: pass diff --git a/mypy_self_check.ini b/mypy_self_check.ini index cb9eb4b2aa35..1a583fa9264e 100644 --- a/mypy_self_check.ini +++ b/mypy_self_check.ini @@ -18,3 +18,7 @@ show_error_codes = True pretty = True always_false = MYPYC plugins = misc/proper_plugin.py + +[mypy-mypy.visitor] +# See docstring for NodeVisitor for motivation. +warn_no_return = False From a838f830f06aab4cce3c915b1b1fc9e1396b297d Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 8 Dec 2019 23:19:13 +0000 Subject: [PATCH 5/9] Add some docs --- docs/source/class_basics.rst | 22 ++++++++++++++++++++++ docs/source/error_code_list.rst | 25 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/docs/source/class_basics.rst b/docs/source/class_basics.rst index 3a1f731fa8dd..b7ec33c85072 100644 --- a/docs/source/class_basics.rst +++ b/docs/source/class_basics.rst @@ -314,6 +314,28 @@ however: in this case, but any attempt to construct an instance will be flagged as an error. +Mypy allows you to omit the body for an abstract method, but if you do so, +it is unsafe to call such method via ``super()``. For example: + +.. code-block:: python + + from abc import abstractmethod + + class Base: + @abstractmethod + def foo(self) -> int: pass + @abstractmethod + def bar(self) -> int: + return 0 + + class Sub(Base): + def foo(self) -> int: + return super().foo() + 1 # error: Call to abstract method "foo" of "Base" + # with trivial body via super() is unsafe + @abstractmethod + def bar(self) -> int: + return super().bar() + 1 # This is OK however. + A class can inherit any number of classes, both abstract and concrete. As with normal overrides, a dynamically typed method can override or implement a statically typed method defined in any base diff --git a/docs/source/error_code_list.rst b/docs/source/error_code_list.rst index 288748876811..5816a9a94d6e 100644 --- a/docs/source/error_code_list.rst +++ b/docs/source/error_code_list.rst @@ -568,6 +568,31 @@ Example: # Error: Cannot instantiate abstract class 'Thing' with abstract attribute 'save' [abstract] t = Thing() +Check that call to an abstract method via super is valid [safe-super] +--------------------------------------------------------------------- + +Abstract methods often don't have any default implementation, i.e. their +bodies are just empty. Calling such methods in subclasses via ``super()`` +will cause runtime errors, so mypy prevents you from doing so: + +.. code-block:: python + + from abc import abstractmethod + + class Base: + @abstractmethod + def foo(self) -> int: ... + + class Sub(Base): + def foo(self) -> int: + return super().foo() + 1 # error: Call to abstract method "foo" of "Base" with + # trivial body via super() is unsafe [safe-super] + + Sub().foo() # This will crash at runtime. + +Mypy considers the following as trivial bodies: a ``pass`` statement, a literal +ellipsis ``...``, a docstring, and a ``raise NotImplementedError`` statement. + Check the target of NewType [valid-newtype] ------------------------------------------- From 3b12ba9fcf99f3c17a96fa47c38b1785c5d9e250 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 8 Dec 2019 23:54:02 +0000 Subject: [PATCH 6/9] More docs; minor tweaks --- docs/source/protocols.rst | 16 +++++++++++++++- mypy/semanal.py | 6 ++++++ mypy/visitor.py | 6 +++--- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/docs/source/protocols.rst b/docs/source/protocols.rst index 56a57b39ef37..8cf7cbbe9c78 100644 --- a/docs/source/protocols.rst +++ b/docs/source/protocols.rst @@ -373,7 +373,21 @@ protocols. If you explicitly subclass these protocols you can inherit these default implementations. Explicitly including a protocol as a base class is also a way of documenting that your class implements a particular protocol, and it forces mypy to verify that your class -implementation is actually compatible with the protocol. +implementation is actually compatible with the protocol. In particular, +omitting a value for an attribute or a method body will make it implicitly +abstract: + +.. code-block:: python + + class SomeProto(Protocol): + attr: int # Note, no right hand side + def method(self) -> str: ... # Literal ... here + + class ExplicitSubclass(SomeProto): + pass + + ExplicitSubclass() # error: Cannot instantiate abstract class 'ExplicitSubclass' + # with abstract attributes 'attr' and 'method' .. note:: diff --git a/mypy/semanal.py b/mypy/semanal.py index f140ef623f8c..88dc9a46f26a 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -741,6 +741,12 @@ def analyze_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: self.process_overload_impl(defn) def process_overload_impl(self, defn: OverloadedFuncDef) -> None: + """Set flags for an overload implementation. + + Currently this includes is_trivial_body and is_abstract. The latter + is for a trivial body in protocols classes, where it makes the method + implicitly abstract. + """ assert defn.impl is not None impl = defn.impl if isinstance(defn.impl, FuncDef) else defn.impl.func if is_trivial_body(impl.body) and self.is_class_scope() and not self.is_stub_file: diff --git a/mypy/visitor.py b/mypy/visitor.py index 64d7f119e668..cd2d085d0c80 100644 --- a/mypy/visitor.py +++ b/mypy/visitor.py @@ -313,7 +313,8 @@ class NodeVisitor(Generic[T], ExpressionVisitor[T], StatementVisitor[T]): methods. As all methods defined here return None by default, subclasses do not always need to override all the methods. - TODO: make the default return value explicit, then turn off the flag. + TODO: make the default return value explicit, then turn on + the warn_no_return flag in mypy_self_check.ini. """ # Not in superclasses: @@ -511,8 +512,7 @@ def visit_list_comprehension(self, o: 'mypy.nodes.ListComprehension') -> T: def visit_set_comprehension(self, o: 'mypy.nodes.SetComprehension') -> T: pass - def visit_dictionary_comprehension(self, o: 'mypy.nodes.DictionaryComprehension' - ) -> T: + def visit_dictionary_comprehension(self, o: 'mypy.nodes.DictionaryComprehension') -> T: pass def visit_generator_expr(self, o: 'mypy.nodes.GeneratorExpr') -> T: From e2e18c5eb709fc7b2bf6d290587526ee2fad726d Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 9 Dec 2019 00:57:28 +0000 Subject: [PATCH 7/9] Some tweaks and extra tests --- mypy/checkexpr.py | 3 ++- mypy/checkmember.py | 23 +++++++++++------ mypy/semanal.py | 6 +++-- mypy/server/astdiff.py | 2 ++ test-data/unit/check-abstract.test | 38 +++++++++++++++++++++++++++++ test-data/unit/check-protocols.test | 8 +++--- test-data/unit/fine-grained.test | 23 ++++++++++++++++- 7 files changed, 89 insertions(+), 14 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 92164a04b0fe..c889c3373a78 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -866,7 +866,8 @@ def check_callable_call(self, if (callee.is_type_obj() and callee.type_object().is_abstract # Exception for Type[...] and not callee.from_type_type - and not callee.type_object().fallback_to_any): + and not callee.type_object().fallback_to_any + and not callee.type_object().is_protocol): type = callee.type_object() self.msg.cannot_instantiate_abstract_class( callee.type_object().name, type.abstract_attributes, diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 3340bd342063..8265a2dcf1de 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -196,14 +196,20 @@ def analyze_instance_member_access(name: str, # Look up the member. First look up the method dictionary. method = info.get_method(name) if method: + unsafe_super = False if mx.is_super: - if isinstance(method, FuncDef) and method.is_abstract and method.is_trivial_body: - mx.msg.unsafe_super(method.name, method.info.name, mx.context) - if isinstance(method, OverloadedFuncDef): + if isinstance(method, FuncDef) and method.is_trivial_body: + unsafe_super = True + impl = method + elif isinstance(method, OverloadedFuncDef): if method.impl: impl = method.impl if isinstance(method.impl, FuncDef) else method.impl.func - if impl.is_trivial_body and impl.is_abstract: - mx.msg.unsafe_super(method.name, method.info.name, mx.context) + unsafe_super = impl.is_trivial_body + if unsafe_super: + ret_type = (impl.type.ret_type if isinstance(impl.type, CallableType) + else AnyType(TypeOfAny.unannotated)) + if not subtypes.is_subtype(NoneType(), ret_type): + mx.msg.unsafe_super(method.name, method.info.name, mx.context) if method.is_property: assert isinstance(method, OverloadedFuncDef) first_item = cast(Decorator, method.items[0]) @@ -354,8 +360,11 @@ def analyze_member_var_access(name: str, if isinstance(vv, Decorator): # The associated Var node of a decorator contains the type. v = vv.var - if mx.is_super and vv.func.is_abstract and vv.func.is_trivial_body: - mx.msg.unsafe_super(vv.func.name, vv.func.info.name, mx.context) + if mx.is_super and vv.func.is_trivial_body: + ret_type = (vv.func.type.ret_type if isinstance(vv.func.type, CallableType) + else AnyType(TypeOfAny.unannotated)) + if not subtypes.is_subtype(NoneType(), ret_type): + mx.msg.unsafe_super(vv.func.name, vv.func.info.name, mx.context) if isinstance(vv, TypeInfo): # If the associated variable is a TypeInfo synthesize a Var node for diff --git a/mypy/semanal.py b/mypy/semanal.py index 88dc9a46f26a..18fcd5c15728 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -604,13 +604,14 @@ def analyze_func_def(self, defn: FuncDef) -> None: if self.is_class_scope(): assert self.type is not None if is_trivial_body(defn.body) and not self.is_stub_file: - defn.is_trivial_body = True if (self.type.is_protocol and (not isinstance(self.scope.function, OverloadedFuncDef) or defn.is_property)): # Mark protocol methods with empty bodies as implicitly abstract. # This makes explicit protocol subclassing type-safe. defn.is_abstract = True + if defn.is_abstract: + defn.is_trivial_body = True if defn.is_coroutine and isinstance(defn.type, CallableType) and not self.deferred: if defn.is_async_generator: @@ -750,10 +751,11 @@ def process_overload_impl(self, defn: OverloadedFuncDef) -> None: assert defn.impl is not None impl = defn.impl if isinstance(defn.impl, FuncDef) else defn.impl.func if is_trivial_body(impl.body) and self.is_class_scope() and not self.is_stub_file: - impl.is_trivial_body = True assert self.type is not None if not self.is_stub_file and self.type.is_protocol: impl.is_abstract = True + if impl.is_abstract: + impl.is_trivial_body = True def analyze_overload_sigs_and_impl( self, diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index accd9015b021..a95ddd9b41de 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -176,6 +176,8 @@ def snapshot_definition(node: Optional[SymbolNode], signature = snapshot_untyped_signature(node) if isinstance(node, FuncDef): is_trivial_body = node.is_trivial_body + elif isinstance(node, OverloadedFuncDef) and node.impl: + is_trivial_body = node.impl.is_trivial_body else: is_trivial_body = False return ('Func', common, diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test index ee7b02c718a2..5b4e9c2670c2 100644 --- a/test-data/unit/check-abstract.test +++ b/test-data/unit/check-abstract.test @@ -1340,6 +1340,7 @@ class P4(Protocol): [builtins fixtures/classmethod.pyi] [case testEmptyBodyUnsafeAbstractSuper] +# flags: --strict-optional from stub import StubProto, StubAbstract from typing import Protocol from abc import abstractmethod @@ -1388,6 +1389,7 @@ class StubAbstract: def meth(self) -> int: ... [case testEmptyBodyUnsafeAbstractSuperProperty] +# flags: --strict-optional from stub import StubProto, StubAbstract from typing import Protocol from abc import abstractmethod @@ -1445,6 +1447,7 @@ class StubAbstract: [builtins fixtures/property.pyi] [case testEmptyBodyUnsafeAbstractSuperOverloads] +# flags: --strict-optional from stub import StubProto from typing import Protocol, overload, Union @@ -1509,3 +1512,38 @@ class StubProto(Protocol): def meth(self, x: int) -> int: ... [builtins fixtures/exception.pyi] + +[case testEmptyBodyNoSuperWarningWithoutStrict] +# flags: --no-strict-optional +from typing import Protocol +from abc import abstractmethod + +class Proto(Protocol): + def meth(self) -> int: ... +class Abstract: + @abstractmethod + def meth(self) -> int: ... + +class SubProto(Proto): + def meth(self) -> int: + return super().meth() +class SubAbstract(Abstract): + def meth(self) -> int: + return super().meth() + +[case testEmptyBodyNoSuperWarningOptionalReturn] +from typing import Protocol, Optional +from abc import abstractmethod + +class Proto(Protocol): + def meth(self) -> Optional[int]: pass +class Abstract: + @abstractmethod + def meth(self) -> Optional[int]: pass + +class SubProto(Proto): + def meth(self) -> int: + return super().meth() +class SubAbstract(Abstract): + def meth(self) -> int: + return super().meth() diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index c0dae9412911..52f1432cbe33 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -393,7 +393,7 @@ class P(C, Protocol): # E: All bases of a protocol must be protocols class P2(P, D, Protocol): # E: All bases of a protocol must be protocols pass -P2() # E: Cannot instantiate abstract class 'P2' with abstract attribute 'attr' +P2() # E: Cannot instantiate protocol class "P2" p: P2 reveal_type(p.attr) # N: Revealed type is 'builtins.int' @@ -1403,7 +1403,8 @@ f2(z) # E: Argument 1 to "f2" has incompatible type "Union[C, D1]"; expected "P2 from typing import Type, Protocol class P(Protocol): - def m(self) -> None: pass + def m(self) -> None: + return None class P1(Protocol): def m(self) -> None: pass class Pbad(Protocol): @@ -1449,7 +1450,8 @@ f(GoodAlias) from typing import Type, Protocol class P(Protocol): - def m(self) -> None: pass + def m(self) -> None: + return None class B(P): pass class C: def m(self) -> None: diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 0b8581851580..b6395ed4c5bc 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -9290,6 +9290,7 @@ class B: == [case testAbstractBodyTurnsEmpty] +# flags: --strict-optional from b import Base class Sub(Base): @@ -9309,4 +9310,24 @@ class Base: def meth(self) -> int: ... [out] == -main:5: error: Call to abstract method "meth" of "Base" with trivial body via super() is unsafe +main:6: error: Call to abstract method "meth" of "Base" with trivial body via super() is unsafe + +[case testAbstractBodyTurnsEmptyProtocol] +# flags: --strict-optional +from b import Base + +class Sub(Base): + def meth(self) -> int: + return super().meth() + +[file b.py] +from typing import Protocol +class Base(Protocol): + def meth(self) -> int: return 0 +[file b.py.2] +from typing import Protocol +class Base(Protocol): + def meth(self) -> int: ... +[out] +== +main:6: error: Call to abstract method "meth" of "Base" with trivial body via super() is unsafe From 843a24a270faa3e44a607d39c3a03ea1020909ff Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 9 Dec 2019 01:07:51 +0000 Subject: [PATCH 8/9] More fixes --- mypy/server/astdiff.py | 8 ++++---- test-data/unit/check-errorcodes.test | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index a95ddd9b41de..08b93e29ef2f 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -174,12 +174,12 @@ def snapshot_definition(node: Optional[SymbolNode], signature = snapshot_type(node.type) else: signature = snapshot_untyped_signature(node) + impl = None # type: Optional[FuncDef] if isinstance(node, FuncDef): - is_trivial_body = node.is_trivial_body + impl = node elif isinstance(node, OverloadedFuncDef) and node.impl: - is_trivial_body = node.impl.is_trivial_body - else: - is_trivial_body = False + impl = node.impl.func if isinstance(node.impl, Decorator) else node.impl + is_trivial_body = impl.is_trivial_body if impl else False return ('Func', common, node.is_property, node.is_final, node.is_class, node.is_static, diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 5a4cad935abd..d5959e702c80 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -705,6 +705,7 @@ class InvalidReturn: [builtins fixtures/bool.pyi] [case testErrorCodeUnsafeSuper_no_empty] +# flags: --strict-optional from abc import abstractmethod class Base: From 50f610b6848c7b751025455a832cd56916f89c9f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 9 Dec 2019 01:34:31 +0000 Subject: [PATCH 9/9] Fix a test and compilation --- mypy_bootstrap.ini | 4 ++++ test-data/unit/check-abstract.test | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/mypy_bootstrap.ini b/mypy_bootstrap.ini index 3a6eee6449d2..a6a5fa3eac15 100644 --- a/mypy_bootstrap.ini +++ b/mypy_bootstrap.ini @@ -13,3 +13,7 @@ warn_redundant_casts = True warn_unused_configs = True show_traceback = True always_true = MYPYC + +[mypy-mypy.visitor] +# See docstring for NodeVisitor for motivation. +warn_no_return = False diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test index 5b4e9c2670c2..d4e251071af3 100644 --- a/test-data/unit/check-abstract.test +++ b/test-data/unit/check-abstract.test @@ -1532,6 +1532,7 @@ class SubAbstract(Abstract): return super().meth() [case testEmptyBodyNoSuperWarningOptionalReturn] +# flags: --strict-optional from typing import Protocol, Optional from abc import abstractmethod @@ -1542,8 +1543,8 @@ class Abstract: def meth(self) -> Optional[int]: pass class SubProto(Proto): - def meth(self) -> int: + def meth(self) -> Optional[int]: return super().meth() class SubAbstract(Abstract): - def meth(self) -> int: + def meth(self) -> Optional[int]: return super().meth()