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
Refactor IPCompleter
Matcher API
#13745
Conversation
6089879
to
d22310b
Compare
the lowest supported Python version catches up.
Is there an automated way to update doctest with |
No there is no automatic ways. I copy and past also with light editting. |
Awesome! The key-only completion for dictionaries will be super convenient! |
Ok, I would say this is good for a review and hopefully merge. I am happy to commit to maintaining this and fixing any problems that might emerge as a result of merging this (which I don't expect frankly since everything should be 100% backward compatible - but we know how it is with big projects). Documentation-wise I hit a minor hiccup with documenting the |
self.type = type | ||
|
||
def __repr__(self): | ||
return f"<SimpleCompletion text={self.text!r} type={self.type!r}>" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WE could almost use a dataclass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided not to use a dataclass due to a performance overhead.
|
||
|
||
@sphinx_options(show_inherited_members=True, exclude_inherited_from=["dict"]) | ||
class SimpleMatcherResult(_MatcherResultBase, TypedDict): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DO you need to inherit both if _MatcherResultBase
is already TypedDict.
and do we want to TypedDict, or simply make a datalass with all the possible existing fields, even if it's slightly backward-incompatible ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DO you need to inherit both if _MatcherResultBase is already TypedDict.
No, not at runtime, but yes if we want to have nice documentation (sphinx-doc/sphinx#10806); a quirk of how TypedDict
it was implemented. I added a comment in the body on this.
and do we want to TypedDict, or simply make a datalass with all the possible existing fields, even if it's slightly backward-incompatible ?
I hesitated here and considered making it a dataclass to, but I preferred TypedDict
for two reasons:
- in future releases we can add a new
NotRequired
field without breaking compatibility (and give notice if we want to make it required in the further future) - it does not require users to import any specific class from IPython to wrap the results (or create a custom class complying with the API), so it would keep it simple (while argument name and type safety is provided by typecheckers)
Thanks, I tried to do a quick pass, and will try to get a deeper look when on my way to CZI meeting next week. In general I think you can be a little bit less careful on backward compatibility, and if you find really old things try to remove them if unnecessary, we only have a few dependees that use old APIs. |
Co-authored-by: Matthias Bussonnier <bussonniermatthias@gmail.com>
Data class has narrower API than named tuple (does not expose item getters) which means it is less likely that the downstream code would be broken in the future if we introduce new attributes. Also, compared to other places where memory-efficient named tuple is used, `CompletionContext` is only created once per completion and thus does not require as low overhead. For the same reason we can forgo slightly more memory-efficient `@property @cache` stack and just use `@cached_property`.
Ok, let's just try this and refine if needs there is. |
Playing with the new completer in an Emacs mode I'm building. So far so good; great work. Seems faster as well. I had formerly overridden the |
Thank you for the feedback @jdtsmith! Would you mind opening an issue with a minimal reproducible example? I don't see what could be the issue as:
And yes, this PR lays a foundation for improved performance (see #13752 for an example). |
Closes #12820 and closes #13735. Supersedes #13734.
Partially fixes #13155 by exposing full context to matchers (but not the hooks yet - that would be a breaking change; I can follow up with it).
This is a gentle, backward and forward-compatible rewrite of Matcher API. I attempted to design the API for performance and future extensibility, while retaining compatibility with existing code.
There is quite some legacy logic in this codebase, which I tried to carefully preserve and document by making any such behaviour explicit, but in a few places I encountered behaviour which I don't know how to tackle (no unit tests, nor comments - not sure if existing behaviour was intended or incidental due to accumulated code complexity). I would highly appreciate help on such cases, suggestions on how to handle deprecations (if any are needed) and general feedback on this PR.
User-facing changes
Completion type is returned in the provisional API for most matchers
Dictionary completions are restricted to dictionary keys
By default completions in dictionary context will only provide dictionary key matches. This is user-configurable.
The heuristic for this can be improved in the future iterations.
Configuration
New configuration options were added:
IPCompleter.suppress_competing_matchers
allows to instruct which matchers should (or should not) short-circuit to return candidate completions ignoring other matchersIPCompleter.disable_matchers
enables selective disabling of matchersExisting configuration options are now aliases:
IPCompleter.merge_completions = False
is an alias forIPCompleter.suppress_competing_matchers = True
Matcher API
Briefly, the new Matcher is defined as:
where
MatcherAPIv1
is the same public API as exposed via (undocumented)IPCompleter.custom_matchers
attribute since v8.0, #12130),MatcherAPIv2
is the new API, and subsequent versions can be added in the future.Matcher API v2
The
MatcherResult
of API v2 is a dictionary, as and resolves an old TODO comment:ipython/IPython/core/completer.py
Lines 2142 to 2145 in 7f51a03
This will enable adding additional attributes to the dictionary without breaking compatibility in the future. It also makes the API simple for users, as they only need to provide
{'completions': list[SimpleCompletion]}
.SimpleCompletion
is a container for completion metadata allowing to add type and in future additional attributes. After profiling initialisation, different forms of attribute/item access and memory use I chose to use a custom class with slots overNamedTuple
orTypedDict
due to inherent performance advantage. This is consistent withCompletion
class which also uses__slots__
.CompletionContext
is intended to provide matchers with information they need to generate completion candidates:Switching between APIs and Matcher configuration
A decorator was added enabling demarking API versions and configuring Matcher-level options. The decorator is optional for v1 API, but required to use v2 API
Maintaining compatibility
Existing methods such as
dict_key_matches
were not touched; instead wrapper methods converting from API v1 to API v2 and adding desired metadata (when applicable) were added; those methods have a new suffix_matcher
. This approach allows users who monkey-patch matcher v1 methods inIPCompleter
to continue using their existing code without any changes.Follow-up tasks
To be discussed in separate issues and addressed in separate PRs:
type
as a string vs as an enum to inform whether it is worth switching to enumsortText
in LSP terms)