Skip to content

Commit

Permalink
Fix metaclass conflict
Browse files Browse the repository at this point in the history
This fixes the regression when #88 combined with #89: mypy-1.0 started
producing the error:

```
Metaclass conflict: the metaclass of a derived class must be a
(non-strict) subclass of the metaclasses of all its bases
```
  • Loading branch information
kedder committed Feb 9, 2023
1 parent 88308ac commit d06e2ac
Show file tree
Hide file tree
Showing 2 changed files with 2 additions and 57 deletions.
9 changes: 2 additions & 7 deletions src/mypy_zope/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,13 +719,8 @@ def _apply_interface(self, impl: TypeInfo, iface: TypeInfo) -> None:
# there is a decorator for the class that will create a "type promotion",
# but ensure this only gets applied a single time per interface.
promote = Instance(iface, [])
if not any(promote in ti._promote for ti in impl.mro):
faketi = TypeInfo(SymbolTable(), iface.defn, iface.module_name)
faketi._promote = [promote]
faketi.metaclass_type = iface.metaclass_type
# Insert the TypeInfo before the builtins.object that's at the end.
assert impl.mro[-1].fullname == 'builtins.object'
impl.mro.insert(len(impl.mro) - 1, faketi)
if promote not in impl._promote:
impl._promote.append(promote)


def plugin(version: str) -> PyType[Plugin]:
Expand Down
50 changes: 0 additions & 50 deletions tests/test_mro_calculation.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,53 +16,3 @@ def mypy_cache_dir(tmp_path_factory):
tdir = tmp_path_factory.mktemp('.mypy_cahe')
print("Setup cache", str(tdir))
return str(tdir)


def test_mro_computation_in_forward_reference_to_implementer(mypy_cache_dir: str) -> None:
sample_name = "forward_reference_to_implementer"

opts = options.Options()
opts.show_traceback = True
opts.namespace_packages = True
opts.cache_dir = mypy_cache_dir
opts.plugins = ['mypy_zope:plugin']
# Config file is needed to load plugins, it doesn't not exist and is not
# supposed to.
opts.config_file = 'not_existing_config.ini'

samplefile = os.path.join(SAMPLES_DIR, f"{sample_name}.py")
base_dir = os.path.dirname(samplefile)
with open(samplefile) as f:
source = BuildSource(
None,
module=sample_name,
text=f.read(),
base_dir=base_dir,
)
result = build.build(sources=[source], options=opts)
assert result.errors == []

# Result.graph is a map from module name to state objects.
state: State = result.graph[sample_name]

# Find Mypy's representation of the Protocol class.
node: Optional[SymbolTableNode] = None
for fullname, symbol_table_node, _type_info in state.tree.local_definitions():
# Use startswith(...) rather than a direct comparison
# because the typename includes a line number at the end
if fullname.startswith(f"{sample_name}.Protocol"):
node = symbol_table_node
break

assert node is not None, f"Failed to find `Protocol` class in mypy's state for {samplefile}"

mro: List[TypeInfo] = node.node.mro
# Expected: [
# <TypeInfo forward_reference_to_implementer.Protocol@21>,
# <TypeInfo forward_reference_to_implementer.IProtocol>,
# <TypeInfo builtins.object>,
# ]
assert len(mro) == 3
assert mro[0].fullname.startswith(f"{sample_name}.Protocol")
assert mro[1].fullname == f"{sample_name}.IProtocol"
assert mro[2].fullname == "builtins.object"

0 comments on commit d06e2ac

Please sign in to comment.