laburnum 1.17.1

An LSP framework for building language servers and compilers, powered by an incremental query tree with content-addressed storage, task-based dataflow, and parallel queries.
Documentation
// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0

use {
  crate::ContentHash,
  std::hash::{
    Hash,
    Hasher,
  },
};

use crate::database::{
  HasPartition, Partition, RecordHandle,
  storage::Partitions,
};

/// Collects record references with partition type information.
///
/// This trait is used by the reference counting GC to collect all records
/// that a given record references. Implementations receive typed `RecordHandle<Part>`
/// values and can route refcount operations to the correct partition store.
///
/// # Example Implementation
///
/// ```ignore
/// struct RefCountIncrementer<'a, P: Partitions> {
///     stores: &'a P::Stores,
/// }
///
/// impl<'a, P: Partitions> References<P> for RefCountIncrementer<'a, P> {
///     fn add<Part: Partition>(&mut self, handle: RecordHandle<Part>)
///     where
///         P::Stores: HasPartition<Part>,
///     {
///         <P::Stores as HasPartition<Part>>::store(self.stores)
///             .increment_refcount(handle.content_hash());
///     }
/// }
/// ```
pub trait References<P: Partitions> {
  /// Add a reference to the collection.
  ///
  /// Called by `Record::collect_references` for each record handle the
  /// record contains. The implementation can use the partition type information
  /// to route operations to the correct partition store.
  fn add<Part: Partition>(&mut self, handle: RecordHandle<Part>)
  where
    P::Stores: HasPartition<Part>;
}

/// Trait for records that can collect their references for GC.
///
/// Records implement this trait to declare which other records they reference.
/// The reference counting GC uses this to maintain accurate refcounts when
/// records are inserted or removed.
///
/// # Default Implementation
///
/// The default implementation collects no references, suitable for leaf records
/// that don't reference other records.
///
/// # Example
///
/// ```ignore
/// impl<P: Partitions> CollectReferences<P> for AstFunction
/// where
///     P::Stores: HasPartition<Cst> + HasPartition<Types> + HasPartition<Ast>,
/// {
///     fn collect_references<R: References<P>>(&self, refs: &mut R) {
///         refs.add(self.cst_node);      // RecordHandle<Cst>
///         refs.add(self.return_type);   // RecordHandle<Types>
///         refs.add(self.body);          // RecordHandle<Ast>
///     }
/// }
/// ```
pub trait CollectReferences<P: Partitions> {
  /// Collect all records this record references.
  ///
  /// Called by the GC when a record is inserted or removed to update
  /// refcounts for all referenced records.
  fn collect_references<R: References<P>>(&self, _refs: &mut R) {
    // Default: no references
  }
}

/// Trait for records in content-addressed storage.
///
/// All records stored in the content-addressed system must implement this
/// trait. It provides content hashing for deduplication and structural sharing.
///
/// Records are identified by their content hash. Two records with identical
/// content (by semantic equality, not pointer equality) should produce the
/// same hash.
///
/// For GC traversal, implement [`CollectReferences<P>`] to declare which
/// other records this record references. The GC uses that trait (not this one)
/// to trace the record graph.
pub trait Record: Send + Sync {
  /// Compute the content hash of this record.
  ///
  /// This hash should be stable - the same record content must always produce
  /// the same hash. Hash semantic content (structure, values) but typically
  /// exclude source positions to enable cache hits when unrelated code moves.
  fn content_hash(&self) -> ContentHash;
}

/// Implement Record for () to support index-only partitions.
///
/// Index-only partitions have `Record = ()` and only store index entries,
/// not CAS records. The content hash is always zero for unit records.
impl Record for () {
  fn content_hash(&self) -> ContentHash {
    ContentHash::new(&[])
  }
}

/// Implement CollectReferences for () to support index-only partitions.
///
/// Unit records have no references, so this is a no-op.
impl<P: Partitions> CollectReferences<P> for () {}


