-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Improve "Name already defined" error message #5067
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
33c673c
f0c91b3
d99eb04
5cfb4dc
880d6ba
12d5111
cfa3408
4a67f14
7d4ef29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -395,7 +395,8 @@ def _visit_func_def(self, defn: FuncDef) -> None: | |
# Redefinition. Conditional redefinition is okay. | ||
n = self.type.names[defn.name()].node | ||
if not self.set_original_def(n, defn): | ||
self.name_already_defined(defn.name(), defn) | ||
self.name_already_defined(defn.name(), defn, | ||
self.type.names[defn.name()]) | ||
self.type.names[defn.name()] = SymbolTableNode(MDEF, defn) | ||
self.prepare_method_signature(defn, self.type) | ||
elif self.is_func_scope(): | ||
|
@@ -406,7 +407,8 @@ def _visit_func_def(self, defn: FuncDef) -> None: | |
# Redefinition. Conditional redefinition is okay. | ||
n = self.locals[-1][defn.name()].node | ||
if not self.set_original_def(n, defn): | ||
self.name_already_defined(defn.name(), defn) | ||
self.name_already_defined(defn.name(), defn, | ||
self.locals[-1][defn.name()]) | ||
else: | ||
self.add_local(defn, defn) | ||
else: | ||
|
@@ -552,9 +554,9 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: | |
"must come last", defn.items[idx]) | ||
else: | ||
for idx in non_overload_indexes[1:]: | ||
self.name_already_defined(defn.name(), defn.items[idx]) | ||
self.name_already_defined(defn.name(), defn.items[idx], first_item) | ||
if defn.impl: | ||
self.name_already_defined(defn.name(), defn.impl) | ||
self.name_already_defined(defn.name(), defn.impl, first_item) | ||
# Remove the non-overloads | ||
for idx in reversed(non_overload_indexes): | ||
del defn.items[idx] | ||
|
@@ -1848,7 +1850,12 @@ def analyze_lvalue(self, lval: Lvalue, nested: bool = False, | |
self.type.names[lval.name] = SymbolTableNode(MDEF, v) | ||
elif explicit_type: | ||
# Don't re-bind types | ||
self.name_already_defined(lval.name, lval) | ||
global_def = self.globals.get(lval.name) | ||
local_def = cast(SymbolTable, self.locals[-1]).get(lval.name) if self.locals and self.locals[-1] else None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you rewrite it using an if statement, then the cast will be unnecessary. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed! |
||
type_def = self.type.names.get(lval.name) if self.type else None | ||
|
||
original_def = global_def or local_def or type_def | ||
self.name_already_defined(lval.name, lval, original_def) | ||
else: | ||
# Bind to an existing name. | ||
lval.accept(self) | ||
|
@@ -3172,7 +3179,7 @@ def add_symbol(self, name: str, node: SymbolTableNode, | |
# Flag redefinition unless this is a reimport of a module. | ||
if not (node.kind == MODULE_REF and | ||
self.locals[-1][name].node == node.node): | ||
self.name_already_defined(name, context) | ||
self.name_already_defined(name, context, self.locals[-1][name]) | ||
self.locals[-1][name] = node | ||
elif self.type: | ||
self.type.names[name] = node | ||
|
@@ -3189,14 +3196,14 @@ def add_symbol(self, name: str, node: SymbolTableNode, | |
if existing.type and node.type and is_same_type(existing.type, node.type): | ||
ok = True | ||
if not ok: | ||
self.name_already_defined(name, context) | ||
self.name_already_defined(name, context, existing) | ||
self.globals[name] = node | ||
|
||
def add_local(self, node: Union[Var, FuncDef, OverloadedFuncDef], ctx: Context) -> None: | ||
assert self.locals[-1] is not None, "Should not add locals outside a function" | ||
name = node.name() | ||
if name in self.locals[-1]: | ||
self.name_already_defined(name, ctx) | ||
self.name_already_defined(name, ctx, self.locals[-1][name]) | ||
node._fullname = name | ||
self.locals[-1][name] = SymbolTableNode(LDEF, node) | ||
|
||
|
@@ -3230,10 +3237,16 @@ def name_not_defined(self, name: str, ctx: Context) -> None: | |
self.add_fixture_note(fullname, ctx) | ||
|
||
def name_already_defined(self, name: str, ctx: Context, | ||
original_ctx: Optional[SymbolTableNode] = None) -> None: | ||
original_ctx: Optional[Union[SymbolTableNode, SymbolNode]] = None) -> None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In general, it is not a good idea to use a union here, but it seems to me the old signature was "wrong", so it is fine in this case. Maybe you can add a short comment about this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The old signature was actually correct, the function used to only be called with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I guess to fix this, the options are
Which do you think makes more sense? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually you might need There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, I'll leave it as-is then. |
||
if original_ctx: | ||
if original_ctx.node and original_ctx.node.get_line() != -1: | ||
extra_msg = ' on line {}'.format(original_ctx.node.get_line()) | ||
if isinstance(original_ctx, SymbolTableNode): | ||
node = original_ctx.node | ||
elif isinstance(original_ctx, SymbolNode): | ||
node = original_ctx | ||
else: | ||
node = None | ||
if node and node.line != -1: | ||
extra_msg = ' on line {}'.format(node.line) | ||
else: | ||
extra_msg = ' (possibly by an import)' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have enough tests for this case? If not you can add one more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure when this would happen (when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, looks like there was one test where this happened. Do you know in what situations this happens? If so, I could add tests for those cases. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this might happen in case of an error or an unresolved circular import. If you can't find a simple additional test case then ignore this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, looks like there's already a test case for that situation ( |
||
else: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -141,7 +141,7 @@ class A: pass | |
x = 0 # type: A | ||
x = 0 # type: A | ||
[out] | ||
main:4: error: Name 'x' already defined | ||
main:4: error: Name 'x' already defined on line 3 | ||
|
||
[case testLocalVarRedefinition] | ||
import typing | ||
|
@@ -150,15 +150,15 @@ def f() -> None: | |
x = 0 # type: A | ||
x = 0 # type: A | ||
[out] | ||
main:5: error: Name 'x' already defined | ||
main:5: error: Name 'x' already defined on line 4 | ||
|
||
[case testClassVarRedefinition] | ||
import typing | ||
class A: | ||
x = 0 # type: object | ||
x = 0 # type: object | ||
[out] | ||
main:4: error: Name 'x' already defined | ||
main:4: error: Name 'x' already defined on line 3 | ||
|
||
[case testMultipleClassDefinitions] | ||
import typing | ||
|
@@ -551,7 +551,7 @@ class A: | |
def g(self) -> None: pass | ||
def f(self, x: object) -> None: pass | ||
[out] | ||
main:5: error: Name 'f' already defined | ||
main:5: error: Name 'f' already defined on line 3 | ||
|
||
[case testInvalidGlobalDecl] | ||
import typing | ||
|
@@ -640,7 +640,7 @@ def f(x) -> None: | |
x = 1 | ||
def g(): pass | ||
[out] | ||
main:5: error: Name 'g' already defined | ||
main:5: error: Name 'g' already defined on line 3 | ||
|
||
[case testRedefinedOverloadedFunction] | ||
from typing import overload, Any | ||
|
@@ -653,7 +653,7 @@ def f() -> None: | |
def p(): pass # fail | ||
[out] | ||
main:3: error: An overloaded function outside a stub file must have an implementation | ||
main:8: error: Name 'p' already defined | ||
main:8: error: Name 'p' already defined on line 3 | ||
|
||
[case testNestedFunctionInMethod] | ||
import typing | ||
|
@@ -1049,7 +1049,7 @@ class t: pass # E: Name 't' already defined on line 2 | |
[case testRedefineTypevar4] | ||
from typing import TypeVar | ||
t = TypeVar('t') | ||
from typing import Generic as t # E: Name 't' already defined | ||
from typing import Generic as t # E: Name 't' already defined on line 2 | ||
[out] | ||
|
||
[case testInvalidStrLiteralType] | ||
|
@@ -1083,7 +1083,7 @@ from typing import overload | |
def dec(x): pass | ||
@dec | ||
def f(): pass | ||
@dec # E: Name 'f' already defined | ||
@dec # E: Name 'f' already defined on line 3 | ||
def f(): pass | ||
[out] | ||
|
||
|
@@ -1180,7 +1180,7 @@ class A: | |
import typing | ||
def f() -> None: | ||
import x | ||
import y as x # E: Name 'x' already defined | ||
import y as x # E: Name 'x' already defined on line 3 | ||
x.y | ||
[file x.py] | ||
y = 1 | ||
|
@@ -1190,7 +1190,7 @@ y = 1 | |
[case testImportTwoModulesWithSameNameInGlobalContext] | ||
import typing | ||
import x | ||
import y as x # E: Name 'x' already defined | ||
import y as x # E: Name 'x' already defined on line 2 | ||
x.y | ||
[file x.py] | ||
y = 1 | ||
|
@@ -1328,8 +1328,8 @@ a = 's' # type: str | |
a = ('spam', 'spam', 'eggs', 'spam') # type: Tuple[str] | ||
|
||
[out] | ||
main:3: error: Name 'a' already defined | ||
main:4: error: Name 'a' already defined | ||
main:3: error: Name 'a' already defined on line 2 | ||
main:4: error: Name 'a' already defined on line 2 | ||
|
||
[case testDuplicateDefFromImport] | ||
from m import A | ||
|
@@ -1347,7 +1347,7 @@ def dec(x: Any) -> Any: | |
@dec | ||
def f() -> None: | ||
pass | ||
@dec # E: Name 'f' already defined | ||
@dec # E: Name 'f' already defined on line 4 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wow, good job updating all these error messages! 👍 |
||
def f() -> None: | ||
pass | ||
[out] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like you missed one more case: global function definition, see
else: # Top-level function
below. You will likely need to patch alsoself.check_no_global
in the same way you do above forself.name_already defined
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
self.check_no_global
already does the right thing?mypy/mypy/semanal.py
Line 3225 in 7eebb8f
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, if the previous context is always correct in
check_no_global
, then no action is required here.