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

//! Symbol usage tracking and binding kind classification.
//!
//! This module provides types for tracking how symbols are used throughout a
//! program, supporting IDE features like "find all references", hover
//! information, and semantic highlighting.
//!
//! # Usage Classification
//!
//! Symbols can be used in three primary ways:
//! - **Definition**: Where the symbol is declared and receives its meaning
//! - **Reference**: Where the symbol is used to access its value or behavior
//! - **Binding**: Where a new name is bound to a value (parameters, patterns,
//!   etc.)
//!
//! # Position Indexing
//!
//! The position index allows efficient lookup of symbols at specific source
//! locations, enabling cursor-based IDE features like hover and
//! go-to-definition.

use crate::{
  core::{Ident, SymbolPath, Value},
  partitions::SymbolEntry,
};

/// Represents how a symbol is being used at a specific location.
///
/// This classification helps IDE features understand the semantic role of each
/// symbol occurrence, enabling features like:
/// - Semantic highlighting (different colors for definitions vs references)
/// - Find references (excluding definitions and bindings)
/// - Rename refactoring (updating all related usages)
///
/// # Examples
///
/// In the following code:
/// ```text
/// fn calculate(x: i32) -> i32 {  // 'calculate' is Definition, 'x' is Binding
///     let y = x + 1;             // 'y' is Binding, 'x' is Reference
///     return y;                  // 'y' is Reference
/// }
///
/// let result = calculate(42);    // 'result' is Binding, 'calculate' is Reference
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SymbolUsage {
  /// Where the symbol is defined.
  ///
  /// This marks the primary declaration site where the symbol receives its
  /// meaning. Examples include function definitions, type declarations, and
  /// variable declarations with initial values.
  Definition,

  /// A reference/use of the symbol.
  ///
  /// This marks locations where the symbol is used to access its value or
  /// invoke its behavior. This is the most common usage type and excludes
  /// the definition site and binding locations.
  Reference,

  /// A binding site (parameter, let binding, pattern matching, etc.).
  ///
  /// This marks locations where a new name is introduced and bound to a value,
  /// but isn't necessarily the primary definition. Examples include function
  /// parameters, let bindings, and pattern match destructuring.
  Binding,
}

/// Entry in the position index tracking a symbol at a specific location.
///
/// The position index provides efficient cursor-based lookup by organizing
/// symbol occurrences by their source position. This enables IDE features
/// like hover information and go-to-definition.
///
/// # Type Parameters
///
/// - `V`: Value type implementing [`Value`]
/// - `I`: Identifier type implementing [`Ident`]
/// - `P`: Symbol path type implementing [`SymbolPath`]
///
/// # Performance
///
/// Position entries are stored in a `BTreeMap` keyed by span start offset,
/// allowing O(log n) lookup by position with range queries for efficiency.
#[derive(Debug)]
pub struct PositionEntry<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  /// The symbol at this position, including the span of this occurrence.
  ///
  /// The span in SymbolId defines the exact source range covered by this
  /// symbol usage, used for precise position-based queries and highlighting.
  pub(crate) symbol_id: SymbolEntry<V, I, P>,

  /// How the symbol is used at this position.
  ///
  /// Determines the semantic role for IDE features like semantic highlighting
  /// and reference finding.
  pub(crate) usage: SymbolUsage,
}

impl<V, I, P> Clone for PositionEntry<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  fn clone(&self) -> Self {
    Self {
      symbol_id: self.symbol_id,
      usage: self.usage.clone(),
    }
  }
}

/// Information about a symbol found at a position.
///
/// Returned by position-based queries to provide complete context about
/// what symbol exists at a specific source location. This enables IDE
/// features to understand both the symbol identity and how it's being used.
///
/// # Type Parameters
///
/// - `V`: Value type implementing [`Value`]
/// - `I`: Identifier type implementing [`Ident`]
/// - `P`: Symbol path type implementing [`SymbolPath`]
///
/// # Usage
///
/// This type is typically returned by `find_symbol_at_position()` to support
/// cursor-based IDE features:
///
/// ```rust,ignore
/// if let Some(symbol_info) = engine.find_symbol_at_position(cursor_offset) {
///     match symbol_info.usage {
///         SymbolUsage::Definition => show_definition_hover(&symbol_info),
///         SymbolUsage::Reference => show_reference_hover(&symbol_info),
///         SymbolUsage::Binding => show_binding_hover(&symbol_info),
///     }
/// }
/// ```
#[derive(Debug)]
pub struct SymbolInfo<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  /// The symbol at this position, including the exact span of this occurrence.
  ///
  /// Use this ID to look up the full symbol details from the symbol engine.
  /// The span in SymbolId defines the precise source range for this symbol
  /// usage, useful for highlighting and determining if a position falls
  /// within this usage.
  pub(crate) symbol_id: SymbolEntry<V, I, P>,

  /// How it's used at this position.
  ///
  /// Indicates whether this occurrence is a definition, reference, or binding,
  /// which affects how IDE features should handle this location.
  pub usage: SymbolUsage,
}

