This patch does three things:
1. Record the current namespace context in pending_xrefs so it can be
used for link resolution later,
2. Pass that recorded namespace context to find_obj() when resolving a
reference, and
3. Wildly and completely rewrite find_obj().
cross-reference support is expanded to tolerate the presence or absence
of either namespace or module, and to cope with the presence or absence
of contextual information for either.
References now work like this:
1. If the explicit reference target is recorded in the domain's object
registry, we link to that target and stop looking. We do this lookup
regardless of how fully qualified the target is, which allows direct
references to modules (which don't have a module component to their
names) or direct references to definitions that may or may not belong
to a namespace or module.
2. If contextual information is available from qapi:namespace or
qapi:module directives, try using those components to find a direct
match to the implied target name.
3. If both prior lookups fail, generate a series of regular expressions
looking for wildcard matches in order from most to least
specific. Any explicitly provided components (namespace, module)
*must* match exactly, but both contextual and entirely omitted
components are allowed to differ from the search result. Note that if
more than one result is found, Sphinx will emit a warning (a build
error for QEMU) and list all of the candidate references.
The practical upshot is that in the large majority of cases, namespace
and module information is not required when creating simple `references`
to definitions from within the same context -- even when identical
definitions exist in other contexts.
Even when using simple `references` from elsewhere in the QEMU
documentation manual, explicit namespace info is not required if there
is only one definition by that name.
Disambiguation *will* be required from outside of the QAPI documentation
when referencing e.g. block-core definitions, which are shared between
QEMU QMP and the QEMU Storage Daemon. In that case, there are two
options:
A: References can be made partially or fully explicit,
e.g. `QMP:block-dirty-bitmap-add` will link to the QEMU version of
the definition, while `QGA:block-dirty-bitmap-add` would link to the
QGA version.
B: If all of the references in a document are intended to go to the same
place, you can insert a "qapi:namespace:: QMP" directive to influence
the fuzzy-searching for later references.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/devel/qapi-domain.rst | 34 ++++++++--
docs/sphinx/qapi_domain.py | 127 ++++++++++++++++++++++++-------------
2 files changed, 114 insertions(+), 47 deletions(-)
diff --git a/docs/devel/qapi-domain.rst b/docs/devel/qapi-domain.rst
index 73e13ab45ca..4705ba255e0 100644
--- a/docs/devel/qapi-domain.rst
+++ b/docs/devel/qapi-domain.rst
@@ -400,11 +400,10 @@ Namespaces
Mimicking the `Python domain target specification syntax
<https://www.sphinx-doc.org/en/master/usage/domains/python.html#target-specification>`_,
QAPI allows you to specify the fully qualified path for a data
-type. QAPI enforces globally unique names, so it's unlikely you'll need
-this specific feature, but it may be extended in the near future to
-allow referencing identically named commands and data types from
-different utilities; i.e. QEMU Storage Daemon vs QMP.
+type.
+* A namespace can be explicitly provided;
+ e.g. ``:qapi:type:`QMP:BitmapSyncMode``
* A module can be explicitly provided;
``:qapi:type:`block-core.BitmapSyncMode``` will render to:
:qapi:type:`block-core.BitmapSyncMode`
@@ -413,6 +412,28 @@ different utilities; i.e. QEMU Storage Daemon vs QMP.
will render to: :qapi:type:`~block-core.BitmapSyncMode`
+Target resolution
+-----------------
+
+Any cross-reference to a QAPI type, whether using the ```any``` style of
+reference or the more explicit ```:qapi:any:`target``` syntax, allows
+for the presence or absence of either the namespace or module
+information.
+
+When absent, their value will be inferred from context by the presence
+of any ``qapi:namespace`` or ``qapi:module`` directives preceding the
+cross-reference.
+
+If no results are found when using the inferred values, other
+namespaces/modules will be searched as a last resort; but any explicitly
+provided values must always match in order to succeed.
+
+This allows for efficient cross-referencing with a minimum of syntax in
+the large majority of cases, but additional context or namespace markup
+may be required outside of the QAPI reference documents when linking to
+items that share a name across multiple documented QAPI schema.
+
+
Custom link text
----------------
@@ -492,6 +513,11 @@ directives are associated with the most recent namespace. This affects
the definition's "fully qualified name", allowing two different
namespaces to create an otherwise identically named definition.
+This directive also influences how reference resolution works for any
+references that do not explicity specify a namespace, so this directive
+can be used to nudge references into preferring targets from within that
+namespace.
+
Example::
.. qapi:namespace:: QMP
diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py
index a204af9b062..a8a85a2de36 100644
--- a/docs/sphinx/qapi_domain.py
+++ b/docs/sphinx/qapi_domain.py
@@ -7,6 +7,7 @@
from __future__ import annotations
+import re
from typing import (
TYPE_CHECKING,
List,
@@ -94,6 +95,7 @@ def process_link(
title: str,
target: str,
) -> tuple[str, str]:
+ refnode["qapi:namespace"] = env.ref_context.get("qapi:namespace")
refnode["qapi:module"] = env.ref_context.get("qapi:module")
# Cross-references that begin with a tilde adjust the title to
@@ -830,40 +832,44 @@ def merge_domaindata(
self.objects[fullname] = obj
def find_obj(
- self, modname: str, name: str, typ: Optional[str]
- ) -> list[tuple[str, ObjectEntry]]:
+ self, namespace: str, modname: str, name: str, typ: Optional[str]
+ ) -> List[Tuple[str, ObjectEntry]]:
"""
- Find a QAPI object for "name", perhaps using the given module.
+ Find a QAPI object for "name", maybe using contextual information.
Returns a list of (name, object entry) tuples.
- :param modname: The current module context (if any!)
- under which we are searching.
- :param name: The name of the x-ref to resolve;
- may or may not include a leading module.
- :param type: The role name of the x-ref we're resolving, if provided.
- (This is absent for "any" lookups.)
+ :param namespace: The current namespace context (if any!) under
+ which we are searching.
+ :param modname: The current module context (if any!) under
+ which we are searching.
+ :param name: The name of the x-ref to resolve; may or may not
+ include leading context.
+ :param type: The role name of the x-ref we're resolving, if
+ provided. This is absent for "any" role lookups.
"""
if not name:
return []
- names: list[str] = []
- matches: list[tuple[str, ObjectEntry]] = []
+ # ##
+ # what to search for
+ # ##
- fullname = name
- if "." in fullname:
- # We're searching for a fully qualified reference;
- # ignore the contextual module.
- pass
- elif modname:
- # We're searching for something from somewhere;
- # try searching the current module first.
- # e.g. :qapi:cmd:`query-block` or `query-block` is being searched.
- fullname = f"{modname}.{name}"
+ parts = list(QAPIDescription.split_fqn(name))
+ explicit = tuple(bool(x) for x in parts)
+
+ # Fill in the blanks where possible:
+ if namespace and not parts[0]:
+ parts[0] = namespace
+ if modname and not parts[1]:
+ parts[1] = modname
+
+ implicit_fqn = ""
+ if all(parts):
+ implicit_fqn = f"{parts[0]}:{parts[1]}.{parts[2]}"
if typ is None:
- # type isn't specified, this is a generic xref.
- # search *all* qapi-specific object types.
+ # :any: lookup, search everything:
objtypes: List[str] = list(self.object_types)
else:
# type is specified and will be a role (e.g. obj, mod, cmd)
@@ -871,25 +877,57 @@ def find_obj(
# using the QAPIDomain.object_types table.
objtypes = self.objtypes_for_role(typ, [])
- if name in self.objects and self.objects[name].objtype in objtypes:
- names = [name]
- elif (
- fullname in self.objects
- and self.objects[fullname].objtype in objtypes
- ):
- names = [fullname]
+ # ##
+ # search!
+ # ##
+
+ def _search(needle: str) -> List[str]:
+ if (
+ needle
+ and needle in self.objects
+ and self.objects[needle].objtype in objtypes
+ ):
+ return [needle]
+ return []
+
+ if found := _search(name):
+ # Exact match!
+ pass
+ elif found := _search(implicit_fqn):
+ # Exact match using contextual information to fill in the gaps.
+ pass
else:
- # exact match wasn't found; e.g. we are searching for
- # `query-block` from a different (or no) module.
- searchname = "." + name
- names = [
- oname
- for oname in self.objects
- if oname.endswith(searchname)
- and self.objects[oname].objtype in objtypes
- ]
-
- matches = [(oname, self.objects[oname]) for oname in names]
+ # No exact hits, perform applicable fuzzy searches.
+ searches = []
+
+ esc = tuple(re.escape(s) for s in parts)
+
+ # Try searching for ns:*.name or ns:name
+ if explicit[0] and not explicit[1]:
+ searches.append(f"^{esc[0]}:([^\\.]+\\.)?{esc[2]}$")
+ # Try searching for *:module.name or module.name
+ if explicit[1] and not explicit[0]:
+ searches.append(f"(^|:){esc[1]}\\.{esc[2]}$")
+ # Try searching for context-ns:*.name or context-ns:name
+ if parts[0] and not (explicit[0] or explicit[1]):
+ searches.append(f"^{esc[0]}:([^\\.]+\\.)?{esc[2]}$")
+ # Try searching for *:context-mod.name or context-mod.name
+ if parts[1] and not (explicit[0] or explicit[1]):
+ searches.append(f"(^|:){esc[1]}\\.{esc[2]}$")
+ # Try searching for *:name, *.name, or name
+ if not (explicit[0] or explicit[1]):
+ searches.append(f"(^|:|\\.){esc[2]}$")
+
+ for search in searches:
+ if found := [
+ oname
+ for oname in self.objects
+ if re.search(search, oname)
+ and self.objects[oname].objtype in objtypes
+ ]:
+ break
+
+ matches = [(oname, self.objects[oname]) for oname in found]
if len(matches) > 1:
matches = [m for m in matches if not m[1].aliased]
return matches
@@ -904,8 +942,9 @@ def resolve_xref(
node: pending_xref,
contnode: Element,
) -> nodes.reference | None:
+ namespace = node.get("qapi:namespace")
modname = node.get("qapi:module")
- matches = self.find_obj(modname, target, typ)
+ matches = self.find_obj(namespace, modname, target, typ)
if not matches:
# Normally, we could pass warn_dangling=True to QAPIXRefRole(),
@@ -958,7 +997,9 @@ def resolve_any_xref(
contnode: Element,
) -> List[Tuple[str, nodes.reference]]:
results: List[Tuple[str, nodes.reference]] = []
- matches = self.find_obj(node.get("qapi:module"), target, None)
+ matches = self.find_obj(
+ node.get("qapi:namespace"), node.get("qapi:module"), target, None
+ )
for name, obj in matches:
rolename = self.role_for_objtype(obj.objtype)
assert rolename is not None
--
2.48.1
I found the commit message's description of reference resolution hard to understand. I think I understand it well enough now to ACK the design. Would be nice to work some of your explanations into the commit message, but we don't have time for that. So I'm merely fixing your QGA vs. QSD typo. We might want to put an improved description into the code. Not today. Thanks!
John Snow <jsnow@redhat.com> writes: > This patch does three things: > > 1. Record the current namespace context in pending_xrefs so it can be > used for link resolution later, > 2. Pass that recorded namespace context to find_obj() when resolving a > reference, and > 3. Wildly and completely rewrite find_obj(). > > cross-reference support is expanded to tolerate the presence or absence > of either namespace or module, and to cope with the presence or absence > of contextual information for either. > > References now work like this: > > 1. If the explicit reference target is recorded in the domain's object > registry, we link to that target and stop looking. We do this lookup > regardless of how fully qualified the target is, which allows direct > references to modules (which don't have a module component to their > names) or direct references to definitions that may or may not belong > to a namespace or module. > > 2. If contextual information is available from qapi:namespace or > qapi:module directives, try using those components to find a direct > match to the implied target name. > > 3. If both prior lookups fail, generate a series of regular expressions > looking for wildcard matches in order from most to least > specific. Any explicitly provided components (namespace, module) > *must* match exactly, but both contextual and entirely omitted > components are allowed to differ from the search result. Note that if > more than one result is found, Sphinx will emit a warning (a build > error for QEMU) and list all of the candidate references. > > The practical upshot is that in the large majority of cases, namespace > and module information is not required when creating simple `references` > to definitions from within the same context -- even when identical > definitions exist in other contexts. Can you illustrate this this examples? > Even when using simple `references` from elsewhere in the QEMU > documentation manual, explicit namespace info is not required if there > is only one definition by that name. Fails safely: if we add a second definition, doc generation errors out. Correct? > Disambiguation *will* be required from outside of the QAPI documentation > when referencing e.g. block-core definitions, which are shared between > QEMU QMP and the QEMU Storage Daemon. In that case, there are two > options: > > A: References can be made partially or fully explicit, > e.g. `QMP:block-dirty-bitmap-add` will link to the QEMU version of > the definition, while `QGA:block-dirty-bitmap-add` would link to the > QGA version. Do you mean "QSD:"? > B: If all of the references in a document are intended to go to the same > place, you can insert a "qapi:namespace:: QMP" directive to influence > the fuzzy-searching for later references. > > Signed-off-by: John Snow <jsnow@redhat.com>
On Thu, Mar 13, 2025 at 2:47 AM Markus Armbruster <armbru@redhat.com> wrote: > John Snow <jsnow@redhat.com> writes: > > > This patch does three things: > > > > 1. Record the current namespace context in pending_xrefs so it can be > > used for link resolution later, > > 2. Pass that recorded namespace context to find_obj() when resolving a > > reference, and > > 3. Wildly and completely rewrite find_obj(). > > > > cross-reference support is expanded to tolerate the presence or absence > > of either namespace or module, and to cope with the presence or absence > > of contextual information for either. > > > > References now work like this: > > > > 1. If the explicit reference target is recorded in the domain's object > > registry, we link to that target and stop looking. We do this lookup > > regardless of how fully qualified the target is, which allows direct > > references to modules (which don't have a module component to their > > names) or direct references to definitions that may or may not belong > > to a namespace or module. > > > > 2. If contextual information is available from qapi:namespace or > > qapi:module directives, try using those components to find a direct > > match to the implied target name. > > > > 3. If both prior lookups fail, generate a series of regular expressions > > looking for wildcard matches in order from most to least > > specific. Any explicitly provided components (namespace, module) > > *must* match exactly, but both contextual and entirely omitted > > components are allowed to differ from the search result. Note that if > > more than one result is found, Sphinx will emit a warning (a build > > error for QEMU) and list all of the candidate references. > > > > The practical upshot is that in the large majority of cases, namespace > > and module information is not required when creating simple `references` > > to definitions from within the same context -- even when identical > > definitions exist in other contexts. > > Can you illustrate this this examples? > do wha? > > > Even when using simple `references` from elsewhere in the QEMU > > documentation manual, explicit namespace info is not required if there > > is only one definition by that name. > > Fails safely: if we add a second definition, doc generation errors out. > Correct? > Yes, see the disambiguation patch for qapi-domain.rst for proof. Roll it back and see what happens! > > > Disambiguation *will* be required from outside of the QAPI documentation > > when referencing e.g. block-core definitions, which are shared between > > QEMU QMP and the QEMU Storage Daemon. In that case, there are two > > options: > > > > A: References can be made partially or fully explicit, > > e.g. `QMP:block-dirty-bitmap-add` will link to the QEMU version of > > the definition, while `QGA:block-dirty-bitmap-add` would link to the > > QGA version. > > Do you mean "QSD:"? > Haha! Yes, I suppose I did. > > > B: If all of the references in a document are intended to go to the same > > place, you can insert a "qapi:namespace:: QMP" directive to influence > > the fuzzy-searching for later references. > > > > Signed-off-by: John Snow <jsnow@redhat.com> > >
John Snow <jsnow@redhat.com> writes:
> On Thu, Mar 13, 2025 at 2:47 AM Markus Armbruster <armbru@redhat.com> wrote:
>
>> John Snow <jsnow@redhat.com> writes:
>>
>> > This patch does three things:
>> >
>> > 1. Record the current namespace context in pending_xrefs so it can be
>> > used for link resolution later,
>> > 2. Pass that recorded namespace context to find_obj() when resolving a
>> > reference, and
>> > 3. Wildly and completely rewrite find_obj().
>> >
>> > cross-reference support is expanded to tolerate the presence or absence
>> > of either namespace or module, and to cope with the presence or absence
>> > of contextual information for either.
>> >
>> > References now work like this:
>> >
>> > 1. If the explicit reference target is recorded in the domain's object
>> > registry, we link to that target and stop looking. We do this lookup
>> > regardless of how fully qualified the target is, which allows direct
>> > references to modules (which don't have a module component to their
>> > names) or direct references to definitions that may or may not belong
>> > to a namespace or module.
>> >
>> > 2. If contextual information is available from qapi:namespace or
>> > qapi:module directives, try using those components to find a direct
>> > match to the implied target name.
>> >
>> > 3. If both prior lookups fail, generate a series of regular expressions
>> > looking for wildcard matches in order from most to least
>> > specific. Any explicitly provided components (namespace, module)
>> > *must* match exactly, but both contextual and entirely omitted
>> > components are allowed to differ from the search result. Note that if
>> > more than one result is found, Sphinx will emit a warning (a build
>> > error for QEMU) and list all of the candidate references.
>> >
>> > The practical upshot is that in the large majority of cases, namespace
>> > and module information is not required when creating simple `references`
>> > to definitions from within the same context -- even when identical
>> > definitions exist in other contexts.
>>
>> Can you illustrate this this examples?
>>
>
> do wha?
Sorry, I went into the curve too fast.
The stuff under "References now work like this" confuses me. I guess it
describes a series of lookups to try one after the other.
I understand a cross-reference consists of namespace (optional), module
(optional), name, and role.
Let's assume role is "any" for simplicity's sake.
Regarding "1. If the explicit ...":
What is a reference's "explicit reference target"? Examples might
help me understand.
What is "recorded in the domain's object registry"?
Can you show me a reference where this lookup succeeds?
Regarding "2. If contextual information ...":
I guess "contextual information" is the context established by
qapi:namespace and qapi:module directives, i.e. the current
namespace and module, if any.
If the cross reference lacks a namespace, we substitute the current
namespace. Same for module.
We then use that "to find a direct match to the implied target
name". Sounds greek to me. Example(s) might help.
Regarding "3. If both prior lookups fail ...":
I guess we get here when namespace or module are absent, and
substituting the current namespace or module doesn't resolve. We
then substitute a wildcard, so to speak, i.e. look in all namespaces
/ modules, and succeed if we find exactly one resolution. Fair?
[...]
On Thu, Mar 13, 2025 at 10:41 AM Markus Armbruster <armbru@redhat.com>
wrote:
> John Snow <jsnow@redhat.com> writes:
>
> > On Thu, Mar 13, 2025 at 2:47 AM Markus Armbruster <armbru@redhat.com>
> wrote:
> >
> >> John Snow <jsnow@redhat.com> writes:
> >>
> >> > This patch does three things:
> >> >
> >> > 1. Record the current namespace context in pending_xrefs so it can be
> >> > used for link resolution later,
> >> > 2. Pass that recorded namespace context to find_obj() when resolving a
> >> > reference, and
> >> > 3. Wildly and completely rewrite find_obj().
> >> >
> >> > cross-reference support is expanded to tolerate the presence or
> absence
> >> > of either namespace or module, and to cope with the presence or
> absence
> >> > of contextual information for either.
> >> >
> >> > References now work like this:
> >> >
> >> > 1. If the explicit reference target is recorded in the domain's object
> >> > registry, we link to that target and stop looking. We do this
> lookup
> >> > regardless of how fully qualified the target is, which allows
> direct
> >> > references to modules (which don't have a module component to their
> >> > names) or direct references to definitions that may or may not
> belong
> >> > to a namespace or module.
> >> >
> >> > 2. If contextual information is available from qapi:namespace or
> >> > qapi:module directives, try using those components to find a direct
> >> > match to the implied target name.
> >> >
> >> > 3. If both prior lookups fail, generate a series of regular
> expressions
> >> > looking for wildcard matches in order from most to least
> >> > specific. Any explicitly provided components (namespace, module)
> >> > *must* match exactly, but both contextual and entirely omitted
> >> > components are allowed to differ from the search result. Note that
> if
> >> > more than one result is found, Sphinx will emit a warning (a build
> >> > error for QEMU) and list all of the candidate references.
> >> >
> >> > The practical upshot is that in the large majority of cases, namespace
> >> > and module information is not required when creating simple
> `references`
> >> > to definitions from within the same context -- even when identical
> >> > definitions exist in other contexts.
> >>
> >> Can you illustrate this this examples?
> >>
> >
> > do wha?
>
> Sorry, I went into the curve too fast.
>
> The stuff under "References now work like this" confuses me. I guess it
> describes a series of lookups to try one after the other.
>
> I understand a cross-reference consists of namespace (optional), module
> (optional), name, and role.
>
> Let's assume role is "any" for simplicity's sake.
>
> Regarding "1. If the explicit ...":
>
> What is a reference's "explicit reference target"? Examples might
> help me understand.
>
explicit lookup: `QMP:block-core:block-dirty-bitmap-add`
If that explicit target matches an object in the object database
*directly*, we match immediately and don't consider other potential
targets. This also applies to things like modules, e.g. `QMP:block-core`
even though the "module" is absent (it IS the module)
We always search for the explicit target no matter how un/fully qualified
it is.
>
> What is "recorded in the domain's object registry"?
>
domain.objects{} - essentially a record of every ObjectDefinition's
"fullname" - the return value from QAPIDefinition._get_fqn().
>
> Can you show me a reference where this lookup succeeds?
>
`QMP:block-core`
`QMP:block-core.block-dirty-bitmap-add`
>
> Regarding "2. If contextual information ...":
>
> I guess "contextual information" is the context established by
> qapi:namespace and qapi:module directives, i.e. the current
> namespace and module, if any.
>
Yep!
>
> If the cross reference lacks a namespace, we substitute the current
> namespace. Same for module.
>
> We then use that "to find a direct match to the implied target
> name". Sounds greek to me. Example(s) might help.
>
If namespace or module is missing from the link target, we try to fill in
the blanks with the contextual information if present.
Example, we are in the block-core section of the QEMU QMP reference manual
document and we reference `block-dirty-bitmap-add`. With context, we are
able to assemble a fully qualified name:
"QMP:block-core.block-dirty-bitmap-add`. This matches an item in the
registry directly, so it matches and no further search is performed.
>
> Regarding "3. If both prior lookups fail ...":
>
> I guess we get here when namespace or module are absent, and
> substituting the current namespace or module doesn't resolve. We
> then substitute a wildcard, so to speak, i.e. look in all namespaces
> / modules, and succeed if we find exactly one resolution. Fair?
>
More or less, though the mechanics are quite a bit more complex than your
overview (and what I wrote in qapi-domain.rst.) We can get here for a few
reasons:
(1) We didn't provide a fully qualified target, and we don't have full
context to construct one. For example, we are not "in" a namespace and/or
not "in" a module. This is quite likely to happen when writing simple
references to a definition name from outside of the transmogfrified QAPI
documentation, e.g. from qapi-domain.rst itself, or dirty-bitmaps.rst, etc.
(2) We didn't provide a fully qualified target, and we are referencing
something from outside of the local context. For example, we are "in" a
module but we are trying to link to a different module's definition. e.g.
we are in QMP:transaction and we reference `block-dirty-bitmap-add`. The
implied FQN will be QMP:transaction.block-dirty-bitmap.add, which will not
resolve.
The fuzzy search portion has an order of precedence for how it searches -
and not all searches are tried universally, they are conditional to what
was provided in the reference target and what context is available.
1. match against the explicitly provided namespace (module was not
explicitly provided)
2. match against the explicitly provided module (namespace was not
explicitly provided)
3. match against the implied namespace (neither namespace/module was
explicitly provided)
4. match against the implied module (neither namespace/module was
explicitly provided)
5. match against the definition name only, from anywhere (neither
namespace/module was explicitly provided)
The searches are performed in order: if a search returns zero results, the
next search is tried. If any search returns one or more results, those
results are returned and we stop searching down the list. The priority
order ensures that any explicitly provided information is *always* used to
find a match, but contextually provided information is merely a "hint" and
can be ignored for the sake of a match.
If find_obj() as a whole returns zero results, Sphinx emits a warning for a
dangling reference. if find_obj() as a whole returns multiple results,
Sphinx emits a warning for the ambiguous cross-reference.
QEMU errors out on any such warnings under our normal build settings.
Clear as mud?
--js
> [...]
>
>
John Snow <jsnow@redhat.com> writes:
> On Thu, Mar 13, 2025 at 10:41 AM Markus Armbruster <armbru@redhat.com>
> wrote:
>
>> John Snow <jsnow@redhat.com> writes:
>>
>> > On Thu, Mar 13, 2025 at 2:47 AM Markus Armbruster <armbru@redhat.com> wrote:
>> >
>> >> John Snow <jsnow@redhat.com> writes:
>> >>
>> >> > This patch does three things:
>> >> >
>> >> > 1. Record the current namespace context in pending_xrefs so it can be
>> >> > used for link resolution later,
>> >> > 2. Pass that recorded namespace context to find_obj() when resolving a
>> >> > reference, and
>> >> > 3. Wildly and completely rewrite find_obj().
>> >> >
>> >> > cross-reference support is expanded to tolerate the presence or absence
>> >> > of either namespace or module, and to cope with the presence or absence
>> >> > of contextual information for either.
>> >> >
>> >> > References now work like this:
>> >> >
>> >> > 1. If the explicit reference target is recorded in the domain's object
>> >> > registry, we link to that target and stop looking. We do this lookup
>> >> > regardless of how fully qualified the target is, which allows direct
>> >> > references to modules (which don't have a module component to their
>> >> > names) or direct references to definitions that may or may not belong
>> >> > to a namespace or module.
>> >> >
>> >> > 2. If contextual information is available from qapi:namespace or
>> >> > qapi:module directives, try using those components to find a direct
>> >> > match to the implied target name.
>> >> >
>> >> > 3. If both prior lookups fail, generate a series of regular expressions
>> >> > looking for wildcard matches in order from most to least
>> >> > specific. Any explicitly provided components (namespace, module)
>> >> > *must* match exactly, but both contextual and entirely omitted
>> >> > components are allowed to differ from the search result. Note that if
>> >> > more than one result is found, Sphinx will emit a warning (a build
>> >> > error for QEMU) and list all of the candidate references.
>> >> >
>> >> > The practical upshot is that in the large majority of cases, namespace
>> >> > and module information is not required when creating simple `references`
>> >> > to definitions from within the same context -- even when identical
>> >> > definitions exist in other contexts.
>> >>
>> >> Can you illustrate this this examples?
>> >>
>> >
>> > do wha?
>>
>> Sorry, I went into the curve too fast.
>>
>> The stuff under "References now work like this" confuses me. I guess it
>> describes a series of lookups to try one after the other.
>>
>> I understand a cross-reference consists of namespace (optional), module
>> (optional), name, and role.
>>
>> Let's assume role is "any" for simplicity's sake.
>>
>> Regarding "1. If the explicit ...":
>>
>> What is a reference's "explicit reference target"? Examples might
>> help me understand.
>>
>
> explicit lookup: `QMP:block-core:block-dirty-bitmap-add`
>
> If that explicit target matches an object in the object database
> *directly*, we match immediately and don't consider other potential
> targets. This also applies to things like modules, e.g. `QMP:block-core`
> even though the "module" is absent (it IS the module)
>
> We always search for the explicit target no matter how un/fully qualified
> it is.
>
>
>>
>> What is "recorded in the domain's object registry"?
>>
>
> domain.objects{} - essentially a record of every ObjectDefinition's
> "fullname" - the return value from QAPIDefinition._get_fqn().
>
>
>>
>> Can you show me a reference where this lookup succeeds?
>>
>
> `QMP:block-core`
> `QMP:block-core.block-dirty-bitmap-add`
So, for this lookup to work, the reference must either be of the form
NAMESPACE:MODULE and resolve to that module in that namespace, or of the
form NAMESPACE:MODULE:DEFN and resolve to that definition in that module
in that namespace. Correct?
These a "fully qualified names (FQN)" in your parlance, right?
Note that the first form is syntactically indistinguishable from
NAMESPACE:DEFN, i.e. a reference to a definition that specifies the
namespace, but not the module.
If the NAMESPACE:MODULE interpretation resolves, we never try the
NAMESPACE:DEFN interpretation, because that happens in later steps.
Correct?
The first form is fully qualified only if it resolves as FQN. So,
whether such a reference is fully qualified is not syntactically
decidable. Hmm.
>> Regarding "2. If contextual information ...":
>>
>> I guess "contextual information" is the context established by
>> qapi:namespace and qapi:module directives, i.e. the current
>> namespace and module, if any.
>>
>
> Yep!
>
>
>>
>> If the cross reference lacks a namespace, we substitute the current
>> namespace. Same for module.
>>
>> We then use that "to find a direct match to the implied target
>> name". Sounds greek to me. Example(s) might help.
>>
>
> If namespace or module is missing from the link target, we try to fill in
> the blanks with the contextual information if present.
>
> Example, we are in the block-core section of the QEMU QMP reference manual
> document and we reference `block-dirty-bitmap-add`. With context, we are
> able to assemble a fully qualified name:
> "QMP:block-core.block-dirty-bitmap-add`. This matches an item in the
> registry directly, so it matches and no further search is performed.
We try this lookup only when the reference lacks a namespace and we are
"in" a namespace, or when it lacks a module and we are "in" a module.
Correct?
We then subsitute current namespace / module for the lacking one(s), and
try the same lookup as in 1. Correct?
If we have a reference of the form MYSTERY, it could either be a
reference to module MYSTERY in the current namespace, or to definition
MYSTERY in the current namespace and module. How do we decide?
>> Regarding "3. If both prior lookups fail ...":
>>
>> I guess we get here when namespace or module are absent, and
>> substituting the current namespace or module doesn't resolve. We
>> then substitute a wildcard, so to speak, i.e. look in all namespaces
>> / modules, and succeed if we find exactly one resolution. Fair?
>>
>
> More or less, though the mechanics are quite a bit more complex than your
> overview (and what I wrote in qapi-domain.rst.) We can get here for a few
> reasons:
>
> (1) We didn't provide a fully qualified target, and we don't have full
> context to construct one. For example, we are not "in" a namespace and/or
> not "in" a module. This is quite likely to happen when writing simple
> references to a definition name from outside of the transmogfrified QAPI
> documentation, e.g. from qapi-domain.rst itself, or dirty-bitmaps.rst, etc.
>
> (2) We didn't provide a fully qualified target, and we are referencing
> something from outside of the local context. For example, we are "in" a
> module but we are trying to link to a different module's definition. e.g.
> we are in QMP:transaction and we reference `block-dirty-bitmap-add`. The
> implied FQN will be QMP:transaction.block-dirty-bitmap.add, which will not
> resolve.
>
> The fuzzy search portion has an order of precedence for how it searches -
> and not all searches are tried universally, they are conditional to what
> was provided in the reference target and what context is available.
>
> 1. match against the explicitly provided namespace (module was not
> explicitly provided)
Look for the name in all of the namespace's modules?
> 2. match against the explicitly provided module (namespace was not
> explicitly provided)
Look for the name in all modules so named in all namespaces?
> 3. match against the implied namespace (neither namespace/module was
> explicitly provided)
?
> 4. match against the implied module (neither namespace/module was
> explicitly provided)
?
> 5. match against the definition name only, from anywhere (neither
> namespace/module was explicitly provided)
Look for the name anywhere?
I need examples :)
> The searches are performed in order: if a search returns zero results, the
> next search is tried. If any search returns one or more results, those
> results are returned and we stop searching down the list. The priority
> order ensures that any explicitly provided information is *always* used to
> find a match, but contextually provided information is merely a "hint" and
> can be ignored for the sake of a match.
>
> If find_obj() as a whole returns zero results, Sphinx emits a warning for a
> dangling reference. if find_obj() as a whole returns multiple results,
> Sphinx emits a warning for the ambiguous cross-reference.
>
> QEMU errors out on any such warnings under our normal build settings.
>
> Clear as mud?
Clearer, but not quite mud, yet.
> --js
>
>
>> [...]
>>
>>
On Thu, Mar 13, 2025, 11:57 AM Markus Armbruster <armbru@redhat.com> wrote:
> John Snow <jsnow@redhat.com> writes:
>
> > On Thu, Mar 13, 2025 at 10:41 AM Markus Armbruster <armbru@redhat.com>
> > wrote:
> >
> >> John Snow <jsnow@redhat.com> writes:
> >>
> >> > On Thu, Mar 13, 2025 at 2:47 AM Markus Armbruster <armbru@redhat.com>
> wrote:
> >> >
> >> >> John Snow <jsnow@redhat.com> writes:
> >> >>
> >> >> > This patch does three things:
> >> >> >
> >> >> > 1. Record the current namespace context in pending_xrefs so it can
> be
> >> >> > used for link resolution later,
> >> >> > 2. Pass that recorded namespace context to find_obj() when
> resolving a
> >> >> > reference, and
> >> >> > 3. Wildly and completely rewrite find_obj().
> >> >> >
> >> >> > cross-reference support is expanded to tolerate the presence or
> absence
> >> >> > of either namespace or module, and to cope with the presence or
> absence
> >> >> > of contextual information for either.
> >> >> >
> >> >> > References now work like this:
> >> >> >
> >> >> > 1. If the explicit reference target is recorded in the domain's
> object
> >> >> > registry, we link to that target and stop looking. We do this
> lookup
> >> >> > regardless of how fully qualified the target is, which allows
> direct
> >> >> > references to modules (which don't have a module component to
> their
> >> >> > names) or direct references to definitions that may or may not
> belong
> >> >> > to a namespace or module.
> >> >> >
> >> >> > 2. If contextual information is available from qapi:namespace or
> >> >> > qapi:module directives, try using those components to find a
> direct
> >> >> > match to the implied target name.
> >> >> >
> >> >> > 3. If both prior lookups fail, generate a series of regular
> expressions
> >> >> > looking for wildcard matches in order from most to least
> >> >> > specific. Any explicitly provided components (namespace, module)
> >> >> > *must* match exactly, but both contextual and entirely omitted
> >> >> > components are allowed to differ from the search result. Note
> that if
> >> >> > more than one result is found, Sphinx will emit a warning (a
> build
> >> >> > error for QEMU) and list all of the candidate references.
> >> >> >
> >> >> > The practical upshot is that in the large majority of cases,
> namespace
> >> >> > and module information is not required when creating simple
> `references`
> >> >> > to definitions from within the same context -- even when identical
> >> >> > definitions exist in other contexts.
> >> >>
> >> >> Can you illustrate this this examples?
> >> >>
> >> >
> >> > do wha?
> >>
> >> Sorry, I went into the curve too fast.
> >>
> >> The stuff under "References now work like this" confuses me. I guess it
> >> describes a series of lookups to try one after the other.
> >>
> >> I understand a cross-reference consists of namespace (optional), module
> >> (optional), name, and role.
> >>
> >> Let's assume role is "any" for simplicity's sake.
> >>
> >> Regarding "1. If the explicit ...":
> >>
> >> What is a reference's "explicit reference target"? Examples might
> >> help me understand.
> >>
> >
> > explicit lookup: `QMP:block-core:block-dirty-bitmap-add`
> >
> > If that explicit target matches an object in the object database
> > *directly*, we match immediately and don't consider other potential
> > targets. This also applies to things like modules, e.g. `QMP:block-core`
> > even though the "module" is absent (it IS the module)
> >
> > We always search for the explicit target no matter how un/fully qualified
> > it is.
> >
> >
> >>
> >> What is "recorded in the domain's object registry"?
> >>
> >
> > domain.objects{} - essentially a record of every ObjectDefinition's
> > "fullname" - the return value from QAPIDefinition._get_fqn().
> >
> >
> >>
> >> Can you show me a reference where this lookup succeeds?
> >>
> >
> > `QMP:block-core`
> > `QMP:block-core.block-dirty-bitmap-add`
>
> So, for this lookup to work, the reference must either be of the form
> NAMESPACE:MODULE and resolve to that module in that namespace, or of the
> form NAMESPACE:MODULE:DEFN and resolve to that definition in that module
> in that namespace. Correct?
>
Yes.
> These a "fully qualified names (FQN)" in your parlance, right?
>
More or less, though as you found below...
> Note that the first form is syntactically indistinguishable from
> NAMESPACE:DEFN, i.e. a reference to a definition that specifies the
> namespace, but not the module.
>
> If the NAMESPACE:MODULE interpretation resolves, we never try the
> NAMESPACE:DEFN interpretation, because that happens in later steps.
> Correct?
>
> The first form is fully qualified only if it resolves as FQN. So,
> whether such a reference is fully qualified is not syntactically
> decidable. Hmm.
>
You're right. There is a weirdness here. I might need to do some more
thinking to make sure it isn't theoretically a problem, but in practice,
right now, it isn't.
Stay tuned, I guess.
> >> Regarding "2. If contextual information ...":
> >>
> >> I guess "contextual information" is the context established by
> >> qapi:namespace and qapi:module directives, i.e. the current
> >> namespace and module, if any.
> >>
> >
> > Yep!
> >
> >
> >>
> >> If the cross reference lacks a namespace, we substitute the current
> >> namespace. Same for module.
> >>
> >> We then use that "to find a direct match to the implied target
> >> name". Sounds greek to me. Example(s) might help.
> >>
> >
> > If namespace or module is missing from the link target, we try to fill in
> > the blanks with the contextual information if present.
> >
> > Example, we are in the block-core section of the QEMU QMP reference
> manual
> > document and we reference `block-dirty-bitmap-add`. With context, we are
> > able to assemble a fully qualified name:
> > "QMP:block-core.block-dirty-bitmap-add`. This matches an item in the
> > registry directly, so it matches and no further search is performed.
>
> We try this lookup only when the reference lacks a namespace and we are
> "in" a namespace, or when it lacks a module and we are "in" a module.
> Correct?
>
or both: if we provided only a name but the context has both a namespace
and module.
essentially the algorithm splits the explicit target into (ns, mod, name)
and for any that are blank, we try to fill in those blanks with context
info where available. Sometimes you have neither explicit nor contextual
info for a component.
Then we do a lookup for an exact match, in order;
1. explicit target name, whatever it was
2. FQN using contextual info
and we stop after the first hit - no chance for multiple results here, just
zero-or-one each step.
i.e. any explicitly provided information is never "overwritten" with
context, context only fills in the blanks where that info was not provided.
If neither of these work, we move on to fuzzy searching.
> We then subsitute current namespace / module for the lacking one(s), and
> try the same lookup as in 1. Correct?
>
Yes!
> If we have a reference of the form MYSTERY, it could either be a
> reference to module MYSTERY in the current namespace, or to definition
> MYSTERY in the current namespace and module. How do we decide?
>
fqn a: NS:MYSTERY
fqn b: NS:MOD:MYSTERY
Given we have a current ns/mod context, it's going to pick the second one.
Hm. Maybe it ought to be ambiguous in this case... I'll have to revise
this. (question is: how soon do we need it?)
> >> Regarding "3. If both prior lookups fail ...":
> >>
> >> I guess we get here when namespace or module are absent, and
> >> substituting the current namespace or module doesn't resolve. We
> >> then substitute a wildcard, so to speak, i.e. look in all namespaces
> >> / modules, and succeed if we find exactly one resolution. Fair?
> >>
> >
> > More or less, though the mechanics are quite a bit more complex than your
> > overview (and what I wrote in qapi-domain.rst.) We can get here for a few
> > reasons:
> >
> > (1) We didn't provide a fully qualified target, and we don't have full
> > context to construct one. For example, we are not "in" a namespace and/or
> > not "in" a module. This is quite likely to happen when writing simple
> > references to a definition name from outside of the transmogfrified QAPI
> > documentation, e.g. from qapi-domain.rst itself, or dirty-bitmaps.rst,
> etc.
> >
> > (2) We didn't provide a fully qualified target, and we are referencing
> > something from outside of the local context. For example, we are "in" a
> > module but we are trying to link to a different module's definition. e.g.
> > we are in QMP:transaction and we reference `block-dirty-bitmap-add`. The
> > implied FQN will be QMP:transaction.block-dirty-bitmap.add, which will
> not
> > resolve.
> >
> > The fuzzy search portion has an order of precedence for how it searches -
> > and not all searches are tried universally, they are conditional to what
> > was provided in the reference target and what context is available.
> >
> > 1. match against the explicitly provided namespace (module was not
> > explicitly provided)
>
> Look for the name in all of the namespace's modules?
>
Yeah. search for "ns:*.name" and "ns:name" basically.
> > 2. match against the explicitly provided module (namespace was not
> > explicitly provided)
>
> Look for the name in all modules so named in all namespaces?
>
Yes.
> > 3. match against the implied namespace (neither namespace/module was
> > explicitly provided)
>
> ?
>
User gave `foo`, but we have a namespace from context, so we look for
ns:*.foo or ns:foo.
> > 4. match against the implied module (neither namespace/module was
> > explicitly provided)
>
> ?
>
User gave `foo`, but we have a module from context, so we search for
*:mod.foo and mod.foo
> > 5. match against the definition name only, from anywhere (neither
> > namespace/module was explicitly provided)
>
> Look for the name anywhere?
>
> I need examples :)
>
user gave `foo`. search for any qapi definition in all modules and
namespaces for anything with the name "foo". The broadest possible search.
Doesn't search for stuff outside of the QAPI domain directly, but be aware
when using `references` that all domains are consulted, so it may in fact
match something else from somewhere else, though not by any doing of the
qapi domain.
i.e. :qapi:any:`foo` will definitely only search using the rules laid out
in this patch/thread, but `foo` will consult all domains (and whine if more
than one result between all domains is identified.)
> > The searches are performed in order: if a search returns zero results,
> the
> > next search is tried. If any search returns one or more results, those
> > results are returned and we stop searching down the list. The priority
> > order ensures that any explicitly provided information is *always* used
> to
> > find a match, but contextually provided information is merely a "hint"
> and
> > can be ignored for the sake of a match.
> >
> > If find_obj() as a whole returns zero results, Sphinx emits a warning
> for a
> > dangling reference. if find_obj() as a whole returns multiple results,
> > Sphinx emits a warning for the ambiguous cross-reference.
> >
> > QEMU errors out on any such warnings under our normal build settings.
> >
> > Clear as mud?
>
> Clearer, but not quite mud, yet.
>
Ultimately, Search in this order and stop at any point any of these
searches return at least one result:
1. Explicitly provided name, whatever it is
2. FQN using contextual info
3. Explicitly provided NS; any module
4. Explicitly provided module; any NS
5. Contextual NS; any module
6. Contextual module; any NS
7. any NS/module.
with searches 3-7 being conditional only when the criteria are met:
3. Must have explicit NS (and no explicit module)
4. Must have explicit module (and no explicit NS)
5. Must have contextual NS (must not have explicit NS nor module)
6. Must have contextual module (must not have explicit NS nor module)
7. Must have neither explicit NS nor module.
In summary:
* Anything explicitly provided *must* match. This information is *never*
ignored.
* Anything implicitly provided through context will never overwrite
anything explicitly provided. This is used as a *hint* in the event
multiple candidates are found, but results are allowed to deviate when
stronger matches are not located.
i.e. contextual information is used to *prefer* a result from that context,
but is not used to *limit* you to that context.
by contrast, explicit info is used to directly filter and restrict search.
(With maybe a bug or two for trying to find module names in some
circumstances. Will have to check that over...)
> > --js
> >
> >
> >> [...]
> >>
> >>
>
>
John Snow <jsnow@redhat.com> writes:
> On Thu, Mar 13, 2025, 11:57 AM Markus Armbruster <armbru@redhat.com> wrote:
>
>> John Snow <jsnow@redhat.com> writes:
>>
>> > On Thu, Mar 13, 2025 at 10:41 AM Markus Armbruster <armbru@redhat.com>
>> > wrote:
>> >
>> >> John Snow <jsnow@redhat.com> writes:
>> >>
>> >> > On Thu, Mar 13, 2025 at 2:47 AM Markus Armbruster <armbru@redhat.com> wrote:
>> >> >
>> >> >> John Snow <jsnow@redhat.com> writes:
>> >> >>
>> >> >> > This patch does three things:
>> >> >> >
>> >> >> > 1. Record the current namespace context in pending_xrefs so it can be
>> >> >> > used for link resolution later,
>> >> >> > 2. Pass that recorded namespace context to find_obj() when resolving a
>> >> >> > reference, and
>> >> >> > 3. Wildly and completely rewrite find_obj().
>> >> >> >
>> >> >> > cross-reference support is expanded to tolerate the presence or absence
>> >> >> > of either namespace or module, and to cope with the presence or absence
>> >> >> > of contextual information for either.
>> >> >> >
>> >> >> > References now work like this:
>> >> >> >
>> >> >> > 1. If the explicit reference target is recorded in the domain's object
>> >> >> > registry, we link to that target and stop looking. We do this lookup
>> >> >> > regardless of how fully qualified the target is, which allows direct
>> >> >> > references to modules (which don't have a module component to their
>> >> >> > names) or direct references to definitions that may or may not belong
>> >> >> > to a namespace or module.
>> >> >> >
>> >> >> > 2. If contextual information is available from qapi:namespace or
>> >> >> > qapi:module directives, try using those components to find a direct
>> >> >> > match to the implied target name.
>> >> >> >
>> >> >> > 3. If both prior lookups fail, generate a series of regular expressions
>> >> >> > looking for wildcard matches in order from most to least
>> >> >> > specific. Any explicitly provided components (namespace, module)
>> >> >> > *must* match exactly, but both contextual and entirely omitted
>> >> >> > components are allowed to differ from the search result. Note that if
>> >> >> > more than one result is found, Sphinx will emit a warning (a build
>> >> >> > error for QEMU) and list all of the candidate references.
>> >> >> >
>> >> >> > The practical upshot is that in the large majority of cases, namespace
>> >> >> > and module information is not required when creating simple `references`
>> >> >> > to definitions from within the same context -- even when identical
>> >> >> > definitions exist in other contexts.
>> >> >>
>> >> >> Can you illustrate this this examples?
>> >> >>
>> >> >
>> >> > do wha?
>> >>
>> >> Sorry, I went into the curve too fast.
>> >>
>> >> The stuff under "References now work like this" confuses me. I guess it
>> >> describes a series of lookups to try one after the other.
>> >>
>> >> I understand a cross-reference consists of namespace (optional), module
>> >> (optional), name, and role.
>> >>
>> >> Let's assume role is "any" for simplicity's sake.
>> >>
>> >> Regarding "1. If the explicit ...":
>> >>
>> >> What is a reference's "explicit reference target"? Examples might
>> >> help me understand.
>> >>
>> >
>> > explicit lookup: `QMP:block-core:block-dirty-bitmap-add`
>> >
>> > If that explicit target matches an object in the object database
>> > *directly*, we match immediately and don't consider other potential
>> > targets. This also applies to things like modules, e.g. `QMP:block-core`
>> > even though the "module" is absent (it IS the module)
>> >
>> > We always search for the explicit target no matter how un/fully qualified
>> > it is.
>> >
>> >
>> >>
>> >> What is "recorded in the domain's object registry"?
>> >>
>> >
>> > domain.objects{} - essentially a record of every ObjectDefinition's
>> > "fullname" - the return value from QAPIDefinition._get_fqn().
>> >
>> >
>> >>
>> >> Can you show me a reference where this lookup succeeds?
>> >>
>> >
>> > `QMP:block-core`
>> > `QMP:block-core.block-dirty-bitmap-add`
>>
>> So, for this lookup to work, the reference must either be of the form
>> NAMESPACE:MODULE and resolve to that module in that namespace, or of the
>> form NAMESPACE:MODULE:DEFN and resolve to that definition in that module
>> in that namespace. Correct?
>>
>
> Yes.
>
>
>> These a "fully qualified names (FQN)" in your parlance, right?
>>
>
> More or less, though as you found below...
>
>
>> Note that the first form is syntactically indistinguishable from
>> NAMESPACE:DEFN, i.e. a reference to a definition that specifies the
>> namespace, but not the module.
>>
>> If the NAMESPACE:MODULE interpretation resolves, we never try the
>> NAMESPACE:DEFN interpretation, because that happens in later steps.
>> Correct?
>>
>> The first form is fully qualified only if it resolves as FQN. So,
>> whether such a reference is fully qualified is not syntactically
>> decidable. Hmm.
>>
>
> You're right. There is a weirdness here. I might need to do some more
> thinking to make sure it isn't theoretically a problem, but in practice,
> right now, it isn't.
Not a blocker, but please do your thinking :)
> Stay tuned, I guess.
>
>
>> >> Regarding "2. If contextual information ...":
>> >>
>> >> I guess "contextual information" is the context established by
>> >> qapi:namespace and qapi:module directives, i.e. the current
>> >> namespace and module, if any.
>> >>
>> >
>> > Yep!
>> >
>> >
>> >>
>> >> If the cross reference lacks a namespace, we substitute the current
>> >> namespace. Same for module.
>> >>
>> >> We then use that "to find a direct match to the implied target
>> >> name". Sounds greek to me. Example(s) might help.
>> >>
>> >
>> > If namespace or module is missing from the link target, we try to fill in
>> > the blanks with the contextual information if present.
>> >
>> > Example, we are in the block-core section of the QEMU QMP reference manual
>> > document and we reference `block-dirty-bitmap-add`. With context, we are
>> > able to assemble a fully qualified name:
>> > "QMP:block-core.block-dirty-bitmap-add`. This matches an item in the
>> > registry directly, so it matches and no further search is performed.
>>
>> We try this lookup only when the reference lacks a namespace and we are
>> "in" a namespace, or when it lacks a module and we are "in" a module.
>> Correct?
>>
>
> or both: if we provided only a name but the context has both a namespace
> and module.
So my or is inclusive :)
> essentially the algorithm splits the explicit target into (ns, mod, name)
> and for any that are blank, we try to fill in those blanks with context
> info where available. Sometimes you have neither explicit nor contextual
> info for a component.
>
> Then we do a lookup for an exact match, in order;
>
> 1. explicit target name, whatever it was
Fully qualified name.
If lookup succeeds, we're done.
If lookup fails, we're also done.
*Except* for the ambiguous form NAMESPACE:MYSTERY. If lookup fails for
that, the name is not fully qualified after all. Probably. Maybe. We
assume it's missing a module, and proceed to 2.
I'm mostly ignoring this exception from now on to keep things simple.
> 2. FQN using contextual info
Partially qualified name, but context can fill the gaps.
If lookup succeeds, we're done.
Else, we proceed to 3.
> and we stop after the first hit - no chance for multiple results here, just
> zero-or-one each step.
>
> i.e. any explicitly provided information is never "overwritten" with
> context, context only fills in the blanks where that info was not provided.
>
> If neither of these work, we move on to fuzzy searching.
>
>
>> We then subsitute current namespace / module for the lacking one(s), and
>> try the same lookup as in 1. Correct?
>>
>
> Yes!
>
>
>> If we have a reference of the form MYSTERY, it could either be a
>> reference to module MYSTERY in the current namespace, or to definition
>> MYSTERY in the current namespace and module. How do we decide?
>>
>
> fqn a: NS:MYSTERY
> fqn b: NS:MOD:MYSTERY
>
> Given we have a current ns/mod context, it's going to pick the second one.
>
> Hm. Maybe it ought to be ambiguous in this case... I'll have to revise
> this. (question is: how soon do we need it?)
While we should try to put this on a more solid foundation, it is not a
blocker.
>> >> Regarding "3. If both prior lookups fail ...":
>> >>
>> >> I guess we get here when namespace or module are absent, and
>> >> substituting the current namespace or module doesn't resolve. We
>> >> then substitute a wildcard, so to speak, i.e. look in all namespaces
>> >> / modules, and succeed if we find exactly one resolution. Fair?
>> >>
>> >
>> > More or less, though the mechanics are quite a bit more complex than your
>> > overview (and what I wrote in qapi-domain.rst.) We can get here for a few
>> > reasons:
>> >
>> > (1) We didn't provide a fully qualified target, and we don't have full
>> > context to construct one.
We skipped 1. because not fully qualified, and we skipped 2. because
context can't fill the gaps.
>> > For example, we are not "in" a namespace and/or
>> > not "in" a module. This is quite likely to happen when writing simple
>> > references to a definition name from outside of the transmogfrified QAPI
>> > documentation, e.g. from qapi-domain.rst itself, or dirty-bitmaps.rst, etc.
Yes.
>> > (2) We didn't provide a fully qualified target, and we are referencing
>> > something from outside of the local context. For example, we are "in" a
>> > module but we are trying to link to a different module's definition. e.g.
>> > we are in QMP:transaction and we reference `block-dirty-bitmap-add`. The
>> > implied FQN will be QMP:transaction.block-dirty-bitmap.add, which will not
>> > resolve.
We skipped 1. because not fully qualified, and we failed 2. because
context filled the gaps, but lookup failed.
>> >
>> > The fuzzy search portion has an order of precedence for how it searches -
>> > and not all searches are tried universally, they are conditional to what
>> > was provided in the reference target and what context is available.
>> >
>> > 1. match against the explicitly provided namespace (module was not
>> > explicitly provided)
>>
>> Look for the name in all of the namespace's modules?
>>
>
> Yeah. search for "ns:*.name" and "ns:name" basically.
Got it.
>> > 2. match against the explicitly provided module (namespace was not
>> > explicitly provided)
>>
>> Look for the name in all modules so named in all namespaces?
>>
>
> Yes.
Got it.
>> > 3. match against the implied namespace (neither namespace/module was
>> > explicitly provided)
>>
>> ?
>>
>
> User gave `foo`, but we have a namespace from context, so we look for
> ns:*.foo or ns:foo.
Got it.
Detail I had not considered until now: a namespace contains modules that
contain definitions, but it also contains definitions directly.
I can't recall offhand how schema.py represents this. I'll figure it
out and report back.
>> > 4. match against the implied module (neither namespace/module was
>> > explicitly provided)
>>
>> ?
>>
>
> User gave `foo`, but we have a module from context, so we search for
> *:mod.foo and mod.foo
Got it.
>> > 5. match against the definition name only, from anywhere (neither
>> > namespace/module was explicitly provided)
>>
>> Look for the name anywhere?
>>
>> I need examples :)
>>
>
> user gave `foo`. search for any qapi definition in all modules and
> namespaces for anything with the name "foo". The broadest possible search.
>
> Doesn't search for stuff outside of the QAPI domain directly, but be aware
> when using `references` that all domains are consulted, so it may in fact
> match something else from somewhere else, though not by any doing of the
> qapi domain.
>
> i.e. :qapi:any:`foo` will definitely only search using the rules laid out
> in this patch/thread, but `foo` will consult all domains (and whine if more
> than one result between all domains is identified.)
Got it, I think.
>> > The searches are performed in order: if a search returns zero results, the
>> > next search is tried. If any search returns one or more results, those
>> > results are returned and we stop searching down the list. The priority
>> > order ensures that any explicitly provided information is *always* used to
>> > find a match, but contextually provided information is merely a "hint" and
>> > can be ignored for the sake of a match.
>> >
>> > If find_obj() as a whole returns zero results, Sphinx emits a warning for a
>> > dangling reference. if find_obj() as a whole returns multiple results,
>> > Sphinx emits a warning for the ambiguous cross-reference.
>> >
>> > QEMU errors out on any such warnings under our normal build settings.
>> >
>> > Clear as mud?
>>
>> Clearer, but not quite mud, yet.
>>
>
> Ultimately, Search in this order and stop at any point any of these
> searches return at least one result:
>
> 1. Explicitly provided name, whatever it is
> 2. FQN using contextual info
> 3. Explicitly provided NS; any module
> 4. Explicitly provided module; any NS
> 5. Contextual NS; any module
> 6. Contextual module; any NS
> 7. any NS/module.
>
> with searches 3-7 being conditional only when the criteria are met:
>
> 3. Must have explicit NS (and no explicit module)
> 4. Must have explicit module (and no explicit NS)
> 5. Must have contextual NS (must not have explicit NS nor module)
> 6. Must have contextual module (must not have explicit NS nor module)
> 7. Must have neither explicit NS nor module.
>
> In summary:
>
> * Anything explicitly provided *must* match. This information is *never*
> ignored.
> * Anything implicitly provided through context will never overwrite
> anything explicitly provided. This is used as a *hint* in the event
> multiple candidates are found, but results are allowed to deviate when
> stronger matches are not located.
>
> i.e. contextual information is used to *prefer* a result from that context,
> but is not used to *limit* you to that context.
>
> by contrast, explicit info is used to directly filter and restrict search.
Makes sense.
> (With maybe a bug or two for trying to find module names in some
> circumstances. Will have to check that over...)
Thank you!
On Thu, Mar 13, 2025 at 2:30 PM Markus Armbruster <armbru@redhat.com> wrote:
> John Snow <jsnow@redhat.com> writes:
>
> > On Thu, Mar 13, 2025, 11:57 AM Markus Armbruster <armbru@redhat.com>
> wrote:
> >
> >> John Snow <jsnow@redhat.com> writes:
> >>
> >> > On Thu, Mar 13, 2025 at 10:41 AM Markus Armbruster <armbru@redhat.com
> >
> >> > wrote:
> >> >
> >> >> John Snow <jsnow@redhat.com> writes:
> >> >>
> >> >> > On Thu, Mar 13, 2025 at 2:47 AM Markus Armbruster <
> armbru@redhat.com> wrote:
> >> >> >
> >> >> >> John Snow <jsnow@redhat.com> writes:
> >> >> >>
> >> >> >> > This patch does three things:
> >> >> >> >
> >> >> >> > 1. Record the current namespace context in pending_xrefs so it
> can be
> >> >> >> > used for link resolution later,
> >> >> >> > 2. Pass that recorded namespace context to find_obj() when
> resolving a
> >> >> >> > reference, and
> >> >> >> > 3. Wildly and completely rewrite find_obj().
> >> >> >> >
> >> >> >> > cross-reference support is expanded to tolerate the presence or
> absence
> >> >> >> > of either namespace or module, and to cope with the presence or
> absence
> >> >> >> > of contextual information for either.
> >> >> >> >
> >> >> >> > References now work like this:
> >> >> >> >
> >> >> >> > 1. If the explicit reference target is recorded in the domain's
> object
> >> >> >> > registry, we link to that target and stop looking. We do
> this lookup
> >> >> >> > regardless of how fully qualified the target is, which
> allows direct
> >> >> >> > references to modules (which don't have a module component
> to their
> >> >> >> > names) or direct references to definitions that may or may
> not belong
> >> >> >> > to a namespace or module.
> >> >> >> >
> >> >> >> > 2. If contextual information is available from qapi:namespace or
> >> >> >> > qapi:module directives, try using those components to find a
> direct
> >> >> >> > match to the implied target name.
> >> >> >> >
> >> >> >> > 3. If both prior lookups fail, generate a series of regular
> expressions
> >> >> >> > looking for wildcard matches in order from most to least
> >> >> >> > specific. Any explicitly provided components (namespace,
> module)
> >> >> >> > *must* match exactly, but both contextual and entirely
> omitted
> >> >> >> > components are allowed to differ from the search result.
> Note that if
> >> >> >> > more than one result is found, Sphinx will emit a warning (a
> build
> >> >> >> > error for QEMU) and list all of the candidate references.
> >> >> >> >
> >> >> >> > The practical upshot is that in the large majority of cases,
> namespace
> >> >> >> > and module information is not required when creating simple
> `references`
> >> >> >> > to definitions from within the same context -- even when
> identical
> >> >> >> > definitions exist in other contexts.
> >> >> >>
> >> >> >> Can you illustrate this this examples?
> >> >> >>
> >> >> >
> >> >> > do wha?
> >> >>
> >> >> Sorry, I went into the curve too fast.
> >> >>
> >> >> The stuff under "References now work like this" confuses me. I
> guess it
> >> >> describes a series of lookups to try one after the other.
> >> >>
> >> >> I understand a cross-reference consists of namespace (optional),
> module
> >> >> (optional), name, and role.
> >> >>
> >> >> Let's assume role is "any" for simplicity's sake.
> >> >>
> >> >> Regarding "1. If the explicit ...":
> >> >>
> >> >> What is a reference's "explicit reference target"? Examples
> might
> >> >> help me understand.
> >> >>
> >> >
> >> > explicit lookup: `QMP:block-core:block-dirty-bitmap-add`
> >> >
> >> > If that explicit target matches an object in the object database
> >> > *directly*, we match immediately and don't consider other potential
> >> > targets. This also applies to things like modules, e.g.
> `QMP:block-core`
> >> > even though the "module" is absent (it IS the module)
> >> >
> >> > We always search for the explicit target no matter how un/fully
> qualified
> >> > it is.
> >> >
> >> >
> >> >>
> >> >> What is "recorded in the domain's object registry"?
> >> >>
> >> >
> >> > domain.objects{} - essentially a record of every ObjectDefinition's
> >> > "fullname" - the return value from QAPIDefinition._get_fqn().
> >> >
> >> >
> >> >>
> >> >> Can you show me a reference where this lookup succeeds?
> >> >>
> >> >
> >> > `QMP:block-core`
> >> > `QMP:block-core.block-dirty-bitmap-add`
> >>
> >> So, for this lookup to work, the reference must either be of the form
> >> NAMESPACE:MODULE and resolve to that module in that namespace, or of the
> >> form NAMESPACE:MODULE:DEFN and resolve to that definition in that module
> >> in that namespace. Correct?
> >>
> >
> > Yes.
> >
> >
> >> These a "fully qualified names (FQN)" in your parlance, right?
> >>
> >
> > More or less, though as you found below...
> >
> >
> >> Note that the first form is syntactically indistinguishable from
> >> NAMESPACE:DEFN, i.e. a reference to a definition that specifies the
> >> namespace, but not the module.
> >>
> >> If the NAMESPACE:MODULE interpretation resolves, we never try the
> >> NAMESPACE:DEFN interpretation, because that happens in later steps.
> >> Correct?
> >>
> >> The first form is fully qualified only if it resolves as FQN. So,
> >> whether such a reference is fully qualified is not syntactically
> >> decidable. Hmm.
> >>
> >
> > You're right. There is a weirdness here. I might need to do some more
> > thinking to make sure it isn't theoretically a problem, but in practice,
> > right now, it isn't.
>
> Not a blocker, but please do your thinking :)
>
> > Stay tuned, I guess.
> >
> >
> >> >> Regarding "2. If contextual information ...":
> >> >>
> >> >> I guess "contextual information" is the context established by
> >> >> qapi:namespace and qapi:module directives, i.e. the current
> >> >> namespace and module, if any.
> >> >>
> >> >
> >> > Yep!
> >> >
> >> >
> >> >>
> >> >> If the cross reference lacks a namespace, we substitute the
> current
> >> >> namespace. Same for module.
> >> >>
> >> >> We then use that "to find a direct match to the implied target
> >> >> name". Sounds greek to me. Example(s) might help.
> >> >>
> >> >
> >> > If namespace or module is missing from the link target, we try to
> fill in
> >> > the blanks with the contextual information if present.
> >> >
> >> > Example, we are in the block-core section of the QEMU QMP reference
> manual
> >> > document and we reference `block-dirty-bitmap-add`. With context, we
> are
> >> > able to assemble a fully qualified name:
> >> > "QMP:block-core.block-dirty-bitmap-add`. This matches an item in the
> >> > registry directly, so it matches and no further search is performed.
> >>
> >> We try this lookup only when the reference lacks a namespace and we are
> >> "in" a namespace, or when it lacks a module and we are "in" a module.
> >> Correct?
> >>
> >
> > or both: if we provided only a name but the context has both a namespace
> > and module.
>
> So my or is inclusive :)
>
> > essentially the algorithm splits the explicit target into (ns, mod, name)
> > and for any that are blank, we try to fill in those blanks with context
> > info where available. Sometimes you have neither explicit nor contextual
> > info for a component.
> >
> > Then we do a lookup for an exact match, in order;
> >
> > 1. explicit target name, whatever it was
>
> Fully qualified name.
>
Yes, for lookup to succeed it should be fully qualified, though if the
target text is "ns:module", that's actually going to succeed here, too.
>
> If lookup succeeds, we're done.
>
> If lookup fails, we're also done.
>
If lookup fails, we actually continue on to #2, but whether or not this
does anything useful depends on whether or not the original target text was
fully qualified or not. If it was, #2 searches with the exact same text and
will fail again and proceed to #3, where because we had a fully qualified
name, none of the search conditions apply and we then just exit.
(It just lacks an early return, but abstractly, if lookup on #1 fails with
a fully qualified name, we are indeed done.)
If lookup fails because it wasn't actually fully qualified, then #2 has
some gaps to try to fill.
>
> *Except* for the ambiguous form NAMESPACE:MYSTERY. If lookup fails for
> that, the name is not fully qualified after all. Probably. Maybe. We
> assume it's missing a module, and proceed to 2.
>
> I'm mostly ignoring this exception from now on to keep things simple.
>
> > 2. FQN using contextual info
>
> Partially qualified name, but context can fill the gaps.
>
> If lookup succeeds, we're done.
>
> Else, we proceed to 3.
>
That's right.
>
> > and we stop after the first hit - no chance for multiple results here,
> just
> > zero-or-one each step.
> >
> > i.e. any explicitly provided information is never "overwritten" with
> > context, context only fills in the blanks where that info was not
> provided.
> >
> > If neither of these work, we move on to fuzzy searching.
> >
> >
> >> We then subsitute current namespace / module for the lacking one(s), and
> >> try the same lookup as in 1. Correct?
> >>
> >
> > Yes!
> >
> >
> >> If we have a reference of the form MYSTERY, it could either be a
> >> reference to module MYSTERY in the current namespace, or to definition
> >> MYSTERY in the current namespace and module. How do we decide?
> >>
> >
> > fqn a: NS:MYSTERY
> > fqn b: NS:MOD:MYSTERY
> >
> > Given we have a current ns/mod context, it's going to pick the second
> one.
> >
> > Hm. Maybe it ought to be ambiguous in this case... I'll have to revise
> > this. (question is: how soon do we need it?)
>
> While we should try to put this on a more solid foundation, it is not a
> blocker.
>
> >> >> Regarding "3. If both prior lookups fail ...":
> >> >>
> >> >> I guess we get here when namespace or module are absent, and
> >> >> substituting the current namespace or module doesn't resolve. We
> >> >> then substitute a wildcard, so to speak, i.e. look in all
> namespaces
> >> >> / modules, and succeed if we find exactly one resolution. Fair?
> >> >>
> >> >
> >> > More or less, though the mechanics are quite a bit more complex than
> your
> >> > overview (and what I wrote in qapi-domain.rst.) We can get here for a
> few
> >> > reasons:
> >> >
> >> > (1) We didn't provide a fully qualified target, and we don't have full
> >> > context to construct one.
>
> We skipped 1. because not fully qualified, and we skipped 2. because
> context can't fill the gaps.
>
we tried #1 and failed, then we tried #2 and failed again.
>
> >> > For example, we are not "in" a namespace
> and/or
> >> > not "in" a module. This is quite likely to happen when writing simple
> >> > references to a definition name from outside of the transmogfrified
> QAPI
> >> > documentation, e.g. from qapi-domain.rst itself, or
> dirty-bitmaps.rst, etc.
>
> Yes.
>
> >> > (2) We didn't provide a fully qualified target, and we are referencing
> >> > something from outside of the local context. For example, we are "in"
> a
> >> > module but we are trying to link to a different module's definition.
> e.g.
> >> > we are in QMP:transaction and we reference `block-dirty-bitmap-add`.
> The
> >> > implied FQN will be QMP:transaction.block-dirty-bitmap.add, which
> will not
> >> > resolve.
>
> We skipped 1. because not fully qualified, and we failed 2. because
> context filled the gaps, but lookup failed.
>
Failed #1 and Failed #2.
>
> >> >
> >> > The fuzzy search portion has an order of precedence for how it
> searches -
> >> > and not all searches are tried universally, they are conditional to
> what
> >> > was provided in the reference target and what context is available.
> >> >
> >> > 1. match against the explicitly provided namespace (module was not
> >> > explicitly provided)
> >>
> >> Look for the name in all of the namespace's modules?
> >>
> >
> > Yeah. search for "ns:*.name" and "ns:name" basically.
>
> Got it.
>
> >> > 2. match against the explicitly provided module (namespace was not
> >> > explicitly provided)
> >>
> >> Look for the name in all modules so named in all namespaces?
> >>
> >
> > Yes.
>
> Got it.
>
> >> > 3. match against the implied namespace (neither namespace/module was
> >> > explicitly provided)
> >>
> >> ?
> >>
> >
> > User gave `foo`, but we have a namespace from context, so we look for
> > ns:*.foo or ns:foo.
>
> Got it.
>
> Detail I had not considered until now: a namespace contains modules that
> contain definitions, but it also contains definitions directly.
>
> I can't recall offhand how schema.py represents this. I'll figure it
> out and report back.
>
I think it gets charged to a module named "qapi-schema". Silly, but it
doesn't really break anything.
> >> > 4. match against the implied module (neither namespace/module was
> >> > explicitly provided)
> >>
> >> ?
> >>
> >
> > User gave `foo`, but we have a module from context, so we search for
> > *:mod.foo and mod.foo
>
> Got it.
>
> >> > 5. match against the definition name only, from anywhere (neither
> >> > namespace/module was explicitly provided)
> >>
> >> Look for the name anywhere?
> >>
> >> I need examples :)
> >>
> >
> > user gave `foo`. search for any qapi definition in all modules and
> > namespaces for anything with the name "foo". The broadest possible
> search.
> >
> > Doesn't search for stuff outside of the QAPI domain directly, but be
> aware
> > when using `references` that all domains are consulted, so it may in fact
> > match something else from somewhere else, though not by any doing of the
> > qapi domain.
> >
> > i.e. :qapi:any:`foo` will definitely only search using the rules laid out
> > in this patch/thread, but `foo` will consult all domains (and whine if
> more
> > than one result between all domains is identified.)
>
> Got it, I think.
>
> >> > The searches are performed in order: if a search returns zero
> results, the
> >> > next search is tried. If any search returns one or more results, those
> >> > results are returned and we stop searching down the list. The priority
> >> > order ensures that any explicitly provided information is *always*
> used to
> >> > find a match, but contextually provided information is merely a
> "hint" and
> >> > can be ignored for the sake of a match.
> >> >
> >> > If find_obj() as a whole returns zero results, Sphinx emits a warning
> for a
> >> > dangling reference. if find_obj() as a whole returns multiple results,
> >> > Sphinx emits a warning for the ambiguous cross-reference.
> >> >
> >> > QEMU errors out on any such warnings under our normal build settings.
> >> >
> >> > Clear as mud?
> >>
> >> Clearer, but not quite mud, yet.
> >>
> >
> > Ultimately, Search in this order and stop at any point any of these
> > searches return at least one result:
> >
> > 1. Explicitly provided name, whatever it is
> > 2. FQN using contextual info
> > 3. Explicitly provided NS; any module
> > 4. Explicitly provided module; any NS
> > 5. Contextual NS; any module
> > 6. Contextual module; any NS
> > 7. any NS/module.
> >
> > with searches 3-7 being conditional only when the criteria are met:
> >
> > 3. Must have explicit NS (and no explicit module)
> > 4. Must have explicit module (and no explicit NS)
> > 5. Must have contextual NS (must not have explicit NS nor module)
> > 6. Must have contextual module (must not have explicit NS nor module)
> > 7. Must have neither explicit NS nor module.
>
I should point out that:
- #3 and #4 are mutually exclusive
- #5 and #6 are mutually exclusive
- #3/#4 and #5/#6 are mutually exclusive
- Only one of #3/#4/#5/#6 will happen for a given reference.
- If we choose #5 or #6, #7 may also happen. If we choose #3 or #4, #7 will
never happen.
I bet I can make the code here cleaner by combining some of these, it's
just a lot of context on the input to keep in mind while designing the
conditionals, so I wrote it out very "explicitly" to help myself make sense
of it ...
> >
> > In summary:
> >
> > * Anything explicitly provided *must* match. This information is *never*
> > ignored.
> > * Anything implicitly provided through context will never overwrite
> > anything explicitly provided. This is used as a *hint* in the event
> > multiple candidates are found, but results are allowed to deviate when
> > stronger matches are not located.
> >
> > i.e. contextual information is used to *prefer* a result from that
> context,
> > but is not used to *limit* you to that context.
> >
> > by contrast, explicit info is used to directly filter and restrict
> search.
>
> Makes sense.
>
> > (With maybe a bug or two for trying to find module names in some
> > circumstances. Will have to check that over...)
>
> Thank you!
>
>
You're welcome!
John Snow <jsnow@redhat.com> writes:
> On Thu, Mar 13, 2025 at 2:30 PM Markus Armbruster <armbru@redhat.com> wrote:
>
>> John Snow <jsnow@redhat.com> writes:
>>
>> > On Thu, Mar 13, 2025, 11:57 AM Markus Armbruster <armbru@redhat.com> wrote:
>> >
>> >> John Snow <jsnow@redhat.com> writes:
>> >>
>> >> > On Thu, Mar 13, 2025 at 10:41 AM Markus Armbruster <armbru@redhat.com wrote:
[...]
>> >> >> Regarding "2. If contextual information ...":
>> >> >>
>> >> >> I guess "contextual information" is the context established by
>> >> >> qapi:namespace and qapi:module directives, i.e. the current
>> >> >> namespace and module, if any.
>> >> >>
>> >> >
>> >> > Yep!
>> >> >
>> >> >
>> >> >>
>> >> >> If the cross reference lacks a namespace, we substitute the current
>> >> >> namespace. Same for module.
>> >> >>
>> >> >> We then use that "to find a direct match to the implied target
>> >> >> name". Sounds greek to me. Example(s) might help.
>> >> >>
>> >> >
>> >> > If namespace or module is missing from the link target, we try to fill in
>> >> > the blanks with the contextual information if present.
>> >> >
>> >> > Example, we are in the block-core section of the QEMU QMP reference manual
>> >> > document and we reference `block-dirty-bitmap-add`. With context, we are
>> >> > able to assemble a fully qualified name:
>> >> > "QMP:block-core.block-dirty-bitmap-add`. This matches an item in the
>> >> > registry directly, so it matches and no further search is performed.
>> >>
>> >> We try this lookup only when the reference lacks a namespace and we are
>> >> "in" a namespace, or when it lacks a module and we are "in" a module.
>> >> Correct?
>> >>
>> >
>> > or both: if we provided only a name but the context has both a namespace
>> > and module.
>>
>> So my or is inclusive :)
>>
>> > essentially the algorithm splits the explicit target into (ns, mod, name)
>> > and for any that are blank, we try to fill in those blanks with context
>> > info where available. Sometimes you have neither explicit nor contextual
>> > info for a component.
>> >
>> > Then we do a lookup for an exact match, in order;
>> >
>> > 1. explicit target name, whatever it was
>>
>> Fully qualified name.
>>
>
> Yes, for lookup to succeed it should be fully qualified, though if the
> target text is "ns:module", that's actually going to succeed here, too.
>
>
>>
>> If lookup succeeds, we're done.
>>
>> If lookup fails, we're also done.
>>
>
> If lookup fails, we actually continue on to #2, but whether or not this
> does anything useful depends on whether or not the original target text was
> fully qualified or not. If it was, #2 searches with the exact same text and
> will fail again and proceed to #3, where because we had a fully qualified
> name, none of the search conditions apply and we then just exit.
>
> (It just lacks an early return, but abstractly, if lookup on #1 fails with
> a fully qualified name, we are indeed done.)
>
> If lookup fails because it wasn't actually fully qualified, then #2 has
> some gaps to try to fill.
So, instead of
if ref is fully qualified:
try to look up ref
else
proceed to 2.
you do
look up ref
if found:
ref must be fully qualified
else
proceed to 2.
Since "proceed to 2. won't do anything useful when ref is fully
qualified", the two should produce the same result.
>> *Except* for the ambiguous form NAMESPACE:MYSTERY. If lookup fails for
>> that, the name is not fully qualified after all. Probably. Maybe. We
>> assume it's missing a module, and proceed to 2.
>>
>> I'm mostly ignoring this exception from now on to keep things simple.
>>
>> > 2. FQN using contextual info
>>
>> Partially qualified name, but context can fill the gaps.
>>
>> If lookup succeeds, we're done.
>>
>> Else, we proceed to 3.
>>
>
> That's right.
>
>
>>
>> > and we stop after the first hit - no chance for multiple results here, just
>> > zero-or-one each step.
>> >
>> > i.e. any explicitly provided information is never "overwritten" with
>> > context, context only fills in the blanks where that info was not provided.
>> >
>> > If neither of these work, we move on to fuzzy searching.
>> >
>> >
>> >> We then subsitute current namespace / module for the lacking one(s), and
>> >> try the same lookup as in 1. Correct?
>> >>
>> >
>> > Yes!
>> >
>> >
>> >> If we have a reference of the form MYSTERY, it could either be a
>> >> reference to module MYSTERY in the current namespace, or to definition
>> >> MYSTERY in the current namespace and module. How do we decide?
>> >>
>> >
>> > fqn a: NS:MYSTERY
>> > fqn b: NS:MOD:MYSTERY
>> >
>> > Given we have a current ns/mod context, it's going to pick the second one.
>> >
>> > Hm. Maybe it ought to be ambiguous in this case... I'll have to revise
>> > this. (question is: how soon do we need it?)
>>
>> While we should try to put this on a more solid foundation, it is not a
>> blocker.
>>
>> >> >> Regarding "3. If both prior lookups fail ...":
>> >> >>
>> >> >> I guess we get here when namespace or module are absent, and
>> >> >> substituting the current namespace or module doesn't resolve. We
>> >> >> then substitute a wildcard, so to speak, i.e. look in all namespaces
>> >> >> / modules, and succeed if we find exactly one resolution. Fair?
>> >> >>
>> >> >
>> >> > More or less, though the mechanics are quite a bit more complex than your
>> >> > overview (and what I wrote in qapi-domain.rst.) We can get here for a few
>> >> > reasons:
>> >> >
>> >> > (1) We didn't provide a fully qualified target, and we don't have full
>> >> > context to construct one.
>>
>> We skipped 1. because not fully qualified, and we skipped 2. because
>> context can't fill the gaps.
>>
>
> we tried #1 and failed, then we tried #2 and failed again.
I think we're describing the same behavior with different words
Behavior:
try to fill the gaps from context
if all filled:
look up
if found:
done
When you say "tried #2 and failed", you're referring to the entire
thing.
When I say "skipped 2.", I'm referring to the lookup.
>> >> > For example, we are not "in" a namespace and/or
>> >> > not "in" a module. This is quite likely to happen when writing simple
>> >> > references to a definition name from outside of the transmogfrified QAPI
>> >> > documentation, e.g. from qapi-domain.rst itself, or dirty-bitmaps.rst, etc.
>>
>> Yes.
>>
>> >> > (2) We didn't provide a fully qualified target, and we are referencing
>> >> > something from outside of the local context. For example, we are "in" a
>> >> > module but we are trying to link to a different module's definition. e.g.
>> >> > we are in QMP:transaction and we reference `block-dirty-bitmap-add`. The
>> >> > implied FQN will be QMP:transaction.block-dirty-bitmap.add, which will not
>> >> > resolve.
>>
>> We skipped 1. because not fully qualified, and we failed 2. because
>> context filled the gaps, but lookup failed.
>>
>
> Failed #1 and Failed #2.
>
>
>>
>> >> >
>> >> > The fuzzy search portion has an order of precedence for how it searches -
>> >> > and not all searches are tried universally, they are conditional to what
>> >> > was provided in the reference target and what context is available.
>> >> >
>> >> > 1. match against the explicitly provided namespace (module was not
>> >> > explicitly provided)
>> >>
>> >> Look for the name in all of the namespace's modules?
>> >>
>> >
>> > Yeah. search for "ns:*.name" and "ns:name" basically.
>>
>> Got it.
>>
>> >> > 2. match against the explicitly provided module (namespace was not
>> >> > explicitly provided)
>> >>
>> >> Look for the name in all modules so named in all namespaces?
>> >>
>> >
>> > Yes.
>>
>> Got it.
>>
>> >> > 3. match against the implied namespace (neither namespace/module was
>> >> > explicitly provided)
>> >>
>> >> ?
>> >>
>> >
>> > User gave `foo`, but we have a namespace from context, so we look for
>> > ns:*.foo or ns:foo.
>>
>> Got it.
>>
>> Detail I had not considered until now: a namespace contains modules that
>> contain definitions, but it also contains definitions directly.
>>
>> I can't recall offhand how schema.py represents this. I'll figure it
>> out and report back.
>>
>
> I think it gets charged to a module named "qapi-schema". Silly, but it
> doesn't really break anything.
Alright, here's how it works.
A QAPI schema consists of the main schema file and optionally included
files.
Each file's definitions go into a module named like the file. The
builtin definitions go into a special module './builtin'.
A definition is *always* in a module. Even when the schema is
monolithic, i.e. includes nothing.
Why do you need to support definitions directly in the namespace?
[...]
© 2016 - 2025 Red Hat, Inc.