Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,15 @@ def find(f: Callable[[V], bool], seq: Sequence[V]) -> V:
return None


def is_no_type_check_decorator(expr: ast35.expr) -> bool:
if isinstance(expr, ast35.Name):
return expr.id == 'no_type_check'
elif isinstance(expr, ast35.Attribute):
if isinstance(expr.value, ast35.Name):
return expr.value.id == 'typing' and expr.attr == 'no_type_check'
return False


class ASTConverter(ast35.NodeTransformer):
def __init__(self,
pyversion: Tuple[int, int],
Expand Down Expand Up @@ -268,15 +277,21 @@ def visit_AsyncFunctionDef(self, n: ast35.AsyncFunctionDef) -> Union[FuncDef, De
def do_func_def(self, n: Union[ast35.FunctionDef, ast35.AsyncFunctionDef],
is_coroutine: bool = False) -> Union[FuncDef, Decorator]:
"""Helper shared between visit_FunctionDef and visit_AsyncFunctionDef."""
args = self.transform_args(n.args, n.lineno)
no_type_check = bool(n.decorator_list and
any(is_no_type_check_decorator(d) for d in n.decorator_list))

args = self.transform_args(n.args, n.lineno, no_type_check=no_type_check)

arg_kinds = [arg.kind for arg in args]
arg_names = [arg.variable.name() for arg in args] # type: List[Optional[str]]
arg_names = [None if argument_elide_name(name) else name for name in arg_names]
if special_function_elide_names(n.name):
arg_names = [None] * len(arg_names)
arg_types = None # type: List[Type]
if n.type_comment is not None:
if no_type_check:
arg_types = [None] * len(args)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I should have mentioned there's another like this in fastparse2.py.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, should've realized.

return_type = None
elif n.type_comment is not None:
try:
func_type_ast = ast35.parse(n.type_comment, '<func_type>', 'func_type')
assert isinstance(func_type_ast, ast35.FunctionType)
Expand All @@ -298,7 +313,7 @@ def do_func_def(self, n: Union[ast35.FunctionDef, ast35.AsyncFunctionDef],
arg_types.insert(0, AnyType())
except SyntaxError:
self.fail(TYPE_COMMENT_SYNTAX_ERROR, n.lineno, n.col_offset)
arg_types = [AnyType() for _ in args]
arg_types = [AnyType()] * len(args)
return_type = AnyType()
else:
arg_types = [a.type_annotation for a in args]
Expand All @@ -312,7 +327,10 @@ def do_func_def(self, n: Union[ast35.FunctionDef, ast35.AsyncFunctionDef],

func_type = None
if any(arg_types) or return_type:
if len(arg_types) > len(arg_kinds):
if len(arg_types) != 1 and any(isinstance(t, EllipsisType) for t in arg_types):
self.fail("Ellipses cannot accompany other argument types "
"in function type signature.", n.lineno, 0)
elif len(arg_types) > len(arg_kinds):
self.fail('Type signature has too many arguments', n.lineno, 0)
elif len(arg_types) < len(arg_kinds):
self.fail('Type signature has too few arguments', n.lineno, 0)
Expand Down Expand Up @@ -356,9 +374,16 @@ def set_type_optional(self, type: Type, initializer: Expression) -> None:
if isinstance(type, UnboundType):
type.optional = optional

def transform_args(self, args: ast35.arguments, line: int) -> List[Argument]:
def transform_args(self,
args: ast35.arguments,
line: int,
no_type_check: bool = False,
) -> List[Argument]:
def make_argument(arg: ast35.arg, default: Optional[ast35.expr], kind: int) -> Argument:
arg_type = TypeConverter(self.errors, line=line).visit(arg.annotation)
if no_type_check:
arg_type = None
else:
arg_type = TypeConverter(self.errors, line=line).visit(arg.annotation)
return Argument(Var(arg.arg), arg_type, self.visit(default), kind)

new_args = []
Expand Down Expand Up @@ -752,7 +777,7 @@ def is_star2arg(k: ast35.keyword) -> bool:
return CallExpr(self.visit(n.func),
arg_types,
arg_kinds,
cast("List[str]", [None for _ in n.args]) + [k.arg for k in n.keywords])
cast("List[str]", [None] * len(n.args)) + [k.arg for k in n.keywords])

# Num(object n) -- a number as a PyObject.
@with_line
Expand Down
23 changes: 19 additions & 4 deletions mypy/fastparse2.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
ARG_POS, ARG_OPT, ARG_STAR, ARG_NAMED, ARG_STAR2
)
from mypy.types import (
Type, CallableType, AnyType, UnboundType,
Type, CallableType, AnyType, UnboundType, EllipsisType
)
from mypy import defaults
from mypy import experiments
Expand Down Expand Up @@ -114,6 +114,15 @@ def find(f: Callable[[V], bool], seq: Sequence[V]) -> V:
return None


def is_no_type_check_decorator(expr: ast27.expr) -> bool:
if isinstance(expr, ast27.Name):
return expr.id == 'no_type_check'
elif isinstance(expr, ast27.Attribute):
if isinstance(expr.value, ast27.Name):
return expr.value.id == 'typing' and expr.attr == 'no_type_check'
return False


class ASTConverter(ast27.NodeTransformer):
def __init__(self,
pyversion: Tuple[int, int],
Expand Down Expand Up @@ -274,7 +283,10 @@ def visit_FunctionDef(self, n: ast27.FunctionDef) -> Statement:
arg_names = [None] * len(arg_names)

arg_types = None # type: List[Type]
if n.type_comment is not None and len(n.type_comment) > 0:
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 = ast35.parse(n.type_comment, '<func_type>', 'func_type')
assert isinstance(func_type_ast, ast35.FunctionType)
Expand All @@ -293,7 +305,7 @@ def visit_FunctionDef(self, n: ast27.FunctionDef) -> Statement:
arg_types.insert(0, AnyType())
except SyntaxError:
self.fail(TYPE_COMMENT_SYNTAX_ERROR, n.lineno, n.col_offset)
arg_types = [AnyType() for _ in args]
arg_types = [AnyType()] * len(args)
return_type = AnyType()
else:
arg_types = [a.type_annotation for a in args]
Expand All @@ -307,7 +319,10 @@ def visit_FunctionDef(self, n: ast27.FunctionDef) -> Statement:

func_type = None
if any(arg_types) or return_type:
if len(arg_types) > len(arg_kinds):
if len(arg_types) != 1 and any(isinstance(t, EllipsisType) for t in arg_types):
self.fail("Ellipses cannot accompany other argument types "
"in function type signature.", n.lineno, 0)
elif len(arg_types) > len(arg_kinds):
self.fail('Type signature has too many arguments', n.lineno, 0)
elif len(arg_types) < len(arg_kinds):
self.fail('Type signature has too few arguments', n.lineno, 0)
Expand Down
3 changes: 2 additions & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1404,10 +1404,11 @@ def store_declared_types(self, lvalue: Lvalue, typ: Type) -> None:
self.fail('Tuple type expected for multiple variables',
lvalue)
elif isinstance(lvalue, StarExpr):
# Historical behavior for the old parser
if isinstance(typ, StarType):
self.store_declared_types(lvalue.expr, typ.type)
else:
self.fail('Star type expected for starred expression', lvalue)
self.store_declared_types(lvalue.expr, typ)
else:
# This has been flagged elsewhere as an error, so just ignore here.
pass
Expand Down
6 changes: 3 additions & 3 deletions mypy/test/testcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,8 @@

# List of files that contain test case descriptions.
files = [
'check-columns.test',
'check-expressions.test',
'check-functions.test',
'check-generic-subtyping.test',
'check-tuples.test',
'check-varargs.test',
]
fast_parser_files = [
Expand Down Expand Up @@ -73,6 +70,9 @@
'check-class-namedtuple.test',
'check-selftype.test',
'check-python2.test',
'check-columns.test',
'check-functions.test',
'check-tuples.test',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot comment on lines 81 ('check-newsyntax.test') and 84 ('check-underscores.test'), but I think those two could be changed from files.append(...) to fast_parser_files.append(...)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch -- I'll do that in my next PR with the rest of the fixes.

]

files.extend(fast_parser_files)
Expand Down
8 changes: 4 additions & 4 deletions test-data/unit/check-columns.test
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# flags: --show-column-numbers
1 +
[out]
main:2:3: error: Parse error before end of line
main:2:4: error: invalid syntax


[case testColumnsNestedFunctions]
Expand Down Expand Up @@ -40,9 +40,9 @@ class A:
pass
A().f()
A().f(1)
A().f('') # E:5: Argument 1 to "f" of "A" has incompatible type "str"; expected "int"
A().f(1, 1) # E:5: Argument 2 to "f" of "A" has incompatible type "int"; expected "str"
A().f(1, 'hello', 'hi') # E:5: Too many arguments for "f" of "A"
A().f('') # E:0: Argument 1 to "f" of "A" has incompatible type "str"; expected "int"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little sad that the column numbers are no longer correct. Can you file an issue to fix that later?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into this briefly, and it appeared that the parser just associates the wrong number with the call. Filed #2749 to look into it further.

A().f(1, 1) # E:0: Argument 2 to "f" of "A" has incompatible type "int"; expected "str"
A().f(1, 'hello', 'hi') # E:0: Too many arguments for "f" of "A"

[case testColumnsMultipleStatementsPerLine]
# flags: --show-column-numbers
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -1475,13 +1475,13 @@ class A:
def f(x, y, z): # type: (..., int) -> None
pass
[out]
main:1: error: Parse error before ): Ellipses cannot accompany other argument types in function type signature.
main:1: error: Ellipses cannot accompany other argument types in function type signature.

[case testEllipsisWithSomethingBeforeItFails]
def f(x, y, z): # type: (int, ...) -> None
pass
[out]
main:1: error: Parse error before ): Ellipses cannot accompany other argument types in function type signature.
main:1: error: Ellipses cannot accompany other argument types in function type signature.

[case testRejectCovariantArgument]
from typing import TypeVar, Generic
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-python2.test
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ def f(x, y, z): # type: (...) -> None
def f(x, y, z): # type: (..., int) -> None
pass
[out]
main:1: error: Type signature has too few arguments
main:1: error: Ellipses cannot accompany other argument types in function type signature.

[case testLambdaTupleArgInPython2]
f = lambda (x, y): x + y
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-tuples.test
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ aa, bb, *cc = t # E: Need type annotation for variable
[case testAssignmentToStarAnnotation]
from typing import List
li, lo = None, None # type: List[int], List[object]
a, b, *c = 1, 2 # type: int, int, *List[int]
a, b, *c = 1, 2 # type: int, int, List[int]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, interesting. This is nowhere spelled out in PEP 484. But given that it was always List[int] I think it's more correct without the star. I filed python/typing#363 to clarify this in the PEP.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is more correct too.

c = lo # E: Incompatible types in assignment (expression has type List[object], variable has type List[int])
c = li
[builtins fixtures/list.pyi]
Expand Down
20 changes: 0 additions & 20 deletions test-data/unit/semanal-errors.test
Original file line number Diff line number Diff line change
Expand Up @@ -149,16 +149,6 @@ z = 0 # type: x
main:4: error: Invalid type "__main__.f"
main:5: error: Invalid type "__main__.x"

[case testTwoStarsInType]
import typing
x, x2 = 1 # type: *object, *object
y, y2 = 1 # type: object, (*object, *object)
z, z2 = 1 # type: *object, (object, *object)
[out]
main:2: error: At most one star type allowed in a tuple
main:3: error: At most one star type allowed in a tuple
main:4: error: Star type only allowed for starred expressions

[case testGlobalVarRedefinition]
import typing
class A: pass
Expand Down Expand Up @@ -419,16 +409,6 @@ main:4: error: Invalid assignment target
main:5: error: Invalid assignment target
main:6: error: Invalid assignment target

[case testInvalidStarType]
a = 1 # type: *int
[out]
main:1: error: Star type only allowed for starred expressions

[case testInvalidStarType]
*a, b = 1 # type: int, int
[out]
main:1: error: Star type expected for starred expression

[case testTwoStarExpressions]
a, *b, *c = 1
*a, (*b, c) = 1
Expand Down