Skip to content

Commit

Permalink
Address review notes.
Browse files Browse the repository at this point in the history
- don't provide default plugin arg to parse()
- disallow mutliple sources of type annoations (PEP-3107, comments, docstrings)
- improve error message
  • Loading branch information
chadrik committed Jun 22, 2017
1 parent 752c01c commit 7dac5be
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 74 deletions.
92 changes: 51 additions & 41 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@

TYPE_COMMENT_SYNTAX_ERROR = 'syntax error in type comment'
TYPE_COMMENT_AST_ERROR = 'invalid type comment or annotation'
TYPE_COMMENT_DOCSTRING_ERROR = ('Arguments parsed from docstring are not '
'present in function signature: {} not in {}')
TYPE_COMMENT_DOCSTRING_ERROR = ('One or more arguments specified in docstring are not '
'present in function signature: {}')


def parse(source: Union[str, bytes], fnam: str = None, errors: Errors = None,
Expand Down Expand Up @@ -129,7 +129,7 @@ def parse_docstring(hook: Callable[[DocstringParserContext], TypeMap], docstring
return_type = type_map.pop('return', AnyType())
if type_map:
errors.report(line, 0,
TYPE_COMMENT_DOCSTRING_ERROR.format(list(type_map), arg_names))
TYPE_COMMENT_DOCSTRING_ERROR.format(list(type_map)))
return arg_types, return_type
return None

Expand Down Expand Up @@ -335,48 +335,58 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef],
if no_type_check:
arg_types = [None] * len(args)
return_type = None
elif n.type_comment is not None:
try:
func_type_ast = ast3.parse(n.type_comment, '<func_type>', 'func_type')
assert isinstance(func_type_ast, ast3.FunctionType)
# for ellipsis arg
if (len(func_type_ast.argtypes) == 1 and
isinstance(func_type_ast.argtypes[0], ast3.Ellipsis)):
if n.returns:
# PEP 484 disallows both type annotations and type comments
self.fail(messages.DUPLICATE_TYPE_SIGNATURES, n.lineno, n.col_offset)
arg_types = [a.type_annotation if a.type_annotation is not None else AnyType()
for a in args]
else:
# PEP 484 disallows both type annotations and type comments
if n.returns or any(a.type_annotation is not None for a in args):
self.fail(messages.DUPLICATE_TYPE_SIGNATURES, n.lineno, n.col_offset)
translated_args = (TypeConverter(self.errors, line=n.lineno)
.translate_expr_list(func_type_ast.argtypes))
arg_types = [a if a is not None else AnyType()
for a in translated_args]
return_type = TypeConverter(self.errors,
line=n.lineno).visit(func_type_ast.returns)

# add implicit self type
if self.in_class() and len(arg_types) < len(args):
arg_types.insert(0, AnyType())
except SyntaxError:
self.fail(TYPE_COMMENT_SYNTAX_ERROR, n.lineno, n.col_offset)
arg_types = [AnyType()] * len(args)
return_type = AnyType()
else:
arg_types = [a.type_annotation for a in args]
return_type = TypeConverter(self.errors, line=n.returns.lineno
if n.returns else n.lineno).visit(n.returns)

doc_types = None # type: Optional[Tuple[List[Type], Type]]
docstring_hook = self.plugin.get_docstring_parser_hook()
if docstring_hook is not None and not any(arg_types) and return_type is None:
if docstring_hook is not None:
doc = ast3.get_docstring(n, clean=False)
if doc:
types = parse_docstring(docstring_hook, doc, real_names, n.lineno, self.errors)
if types is not None:
arg_types, return_type = types
doc_types = parse_docstring(docstring_hook, doc, real_names, n.lineno,
self.errors)

if n.type_comment is not None:
if doc_types is not None:
# PEP 484 disallows both type annotations and type comments
self.fail(messages.DUPLICATE_TYPE_SIGNATURES, n.lineno, n.col_offset)
try:
func_type_ast = ast3.parse(n.type_comment, '<func_type>', 'func_type')
assert isinstance(func_type_ast, ast3.FunctionType)
# for ellipsis arg
if (len(func_type_ast.argtypes) == 1 and
isinstance(func_type_ast.argtypes[0], ast3.Ellipsis)):
if n.returns:
# PEP 484 disallows both type annotations and type comments
self.fail(messages.DUPLICATE_TYPE_SIGNATURES, n.lineno, n.col_offset)
arg_types = [a.type_annotation if a.type_annotation is not None
else AnyType() for a in args]
else:
# PEP 484 disallows both type annotations and type comments
if n.returns or any(a.type_annotation is not None for a in args):
self.fail(messages.DUPLICATE_TYPE_SIGNATURES, n.lineno, n.col_offset)
translated_args = (TypeConverter(self.errors, line=n.lineno)
.translate_expr_list(func_type_ast.argtypes))
arg_types = [a if a is not None else AnyType()
for a in translated_args]
return_type = TypeConverter(self.errors,
line=n.lineno).visit(func_type_ast.returns)

# add implicit self type
if self.in_class() and len(arg_types) < len(args):
arg_types.insert(0, AnyType())
except SyntaxError:
self.fail(TYPE_COMMENT_SYNTAX_ERROR, n.lineno, n.col_offset)
arg_types = [AnyType()] * len(args)
return_type = AnyType()
elif doc_types is not None:
# PEP 484 disallows both type annotations and type comments
if (n.type_comment is not None or n.returns or
any(a.type_annotation is not None for a in args)):
self.fail(messages.DUPLICATE_TYPE_SIGNATURES, n.lineno, n.col_offset)
arg_types, return_type = doc_types
else:
arg_types = [a.type_annotation for a in args]
return_type = TypeConverter(self.errors, line=n.returns.lineno
if n.returns else n.lineno).visit(n.returns)

for arg, arg_type in zip(args, arg_types):
self.set_type_optional(arg_type, arg.initializer)
Expand Down
72 changes: 40 additions & 32 deletions mypy/fastparse2.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,42 +299,50 @@ def visit_FunctionDef(self, n: ast27.FunctionDef) -> Statement:
if (n.decorator_list and any(is_no_type_check_decorator(d) for d in n.decorator_list)):
arg_types = [None] * len(args)
return_type = None
elif n.type_comment is not None and len(n.type_comment) > 0:
try:
func_type_ast = ast3.parse(n.type_comment, '<func_type>', 'func_type')
assert isinstance(func_type_ast, ast3.FunctionType)
# for ellipsis arg
if (len(func_type_ast.argtypes) == 1 and
isinstance(func_type_ast.argtypes[0], ast3.Ellipsis)):
arg_types = [a.type_annotation if a.type_annotation is not None else AnyType()
for a in args]
else:
# PEP 484 disallows both type annotations and type comments
if any(a.type_annotation is not None for a in args):
self.fail(messages.DUPLICATE_TYPE_SIGNATURES, n.lineno, n.col_offset)
arg_types = [a if a is not None else AnyType() for
a in converter.translate_expr_list(func_type_ast.argtypes)]
return_type = converter.visit(func_type_ast.returns)

# add implicit self type
if self.in_class() and len(arg_types) < len(args):
arg_types.insert(0, AnyType())
except SyntaxError:
self.fail(TYPE_COMMENT_SYNTAX_ERROR, n.lineno, n.col_offset)
arg_types = [AnyType()] * len(args)
return_type = AnyType()
else:
arg_types = [a.type_annotation for a in args]
return_type = converter.visit(None)

doc_types = None # type: Optional[Tuple[List[Type], Type]]
docstring_hook = self.plugin.get_docstring_parser_hook()
if docstring_hook is not None and not any(arg_types) and return_type is None:
if docstring_hook is not None:
doc = ast27.get_docstring(n, clean=False)
if doc:
types = parse_docstring(docstring_hook, doc.decode('unicode_escape'),
real_names, n.lineno, self.errors)
if types is not None:
arg_types, return_type = types
doc_types = parse_docstring(docstring_hook, doc.decode('unicode-escape'),
real_names, n.lineno, self.errors)

if n.type_comment is not None and len(n.type_comment) > 0:
if doc_types is not None:
# PEP 484 disallows both type annotations and type comments
self.fail(messages.DUPLICATE_TYPE_SIGNATURES, n.lineno, n.col_offset)
try:
func_type_ast = ast3.parse(n.type_comment, '<func_type>', 'func_type')
assert isinstance(func_type_ast, ast3.FunctionType)
# for ellipsis arg
if (len(func_type_ast.argtypes) == 1 and
isinstance(func_type_ast.argtypes[0], ast3.Ellipsis)):
arg_types = [a.type_annotation if a.type_annotation is not None
else AnyType() for a in args]
else:
# PEP 484 disallows both type annotations and type comments
if any(a.type_annotation is not None for a in args):
self.fail(messages.DUPLICATE_TYPE_SIGNATURES, n.lineno, n.col_offset)
arg_types = [a if a is not None else AnyType() for
a in converter.translate_expr_list(func_type_ast.argtypes)]
return_type = converter.visit(func_type_ast.returns)

# add implicit self type
if self.in_class() and len(arg_types) < len(args):
arg_types.insert(0, AnyType())
except SyntaxError:
self.fail(TYPE_COMMENT_SYNTAX_ERROR, n.lineno, n.col_offset)
arg_types = [AnyType()] * len(args)
return_type = AnyType()
elif doc_types is not None:
# PEP 484 disallows both type annotations and type comments
if any(a.type_annotation is not None for a in args):
self.fail(messages.DUPLICATE_TYPE_SIGNATURES, n.lineno, n.col_offset)
arg_types, return_type = doc_types
else:
arg_types = [a.type_annotation for a in args]
return_type = converter.visit(None)

for arg, arg_type in zip(args, arg_types):
self.set_type_optional(arg_type, arg.initializer)
Expand Down
87 changes: 86 additions & 1 deletion test-data/unit/check-docstring-hook.test
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,48 @@ plugins=<ROOT>/test-data/unit/plugins/docstring.py
[out]
main:3: error: invalid type comment or annotation

[case testDuplicateTypeSignaturesAnnotations]
# flags: --config-file tmp/mypy.ini
def f(x: int):
"""
x: int
"""
return None
[file mypy.ini]
[[mypy]
plugins=<ROOT>/test-data/unit/plugins/docstring.py
[out]
main:2: error: Function has duplicate type signatures

[case testDuplicateTypeSignaturesComments]
# flags: --config-file tmp/mypy.ini
def f(x):
# type: (int) -> None
"""
x: int
"""
return None
[file mypy.ini]
[[mypy]
plugins=<ROOT>/test-data/unit/plugins/docstring.py
[out]
main:2: error: Function has duplicate type signatures

[case testDuplicateTypeSignaturesMultiComments]
# flags: --config-file tmp/mypy.ini
def f(x # type: int
):
# type: (...) -> None
"""
x: int
"""
return None
[file mypy.ini]
[[mypy]
plugins=<ROOT>/test-data/unit/plugins/docstring.py
[out]
main:2: error: Function has duplicate type signatures

-- Python 2.7
-- -------------------------

Expand Down Expand Up @@ -81,4 +123,47 @@ class A:
A().f('') # E: Argument 1 to "f" of "A" has incompatible type "str"; expected "int"
[file mypy.ini]
[[mypy]
plugins=<ROOT>/test-data/unit/plugins/docstring.py
plugins=<ROOT>/test-data/unit/plugins/docstring.py

[case testInvalidDocstringAnnotation27]
# flags: --config-file tmp/mypy.ini --python-version 2.7
def f(x):
"""
x: B/A/D
return: None
"""
return None
[file mypy.ini]
[[mypy]
plugins=<ROOT>/test-data/unit/plugins/docstring.py
[out]
main:3: error: invalid type comment or annotation

[case testDuplicateTypeSignaturesComments27]
# flags: --config-file tmp/mypy.ini --python-version 2.7
def f(x):
# type: (int) -> None
"""
x: int
"""
return None
[file mypy.ini]
[[mypy]
plugins=<ROOT>/test-data/unit/plugins/docstring.py
[out]
main:2: error: Function has duplicate type signatures

[case testDuplicateTypeSignaturesMultiComments27]
# flags: --config-file tmp/mypy.ini --python-version 2.7
def f(x # type: int
):
# type: (...) -> None
"""
x: int
"""
return None
[file mypy.ini]
[[mypy]
plugins=<ROOT>/test-data/unit/plugins/docstring.py
[out]
main:2: error: Function has duplicate type signatures

0 comments on commit 7dac5be

Please sign in to comment.