Skip to content

Commit

Permalink
Make reported line and column numbers more precise (#7578)
Browse files Browse the repository at this point in the history
Most notably, report more precise error locations for incompatible
return value types and incompatible default argument values.

Also add some tests.

Work towards #7053.
  • Loading branch information
JukkaL committed Oct 7, 2019
1 parent ad90d62 commit cef7a63
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 19 deletions.
28 changes: 19 additions & 9 deletions mypy/checker.py
Expand Up @@ -996,8 +996,13 @@ def check_default_args(self, item: FuncItem, body_is_trivial: bool) -> None:
msg += "tuple argument {}".format(name[12:])
else:
msg += 'argument "{}"'.format(name)
self.check_simple_assignment(arg.variable.type, arg.initializer,
context=arg, msg=msg, lvalue_name='argument', rvalue_name='default',
self.check_simple_assignment(
arg.variable.type,
arg.initializer,
context=arg.initializer,
msg=msg,
lvalue_name='argument',
rvalue_name='default',
code=codes.ASSIGNMENT)

def is_forward_op_method(self, method_name: str) -> bool:
Expand Down Expand Up @@ -3002,7 +3007,8 @@ def check_return_stmt(self, s: ReturnStmt) -> None:
subtype=typ,
supertype_label='expected',
supertype=return_type,
context=s,
context=s.expr,
outer_context=s,
msg=message_registry.INCOMPATIBLE_RETURN_VALUE_TYPE,
code=codes.RETURN_VALUE)
else:
Expand Down Expand Up @@ -3806,13 +3812,17 @@ def find_isinstance_check(self, node: Expression
# Helpers
#

def check_subtype(self, subtype: Type, supertype: Type, context: Context,
def check_subtype(self,
subtype: Type,
supertype: Type,
context: Context,
msg: str = message_registry.INCOMPATIBLE_TYPES,
subtype_label: Optional[str] = None,
supertype_label: Optional[str] = None, *,
code: Optional[ErrorCode] = None) -> bool:
"""Generate an error if the subtype is not compatible with
supertype."""
supertype_label: Optional[str] = None,
*,
code: Optional[ErrorCode] = None,
outer_context: Optional[Context] = None) -> bool:
"""Generate an error if the subtype is not compatible with supertype."""
if is_subtype(subtype, supertype):
return True

Expand All @@ -3830,7 +3840,7 @@ def check_subtype(self, subtype: Type, supertype: Type, context: Context,
extra_info.append(subtype_label + ' ' + subtype_str)
if supertype_label is not None:
extra_info.append(supertype_label + ' ' + supertype_str)
note_msg = make_inferred_type_note(context, subtype,
note_msg = make_inferred_type_note(outer_context or context, subtype,
supertype, supertype_str)
if isinstance(subtype, Instance) and isinstance(supertype, Instance):
notes = append_invariance_notes([], subtype, supertype)
Expand Down
4 changes: 2 additions & 2 deletions mypy/fastparse.py
Expand Up @@ -206,11 +206,11 @@ def parse_type_comment(type_comment: str,
"""
try:
typ = ast3_parse(type_comment, '<type_comment>', 'eval')
except SyntaxError as e:
except SyntaxError:
if errors is not None:
stripped_type = type_comment.split("#", 2)[0].strip()
err_msg = "{} '{}'".format(TYPE_COMMENT_SYNTAX_ERROR, stripped_type)
errors.report(line, e.offset, err_msg, blocker=True, code=codes.SYNTAX)
errors.report(line, column, err_msg, blocker=True, code=codes.SYNTAX)
return None, None
else:
raise
Expand Down
6 changes: 4 additions & 2 deletions mypy/messages.py
Expand Up @@ -1873,8 +1873,10 @@ def append_invariance_notes(notes: List[str], arg_type: Instance,
return notes


def make_inferred_type_note(context: Context, subtype: Type,
supertype: Type, supertype_str: str) -> str:
def make_inferred_type_note(context: Context,
subtype: Type,
supertype: Type,
supertype_str: str) -> str:
"""Explain that the user may have forgotten to type a variable.
The user does not expect an error if the inferred container type is the same as the return
Expand Down
2 changes: 1 addition & 1 deletion mypy/nodes.py
Expand Up @@ -559,7 +559,7 @@ def set_line(self,
end_line: Optional[int] = None) -> None:
super().set_line(target, column, end_line)

if self.initializer:
if self.initializer and self.initializer.line < 0:
self.initializer.set_line(self.line, self.column, self.end_line)

self.variable.set_line(self.line, self.column, self.end_line)
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-classes.test
Expand Up @@ -2973,8 +2973,8 @@ def error(u_c: Type[U]) -> P:
return new_pro(u_c) # Error here, see below
[out]
main:11: note: Revealed type is '__main__.WizUser*'
main:13: error: Incompatible return value type (got "U", expected "P")
main:13: error: Value of type variable "P" of "new_pro" cannot be "U"
main:13: error: Incompatible return value type (got "U", expected "P")

[case testTypeUsingTypeCCovariance]
from typing import Type, TypeVar
Expand Down
30 changes: 26 additions & 4 deletions test-data/unit/check-columns.test
Expand Up @@ -10,8 +10,8 @@ main:2:5: error: invalid syntax
import typing
def f() -> 'A':
def g() -> 'B':
return A() # E:9: Incompatible return value type (got "A", expected "B")
return B() # E:5: Incompatible return value type (got "B", expected "A")
return A() # E:16: Incompatible return value type (got "A", expected "B")
return B() # E:12: Incompatible return value type (got "B", expected "A")
class A: pass
class B: pass

Expand Down Expand Up @@ -133,9 +133,12 @@ def foobar(): pass

[builtins fixtures/module.pyi]

[case testColumnUnexpectedKeywordArg]
[case testColumnUnexpectedOrMissingKeywordArg]
def f(): pass
# TODO: Point to "x" instead
(f(x=1)) # E:2: Unexpected keyword argument "x" for "f"
def g(*, x: int) -> None: pass
(g()) # E:2: Missing named argument "x" for "g"

[case testColumnDefinedHere]
class A: pass
Expand Down Expand Up @@ -203,6 +206,14 @@ def f(x, y): pass
(f()) # E:2: Too few arguments for "f"
(f(y=1)) # E:2: Missing positional argument "x" in call to "f"

[case testColumnTooFewSuperArgs_python2]
class A:
def f(self):
pass
class B(A):
def f(self): # type: () -> None
super().f() # E:9: Too few arguments for "super"

[case testColumnListOrDictItemHasIncompatibleType]
from typing import List, Dict
x: List[int] = [
Expand Down Expand Up @@ -258,7 +269,7 @@ if int():

[case testColumnIncompatibleDefault]
if int():
def f(x: int = '') -> None: # E:5: Incompatible default for argument "x" (default has type "str", argument has type "int")
def f(x: int = '') -> None: # E:20: Incompatible default for argument "x" (default has type "str", argument has type "int")
pass

[case testColumnMissingProtocolMember]
Expand Down Expand Up @@ -326,6 +337,13 @@ if int():
main:2:11: error: Syntax error in type annotation
main:2:11: note: Suggestion: Is there a spurious trailing comma?

[case testColumnSyntaxErrorInTypeAnnotation2]
if int():
# TODO: It would be better to point to the type comment
xyz = 0 # type: blurbnard blarb
[out]
main:3:5: error: syntax error in type comment 'blurbnard blarb'

[case testColumnProperty]
class A:
@property
Expand Down Expand Up @@ -383,3 +401,7 @@ def f(x: T) -> T:
n: int = '' # E:14: Incompatible types in assignment (expression has type "str", variable has type "int")
return x
[builtins fixtures/list.pyi]

[case testColumnReturnValueExpected]
def f() -> int:
return # E:5: Return value expected

0 comments on commit cef7a63

Please sign in to comment.