Skip to content
Open
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
6 changes: 5 additions & 1 deletion mypyc/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,15 +485,19 @@ def construct_groups(
else:
groups = [(sources, None)]

# Generate missing names
# Generate missing names.
# Sort the modules to make the compilation results consistent regardless of
# the source file order passed to mypycify.
for i, (group, name) in enumerate(groups):
group = sorted(group, key=lambda source: source.module)
if use_shared_lib and not name:
if group_name_override is not None:
name = group_name_override
else:
name = group_name([source.module for source in group])
groups[i] = (group, name)

groups = sorted(groups, key=lambda g: (g[1] or "", [s.module for s in g[0]]))
return groups


Expand Down
4 changes: 2 additions & 2 deletions mypyc/codegen/emitmodule.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ def compile_modules_to_ir(

# Process the graph by SCC in topological order, like we do in mypy.build
for scc in sorted_components(result.graph):
scc_states = [result.graph[id] for id in scc.mod_ids]
scc_states = [result.graph[id] for id in sorted(scc.mod_ids)]
trees = [st.tree for st in scc_states if st.id in mapper.group_map and st.tree]

if not trees:
Expand Down Expand Up @@ -1441,7 +1441,7 @@ def _toposort_visit(name: str) -> None:
if decl.mark:
return

for child in decl.declaration.dependencies:
for child in sorted(decl.declaration.dependencies):
_toposort_visit(child)

result.append(decl.declaration)
Expand Down
4 changes: 2 additions & 2 deletions mypyc/irbuild/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1076,11 +1076,11 @@ def get_sequence_type_from_type(self, target_type: Type) -> RType:
items = target_type.items
assert items, "This function does not support empty tuples"
# Tuple might have elements of different types.
rtypes = set(map(self.mapper.type_to_rtype, items))
rtypes = list(dict.fromkeys(self.mapper.type_to_rtype(item) for item in items))
if len(rtypes) == 1:
return rtypes.pop()
else:
return RUnion.make_simplified_union(list(rtypes))
return RUnion.make_simplified_union(rtypes)
assert False, target_type

def get_dict_base_type(self, expr: Expression) -> list[Instance]:
Expand Down
76 changes: 76 additions & 0 deletions mypyc/test/test_emitmodule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from __future__ import annotations

import tempfile
import unittest
from pathlib import Path

import pytest

from mypy import build
from mypy.options import Options
from mypyc.build import construct_groups
from mypyc.codegen import emitmodule
from mypyc.errors import Errors
from mypyc.irbuild.mapper import Mapper
from mypyc.options import CompilerOptions


class FakeSCC:
def __init__(self, mod_ids: list[str]) -> None:
self.mod_ids = mod_ids


class TestEmitModule(unittest.TestCase):
def test_compile_modules_to_ir_orders_scc_members_deterministically(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir, pytest.MonkeyPatch.context() as monkeypatch:
tmp_path = Path(tmp_dir)
a_py = tmp_path / "a.py"
b_py = tmp_path / "b.py"
a_py.write_text("import b\n\nclass A: pass\nclass C(A): pass\n", encoding="utf-8")
b_py.write_text(
"import a\n\nclass B(a.A): pass\nclass D(a.A): pass\n", encoding="utf-8"
)

sources = [
build.BuildSource(str(a_py), "a", None),
build.BuildSource(str(b_py), "b", None),
]
options = Options()
options.preserve_asts = True
options.mypy_path = [str(tmp_path)]
options.cache_dir = str(tmp_path / ".mypy_cache")
for source in sources:
options.per_module_options.setdefault(source.module, {})["mypyc"] = True

compiler_options = CompilerOptions(strict_traceback_checks=True)
groups = construct_groups(
sources, False, use_shared_lib=True, group_name_override=None
)
result = emitmodule.parse_and_typecheck(sources, options, compiler_options, groups)
try:
group_map = {
source.module: lib_name for group, lib_name in groups for source in group
}
children_by_order = []
for order in (["a", "b"], ["b", "a"]):
monkeypatch.setattr(
emitmodule,
"sorted_components",
lambda graph, order=order: [FakeSCC(order)],
)
mapper = Mapper(group_map)
errors = Errors(options)
modules = emitmodule.compile_modules_to_ir(
result, mapper, compiler_options, errors
)
assert errors.num_errors == 0, errors.new_messages()
classes = {
cl.fullname: cl for module in modules.values() for cl in module.classes
}
children = classes["a.A"].children
assert children is not None
children_by_order.append([child.fullname for child in children])

assert children_by_order[1] == children_by_order[0]
finally:
result.manager.metastore.close()
Loading