Skip to content

Commit bf94169

Browse files
ilevkivskyiPattenR
authored andcommitted
New analyzer: resolve name clashes between generated and existing nodes (python#6972)
Fixes python#6454. Fixes python#6973. Note that the solution doesn't guarantee the order of numbers N in foo-redefinitionN coincides with the textual order of nodes. But it looks like we don't need this for anything.
1 parent 6a41af6 commit bf94169

File tree

5 files changed

+39
-12
lines changed

5 files changed

+39
-12
lines changed

mypy/newsemanal/semanal_namedtuple.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
)
2121
from mypy.options import Options
2222
from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError
23+
from mypy.util import get_unique_redefinition_name
2324

2425
MYPY = False
2526
if MYPY:
@@ -493,10 +494,15 @@ def save_namedtuple_body(self, named_tuple_info: TypeInfo) -> Iterator[None]:
493494
# Restore the names in the original symbol table. This ensures that the symbol
494495
# table contains the field objects created by build_namedtuple_typeinfo. Exclude
495496
# __doc__, which can legally be overwritten by the class.
496-
named_tuple_info.names.update({
497-
key: value for key, value in nt_names.items()
498-
if key not in named_tuple_info.names or key != '__doc__'
499-
})
497+
for key, value in nt_names.items():
498+
if key in named_tuple_info.names:
499+
if key == '__doc__':
500+
continue
501+
# Keep existing (user-provided) definitions under mangled names, so they
502+
# get semantically analyzed.
503+
r_key = get_unique_redefinition_name(key, named_tuple_info.names)
504+
named_tuple_info.names[r_key] = named_tuple_info.names[key]
505+
named_tuple_info.names[key] = value
500506

501507
# Helpers
502508

mypy/plugins/common.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from mypy.semanal import set_callable_name
99
from mypy.types import CallableType, Overloaded, Type, TypeVarDef, LiteralType, Instance
1010
from mypy.typevars import fill_typevars
11+
from mypy.util import get_unique_redefinition_name
1112

1213

1314
def _get_decorator_bool_argument(
@@ -117,6 +118,13 @@ def add_method(
117118
func._fullname = info.fullname() + '.' + name
118119
func.line = info.line
119120

121+
# NOTE: we would like the plugin generated node to dominate, but we still
122+
# need to keep any existing definitions so they get semantically analyzed.
123+
if name in info.names:
124+
# Get a nice unique name instead.
125+
r_name = get_unique_redefinition_name(name, info.names)
126+
info.names[r_name] = info.names[name]
127+
120128
info.names[name] = SymbolTableNode(MDEF, func, plugin_generated=True)
121129
info.defn.defs.body.append(func)
122130

mypy/util.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import re
55
import subprocess
66
import sys
7-
from typing import TypeVar, List, Tuple, Optional, Dict, Sequence, Iterable
7+
from typing import TypeVar, List, Tuple, Optional, Dict, Sequence, Iterable, Container
88

99
MYPY = False
1010
if MYPY:
@@ -290,6 +290,22 @@ def unmangle(name: str) -> str:
290290
return name.rstrip("'")
291291

292292

293+
def get_unique_redefinition_name(name: str, existing: Container[str]) -> str:
294+
"""Get a simple redefinition name not present among existing.
295+
296+
For example, for name 'foo' we try 'foo-redefinition', 'foo-redefinition2',
297+
'foo-redefinition3', etc. until we find one that is not in existing.
298+
"""
299+
r_name = name + '-redefinition'
300+
if r_name not in existing:
301+
return r_name
302+
303+
i = 2
304+
while r_name + str(i) in existing:
305+
i += 1
306+
return r_name + str(i)
307+
308+
293309
def check_python_version(program: str) -> None:
294310
"""Report issues with the Python used to run mypy, dmypy, or stubgen"""
295311
# Check for known bad Python versions.

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -587,9 +587,7 @@ reveal_type(takes_base(Base(1))) # E: Revealed type is 'builtins.int'
587587
reveal_type(takes_base(Child(1))) # E: Revealed type is 'builtins.int'
588588
[builtins fixtures/tuple.pyi]
589589

590-
-- Depends on collecting functions via AST, not symbol tables.
591590
[case testNewNamedTupleIllegalNames]
592-
# flags: --no-new-semantic-analyzer
593591
from typing import Callable, NamedTuple
594592

595593
class XMethBad(NamedTuple):
@@ -614,16 +612,16 @@ class AnnotationsAsAMethod(NamedTuple):
614612

615613
class ReuseNames(NamedTuple):
616614
x: int
617-
def x(self) -> str: # E: Name 'x' already defined on line 23
615+
def x(self) -> str: # E: Name 'x' already defined on line 22
618616
return ''
619617

620618
def y(self) -> int:
621619
return 0
622-
y: str # E: Name 'y' already defined on line 27
620+
y: str # E: Name 'y' already defined on line 26
623621

624622
class ReuseCallableNamed(NamedTuple):
625623
z: Callable[[ReuseNames], int]
626-
def z(self) -> int: # E: Name 'z' already defined on line 32
624+
def z(self) -> int: # E: Name 'z' already defined on line 31
627625
return 0
628626

629627
[builtins fixtures/dict.pyi]

test-data/unit/check-dataclasses.test

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,9 +393,8 @@ class Application: # E: eq must be True if order is True
393393

394394
[builtins fixtures/list.pyi]
395395

396-
-- Blocked by #6454
397396
[case testDataclassOrderingWithCustomMethods]
398-
# flags: --python-version 3.6 --no-new-semantic-analyzer
397+
# flags: --python-version 3.6
399398
from dataclasses import dataclass
400399

401400
@dataclass(order=True)

0 commit comments

Comments
 (0)