impl<V, I, P> Clone for SymbolInfo<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  fn clone(&self) -> Self {
    Self {
      symbol_id: self.symbol_id,
      usage: self.usage.clone(),
    }
  }
}

#[cfg(test)]
impl<V, I, P> SymbolInfo<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  /// Create new symbol information.
  pub(crate) fn new(
    symbol_id: SymbolEntry<V, I, P>,
    usage: SymbolUsage,
  ) -> Self {
    Self { symbol_id, usage }
  }
}

impl<V, I, P> SymbolInfo<V, I, P>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
{
  /// Get the span of this occurrence.
  pub fn span(&self) -> laburnum::Span {
    self.symbol_id.span
  }

  /// Check if this symbol info covers the given position.
  pub fn contains_position(
    &self,
    cache: &laburnum::SpanCache,
    position: u32,
  ) -> bool {
    self.symbol_id.span.contains(cache, position)
  }

  /// Check if this is a definition occurrence.
  pub fn is_definition(&self) -> bool {
    matches!(self.usage, SymbolUsage::Definition)
  }

  /// Check if this is a reference occurrence.
  pub fn is_reference(&self) -> bool {
    matches!(self.usage, SymbolUsage::Reference)
  }

  /// Check if this is a binding occurrence.
  pub fn is_binding(&self) -> bool {
    matches!(self.usage, SymbolUsage::Binding)
  }

  /// Get the symbol entry for this symbol info.
  pub fn symbol_entry(&self) -> SymbolEntry<V, I, P> {
    self.symbol_id
  }
}

#[cfg(test)]
mod tests {
  use super::*;
  use crate::test_helpers::*;

  #[test]
  fn symbol_usage_variants_distinct() {
    assert_ne!(SymbolUsage::Definition, SymbolUsage::Reference);
    assert_ne!(SymbolUsage::Definition, SymbolUsage::Binding);
    assert_ne!(SymbolUsage::Reference, SymbolUsage::Binding);
  }

  #[test]
  fn symbol_info_is_definition() {
    let mut cache = test_span_cache();
    let info =
      SymbolInfo::<DV, SI, TP>::new(dummy_symbol_entry(&mut cache), SymbolUsage::Definition);
    assert!(info.is_definition());
    assert!(!info.is_reference());
    assert!(!info.is_binding());
  }

  #[test]
  fn symbol_info_is_reference() {
    let mut cache = test_span_cache();
    let info =
      SymbolInfo::<DV, SI, TP>::new(dummy_symbol_entry(&mut cache), SymbolUsage::Reference);
    assert!(info.is_reference());
    assert!(!info.is_definition());
    assert!(!info.is_binding());
  }

  #[test]
  fn symbol_info_is_binding() {
    let mut cache = test_span_cache();
    let info =
      SymbolInfo::<DV, SI, TP>::new(dummy_symbol_entry(&mut cache), SymbolUsage::Binding);
    assert!(info.is_binding());
    assert!(!info.is_definition());
    assert!(!info.is_reference());
  }

  #[test]
  fn symbol_info_span_returns_entry_span() {
    let mut cache = test_span_cache();
    let entry = dummy_symbol_entry(&mut cache);
    let expected_span = entry.span;
    let info = SymbolInfo::<DV, SI, TP>::new(entry, SymbolUsage::Definition);
    assert_eq!(info.span(), expected_span);
  }

  #[test]
  fn symbol_info_symbol_entry() {
    let mut cache = test_span_cache();
    let entry = dummy_symbol_entry(&mut cache);
    let info = SymbolInfo::<DV, SI, TP>::new(entry, SymbolUsage::Definition);
    let returned = info.symbol_entry();
    assert_eq!(returned.span, entry.span);
  }
}