Skip to content

Commit 0447473

Browse files
sid-kapilevkivskyi
authored andcommitted
Improve "Name already defined" error message (#5067)
In semanal.py, name_already_defined takes an optional original_ctx: Optional[SymbolTableNode] argument which is used to print the line number of the original context where a name was defined already. In many cases, the code doesn't pass anything for this argument, even though there is an original context that makes sense. This PR does this for the name_already_defined call in add_symbol, as discussed in #4722.
1 parent d3b32b7 commit 0447473

10 files changed

+73
-52
lines changed

mypy/semanal.py

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,8 @@ def _visit_func_def(self, defn: FuncDef) -> None:
395395
# Redefinition. Conditional redefinition is okay.
396396
n = self.type.names[defn.name()].node
397397
if not self.set_original_def(n, defn):
398-
self.name_already_defined(defn.name(), defn)
398+
self.name_already_defined(defn.name(), defn,
399+
self.type.names[defn.name()])
399400
self.type.names[defn.name()] = SymbolTableNode(MDEF, defn)
400401
self.prepare_method_signature(defn, self.type)
401402
elif self.is_func_scope():
@@ -406,7 +407,8 @@ def _visit_func_def(self, defn: FuncDef) -> None:
406407
# Redefinition. Conditional redefinition is okay.
407408
n = self.locals[-1][defn.name()].node
408409
if not self.set_original_def(n, defn):
409-
self.name_already_defined(defn.name(), defn)
410+
self.name_already_defined(defn.name(), defn,
411+
self.locals[-1][defn.name()])
410412
else:
411413
self.add_local(defn, defn)
412414
else:
@@ -554,9 +556,9 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
554556
"must come last", defn.items[idx])
555557
else:
556558
for idx in non_overload_indexes[1:]:
557-
self.name_already_defined(defn.name(), defn.items[idx])
559+
self.name_already_defined(defn.name(), defn.items[idx], first_item)
558560
if defn.impl:
559-
self.name_already_defined(defn.name(), defn.impl)
561+
self.name_already_defined(defn.name(), defn.impl, first_item)
560562
# Remove the non-overloads
561563
for idx in reversed(non_overload_indexes):
562564
del defn.items[idx]
@@ -1851,7 +1853,19 @@ def analyze_lvalue(self, lval: Lvalue, nested: bool = False,
18511853
self.type.names[lval.name] = SymbolTableNode(MDEF, v)
18521854
elif explicit_type:
18531855
# Don't re-bind types
1854-
self.name_already_defined(lval.name, lval)
1856+
global_def = self.globals.get(lval.name)
1857+
if self.locals:
1858+
locals_last = self.locals[-1]
1859+
if locals_last:
1860+
local_def = locals_last.get(lval.name)
1861+
else:
1862+
local_def = None
1863+
else:
1864+
local_def = None
1865+
type_def = self.type.names.get(lval.name) if self.type else None
1866+
1867+
original_def = global_def or local_def or type_def
1868+
self.name_already_defined(lval.name, lval, original_def)
18551869
else:
18561870
# Bind to an existing name.
18571871
lval.accept(self)
@@ -3180,7 +3194,7 @@ def add_symbol(self, name: str, node: SymbolTableNode,
31803194
# Flag redefinition unless this is a reimport of a module.
31813195
if not (node.kind == MODULE_REF and
31823196
self.locals[-1][name].node == node.node):
3183-
self.name_already_defined(name, context)
3197+
self.name_already_defined(name, context, self.locals[-1][name])
31843198
self.locals[-1][name] = node
31853199
elif self.type:
31863200
self.type.names[name] = node
@@ -3197,14 +3211,14 @@ def add_symbol(self, name: str, node: SymbolTableNode,
31973211
if existing.type and node.type and is_same_type(existing.type, node.type):
31983212
ok = True
31993213
if not ok:
3200-
self.name_already_defined(name, context)
3214+
self.name_already_defined(name, context, existing)
32013215
self.globals[name] = node
32023216

32033217
def add_local(self, node: Union[Var, FuncDef, OverloadedFuncDef], ctx: Context) -> None:
32043218
assert self.locals[-1] is not None, "Should not add locals outside a function"
32053219
name = node.name()
32063220
if name in self.locals[-1]:
3207-
self.name_already_defined(name, ctx)
3221+
self.name_already_defined(name, ctx, self.locals[-1][name])
32083222
node._fullname = name
32093223
self.locals[-1][name] = SymbolTableNode(LDEF, node)
32103224

@@ -3238,14 +3252,21 @@ def name_not_defined(self, name: str, ctx: Context) -> None:
32383252
self.add_fixture_note(fullname, ctx)
32393253

32403254
def name_already_defined(self, name: str, ctx: Context,
3241-
original_ctx: Optional[SymbolTableNode] = None) -> None:
3242-
if original_ctx:
3243-
if original_ctx.node and original_ctx.node.get_line() != -1:
3244-
extra_msg = ' on line {}'.format(original_ctx.node.get_line())
3245-
else:
3246-
extra_msg = ' (possibly by an import)'
3255+
original_ctx: Optional[Union[SymbolTableNode, SymbolNode]] = None) -> None:
3256+
if isinstance(original_ctx, SymbolTableNode):
3257+
node = original_ctx.node
3258+
elif isinstance(original_ctx, SymbolNode):
3259+
node = original_ctx
3260+
3261+
if isinstance(original_ctx, SymbolTableNode) and original_ctx.kind == MODULE_REF:
3262+
# Since this is an import, original_ctx.node points to the module definition.
3263+
# Therefore its line number is always 1, which is not useful for this
3264+
# error message.
3265+
extra_msg = ' (by an import)'
3266+
elif node and node.line != -1:
3267+
extra_msg = ' on line {}'.format(node.line)
32473268
else:
3248-
extra_msg = ''
3269+
extra_msg = ' (possibly by an import)'
32493270
self.fail("Name '{}' already defined{}".format(name, extra_msg), ctx)
32503271

32513272
def fail(self, msg: str, ctx: Context, serious: bool = False, *,

mypy/semanal_pass1.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ def add_symbol(self, name: str, node: SymbolTableNode,
337337
# Flag redefinition unless this is a reimport of a module.
338338
if not (node.kind == MODULE_REF and
339339
self.sem.locals[-1][name].node == node.node):
340-
self.sem.name_already_defined(name, context)
340+
self.sem.name_already_defined(name, context, self.sem.locals[-1][name])
341341
self.sem.locals[-1][name] = node
342342
else:
343343
assert self.sem.type is None # Pass 1 doesn't look inside classes
@@ -353,6 +353,6 @@ def add_symbol(self, name: str, node: SymbolTableNode,
353353
if existing.type and node.type and is_same_type(existing.type, node.type):
354354
ok = True
355355
if not ok:
356-
self.sem.name_already_defined(name, context)
356+
self.sem.name_already_defined(name, context, existing)
357357
elif not existing:
358358
self.sem.globals[name] = node

test-data/unit/check-attr.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -721,13 +721,13 @@ reveal_type(A) # E: Revealed type is 'def (b: Any, a: Any) -> __main__.A'
721721
class B:
722722
a: int = attr.ib(default=8)
723723
b: int = attr.ib()
724-
a: int = attr.ib() # E: Name 'a' already defined
724+
a: int = attr.ib() # E: Name 'a' already defined on line 10
725725
reveal_type(B) # E: Revealed type is 'def (b: builtins.int, a: builtins.int) -> __main__.B'
726726
@attr.s(auto_attribs=True)
727727
class C:
728728
a: int = 8
729729
b: int
730-
a: int = attr.ib() # E: Name 'a' already defined
730+
a: int = attr.ib() # E: Name 'a' already defined on line 16
731731
reveal_type(C) # E: Revealed type is 'def (a: builtins.int, b: builtins.int) -> __main__.C'
732732
[builtins fixtures/bool.pyi]
733733

test-data/unit/check-class-namedtuple.test

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -598,16 +598,16 @@ class AnnotationsAsAMethod(NamedTuple):
598598

599599
class ReuseNames(NamedTuple):
600600
x: int
601-
def x(self) -> str: # E: Name 'x' already defined
601+
def x(self) -> str: # E: Name 'x' already defined on line 22
602602
return ''
603603

604604
def y(self) -> int:
605605
return 0
606-
y: str # E: Name 'y' already defined
606+
y: str # E: Name 'y' already defined on line 26
607607

608608
class ReuseCallableNamed(NamedTuple):
609609
z: Callable[[ReuseNames], int]
610-
def z(self) -> int: # E: Name 'z' already defined
610+
def z(self) -> int: # E: Name 'z' already defined on line 31
611611
return 0
612612

613613
[builtins fixtures/dict.pyi]

test-data/unit/check-functions.test

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,7 +1310,7 @@ if x:
13101310
def f(x: int) -> None: pass
13111311
else:
13121312
# TODO: This should be okay.
1313-
@dec # E: Name 'f' already defined
1313+
@dec # E: Name 'f' already defined on line 7
13141314
def f(): pass
13151315

13161316
[case testConditionalFunctionDefinitionUsingDecorator4]
@@ -1323,7 +1323,7 @@ if x:
13231323
def f(x: str) -> None: pass
13241324
else:
13251325
# TODO: We should report an incompatible redefinition.
1326-
@dec # E: Name 'f' already defined
1326+
@dec # E: Name 'f' already defined on line 7
13271327
def f(): pass
13281328

13291329
[case testConditionalRedefinitionOfAnUnconditionalFunctionDefinition1]
@@ -1490,7 +1490,7 @@ x = None # type: Any
14901490
class A:
14911491
if x:
14921492
def f(self): pass
1493-
def f(self): pass # E: Name 'f' already defined
1493+
def f(self): pass # E: Name 'f' already defined on line 5
14941494

14951495
[case testIncompatibleConditionalMethodDefinition]
14961496
from typing import Any

test-data/unit/check-incremental.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1020,7 +1020,7 @@ import a.b
10201020
[rechecked b]
10211021
[stale]
10221022
[out2]
1023-
tmp/b.py:4: error: Name 'a' already defined
1023+
tmp/b.py:4: error: Name 'a' already defined on line 3
10241024

10251025
[case testIncrementalSilentImportsAndImportsInClass]
10261026
# flags: --ignore-missing-imports

test-data/unit/check-newsyntax.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ a = 5 # E: Incompatible types in assignment (expression has type "int", variabl
1919
b: str = 5 # E: Incompatible types in assignment (expression has type "int", variable has type "str")
2020

2121
zzz: int
22-
zzz: str # E: Name 'zzz' already defined
22+
zzz: str # E: Name 'zzz' already defined on line 10
2323
[out]
2424

2525
[case testNewSyntaxWithDict]

test-data/unit/check-overloading.test

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,18 @@ f(0)
99

1010
@overload # E: Name 'overload' is not defined
1111
def g(a:int): pass
12-
def g(a): pass # E: Name 'g' already defined
12+
def g(a): pass # E: Name 'g' already defined on line 8
1313
g(0)
1414

1515
@something # E: Name 'something' is not defined
1616
def r(a:int): pass
17-
def r(a): pass # E: Name 'r' already defined
17+
def r(a): pass # E: Name 'r' already defined on line 13
1818
r(0)
1919
[out]
2020
main:1: error: Name 'overload' is not defined
21-
main:3: error: Name 'f' already defined
21+
main:3: error: Name 'f' already defined on line 1
2222
main:3: error: Name 'overload' is not defined
23-
main:5: error: Name 'f' already defined
23+
main:5: error: Name 'f' already defined on line 1
2424

2525
[case testTypeCheckOverloadWithImplementation]
2626
from typing import overload, Any
@@ -143,9 +143,9 @@ def deco(fun): ...
143143

144144
@deco
145145
def f(x: 'A') -> 'B': ...
146-
@deco # E: Name 'f' already defined
146+
@deco # E: Name 'f' already defined on line 5
147147
def f(x: 'B') -> 'A': ...
148-
@deco # E: Name 'f' already defined
148+
@deco # E: Name 'f' already defined on line 5
149149
def f(x: Any) -> Any: ...
150150

151151
class A: pass
@@ -1089,7 +1089,7 @@ def f(a: int) -> None: pass
10891089
@overload
10901090
def f(a: str) -> None: pass
10911091
[out]
1092-
tmp/foo.pyi:3: error: Name 'f' already defined
1092+
tmp/foo.pyi:3: error: Name 'f' already defined on line 2
10931093
tmp/foo.pyi:3: error: Single overload definition, multiple required
10941094

10951095
[case testNonconsecutiveOverloads]
@@ -1103,7 +1103,7 @@ def f(a: int) -> None: pass
11031103
def f(a: str) -> None: pass
11041104
[out]
11051105
tmp/foo.pyi:2: error: Single overload definition, multiple required
1106-
tmp/foo.pyi:5: error: Name 'f' already defined
1106+
tmp/foo.pyi:5: error: Name 'f' already defined on line 2
11071107
tmp/foo.pyi:5: error: Single overload definition, multiple required
11081108

11091109
[case testNonconsecutiveOverloadsMissingFirstOverload]
@@ -1115,7 +1115,7 @@ def f(a: int) -> None: pass
11151115
@overload
11161116
def f(a: str) -> None: pass
11171117
[out]
1118-
tmp/foo.pyi:4: error: Name 'f' already defined
1118+
tmp/foo.pyi:4: error: Name 'f' already defined on line 2
11191119
tmp/foo.pyi:4: error: Single overload definition, multiple required
11201120

11211121
[case testNonconsecutiveOverloadsMissingLaterOverload]
@@ -1173,7 +1173,7 @@ class Test(object):
11731173
def do_chain(self) -> int:
11741174
return 2
11751175

1176-
@do_chain.chain # E: Name 'do_chain' already defined
1176+
@do_chain.chain # E: Name 'do_chain' already defined on line 10
11771177
def do_chain(self) -> int:
11781178
return 3
11791179

test-data/unit/check-unsupported.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ def g(): pass
1313
@d # E
1414
def g(x): pass
1515
[out]
16-
tmp/foo.pyi:5: error: Name 'f' already defined
17-
tmp/foo.pyi:7: error: Name 'g' already defined
16+
tmp/foo.pyi:5: error: Name 'f' already defined on line 3
17+
tmp/foo.pyi:7: error: Name 'g' already defined on line 6

test-data/unit/semanal-errors.test

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ class A: pass
141141
x = 0 # type: A
142142
x = 0 # type: A
143143
[out]
144-
main:4: error: Name 'x' already defined
144+
main:4: error: Name 'x' already defined on line 3
145145

146146
[case testLocalVarRedefinition]
147147
import typing
@@ -150,15 +150,15 @@ def f() -> None:
150150
x = 0 # type: A
151151
x = 0 # type: A
152152
[out]
153-
main:5: error: Name 'x' already defined
153+
main:5: error: Name 'x' already defined on line 4
154154

155155
[case testClassVarRedefinition]
156156
import typing
157157
class A:
158158
x = 0 # type: object
159159
x = 0 # type: object
160160
[out]
161-
main:4: error: Name 'x' already defined
161+
main:4: error: Name 'x' already defined on line 3
162162

163163
[case testMultipleClassDefinitions]
164164
import typing
@@ -551,7 +551,7 @@ class A:
551551
def g(self) -> None: pass
552552
def f(self, x: object) -> None: pass
553553
[out]
554-
main:5: error: Name 'f' already defined
554+
main:5: error: Name 'f' already defined on line 3
555555

556556
[case testInvalidGlobalDecl]
557557
import typing
@@ -640,7 +640,7 @@ def f(x) -> None:
640640
x = 1
641641
def g(): pass
642642
[out]
643-
main:5: error: Name 'g' already defined
643+
main:5: error: Name 'g' already defined on line 3
644644

645645
[case testRedefinedOverloadedFunction]
646646
from typing import overload, Any
@@ -653,7 +653,7 @@ def f() -> None:
653653
def p(): pass # fail
654654
[out]
655655
main:3: error: An overloaded function outside a stub file must have an implementation
656-
main:8: error: Name 'p' already defined
656+
main:8: error: Name 'p' already defined on line 3
657657

658658
[case testNestedFunctionInMethod]
659659
import typing
@@ -1049,7 +1049,7 @@ class t: pass # E: Name 't' already defined on line 2
10491049
[case testRedefineTypevar4]
10501050
from typing import TypeVar
10511051
t = TypeVar('t')
1052-
from typing import Generic as t # E: Name 't' already defined
1052+
from typing import Generic as t # E: Name 't' already defined on line 2
10531053
[out]
10541054

10551055
[case testInvalidStrLiteralType]
@@ -1083,7 +1083,7 @@ from typing import overload
10831083
def dec(x): pass
10841084
@dec
10851085
def f(): pass
1086-
@dec # E: Name 'f' already defined
1086+
@dec # E: Name 'f' already defined on line 3
10871087
def f(): pass
10881088
[out]
10891089

@@ -1180,7 +1180,7 @@ class A:
11801180
import typing
11811181
def f() -> None:
11821182
import x
1183-
import y as x # E: Name 'x' already defined
1183+
import y as x # E: Name 'x' already defined (by an import)
11841184
x.y
11851185
[file x.py]
11861186
y = 1
@@ -1190,7 +1190,7 @@ y = 1
11901190
[case testImportTwoModulesWithSameNameInGlobalContext]
11911191
import typing
11921192
import x
1193-
import y as x # E: Name 'x' already defined
1193+
import y as x # E: Name 'x' already defined (by an import)
11941194
x.y
11951195
[file x.py]
11961196
y = 1
@@ -1328,8 +1328,8 @@ a = 's' # type: str
13281328
a = ('spam', 'spam', 'eggs', 'spam') # type: Tuple[str]
13291329

13301330
[out]
1331-
main:3: error: Name 'a' already defined
1332-
main:4: error: Name 'a' already defined
1331+
main:3: error: Name 'a' already defined on line 2
1332+
main:4: error: Name 'a' already defined on line 2
13331333

13341334
[case testDuplicateDefFromImport]
13351335
from m import A
@@ -1347,7 +1347,7 @@ def dec(x: Any) -> Any:
13471347
@dec
13481348
def f() -> None:
13491349
pass
1350-
@dec # E: Name 'f' already defined
1350+
@dec # E: Name 'f' already defined on line 4
13511351
def f() -> None:
13521352
pass
13531353
[out]

0 commit comments

Comments
 (0)