/// Compute content hash from a type that implements Hash.
///
/// This is a helper for implementing Record::content_hash() using
/// the standard library's Hash trait.
pub fn hash_record<T: Hash>(value: &T) -> ContentHash {
  let mut hasher = HashToContent::default();
  value.hash(&mut hasher);
  ContentHash::new(&hasher.0)
}

/// Bridge between std::hash::Hasher and ContentHash.
#[derive(Default)]
struct HashToContent(Vec<u8>);

impl Hasher for HashToContent {
  fn finish(&self) -> u64 {
    0
  }

  fn write(&mut self, bytes: &[u8]) {
    self.0.extend_from_slice(bytes);
  }
}

/// Laburnum-level records managed by Laburnum.
///
/// These records store Laburnum data like workspace configuration and LSP
/// protocol information. User-defined record types embed `LaburnumRecord` to
/// support both language-specific and Laburnum-level data in the same storage.
///
/// # Storage Convention
///
/// Laburnum records use path-based partition keys:
/// - Workspace config: `/workspace` partition, `config` sort key
/// - Future records will follow the pattern: `/resource_type/resource_name`
///
/// # Example
///
/// ```ignore
/// // User record type that embeds Laburnum records
/// enum MyRecord {
///   Function { name: String },
///   Variable { name: String, value: String },
///   Laburnum(LaburnumRecord),  // Laburnum records
/// }
///
/// impl From<LaburnumRecord> for MyRecord {
///   fn from(record: LaburnumRecord) -> Self {
///     MyRecord::Laburnum(record)
///   }
/// }
/// ```
#[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize)]
pub enum LaburnumRecord {
  WorkspaceConfig { value: String },
}

impl bluegum::Bluegum for LaburnumRecord {
  fn node(&self, b: &mut bluegum::Builder) {
    match self {
      LaburnumRecord::WorkspaceConfig { value } => {
        b.name("LaburnumRecord::WorkspaceConfig")
          .field("value", value);
      }
    }
  }
}

impl bluegum::BluegumWithState<dyn crate::SpanResolver> for LaburnumRecord {}

/// Provides read-only access to Laburnum records stored in user record types.
///
/// Language servers implement this trait on their `RecordRef` type to expose
/// Laburnum records. This allows Laburnum to query stored configuration and
/// protocol data without coupling to specific language implementations.
///
/// # Implementation Pattern
///
/// User record types embed `LaburnumRecord` in a dedicated variant and
/// implement this trait to provide access:
///
/// ```ignore
/// enum MyRecordRef<'a> {
///   Function { name: &'a str },
///   Laburnum(&'a LaburnumRecord),
/// }
///
/// impl<'a> LaburnumRecordRef for MyRecordRef<'a> {
///   fn as_laburnum_record(&self) -> Option<&LaburnumRecord> {
///     match self {
///       MyRecordRef::Laburnum(lr) => Some(lr),
///       _ => None,
///     }
///   }
/// }
/// ```
///
/// # Usage
///
/// Laburnum code queries records via this trait:
///
/// ```ignore
/// if let Some(record_ref) = storage.get(&index) {
///   if let Some(laburnum_record) = record_ref.as_laburnum_record() {
///     // Access Laburnum data
///   }
/// }
/// ```
// -- define_record_enum! ------------------------------------------------------
/// Generate an owned `${Name}Record` enum and a borrowed `${Name}RecordRef<'a>`
/// enum from a list of variant names.
///
/// For each variant `Foo`, the macro expects a type `FooRecord` to exist in
/// scope. It generates:
///
/// 1. `pub enum ${Name}Record` with `#[derive(Clone)]`
/// 2. `pub enum ${Name}RecordRef<'a>` with `#[derive(Clone, Copy)]`
/// 3. `impl ${Name}Record { pub fn as_ref(&self) -> ${Name}RecordRef<'_> }`
/// 4. `impl<'a> From<&'a ${Name}Record> for ${Name}RecordRef<'a>`
/// 5. `impl From<FooRecord> for ${Name}Record` for each variant
///
/// Trait impls (`Hash`, `PartialEq`, `Debug`, `Serialize`, `LaburnumRecordRef`,
/// etc.) are NOT generated and must be written by hand.
#[macro_export]
macro_rules! define_record_enum {
  (
    $vis:vis enum $name:ident {
      $($variant:ident),* $(,)?
    }
  ) => {
    $crate::paste::paste! {
      #[derive(Clone)]
      $vis enum [<$name Record>] {
        $(
          $variant([<$variant Record>]),
        )*
      }

      #[derive(Clone, Copy)]
      $vis enum [<$name RecordRef>]<'a> {
        $(
          $variant(&'a [<$variant Record>]),
        )*
      }

      impl [<$name Record>] {
        pub fn as_ref(&self) -> [<$name RecordRef>]<'_> {
          match self {
            $(
              | [<$name Record>]::$variant(inner) => [<$name RecordRef>]::$variant(inner),
            )*
          }
        }
      }

      impl<'a> From<&'a [<$name Record>]> for [<$name RecordRef>]<'a> {
        fn from(record: &'a [<$name Record>]) -> Self {
          record.as_ref()
        }
      }

      $(
        impl From<[<$variant Record>]> for [<$name Record>] {
          fn from(record: [<$variant Record>]) -> Self {
            [<$name Record>]::$variant(record)
          }
        }
      )*
    }
  };
}

