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}