Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

crates/mimir-schema/src/moniker.rs

//! THE SCIP moniker formatter — the one writer (LAW 5).
//!
//! Every Mímir node id is a SCIP symbol string in the shape `rust-analyzer scip`
//! emits: `rust-analyzer cargo <package> <version> <descriptors>`. This module is
//! the ONLY place that string is assembled. `nornir::coderoom` (syntactic, world
//! ring) and `mimir-driver` (resolved, rustc) both call into it, so a definition
//! written by either producer gets the same bytes.
//!
//! Descriptor grammar (SCIP `format_symbol_with`):
//!
//! | suffix          | form        | used for                                   |
//! |-----------------|-------------|--------------------------------------------|
//! | `Namespace`     | `name/`     | modules (and the crate root, `crate/`)     |
//! | `Type`          | `name#`     | struct · enum · union · trait · type alias · variant |
//! | `Term`          | `name.`     | const · static · field                     |
//! | `Method`        | `name().`   | fn · method                                |
//! | `TypeParameter` | `[name]`    | generic params, the impl self/trait slots  |
//! | `Parameter`     | `(name)`    | fn params                                  |
//! | `Meta`          | `name:`     | derive / attribute macros, non-lib target prefix (`` `bin=x`: ``), mono instances, cargo features (`` `feature=x`: ``), compiled-out items' predicate |
//! | `Macro`         | `name!`     | bang macros (derive / attribute macros are `Meta`, `Debug:`) |
//!
//! ## Targets of one package
//!
//! A package's LIB target (and the `--test` harness of that lib) has
//! rust-analyzer's ids, byte for byte: `… lgz 0.3.14 crate/`, `… lgz 0.3.14
//! chunk/split().`. rust-analyzer has no answer for the OTHER targets of the
//! package — it gives a bin's `crate/` and `main().` the lib's ids (and warns
//! about its own duplicate symbols) — so every node of a non-lib target
//! starts with one `Meta` descriptor naming the target, [`target_prefix`]:
//! `` `bin=lgz`: ``, `` `test=roundtrip`: ``, `` `bench=inflate`: ``,
//! `` `example=demo`: `` (cargo's target kind `=` the target's crate name).
//! The kind is part of it because cargo lets a package have a test, a bench,
//! an example and a bin of ONE name, and a bin named like the lib is the
//! default layout (`src/lib.rs` + `src/main.rs`). A target's own `--test`
//! harness keeps the target's ids.
//!
//! Impl scopes are rust-analyzer's: `impl#[Self]` for an inherent impl and
//! `impl#[Self][Trait]` for a trait impl, then the item — `impl#[Point]new().`,
//! `impl#[Dog][Greet]greet().`, ``impl#[i32][`Add<Self>`]add().`` (observed from
//! `rust-analyzer scip`, rust-analyzer 1.101.0-nightly 2822155, pinned in
//! tests/schema_and_moniker.rs). See [`push_impl`].

/// The SCIP scheme every Mímir moniker uses — rust-analyzer's, so a driver row
/// and an `rust-analyzer scip` row for the same item are the same key.
pub const SCHEME: &str = "rust-analyzer";
/// The SCIP package manager field.
pub const MANAGER: &str = "cargo";
/// Descriptor of a crate's ROOT node. A module literally named `crate` cannot
/// exist (keyword), so this can never collide with a real module.
pub const CRATE_ROOT_DESCRIPTOR: &str = "crate/";

/// One SCIP descriptor suffix.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Suffix {
    Namespace,
    Type,
    Term,
    Method,
    TypeParameter,
    Parameter,
    Meta,
    Macro,
}

/// SCIP name escaping: a name that is not a simple identifier is backtick-quoted
/// with `` ` `` doubled — verbatim the rule in `scip::symbol::escape_name`. Rust
/// idents never trip it, but the moniker must stay parseable by the CANONICAL
/// parser for the round-trip oracle to mean anything.
pub fn escape_name(name: &str) -> String {
    if is_simple_identifier(name) {
        name.to_string()
    } else {
        format!("`{}`", name.replace('`', "``"))
    }
}

fn is_simple_identifier(name: &str) -> bool {
    name.chars()
        .all(|c| c == '_' || c == '+' || c == '-' || c == '$' || c.is_ascii_alphanumeric())
}

/// Append ONE descriptor (escaped name + suffix) to `out`.
pub fn push_descriptor(out: &mut String, suffix: Suffix, name: &str) {
    let n = escape_name(name);
    match suffix {
        Suffix::Namespace => {
            out.push_str(&n);
            out.push('/');
        }
        Suffix::Type => {
            out.push_str(&n);
            out.push('#');
        }
        Suffix::Term => {
            out.push_str(&n);
            out.push('.');
        }
        Suffix::Method => {
            out.push_str(&n);
            out.push_str("().");
        }
        Suffix::TypeParameter => {
            out.push('[');
            out.push_str(&n);
            out.push(']');
        }
        Suffix::Parameter => {
            out.push('(');
            out.push_str(&n);
            out.push(')');
        }
        Suffix::Meta => {
            out.push_str(&n);
            out.push(':');
        }
        Suffix::Macro => {
            out.push_str(&n);
            out.push('!');
        }
    }
}

/// rust-analyzer's impl scope: `impl#[Self]` (inherent) or `impl#[Self][Trait]`
/// (trait impl). The impl's items follow directly: `impl#[Dog][Greet]greet().`.
pub fn push_impl(out: &mut String, self_name: &str, trait_name: Option<&str>) {
    push_descriptor(out, Suffix::Type, "impl");
    push_descriptor(out, Suffix::TypeParameter, self_name);
    if let Some(t) = trait_name {
        push_descriptor(out, Suffix::TypeParameter, t);
    }
}

/// [`push_impl`] as an owned string.
pub fn impl_descriptor(self_name: &str, trait_name: Option<&str>) -> String {
    let mut s = String::new();
    push_impl(&mut s, self_name, trait_name);
    s
}

/// rust-analyzer's version field for the toolchain's own crates (std, core,
/// alloc, proc_macro, test …): not a semver, the library's source URL.
pub fn lang_version(crate_name: &str) -> String {
    format!("https://github.com/rust-lang/rust/library/{crate_name}")
}

/// One descriptor as an owned string.
pub fn descriptor(suffix: Suffix, name: &str) -> String {
    let mut s = String::new();
    push_descriptor(&mut s, suffix, name);
    s
}

/// The suffix for one item kind in the `symbol_facts.item_kind` vocabulary
/// (`fn` / `method` / `struct` / `enum` / `trait` / `type` / `const` / `static`
/// / `macro` / `mod`) — the vocabulary `nornir::coderoom` writes. `None` for a
/// kind SCIP has no descriptor for.
pub fn suffix_for_kind(kind: &str) -> Option<Suffix> {
    Some(match kind {
        "mod" => Suffix::Namespace,
        "struct" | "enum" | "trait" | "type" => Suffix::Type,
        "fn" | "method" => Suffix::Method,
        "const" | "static" => Suffix::Term,
        "macro" => Suffix::Macro,
        _ => return None,
    })
}

/// The SCIP descriptor for one item of a `symbol_facts.item_kind` — the grammar
/// `scip::symbol::format_symbol_with` emits. `None` for a kind SCIP has no
/// descriptor for.
pub fn descriptor_for_kind(kind: &str, name: &str) -> Option<String> {
    suffix_for_kind(kind).map(|s| descriptor(s, name))
}

/// **The moniker** — `rust-analyzer cargo <package> <version> <descriptors>`.
/// `descriptors` is the already-suffixed descriptor chain (`m/B#go().`).
pub fn moniker(package: &str, version: &str, descriptors: &str) -> String {
    let mut s = String::with_capacity(
        SCHEME.len() + MANAGER.len() + package.len() + version.len() + descriptors.len() + 4,
    );
    s.push_str(SCHEME);
    s.push(' ');
    s.push_str(MANAGER);
    s.push(' ');
    s.push_str(package);
    s.push(' ');
    s.push_str(version);
    s.push(' ');
    s.push_str(descriptors);
    s
}

/// Cargo target kinds whose nodes carry rust-analyzer's ids unprefixed: the
/// package's library in any of its crate types.
pub const LIB_TARGET_KINDS: [&str; 6] = ["lib", "rlib", "dylib", "cdylib", "staticlib", "proc-macro"];

/// The descriptor prefix of every node of ONE cargo target of a package:
/// empty for the lib target ([`LIB_TARGET_KINDS`]), and for any other target a
/// `Meta` descriptor `` `<kind>=<crate_name>`: `` — `kind` as cargo names it
/// (`bin`, `example`, `test`, `bench`), `crate_name` the target's crate name
/// (`-` → `_`). So two targets of one package never share an id, whatever
/// their names; `=` cannot occur in a Rust identifier, so the descriptor can
/// never be an item's own (a macro called `test`, a module called `bin`).
pub fn target_prefix(kind: &str, crate_name: &str) -> String {
    if LIB_TARGET_KINDS.contains(&kind) {
        return String::new();
    }
    descriptor(Suffix::Meta, &format!("{kind}={crate_name}"))
}

/// The moniker of a crate's ROOT node (`… <package> <version> crate/`).
pub fn crate_moniker(package: &str, version: &str) -> String {
    moniker(package, version, CRATE_ROOT_DESCRIPTOR)
}

/// The node id of ONE monomorphised instance of a generic item: the generic
/// item's moniker + a `Meta` descriptor holding the instance as rustc names it
/// (the hash-free demangled symbol, e.g. `a::ident::<u32>`). SCIP has no
/// instance concept; the meta descriptor keeps the id parseable, ties it to
/// its generic item by prefix, and is deterministic per instance.
pub fn mono_instance(generic_moniker: &str, instance: &str) -> String {
    let mut s = String::with_capacity(generic_moniker.len() + instance.len() + 3);
    s.push_str(generic_moniker);
    push_descriptor(&mut s, Suffix::Meta, instance);
    s
}

/// The node id of ONE cargo feature of `package@version`: a `Meta` descriptor
/// named `feature=<name>`. `=` cannot occur in a Rust identifier, so the name
/// is always backtick-escaped and can never be the id of an item — a module or
/// macro called `feature` included. One per `crate@version`: the same feature
/// name in two versions of a crate is two nodes.
pub fn feature(package: &str, version: &str, name: &str) -> String {
    moniker(package, version, &descriptor(Suffix::Meta, &format!("feature={name}")))
}

/// The `package` field of every table node id: not a cargo package (`@`
/// cannot start one), the same convention as the log's `@universe`.
pub const TABLE_PACKAGE: &str = "@warehouse";
/// The `version` field of a table node id: SCIP's placeholder for "none".
pub const TABLE_VERSION: &str = ".";

/// The node id of ONE warehouse table (node kind `table`): a `Meta`
/// descriptor named `table=<name>` under [`TABLE_PACKAGE`]. No crate, no
/// version, no repo is in it — the id is the table's NAME and nothing else,
/// so every crate of every repo that touches the table points at the same
/// node. `=` keeps it apart from any item id, as with [`feature`].
///
/// The namespace / catalog the table lives in is NOT part of the id: it is a
/// runtime value at nearly every access site. Two tables of one name in two
/// catalogs are therefore one node.
pub fn table(name: &str) -> String {
    moniker(TABLE_PACKAGE, TABLE_VERSION, &descriptor(Suffix::Meta, &format!("table={name}")))
}

/// The `package` field of a BUILT PRODUCT's node id (node kind `artifact`):
/// not a cargo package, like [`TABLE_PACKAGE`].
pub const ARTIFACT_PACKAGE: &str = "@build-thing";
/// The `package` field of a COMMIT node id (node kind `commit`).
pub const COMMIT_PACKAGE: &str = "@git";

/// The node id of ONE built product: a `Meta` descriptor `artifact=<id>` under
/// [`ARTIFACT_PACKAGE`], where `id` is the artifact's content hash (sha256) —
/// the same bytes built twice are one node.
pub fn artifact(content_id: &str) -> String {
    moniker(ARTIFACT_PACKAGE, TABLE_VERSION, &descriptor(Suffix::Meta, &format!("artifact={content_id}")))
}

/// The node id of commit `sha` of `repo` (`commit=<repo>@<sha>` under
/// [`COMMIT_PACKAGE`]).
pub fn commit(repo: &str, sha: &str) -> String {
    moniker(COMMIT_PACKAGE, TABLE_VERSION, &descriptor(Suffix::Meta, &format!("commit={repo}@{sha}")))
}

/// The `package` field of every SYSTEM CALL node id (node kind `syscall`)
/// and of the boundary producer's own crate nodes: not a cargo package.
pub const SYSCALL_PACKAGE: &str = "@syscall";
/// The `package` field of every KERNEL node id (types and functions read
/// from the kernel's BTF); the version field is the kernel release.
pub const KERNEL_PACKAGE: &str = "@kernel";

/// The node id of system call `name` of architecture `arch`
/// (`… @syscall . x86_64/openat().`): no version, no kernel release — the
/// call's NAME on its architecture. The ONE node for a system call whose
/// number is chosen at run time is `syscall(arch, "<indirect>")`.
pub fn syscall(arch: &str, name: &str) -> String {
    let mut d = descriptor(Suffix::Namespace, arch);
    push_descriptor(&mut d, Suffix::Method, name);
    moniker(SYSCALL_PACKAGE, TABLE_VERSION, &d)
}

/// The module node holding one architecture's system calls (`… @syscall . x86_64/`).
pub fn syscall_arch(arch: &str) -> String {
    moniker(SYSCALL_PACKAGE, TABLE_VERSION, &descriptor(Suffix::Namespace, arch))
}

/// The crate node every system call table hangs under (`… @syscall . crate/`).
pub fn syscall_crate() -> String {
    crate_moniker(SYSCALL_PACKAGE, TABLE_VERSION)
}

/// The crate node of the boundary MAP of one dependency-universe owner
/// (`owner` = `libc@0.2.177:lib`) for one libc (`gnu` / `musl`): the
/// SYSCALLS edges out of that owner's functions hang under it, and the
/// owner's crate DEPENDS_ON it.
pub fn syscall_map_crate(owner: &str, libc: &str) -> String {
    let mut d = descriptor(Suffix::Meta, &format!("map={owner}/{libc}"));
    d.push_str(CRATE_ROOT_DESCRIPTOR);
    moniker(SYSCALL_PACKAGE, TABLE_VERSION, &d)
}

/// The crate node of one kernel read from BTF (`… @kernel <release> crate/`).
pub fn kernel_crate(release: &str) -> String {
    crate_moniker(KERNEL_PACKAGE, release)
}

/// The module node of one C name space of a kernel (`struct`, `union`,
/// `enum`, `typedef`, `func`): `… @kernel <release> struct/`.
pub fn kernel_ns(release: &str, ns: &str) -> String {
    moniker(KERNEL_PACKAGE, release, &descriptor(Suffix::Namespace, ns))
}

/// The node id of one kernel item read from BTF: `struct/task_struct#`,
/// `typedef/pid_t#`, `func/__x64_sys_read().`. A SECOND definition of one
/// name in one name space (BTF keeps differing layouts apart) carries a
/// `` `btf=<type id>`: `` meta descriptor.
pub fn kernel_item(release: &str, ns: &str, name: &str, dup: Option<u32>) -> String {
    let mut d = descriptor(Suffix::Namespace, ns);
    push_descriptor(&mut d, if ns == "func" { Suffix::Method } else { Suffix::Type }, name);
    if let Some(id) = dup {
        push_descriptor(&mut d, Suffix::Meta, &format!("btf={id}"));
    }
    moniker(KERNEL_PACKAGE, release, &d)
}

/// The table name back out of a [`table`] id; `None` for any other id.
pub fn table_name(id: &str) -> Option<String> {
    let (package, version, d) = parse(id)?;
    if package != TABLE_PACKAGE || version != TABLE_VERSION {
        return None;
    }
    let quoted = d.strip_suffix(':')?;
    let inner = quoted.strip_prefix('`')?.strip_suffix('`')?.replace("``", "`");
    inner.strip_prefix("table=").map(str::to_string)
}

/// The scope a node's CHILDREN are named in, as a moniker prefix: the node's
/// own id, except for a crate root, whose children start empty (`crate/` is
/// the root's node id only — `… a 0.1.0 crate/` → `… a 0.1.0 `, and a
/// non-lib target's ``… b 0.2.0 `bin=bctl`:crate/`` → ``… b 0.2.0 `bin=bctl`:``).
pub fn child_scope(node_id: &str) -> &str {
    match parse(node_id) {
        Some((_, _, d)) if d == CRATE_ROOT_DESCRIPTOR || d.ends_with(&format!(":{CRATE_ROOT_DESCRIPTOR}")) => {
            &node_id[..node_id.len() - CRATE_ROOT_DESCRIPTOR.len()]
        }
        _ => node_id,
    }
}

/// The node id of an item the build compiled OUT (node kind `cfg_gated_out`):
/// `scope` ([`child_scope`] of the enclosing node, plus the descriptors of any
/// compiled-out containers in between) + the item's own descriptor + a `Meta`
/// descriptor holding the predicate that gates it, e.g.
/// ``… a 0.1.0 store/open().`cfg(feature = "s3")`:``.
///
/// It is deliberately NOT the id the item has when it is compiled in: the
/// predicate is part of the identity, so the usual pair `#[cfg(feature = "x")]
/// fn f` / `#[cfg(not(feature = "x"))] fn f` is two ids (one resolved, one
/// stub), and nobody can mistake the stub for a resolved item. Same shape as
/// [`mono_instance`] (item id + a meta descriptor); the meta name always
/// starts with `cfg(`.
pub fn cfg_gated_out(scope: &str, item_descriptor: &str, predicate: &str) -> String {
    let mut s = String::with_capacity(scope.len() + item_descriptor.len() + predicate.len() + 3);
    s.push_str(scope);
    s.push_str(item_descriptor);
    push_descriptor(&mut s, Suffix::Meta, predicate);
    s
}

/// The `(package, version, descriptors)` triple back out of a moniker this
/// module wrote. `None` for anything not in the `rust-analyzer cargo` shape.
pub fn parse(moniker: &str) -> Option<(&str, &str, &str)> {
    let rest = moniker.strip_prefix(SCHEME)?.strip_prefix(' ')?;
    let rest = rest.strip_prefix(MANAGER)?.strip_prefix(' ')?;
    let (package, rest) = rest.split_once(' ')?;
    let (version, descriptors) = rest.split_once(' ')?;
    Some((package, version, descriptors))
}

/// The version field a [`stable`] id carries in place of the real one.
pub const STABLE_VERSION: &str = "*";

/// **The stable id** — the moniker with its version replaced by
/// [`STABLE_VERSION`], so the same item at two dependency (or own) versions
/// has ONE identity in the log. The real version stays in the stored row;
/// only identity (delta and fold keying) uses this form. Anything not in the
/// `rust-analyzer cargo` shape — table, artifact and commit ids, foreign
/// symbols — is returned unchanged.
pub fn stable(id: &str) -> std::borrow::Cow<'_, str> {
    match parse(id) {
        Some((_, STABLE_VERSION, _)) | None => std::borrow::Cow::Borrowed(id),
        Some((package, _, descriptors)) => std::borrow::Cow::Owned(moniker(package, STABLE_VERSION, descriptors)),
    }
}