Skip to content

[mypyc] Make namegen not need to use suffixes to disambiguate #7676

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

Merged
merged 2 commits into from
Oct 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 9 additions & 25 deletions mypyc/namegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,10 @@ class NameGenerator:
respectively. If the modules are 'bar.foo' and 'baz.foo', the
prefixes will be 'bar_foo_' and 'baz_foo_'.

* Replace '.' in the Python name with '_' in the C name. This can
obviously generate conflicts at C name level. If multiple Python
names would map to the same C name using the basic algorithm,
add suffixes _2, _3, etc. to make the C names unique.

* Keep a dictionary of mappings so that we can translate each name
consistently within a build.
* Replace '.' in the Python name with '___' in the C name. (And
replace the unlikely but possible '___' with '___3_'. This
collides '___' with '.3_', but this is OK because names
may not start with a digit.)

The generated should be internal to a build and thus the mapping is
arbitrary. Just generating names '1', '2', ... would be correct,
Expand Down Expand Up @@ -66,31 +63,19 @@ def private_name(self, module: str, partial_name: Optional[str] = None) -> str:
"""
# TODO: Support unicode
if partial_name is None:
return self.module_map[module].rstrip('_')
return exported_name(self.module_map[module].rstrip('.'))
if (module, partial_name) in self.translations:
return self.translations[module, partial_name]
if module in self.module_map:
module_prefix = self.module_map[module]
elif module:
module_prefix = module.replace('.', '_') + '_'
module_prefix = module + '.'
else:
module_prefix = ''
candidate = '{}{}'.format(module_prefix, partial_name.replace('.', '_'))
actual = self.make_unique(candidate)
actual = exported_name('{}{}'.format(module_prefix, partial_name))
self.translations[module, partial_name] = actual
self.used_names.add(actual)
return actual

def make_unique(self, name: str) -> str:
if name not in self.used_names:
return name
i = 2
while True:
candidate = '{}_{}'.format(name, i)
if candidate not in self.used_names:
return candidate
i += 1


def exported_name(fullname: str) -> str:
"""Return a C name usable for an exported definition.
Expand All @@ -100,8 +85,7 @@ def exported_name(fullname: str) -> str:
builds.
"""
# TODO: Support unicode
# TODO: Ensure that there are no conflicts?
return fullname.replace('.', '___')
return fullname.replace('___', '___3_').replace('.', '___')


def make_module_translation_map(names: List[str]) -> Dict[str, str]:
Expand All @@ -124,5 +108,5 @@ def candidate_suffixes(fullname: str) -> List[str]:
components = fullname.split('.')
result = ['']
for i in range(len(components)):
result.append('_'.join(components[-i - 1:]) + '_')
result.append('.'.join(components[-i - 1:]) + '.')
return result
9 changes: 5 additions & 4 deletions mypyc/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -1416,11 +1416,12 @@ def __init__(self,
else:
self.bound_sig = FuncSignature(sig.args[1:], sig.ret_type)

@property
def shortname(self) -> str:
return self.class_name + '.' + self.name if self.class_name else self.name
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work with nested classes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing about mypyc works with nested classes


def cname(self, names: NameGenerator) -> str:
name = self.name
if self.class_name:
name += '_' + self.class_name
return names.private_name(self.module_name, name)
return names.private_name(self.module_name, self.shortname)


class FuncIR:
Expand Down
3 changes: 3 additions & 0 deletions mypyc/test-data/run.test
Original file line number Diff line number Diff line change
Expand Up @@ -4345,6 +4345,9 @@ class C:
def foo(self) -> None:
def bar(self) -> None:
pass

def C___foo() -> None: pass

class D:
def foo(self) -> None:
def bar(self) -> None:
Expand Down
35 changes: 18 additions & 17 deletions mypyc/test/test_namegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,34 @@

class TestNameGen(unittest.TestCase):
def test_candidate_suffixes(self) -> None:
assert candidate_suffixes('foo') == ['', 'foo_']
assert candidate_suffixes('foo.bar') == ['', 'bar_', 'foo_bar_']
assert candidate_suffixes('foo') == ['', 'foo.']
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be useful to have an end-to-end test case that has things like triple underscores in names and possible name clashes, such as having names C.foo and C___foo in the same namespace?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add that to the name clash issue test

assert candidate_suffixes('foo.bar') == ['', 'bar.', 'foo.bar.']

def test_exported_name(self) -> None:
assert exported_name('foo') == 'foo'
assert exported_name('foo.bar') == 'foo___bar'

def test_make_module_translation_map(self) -> None:
assert make_module_translation_map(
['foo', 'bar']) == {'foo': 'foo_', 'bar': 'bar_'}
['foo', 'bar']) == {'foo': 'foo.', 'bar': 'bar.'}
assert make_module_translation_map(
['foo.bar', 'foo.baz']) == {'foo.bar': 'bar_', 'foo.baz': 'baz_'}
['foo.bar', 'foo.baz']) == {'foo.bar': 'bar.', 'foo.baz': 'baz.'}
assert make_module_translation_map(
['zar', 'foo.bar', 'foo.baz']) == {'foo.bar': 'bar_',
'foo.baz': 'baz_',
'zar': 'zar_'}
['zar', 'foo.bar', 'foo.baz']) == {'foo.bar': 'bar.',
'foo.baz': 'baz.',
'zar': 'zar.'}
assert make_module_translation_map(
['foo.bar', 'fu.bar', 'foo.baz']) == {'foo.bar': 'foo_bar_',
'fu.bar': 'fu_bar_',
'foo.baz': 'baz_'}
['foo.bar', 'fu.bar', 'foo.baz']) == {'foo.bar': 'foo.bar.',
'fu.bar': 'fu.bar.',
'foo.baz': 'baz.'}

def test_name_generator(self) -> None:
g = NameGenerator(['foo', 'foo.zar'])
assert g.private_name('foo', 'f') == 'foo_f'
assert g.private_name('foo', 'C.x.y') == 'foo_C_x_y'
assert g.private_name('foo', 'C.x.y') == 'foo_C_x_y'
assert g.private_name('foo.zar', 'C.x.y') == 'zar_C_x_y'
assert g.private_name('foo', 'C.x_y') == 'foo_C_x_y_2'
assert g.private_name('foo', 'C_x_y') == 'foo_C_x_y_3'
assert g.private_name('foo', 'C_x_y') == 'foo_C_x_y_3'
assert g.private_name('foo', 'f') == 'foo___f'
assert g.private_name('foo', 'C.x.y') == 'foo___C___x___y'
assert g.private_name('foo', 'C.x.y') == 'foo___C___x___y'
assert g.private_name('foo.zar', 'C.x.y') == 'zar___C___x___y'
assert g.private_name('foo', 'C.x_y') == 'foo___C___x_y'
assert g.private_name('foo', 'C_x_y') == 'foo___C_x_y'
assert g.private_name('foo', 'C_x_y') == 'foo___C_x_y'
assert g.private_name('foo', '___') == 'foo______3_'