Skip to main content

perl_symbol_types/
lib.rs

1//! Unified Perl symbol taxonomy for LSP tooling.
2//!
3//! This crate provides a single, authoritative definition of Perl symbol kinds
4//! used across the parser, semantic analyzer, workspace index, and LSP providers.
5//!
6//! # Design Goals
7//!
8//! - **Single source of truth**: All symbol classification flows through this crate
9//! - **Perl semantics**: Distinguishes variables by sigil type (scalar/array/hash)
10//! - **LSP compatibility**: Direct mapping to LSP protocol symbol kinds
11//! - **Zero-cost abstractions**: Enum variants are `Copy` types with inline methods
12
13use serde::{Deserialize, Serialize};
14
15/// Variable sigil classification for Perl's three primary container types.
16///
17/// Perl distinguishes variables by their sigil prefix, which determines
18/// the container type and access semantics.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
20pub enum VarKind {
21    /// Scalar variable (`$foo`) - holds a single value
22    Scalar,
23    /// Array variable (`@foo`) - holds an ordered list
24    Array,
25    /// Hash variable (`%foo`) - holds key-value pairs
26    Hash,
27}
28
29impl VarKind {
30    /// Returns the sigil character for this variable kind.
31    ///
32    /// # Examples
33    ///
34    /// ```
35    /// use perl_symbol_types::VarKind;
36    ///
37    /// assert_eq!(VarKind::Scalar.sigil(), "$");
38    /// assert_eq!(VarKind::Array.sigil(), "@");
39    /// assert_eq!(VarKind::Hash.sigil(), "%");
40    /// ```
41    #[inline]
42    pub const fn sigil(self) -> &'static str {
43        match self {
44            VarKind::Scalar => "$",
45            VarKind::Array => "@",
46            VarKind::Hash => "%",
47        }
48    }
49}
50
51/// Unified Perl symbol classification for LSP tooling.
52///
53/// This enum represents all meaningful symbol types in Perl code, designed
54/// to be the canonical taxonomy across all crates in the perl-lsp ecosystem.
55///
56/// # LSP Protocol Mapping
57///
58/// Each variant maps to an LSP `SymbolKind` number via [`Self::to_lsp_kind()`]:
59///
60/// | Variant | LSP Kind | Number | Description |
61/// |---------|----------|--------|-------------|
62/// | `Package` | Module | 2 | Package declaration |
63/// | `Class` | Class | 5 | OO class (Moose, Moo, class keyword) |
64/// | `Role` | Interface | 8 | Role definition (Moose::Role) |
65/// | `Subroutine` | Function | 12 | Standalone subroutine |
66/// | `Method` | Method | 6 | OO method |
67/// | `Variable(_)` | Variable | 13 | Variables (scalar, array, hash) |
68/// | `Constant` | Constant | 14 | use constant or Readonly |
69/// | `Import` | Module | 2 | Imported symbol |
70/// | `Export` | Function | 12 | Exported symbol |
71/// | `Label` | Key | 20 | Loop/block label |
72/// | `Format` | Struct | 23 | format declaration |
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
74pub enum SymbolKind {
75    // -------------------------------------------------------------------------
76    // Package/namespace types
77    // -------------------------------------------------------------------------
78    /// Package declaration (`package Foo;`)
79    Package,
80    /// OO class declaration (class keyword, Moose, Moo)
81    Class,
82    /// Role definition (role keyword, Moose::Role)
83    Role,
84
85    // -------------------------------------------------------------------------
86    // Callable types
87    // -------------------------------------------------------------------------
88    /// Subroutine definition (`sub name { }`)
89    Subroutine,
90    /// Method definition in OO context
91    Method,
92
93    // -------------------------------------------------------------------------
94    // Variable types
95    // -------------------------------------------------------------------------
96    /// Variable declaration with sigil-based container type
97    Variable(VarKind),
98
99    // -------------------------------------------------------------------------
100    // Value/reference types
101    // -------------------------------------------------------------------------
102    /// Constant value (`use constant NAME => value`)
103    Constant,
104    /// Imported symbol from `use` statement
105    Import,
106    /// Exported symbol via Exporter
107    Export,
108
109    // -------------------------------------------------------------------------
110    // Control flow and special types
111    // -------------------------------------------------------------------------
112    /// Loop/block label (`LABEL: while ...`)
113    Label,
114    /// Format declaration (`format STDOUT =`)
115    Format,
116}
117
118impl SymbolKind {
119    /// Convert to LSP-compliant symbol kind number (workspace profile).
120    ///
121    /// Maps Perl symbol types to the closest LSP protocol equivalents.
122    /// This is the default mapping used for workspace symbols where a
123    /// generic "Variable" kind is appropriate for all variable types.
124    ///
125    /// See the enum documentation for the full mapping table.
126    #[inline]
127    pub const fn to_lsp_kind(self) -> u32 {
128        match self {
129            SymbolKind::Package => 2,      // Module
130            SymbolKind::Class => 5,        // Class
131            SymbolKind::Role => 8,         // Interface
132            SymbolKind::Subroutine => 12,  // Function
133            SymbolKind::Method => 6,       // Method
134            SymbolKind::Variable(_) => 13, // Variable
135            SymbolKind::Constant => 14,    // Constant
136            SymbolKind::Import => 2,       // Module
137            SymbolKind::Export => 12,      // Function
138            SymbolKind::Label => 20,       // Key
139            SymbolKind::Format => 23,      // Struct
140        }
141    }
142
143    /// Convert to LSP symbol kind with richer variable type distinctions.
144    ///
145    /// This mapping provides a richer UI experience for document symbols
146    /// by distinguishing between scalar, array, and hash variables:
147    ///
148    /// | Variable Type | LSP Kind | Number | Icon |
149    /// |---------------|----------|--------|------|
150    /// | Scalar (`$`) | Variable | 13 | generic variable |
151    /// | Array (`@`) | Array | 18 | array/list icon |
152    /// | Hash (`%`) | Object | 19 | object/dict icon |
153    ///
154    /// Use this for document symbol providers where visual distinction
155    /// of variable types improves navigation.
156    #[inline]
157    pub const fn to_lsp_kind_document_symbol(self) -> u32 {
158        match self {
159            SymbolKind::Package => 2,                    // Module
160            SymbolKind::Class => 5,                      // Class
161            SymbolKind::Role => 8,                       // Interface
162            SymbolKind::Subroutine => 12,                // Function
163            SymbolKind::Method => 6,                     // Method
164            SymbolKind::Variable(VarKind::Scalar) => 13, // Variable
165            SymbolKind::Variable(VarKind::Array) => 18,  // Array
166            SymbolKind::Variable(VarKind::Hash) => 19,   // Object
167            SymbolKind::Constant => 14,                  // Constant
168            SymbolKind::Import => 2,                     // Module
169            SymbolKind::Export => 12,                    // Function
170            SymbolKind::Label => 20,                     // Key
171            SymbolKind::Format => 23,                    // Struct
172        }
173    }
174
175    /// Returns the sigil for this symbol kind if applicable.
176    ///
177    /// Only variable symbols have sigils; all other symbols return `None`.
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// use perl_symbol_types::{SymbolKind, VarKind};
183    ///
184    /// assert_eq!(SymbolKind::Variable(VarKind::Scalar).sigil(), Some("$"));
185    /// assert_eq!(SymbolKind::Variable(VarKind::Array).sigil(), Some("@"));
186    /// assert_eq!(SymbolKind::Subroutine.sigil(), None);
187    /// ```
188    #[inline]
189    pub const fn sigil(self) -> Option<&'static str> {
190        match self {
191            SymbolKind::Variable(vk) => Some(vk.sigil()),
192            _ => None,
193        }
194    }
195
196    /// Returns true if this is any variable type.
197    #[inline]
198    pub const fn is_variable(self) -> bool {
199        matches!(self, SymbolKind::Variable(_))
200    }
201
202    /// Returns true if this is a callable type (subroutine or method).
203    #[inline]
204    pub const fn is_callable(self) -> bool {
205        matches!(self, SymbolKind::Subroutine | SymbolKind::Method)
206    }
207
208    /// Returns true if this is a namespace type (package, class, or role).
209    #[inline]
210    pub const fn is_namespace(self) -> bool {
211        matches!(self, SymbolKind::Package | SymbolKind::Class | SymbolKind::Role)
212    }
213
214    /// Create a scalar variable symbol kind.
215    #[inline]
216    pub const fn scalar() -> Self {
217        SymbolKind::Variable(VarKind::Scalar)
218    }
219
220    /// Create an array variable symbol kind.
221    #[inline]
222    pub const fn array() -> Self {
223        SymbolKind::Variable(VarKind::Array)
224    }
225
226    /// Create a hash variable symbol kind.
227    #[inline]
228    pub const fn hash() -> Self {
229        SymbolKind::Variable(VarKind::Hash)
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236
237    #[test]
238    fn test_var_kind_sigils() {
239        assert_eq!(VarKind::Scalar.sigil(), "$");
240        assert_eq!(VarKind::Array.sigil(), "@");
241        assert_eq!(VarKind::Hash.sigil(), "%");
242    }
243
244    #[test]
245    fn test_symbol_kind_sigils() {
246        assert_eq!(SymbolKind::Variable(VarKind::Scalar).sigil(), Some("$"));
247        assert_eq!(SymbolKind::Variable(VarKind::Array).sigil(), Some("@"));
248        assert_eq!(SymbolKind::Variable(VarKind::Hash).sigil(), Some("%"));
249        assert_eq!(SymbolKind::Subroutine.sigil(), None);
250        assert_eq!(SymbolKind::Package.sigil(), None);
251    }
252
253    #[test]
254    fn test_lsp_kind_mapping() {
255        assert_eq!(SymbolKind::Package.to_lsp_kind(), 2);
256        assert_eq!(SymbolKind::Class.to_lsp_kind(), 5);
257        assert_eq!(SymbolKind::Method.to_lsp_kind(), 6);
258        assert_eq!(SymbolKind::Role.to_lsp_kind(), 8);
259        assert_eq!(SymbolKind::Subroutine.to_lsp_kind(), 12);
260        assert_eq!(SymbolKind::Variable(VarKind::Scalar).to_lsp_kind(), 13);
261        assert_eq!(SymbolKind::Constant.to_lsp_kind(), 14);
262        assert_eq!(SymbolKind::Label.to_lsp_kind(), 20);
263        assert_eq!(SymbolKind::Format.to_lsp_kind(), 23);
264    }
265
266    #[test]
267    fn test_lsp_kind_document_symbol_mapping() {
268        // Non-variable types should match to_lsp_kind()
269        assert_eq!(SymbolKind::Package.to_lsp_kind_document_symbol(), 2);
270        assert_eq!(SymbolKind::Class.to_lsp_kind_document_symbol(), 5);
271        assert_eq!(SymbolKind::Subroutine.to_lsp_kind_document_symbol(), 12);
272
273        // Variable types get richer distinctions
274        assert_eq!(SymbolKind::Variable(VarKind::Scalar).to_lsp_kind_document_symbol(), 13); // Variable
275        assert_eq!(SymbolKind::Variable(VarKind::Array).to_lsp_kind_document_symbol(), 18); // Array
276        assert_eq!(SymbolKind::Variable(VarKind::Hash).to_lsp_kind_document_symbol(), 19); // Object
277    }
278
279    #[test]
280    fn test_convenience_constructors() {
281        assert_eq!(SymbolKind::scalar(), SymbolKind::Variable(VarKind::Scalar));
282        assert_eq!(SymbolKind::array(), SymbolKind::Variable(VarKind::Array));
283        assert_eq!(SymbolKind::hash(), SymbolKind::Variable(VarKind::Hash));
284    }
285
286    #[test]
287    fn test_category_predicates() {
288        assert!(SymbolKind::Variable(VarKind::Scalar).is_variable());
289        assert!(!SymbolKind::Subroutine.is_variable());
290
291        assert!(SymbolKind::Subroutine.is_callable());
292        assert!(SymbolKind::Method.is_callable());
293        assert!(!SymbolKind::Variable(VarKind::Scalar).is_callable());
294
295        assert!(SymbolKind::Package.is_namespace());
296        assert!(SymbolKind::Class.is_namespace());
297        assert!(SymbolKind::Role.is_namespace());
298        assert!(!SymbolKind::Subroutine.is_namespace());
299    }
300}