Skip to content

Commit cef7a63

Browse files
authored
Make reported line and column numbers more precise (#7578)
Most notably, report more precise error locations for incompatible return value types and incompatible default argument values. Also add some tests. Work towards #7053.
1 parent ad90d62 commit cef7a63

File tree

6 files changed

+53
-19
lines changed

6 files changed

+53
-19
lines changed

mypy/checker.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -996,8 +996,13 @@ def check_default_args(self, item: FuncItem, body_is_trivial: bool) -> None:
996996
msg += "tuple argument {}".format(name[12:])
997997
else:
998998
msg += 'argument "{}"'.format(name)
999-
self.check_simple_assignment(arg.variable.type, arg.initializer,
1000-
context=arg, msg=msg, lvalue_name='argument', rvalue_name='default',
999+
self.check_simple_assignment(
1000+
arg.variable.type,
1001+
arg.initializer,
1002+
context=arg.initializer,
1003+
msg=msg,
1004+
lvalue_name='argument',
1005+
rvalue_name='default',
10011006
code=codes.ASSIGNMENT)
10021007

10031008
def is_forward_op_method(self, method_name: str) -> bool:
@@ -3002,7 +3007,8 @@ def check_return_stmt(self, s: ReturnStmt) -> None:
30023007
subtype=typ,
30033008
supertype_label='expected',
30043009
supertype=return_type,
3005-
context=s,
3010+
context=s.expr,
3011+
outer_context=s,
30063012
msg=message_registry.INCOMPATIBLE_RETURN_VALUE_TYPE,
30073013
code=codes.RETURN_VALUE)
30083014
else:
@@ -3806,13 +3812,17 @@ def find_isinstance_check(self, node: Expression
38063812
# Helpers
38073813
#
38083814

3809-
def check_subtype(self, subtype: Type, supertype: Type, context: Context,
3815+
def check_subtype(self,
3816+
subtype: Type,
3817+
supertype: Type,
3818+
context: Context,
38103819
msg: str = message_registry.INCOMPATIBLE_TYPES,
38113820
subtype_label: Optional[str] = None,
3812-
supertype_label: Optional[str] = None, *,
3813-
code: Optional[ErrorCode] = None) -> bool:
3814-
"""Generate an error if the subtype is not compatible with
3815-
supertype."""
3821+
supertype_label: Optional[str] = None,
3822+
*,
3823+
code: Optional[ErrorCode] = None,
3824+
outer_context: Optional[Context] = None) -> bool:
3825+
"""Generate an error if the subtype is not compatible with supertype."""
38163826
if is_subtype(subtype, supertype):
38173827
return True
38183828

@@ -3830,7 +3840,7 @@ def check_subtype(self, subtype: Type, supertype: Type, context: Context,
38303840
extra_info.append(subtype_label + ' ' + subtype_str)
38313841
if supertype_label is not None:
38323842
extra_info.append(supertype_label + ' ' + supertype_str)
3833-
note_msg = make_inferred_type_note(context, subtype,
3843+
note_msg = make_inferred_type_note(outer_context or context, subtype,
38343844
supertype, supertype_str)
38353845
if isinstance(subtype, Instance) and isinstance(supertype, Instance):
38363846
notes = append_invariance_notes([], subtype, supertype)

mypy/fastparse.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,11 @@ def parse_type_comment(type_comment: str,
206206
"""
207207
try:
208208
typ = ast3_parse(type_comment, '<type_comment>', 'eval')
209-
except SyntaxError as e:
209+
except SyntaxError:
210210
if errors is not None:
211211
stripped_type = type_comment.split("#", 2)[0].strip()
212212
err_msg = "{} '{}'".format(TYPE_COMMENT_SYNTAX_ERROR, stripped_type)
213-
errors.report(line, e.offset, err_msg, blocker=True, code=codes.SYNTAX)
213+
errors.report(line, column, err_msg, blocker=True, code=codes.SYNTAX)
214214
return None, None
215215
else:
216216
raise

mypy/messages.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1873,8 +1873,10 @@ def append_invariance_notes(notes: List[str], arg_type: Instance,
18731873
return notes
18741874

18751875

1876-
def make_inferred_type_note(context: Context, subtype: Type,
1877-
supertype: Type, supertype_str: str) -> str:
1876+
def make_inferred_type_note(context: Context,
1877+
subtype: Type,
1878+
supertype: Type,
1879+
supertype_str: str) -> str:
18781880
"""Explain that the user may have forgotten to type a variable.
18791881
18801882
The user does not expect an error if the inferred container type is the same as the return

mypy/nodes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ def set_line(self,
559559
end_line: Optional[int] = None) -> None:
560560
super().set_line(target, column, end_line)
561561

562-
if self.initializer:
562+
if self.initializer and self.initializer.line < 0:
563563
self.initializer.set_line(self.line, self.column, self.end_line)
564564

565565
self.variable.set_line(self.line, self.column, self.end_line)

test-data/unit/check-classes.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2973,8 +2973,8 @@ def error(u_c: Type[U]) -> P:
29732973
return new_pro(u_c) # Error here, see below
29742974
[out]
29752975
main:11: note: Revealed type is '__main__.WizUser*'
2976-
main:13: error: Incompatible return value type (got "U", expected "P")
29772976
main:13: error: Value of type variable "P" of "new_pro" cannot be "U"
2977+
main:13: error: Incompatible return value type (got "U", expected "P")
29782978

29792979
[case testTypeUsingTypeCCovariance]
29802980
from typing import Type, TypeVar

test-data/unit/check-columns.test

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ main:2:5: error: invalid syntax
1010
import typing
1111
def f() -> 'A':
1212
def g() -> 'B':
13-
return A() # E:9: Incompatible return value type (got "A", expected "B")
14-
return B() # E:5: Incompatible return value type (got "B", expected "A")
13+
return A() # E:16: Incompatible return value type (got "A", expected "B")
14+
return B() # E:12: Incompatible return value type (got "B", expected "A")
1515
class A: pass
1616
class B: pass
1717

@@ -133,9 +133,12 @@ def foobar(): pass
133133

134134
[builtins fixtures/module.pyi]
135135

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

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

209+
[case testColumnTooFewSuperArgs_python2]
210+
class A:
211+
def f(self):
212+
pass
213+
class B(A):
214+
def f(self): # type: () -> None
215+
super().f() # E:9: Too few arguments for "super"
216+
206217
[case testColumnListOrDictItemHasIncompatibleType]
207218
from typing import List, Dict
208219
x: List[int] = [
@@ -258,7 +269,7 @@ if int():
258269

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

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

340+
[case testColumnSyntaxErrorInTypeAnnotation2]
341+
if int():
342+
# TODO: It would be better to point to the type comment
343+
xyz = 0 # type: blurbnard blarb
344+
[out]
345+
main:3:5: error: syntax error in type comment 'blurbnard blarb'
346+
329347
[case testColumnProperty]
330348
class A:
331349
@property
@@ -383,3 +401,7 @@ def f(x: T) -> T:
383401
n: int = '' # E:14: Incompatible types in assignment (expression has type "str", variable has type "int")
384402
return x
385403
[builtins fixtures/list.pyi]
404+
405+
[case testColumnReturnValueExpected]
406+
def f() -> int:
407+
return # E:5: Return value expected

0 commit comments

Comments
 (0)