symbolique 0.1.0

Symbol table pipeline for language servers — parse, link, merge, and resolve symbols across files, built on the laburnum LSP framework.
// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0

//! Record types for symbolique partitions.
//!
//! This module defines the record types that are stored in symbolique's
//! partitions. These types separate shape data (content-addressed, no spans)
//! from index entries (path-keyed, with spans).
//!
//! # Architecture (ADR0003)
//!
//! - **`Symbols<V, I, P>`**: Single CAS partition for all symbol shapes
//! - **`SymbolEntry<V, I, P>`**: Index entry pointing to shapes in `Symbols`
//! - **`ResolutionEntry<V, I, P>`**: Index entry for resolution mappings
//!
//! Stage partitions (FileSymbols, LinkedSymbols, MergedSymbols) are now
//! index-only, using `SymbolEntry` as their `IndexEntry` type.

use {
  crate::core::{Ident, SymbolPath, Value},
  laburnum::{
    ContentHash,
    database::{RecordHandle, partitions::IndexEntry},
    record::CollectReferences,
  },
  std::hash::Hash,
};

use super::symbols::Symbols;

/// Index entry for symbol occurrences in stage partitions.
///
/// Used by FileSymbols, LinkedSymbols, and MergedSymbols partitions as their
/// `IndexEntry` type. Links a path location to a symbol shape in the shared
/// `Symbols` partition.
///
/// # Type Parameters
///
/// - `V`: Value type for literals
/// - `I`: Identifier type for names
/// - `P`: Symbol path type
///
/// # GC Role
///
/// Index entries are GC roots. The `symbol` field references a CAS record
/// in the `Symbols` partition, keeping it alive via reference counting.
#[derive(Debug, Clone)]
pub struct SymbolEntry<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  /// Source location of this symbol occurrence
  pub span: laburnum::Span,
  /// Handle to the content-addressed shape record in `Symbols` partition
  pub symbol: RecordHandle<Symbols<V, I, P>>,
}

impl<V, I, P> Copy for SymbolEntry<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
}

impl<V, I, P> Hash for SymbolEntry<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
    self.span.hash(state);
    self.symbol.hash(state);
  }
}

impl<V, I, P> PartialEq for SymbolEntry<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  fn eq(&self, other: &Self) -> bool {
    self.span == other.span && self.symbol == other.symbol
  }
}

impl<V, I, P> Eq for SymbolEntry<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
}

impl<V, I, P> SymbolEntry<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  /// Create a new symbol entry.
  pub fn new(
    span: laburnum::Span,
    symbol: RecordHandle<Symbols<V, I, P>>,
  ) -> Self {
    Self { span, symbol }
  }
}

impl<V, I, P> IndexEntry for SymbolEntry<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  fn primary_hash(&self) -> Option<ContentHash> {
    Some(self.symbol.content_hash())
  }
}

impl<V, I, P> laburnum::database::partitions::IndexedEntry
  for SymbolEntry<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  fn content_hash(&self) -> ContentHash {
    self.symbol.content_hash()
  }
}

impl<V, I, P> bluegum::Bluegum for SymbolEntry<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  fn node(&self, b: &mut bluegum::Builder) {
    b.name("SymbolEntry")
      .field("span", self.span)
      .field("symbol", self.symbol.content_hash());
  }
}

impl<V, I, P> bluegum::BluegumWithState<dyn laburnum::SpanResolver>
  for SymbolEntry<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  fn node_with_state(
    &self,
    b: &mut bluegum::Builder,
    _state: &dyn laburnum::SpanResolver,
  ) {
    b.name("SymbolEntry")
      .field("symbol", self.symbol.content_hash());
  }
}

impl<Ps, V, I, P> CollectReferences<Ps> for SymbolEntry<V, I, P>
where
  Ps: laburnum::database::storage::Partitions,
  Ps::Stores: laburnum::database::partitions::HasPartition<Symbols<V, I, P>>,
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  fn collect_references<R: laburnum::record::References<Ps>>(
    &self,
    refs: &mut R,
  ) {
    refs.add(self.symbol);
  }
}

/// Index entry for resolution mappings.
///
/// Used by the SymbolResolution partition as its `IndexEntry` type. Maps a
/// reference path to its resolved target definition in the `Symbols` partition.
///
/// # Type Parameters
///
/// - `V`: Value type for literals
/// - `I`: Identifier type for names
/// - `P`: Symbol path type
///
/// # GC Role
///
/// Index entries are GC roots. The `target` field references a CAS record
/// in the `Symbols` partition, keeping it alive via reference counting.
#[derive(Debug, Clone, Hash)]
pub struct ResolutionEntry<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  /// Path of the resolved definition
  pub target_path: P,
  /// Handle to the target definition's shape in `Symbols` partition
  pub target: RecordHandle<Symbols<V, I, P>>,
}

impl<V, I, P> ResolutionEntry<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  /// Create a new resolution entry.
  pub fn new(target_path: P, target: RecordHandle<Symbols<V, I, P>>) -> Self {
    Self {
      target_path,
      target,
    }
  }
}

impl<V, I, P> IndexEntry for ResolutionEntry<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  fn primary_hash(&self) -> Option<ContentHash> {
    Some(self.target.content_hash())
  }
}

impl<V, I, P> laburnum::database::partitions::IndexedEntry
  for ResolutionEntry<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  fn content_hash(&self) -> ContentHash {
    self.target.content_hash()
  }
}

impl<V, I, P> bluegum::Bluegum for ResolutionEntry<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  fn node(&self, b: &mut bluegum::Builder) {
    b.name("ResolutionEntry")
      .field("target_path", format!("{:?}", self.target_path))
      .field("target", self.target.content_hash());
  }
}

impl<V, I, P> bluegum::BluegumWithState<dyn laburnum::SpanResolver>
  for ResolutionEntry<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
}

impl<Ps, V, I, P> CollectReferences<Ps> for ResolutionEntry<V, I, P>
where
  Ps: laburnum::database::storage::Partitions,
  Ps::Stores: laburnum::database::partitions::HasPartition<Symbols<V, I, P>>,
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  fn collect_references<R: laburnum::record::References<Ps>>(
    &self,
    refs: &mut R,
  ) {
    refs.add(self.target);
  }
}