/// Trait for records that need access to the source cache for serialization.
///
/// Records annotated with `@with_source_cache` in `define_partitions!` must
/// implement this trait. The source cache provides access to source text for
/// resolving spans into human-readable strings.
pub trait SerializeWithSourceCache {
  fn serialize_with_source_cache<P, T, Ser>(
    &self,
    source_cache: &crate::source::SourceCache<P, T>,
    serializer: Ser,
  ) -> Result<Ser::Ok, Ser::Error>
  where
    P: crate::database::storage::Partitions,
    T: crate::protocol::lsp::LanguageServer<P>,
    Ser: serde::Serializer;
}

pub trait LaburnumRecordRef: serde::Serialize {
  /// Return a reference to the Laburnum record if this is a Laburnum record,
  /// otherwise `None`.
  fn as_laburnum_record(&self) -> Option<&LaburnumRecord>;

  /// Return a reference to the diagnostic if this record implements the
  /// Diagnostic trait, otherwise `None`.
  fn as_dyn_diagnostic(
    &self,
  ) -> Option<&dyn crate::partitions::diagnostics::DiagnosticRecord> {
    None
  }

  fn as_document_symbol(
    &self,
  ) -> Option<&dyn crate::partitions::document_symbols::DocumentSymbolRecord>
  {
    None
  }

  fn as_workspace_symbol(
    &self,
  ) -> Option<&crate::protocol::lsp::WorkspaceSymbol> {
    None
  }

  fn as_text_document_position(
    &self,
  ) -> Option<
    &dyn crate::partitions::text_document_position::TextDocumentPositionRecord,
  > {
    None
  }

  fn as_text_document_reference(
    &self,
  ) -> Option<&dyn crate::partitions::text_document_references::TextDocumentReferenceRecord>
  {
    None
  }

  fn as_folding_range(
    &self,
  ) -> Option<&dyn crate::partitions::document_folding_range::FoldingRangeRecord>
  {
    None
  }

  fn as_work_done_progress(
    &self,
  ) -> Option<&dyn crate::partitions::work_done_progress::WorkDoneProgressRecord>
  {
    None
  }

  /// Serialize the record with access to source cache for extracting text from
  /// spans.
  ///
  /// Implementations can use `source_cache.text_for_span(span, span_cache)` to
  /// retrieve the original source text for identifiers and other span-based
  /// data.
  fn serialize_with_source_cache<P, T, Ser>(
    &self,
    source_cache: &crate::source::SourceCache<P, T>,
    serializer: Ser,
  ) -> Result<Ser::Ok, Ser::Error>
  where
    P: crate::database::storage::Partitions,
    T: crate::protocol::lsp::LanguageServer<P>,
    Ser: serde::Serializer;
}