diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 5604e0a51e86..3e667a984ec8 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -124,7 +124,9 @@ def ast3_parse(source: Union[str, bytes], filename: str, mode: str, TYPE_COMMENT_SYNTAX_ERROR = 'syntax error in type comment' # type: Final -TYPE_IGNORE_PATTERN = re.compile(r'[^#]*#\s*type:\s*ignore\s*(\[[^[#]*\]\s*)?($|#)') +INVALID_TYPE_IGNORE = 'Invalid "type: ignore" comment' # type: Final + +TYPE_IGNORE_PATTERN = re.compile(r'[^#]*#\s*type:\s*ignore\s*(.*)') def parse(source: Union[str, bytes], @@ -170,13 +172,21 @@ def parse(source: Union[str, bytes], return tree -def parse_type_ignore_tag(tag: Optional[str]) -> List[str]: - # TODO: Implement proper parsing and error checking - if not tag: +def parse_type_ignore_tag(tag: Optional[str]) -> Optional[List[str]]: + """Parse optional "[code, ...]" tag after "# type: ignore". + + Return: + * [] if no tag was found (ignore all errors) + * list of ignored error codes if a tag was found + * None if the tag was invalid. + """ + if not tag or tag.strip() == '' or tag.strip().startswith('#'): + # No tag -- ignore all errors. return [] - m = re.match(r'\s*\[([^#]*)\]', tag) + m = re.match(r'\s*\[([^]#]*)\]\s*(#.*)?$', tag) if m is None: - return [] + # Invalid "# type: ignore" comment. + return None return [code.strip() for code in m.group(1).split(',')] @@ -206,6 +216,11 @@ def parse_type_comment(type_comment: str, # Typeshed has a non-optional return type for group! tag = cast(Any, extra_ignore).group(1) # type: Optional[str] ignored = parse_type_ignore_tag(tag) # type: Optional[List[str]] + if ignored is None: + if errors is not None: + errors.report(line, column, INVALID_TYPE_IGNORE, code=codes.SYNTAX) + else: + raise SyntaxError else: ignored = None assert isinstance(typ, ast3_Expression) @@ -451,8 +466,13 @@ def translate_module_id(self, id: str) -> str: return id def visit_Module(self, mod: ast3.Module) -> MypyFile: - self.type_ignores = {ti.lineno: parse_type_ignore_tag(ti.tag) # type: ignore[attr-defined] - for ti in mod.type_ignores} + self.type_ignores = {} + for ti in mod.type_ignores: + parsed = parse_type_ignore_tag(ti.tag) # type: ignore[attr-defined] + if parsed is not None: + self.type_ignores[ti.lineno] = parsed + else: + self.fail(INVALID_TYPE_IGNORE, ti.lineno, -1) body = self.fix_function_overloads(self.translate_stmt_list(mod.body, ismodule=True)) return MypyFile(body, self.imports, diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py index 61dd44228a39..9d85ea3486ba 100644 --- a/mypy/fastparse2.py +++ b/mypy/fastparse2.py @@ -47,7 +47,7 @@ from mypy.errors import Errors from mypy.fastparse import ( TypeConverter, parse_type_comment, bytes_to_human_readable_repr, parse_type_ignore_tag, - TYPE_IGNORE_PATTERN + TYPE_IGNORE_PATTERN, INVALID_TYPE_IGNORE ) from mypy.options import Options from mypy.reachability import mark_block_unreachable @@ -342,8 +342,13 @@ def translate_module_id(self, id: str) -> str: return id def visit_Module(self, mod: ast27.Module) -> MypyFile: - self.type_ignores = {ti.lineno: parse_type_ignore_tag(ti.tag) # type: ignore[attr-defined] - for ti in mod.type_ignores} + self.type_ignores = {} + for ti in mod.type_ignores: + parsed = parse_type_ignore_tag(ti.tag) # type: ignore[attr-defined] + if parsed is not None: + self.type_ignores[ti.lineno] = parsed + else: + self.fail(INVALID_TYPE_IGNORE, ti.lineno, -1) body = self.fix_function_overloads(self.translate_stmt_list(mod.body)) return MypyFile(body, self.imports, @@ -553,7 +558,10 @@ def get_type(self, if extra_ignore: tag = cast(Any, extra_ignore).group(1) # type: Optional[str] ignored = parse_type_ignore_tag(tag) - self.type_ignores[converter.line] = ignored + if ignored is None: + self.fail(INVALID_TYPE_IGNORE, converter.line, -1) + else: + self.type_ignores[converter.line] = ignored return typ return None diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index c86b11eb023f..87143a2907c9 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -163,6 +163,55 @@ from defusedxml import xyz # type: ignore[import] import nostub # type: ignore[import] from defusedxml import xyz # type: ignore[import] +[case testErrorCodeBadIgnore] +import nostub # type: ignore xyz # E: Invalid "type: ignore" comment [syntax] +import nostub # type: ignore[ # E: Invalid "type: ignore" comment [syntax] +import nostub # type: ignore[foo # E: Invalid "type: ignore" comment [syntax] +import nostub # type: ignore[foo, # E: Invalid "type: ignore" comment [syntax] +import nostub # type: ignore[foo]] # E: Invalid "type: ignore" comment [syntax] +import nostub # type: ignore[foo][bar] # E: Invalid "type: ignore" comment [syntax] +import nostub # type: ignore[foo] [bar] # E: Invalid "type: ignore" comment [syntax] + +x = 0 # type: ignore[ # E: Invalid "type: ignore" comment [syntax] + +def f(x, # type: int # type: ignore[ # E: Invalid "type: ignore" comment [syntax] + ): + # type: (...) -> None + pass + +[case testErrorCodeBadIgnoreNoExtraComment] +# Omit the E: ... comments, as they affect parsing +import nostub # type: ignore xyz +import nostub # type: ignore[xyz +import nostub # type: ignore[xyz][xyz] +x = 0 # type: ignore[ +def f(x, # type: int # type: ignore[ + ): + # type: (...) -> None + pass +[out] +main:2: error: Invalid "type: ignore" comment [syntax] +main:3: error: Invalid "type: ignore" comment [syntax] +main:4: error: Invalid "type: ignore" comment [syntax] +main:5: error: Invalid "type: ignore" comment [syntax] +main:6: error: Invalid "type: ignore" comment [syntax] + +[case testErrorCodeBadIgnore_python2] +import nostub # type: ignore xyz +import nostub # type: ignore[xyz # Comment [x] +import nostub # type: ignore[xyz][xyz] +x = 0 # type: ignore[ +def f(x, # type: int # type: ignore[ + ): + # type: (...) -> None + pass +[out] +main:1: error: Invalid "type: ignore" comment [syntax] +main:2: error: Invalid "type: ignore" comment [syntax] +main:3: error: Invalid "type: ignore" comment [syntax] +main:4: error: Invalid "type: ignore" comment [syntax] +main:5: error: Invalid "type: ignore" comment [syntax] + [case testErrorCodeArgKindAndCount] def f(x: int) -> None: pass # N: "f" defined here f() # E: Too few arguments for "f" [call-arg] @@ -607,3 +656,19 @@ class A: def g(self: A) -> None: pass A.f = g # E: Cannot assign to a method [assignment] + +[case testErrorCodeTypeIgnoreMisspelled1] +x = y # type: ignored[foo] +xx = y # type: ignored [foo] +[out] +main:1: error: Name 'ignored' is not defined [name-defined] +main:1: error: Name 'y' is not defined [name-defined] +main:2: error: Name 'ignored' is not defined [name-defined] +main:2: error: Name 'y' is not defined [name-defined] + +[case testErrorCodeTypeIgnoreMisspelled2] +x = y # type: int # type: ignored[foo] +x = y # type: int # type: ignored [foo] +[out] +main:1: error: syntax error in type comment 'int' [syntax] +main:2: error: syntax error in type comment 'int' [syntax]
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: