Skip to main content
Immutable postorder arenas, verified matching, and typed edit scripts. This crate has no filesystem, Git, Tree-sitter, or worker runtime dependency. The optional serde feature is enabled by default.

Main exports

Data conversion belongs to differens-tiers::tree_from_value; core consumers do not need to depend on the parsers if they construct their own trees.

Build trees

TreeBuilder::push(NodeSpec) returns a Result<NodeId, BuildError>. Children must precede their parent, in source order, with contiguous postorder ranges. Shared children, invalid byte ranges, zero source lines, and disconnected forests are rejected. finish(root) returns Result<Arc<Tree>, BuildError>. Tree::node(NodeId) borrows immutable metadata. root_ref() on Arc<Tree> returns an owned NodeRef; it holds the tree alive and exposes children() and parent(). An ID belongs to one tree. Spans use UTF-8 byte offsets; lines are one-based. NodeSpec::match_key: Option<String> supplies an internal identity hint for same-named containers, such as go::basicWriter for an overloaded Go method. Tree::match_key(id) and NodeRef::match_key() expose it. Keys occupy a sparse tree-level map and distinguish named-container paths. Each hinted node and its descendants match within the same (kind, key) scope; nested hints override their ancestor’s scope. Labels remain editable within a scope, so method renames still work. This prevents identical bodies from being matched into an unrelated receiver’s helper. Keys do not change labels, legacy JSON, hashes, or exact content equality. Detachment and flat snapshots preserve the applicable keys.

Compare

DiffResult has changes, node_count, and optional fallback. A tree larger than max_nodes returns empty changes and Some(Fallback::Lines). The default core limit is usize::MAX; callers choose their budget. EditAction variants are Insert, Delete, Update, and Move. Each owns node references and a nearest-first Vec<NodeContext>. Updates carry UpdateDetail::Renamed { from, to } or ValueChanged { from, to }. A scalar type transition qualifies the values with their types. Missing values remain None. Actions are ordered updates, moves, deletions, insertions. Subtree additions/removals absorb descendant edits; sibling reorders use a longest increasing subsequence, anchoring named containers whose relative order stays unchanged before aligning anonymous siblings between them. This is a deterministic heuristic edit script, not a guarantee of a globally minimal tree edit distance.

Hashes and serialization

hash_text(&str) -> u64 preserves the legacy 53-bit folded FNV hash over UTF-16 code units. Content and structure fingerprints select candidates; full topology and field checks verify them. trees_equal(&NodeRef, &NodeRef) uses iterative exact equality. Node serialization preserves nested legacy JSON. NodeRef::shallow() emits metadata with empty children while retaining the owning tree internally. Flat ownership and iterative traversal avoid recursive stack growth for deep construction, equality, matching, JSON encoding, and drop. Adapters using TreeBuilder::with_source or set_legacy_source keep native UTF-8 byte spans while serializing legacy UTF-16 code-unit spans. Detached nodes and flat snapshots retain only the coordinate deltas needed by their node boundaries. A small candidate retains only the coordinate metadata needed for its subtree, even after a large Unicode prefix. Tree::auxiliary_heap_bytes() exposes a conservative retained-size estimate for offset and identity maps.

Matching options

DiffResult::node_count is the sum of both input tree sizes. A fallback result has no actions; it does not mean that the sources are equal. The caller must compare lines, or use diff_with_tier to handle that step.

Node metadata

NodeRef keeps the tree alive through shared ownership. Child IDs do not own nested nodes, so dropping a deeply nested tree does not recurse through one heap allocation per node. Use NodeSpec as builder input; finished node metadata is immutable.

Action fields

context is a nearest-ancestor-first vector of NodeContext { kind, label }. Use action.node(), action.context(), and action.action_type() when you do not need to match every variant. Rename details contain from and to strings; value details retain optional values. JSON uses camelCase names for move fields.

Compare typed value trees

For source parsing and typed data construction, add a differens-tiers dependency. The installation guide shows path dependencies for an unpublished checkout.