Skip to content

Commit cef7a63

Browse files
authored
Make reported line and column numbers more precise (python#7578)
Most notably, report more precise error locations for incompatible return value types and incompatible default argument values. Also add some tests. Work towards python#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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

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:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy