interfaces: convert the zope `Attribute` attrs to regular fields
At this point, we should have a useful protocol class.
The file syntax requires the type to be supplied for any fields that are
declared, but we'll leave the complex ones partially unspecified for now, for
simplicity. (Also, the things documented as `Callable` are really as future
type annotating worked showed- roll with it for now, but they're marked as TODO
for fixing later.) All of the fields and all of the attrs will need type
annotations, or the type rules say they are considered to be `Any`. That can be
done in a separate pass, possibly applying the `dirstate.pyi` file generated
from the concrete class.
The first cut of this turned the `interfaceutil.Attribute` fields into plain
fields, and thus the types on them. PyCharm flagged a few things as having
incompatible signatures when the concrete dirstate class subclassed this, when
the concrete class has them declared as `@property`. So they've been changed to
`@property` here in those cases. The remaining fields that are decorated in the
concrete class have comments noting the differences. We'll see if they need to
be changed going forward, but leave them for now. We'll be in trouble if the
`@util.propertycache` is needed, because we can't import that module here at
runtime, due to circular imports.
--- a/mercurial/interfaces/dirstate.py Thu Sep 26 18:09:33 2024 -0400
+++ b/mercurial/interfaces/dirstate.py Thu Sep 26 18:15:36 2024 -0400
@@ -1,12 +1,19 @@
from __future__ import annotations
import contextlib
+import typing
from typing import (
+ Callable,
Protocol,
)
-from . import util as interfaceutil
+if typing.TYPE_CHECKING:
+ # Almost all mercurial modules are only imported in the type checking phase
+ # to avoid circular imports
+ from .. import (
+ match as matchmod,
+ )
class idirstate(Protocol):
@@ -31,16 +38,22 @@
# TODO: all these private methods and attributes should be made
# public or removed from the interface.
- _ignore = interfaceutil.Attribute("""Matcher for ignored files.""")
- is_changing_any = interfaceutil.Attribute(
+
+ # TODO: decorate with `@rootcache(b'.hgignore')` like dirstate class?
+ _ignore: matchmod.basematcher
+ """Matcher for ignored files."""
+
+ @property
+ def is_changing_any(self) -> bool:
"""True if any changes in progress."""
- )
- is_changing_parents = interfaceutil.Attribute(
+
+ @property
+ def is_changing_parents(self) -> bool:
"""True if parents changes in progress."""
- )
- is_changing_files = interfaceutil.Attribute(
+
+ @property
+ def is_changing_files(self) -> bool:
"""True if file tracking changes in progress."""
- )
def _ignorefiles(self):
"""Return a list of files containing patterns to ignore."""
@@ -48,8 +61,19 @@
def _ignorefileandline(self, f):
"""Given a file `f`, return the ignore file and line that ignores it."""
- _checklink = interfaceutil.Attribute("""Callable for checking symlinks.""")
- _checkexec = interfaceutil.Attribute("""Callable for checking exec bits.""")
+ # TODO: decorate with `@util.propertycache` like dirstate class?
+ # (can't because circular import)
+ # TODO: The doc looks wrong- the core class has this as a @property, not a
+ # callable.
+ _checklink: Callable
+ """Callable for checking symlinks."""
+
+ # TODO: decorate with `@util.propertycache` like dirstate class?
+ # (can't because circular import)
+ # TODO: The doc looks wrong- the core class has this as a @property, not a
+ # callable.
+ _checkexec: Callable
+ """Callable for checking exec bits."""
@contextlib.contextmanager
def changing_parents(self, repo):