Skip to main content

tsz_solver/
def.rs

1//! Definition identifiers and storage for the solver.
2//!
3//! This module provides a Solver-owned definition identifier (`DefId`) that
4//! replaces `SymbolRef` in types, enabling:
5//!
6//! - **Decoupling**: Solver is independent of Binder's symbol representation
7//! - **Testing**: Types can be created and tested without a full Binder
8//! - **Caching**: `DefId` provides a stable key for Salsa memoization
9//!
10//! ## Migration Path
11//!
12//! The transition from `SymbolRef` to `DefId` happens incrementally:
13//!
14//! 1. `TypeData::Ref(SymbolRef)` remains for backward compatibility
15//! 2. New `TypeData::Lazy(DefId)` is added for migrated code
16//! 3. Eventually, `Ref(SymbolRef)` is removed entirely
17//!
18//! ## `DefId` Allocation Strategies
19//!
20//! | Mode | Strategy | Use Case |
21//! |------|----------|----------|
22//! | CLI  | Sequential allocation | Fresh start each compilation |
23//! | LSP  | Content-addressed hash | Stable IDs across edits |
24
25use crate::types::{ObjectFlags, ObjectShape, PropertyInfo, TypeId, TypeParamInfo};
26use dashmap::DashMap;
27use std::sync::Arc;
28use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
29use tracing::trace;
30use tsz_common::interner::Atom;
31
32/// Global counter for assigning unique instance IDs to `DefinitionStore` instances.
33/// Used for debugging `DefId` collision issues.
34static NEXT_INSTANCE_ID: AtomicU64 = AtomicU64::new(1);
35
36// =============================================================================
37// DefId - Solver-Owned Definition Identifier
38// =============================================================================
39
40/// Solver-owned definition identifier.
41///
42/// Unlike `SymbolRef` which references Binder symbols, `DefId` is owned by
43/// the Solver and can be created without Binder context.
44///
45/// ## Comparison with `SymbolRef`
46///
47/// | Aspect | SymbolRef | DefId |
48/// |--------|-----------|-------|
49/// | Owner | Binder | Solver |
50/// | Stable across edits | No | Yes (with content-hash) |
51/// | Requires Binder | Yes | No |
52/// | Supports testing | Limited | Full |
53#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
54pub struct DefId(pub u32);
55
56impl DefId {
57    /// Sentinel value for invalid `DefId`.
58    pub const INVALID: Self = Self(0);
59
60    /// First valid `DefId`.
61    pub const FIRST_VALID: u32 = 1;
62
63    /// Check if this `DefId` is valid.
64    pub const fn is_valid(self) -> bool {
65        self.0 >= Self::FIRST_VALID
66    }
67}
68
69// =============================================================================
70// DefKind - Definition Kind
71// =============================================================================
72
73/// Kind of type definition.
74///
75/// Affects evaluation and subtype checking behavior:
76///
77/// | Kind | Expansion | Nominal | Example |
78/// |------|-----------|---------|---------|
79/// | TypeAlias | Always expand | No | `type Foo = number` |
80/// | Interface | Lazy expand | No | `interface Point { x: number }` |
81/// | Class | Lazy expand | Yes (with brand) | `class Foo {}` |
82/// | Enum | Special handling | Yes | `enum Color { Red, Green }` |
83/// | Namespace | Export lookup | No | `namespace NS { export type T = number }` |
84#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
85pub enum DefKind {
86    /// Type alias: always expand (transparent).
87    /// `type Foo<T> = T | null`
88    TypeAlias,
89
90    /// Interface: keep opaque until needed.
91    /// `interface Point { x: number; y: number }`
92    Interface,
93
94    /// Class: opaque with nominal brand.
95    /// `class User { constructor(public name: string) {} }`
96    Class,
97
98    /// Enum: special handling for member access.
99    /// `enum Direction { Up, Down, Left, Right }`
100    Enum,
101
102    /// Namespace/Module: container for exported types and values.
103    /// `namespace NS { export type T = number }`
104    Namespace,
105}
106
107// =============================================================================
108// Definition Info - Stored Definition Data
109// =============================================================================
110
111/// Complete information about a type definition.
112///
113/// This is stored in `DefinitionStore` and retrieved by `DefId`.
114#[derive(Clone, Debug)]
115pub struct DefinitionInfo {
116    /// Kind of definition (affects evaluation strategy)
117    pub kind: DefKind,
118
119    /// Name of the definition (for diagnostics)
120    pub name: Atom,
121
122    /// Type parameters for generic definitions
123    pub type_params: Vec<TypeParamInfo>,
124
125    /// The body `TypeId` (structural representation)
126    /// For lazy definitions, this may be computed on demand
127    pub body: Option<TypeId>,
128
129    /// For classes: the instance type's structural shape
130    pub instance_shape: Option<Arc<ObjectShape>>,
131
132    /// For classes: the static type's structural shape
133    pub static_shape: Option<Arc<ObjectShape>>,
134
135    /// For classes: parent class `DefId` (if extends)
136    pub extends: Option<DefId>,
137
138    /// For classes/interfaces: implemented interfaces
139    pub implements: Vec<DefId>,
140
141    /// For enums: member names and values
142    pub enum_members: Vec<(Atom, EnumMemberValue)>,
143
144    /// For namespaces/modules: exported members
145    /// Maps export name to the `DefId` of the exported type
146    pub exports: Vec<(Atom, DefId)>,
147
148    /// Optional file identifier for debugging
149    pub file_id: Option<u32>,
150
151    /// Optional span for source location
152    pub span: Option<(u32, u32)>,
153
154    /// The binder `SymbolId` that this `DefId` was created from.
155    /// Used for cross-context cycle detection: the same interface may get
156    /// different `DefIds` in different checker contexts, but the `SymbolId`
157    /// stays the same. This enables coinductive cycle detection for recursive
158    /// generic interfaces (e.g., `Promise<T>` vs `PromiseLike<T>`).
159    pub symbol_id: Option<u32>,
160}
161
162/// Enum member value.
163#[derive(Clone, Debug, PartialEq)]
164pub enum EnumMemberValue {
165    /// Numeric enum member
166    Number(f64),
167    /// String enum member
168    String(Atom),
169    /// Computed (not yet evaluated)
170    Computed,
171}
172
173impl DefinitionInfo {
174    /// Create a new type alias definition.
175    pub const fn type_alias(name: Atom, type_params: Vec<TypeParamInfo>, body: TypeId) -> Self {
176        Self {
177            kind: DefKind::TypeAlias,
178            name,
179            type_params,
180            body: Some(body),
181            instance_shape: None,
182            static_shape: None,
183            extends: None,
184            implements: Vec::new(),
185            enum_members: Vec::new(),
186            exports: Vec::new(),
187            file_id: None,
188            span: None,
189            symbol_id: None,
190        }
191    }
192
193    /// Create a new interface definition.
194    pub fn interface(
195        name: Atom,
196        type_params: Vec<TypeParamInfo>,
197        properties: Vec<PropertyInfo>,
198    ) -> Self {
199        let shape = ObjectShape {
200            flags: ObjectFlags::empty(),
201            properties,
202            string_index: None,
203            number_index: None,
204            symbol: None,
205        };
206        Self {
207            kind: DefKind::Interface,
208            name,
209            type_params,
210            body: None, // Body computed on demand
211            instance_shape: Some(Arc::new(shape)),
212            static_shape: None,
213            extends: None,
214            implements: Vec::new(),
215            enum_members: Vec::new(),
216            exports: Vec::new(),
217            file_id: None,
218            span: None,
219            symbol_id: None,
220        }
221    }
222
223    /// Create a new class definition.
224    pub fn class(
225        name: Atom,
226        type_params: Vec<TypeParamInfo>,
227        instance_properties: Vec<PropertyInfo>,
228        static_properties: Vec<PropertyInfo>,
229    ) -> Self {
230        let instance_shape = ObjectShape {
231            flags: ObjectFlags::empty(),
232            properties: instance_properties,
233            string_index: None,
234            number_index: None,
235            symbol: None,
236        };
237        let static_shape = ObjectShape {
238            flags: ObjectFlags::empty(),
239            properties: static_properties,
240            string_index: None,
241            number_index: None,
242            symbol: None,
243        };
244        Self {
245            kind: DefKind::Class,
246            name,
247            type_params,
248            body: None,
249            instance_shape: Some(Arc::new(instance_shape)),
250            static_shape: Some(Arc::new(static_shape)),
251            extends: None,
252            implements: Vec::new(),
253            enum_members: Vec::new(),
254            exports: Vec::new(),
255            file_id: None,
256            span: None,
257            symbol_id: None,
258        }
259    }
260
261    /// Create a new enum definition.
262    pub const fn enumeration(name: Atom, members: Vec<(Atom, EnumMemberValue)>) -> Self {
263        Self {
264            kind: DefKind::Enum,
265            name,
266            type_params: Vec::new(),
267            body: None,
268            instance_shape: None,
269            static_shape: None,
270            extends: None,
271            implements: Vec::new(),
272            enum_members: members,
273            exports: Vec::new(),
274            file_id: None,
275            span: None,
276            symbol_id: None,
277        }
278    }
279
280    /// Create a new namespace definition.
281    pub const fn namespace(name: Atom, exports: Vec<(Atom, DefId)>) -> Self {
282        Self {
283            kind: DefKind::Namespace,
284            name,
285            type_params: Vec::new(),
286            body: None,
287            instance_shape: None,
288            static_shape: None,
289            extends: None,
290            implements: Vec::new(),
291            enum_members: Vec::new(),
292            exports,
293            file_id: None,
294            span: None,
295            symbol_id: None,
296        }
297    }
298
299    /// Set the extends parent for a class.
300    pub const fn with_extends(mut self, parent: DefId) -> Self {
301        self.extends = Some(parent);
302        self
303    }
304
305    /// Set implemented interfaces.
306    pub fn with_implements(mut self, interfaces: Vec<DefId>) -> Self {
307        self.implements = interfaces;
308        self
309    }
310
311    /// Set exports for a namespace/module.
312    pub fn with_exports(mut self, exports: Vec<(Atom, DefId)>) -> Self {
313        self.exports = exports;
314        self
315    }
316
317    /// Add an export to the namespace/module.
318    pub fn add_export(&mut self, name: Atom, def_id: DefId) {
319        self.exports.push((name, def_id));
320    }
321
322    /// Look up an export by name.
323    pub fn get_export(&self, name: Atom) -> Option<DefId> {
324        self.exports
325            .iter()
326            .find(|(n, _)| *n == name)
327            .map(|(_, d)| *d)
328    }
329
330    /// Set file ID for debugging.
331    pub const fn with_file_id(mut self, file_id: u32) -> Self {
332        self.file_id = Some(file_id);
333        self
334    }
335
336    /// Set source span.
337    pub const fn with_span(mut self, start: u32, end: u32) -> Self {
338        self.span = Some((start, end));
339        self
340    }
341}
342
343// =============================================================================
344// DefinitionStore - Storage for Definitions
345// =============================================================================
346
347/// Thread-safe storage for type definitions.
348///
349/// Uses `DashMap` for concurrent access from multiple checking threads.
350///
351/// ## Usage
352///
353/// ```ignore
354/// let store = DefinitionStore::new();
355///
356/// // Register a type alias
357/// let def_id = store.register(DefinitionInfo::type_alias(
358///     interner.intern_string("Foo"),
359///     vec![],
360///     TypeId::NUMBER,
361/// ));
362///
363/// // Look up later
364/// let info = store.get(def_id).expect("definition exists");
365/// ```
366#[derive(Debug)]
367pub struct DefinitionStore {
368    /// Unique instance ID for debugging (tracks which store instance this is)
369    instance_id: u64,
370
371    /// `DefId` -> `DefinitionInfo` mapping
372    definitions: DashMap<DefId, DefinitionInfo>,
373
374    /// Next available `DefId`
375    next_id: AtomicU32,
376}
377
378impl Default for DefinitionStore {
379    fn default() -> Self {
380        Self::new()
381    }
382}
383
384impl DefinitionStore {
385    /// Create a new definition store.
386    pub fn new() -> Self {
387        let instance_id = NEXT_INSTANCE_ID.fetch_add(1, Ordering::SeqCst);
388        trace!(instance_id, "DefinitionStore::new - creating new instance");
389        Self {
390            instance_id,
391            definitions: DashMap::new(),
392            next_id: AtomicU32::new(DefId::FIRST_VALID),
393        }
394    }
395
396    /// Allocate a fresh `DefId`.
397    fn allocate(&self) -> DefId {
398        let id = self.next_id.fetch_add(1, Ordering::SeqCst);
399        trace!(
400            instance_id = self.instance_id,
401            allocated_def_id = %id,
402            next_will_be = %(id + 1),
403            "DefinitionStore::allocate"
404        );
405        DefId(id)
406    }
407
408    /// Register a new definition and return its `DefId`.
409    pub fn register(&self, info: DefinitionInfo) -> DefId {
410        let id = self.allocate();
411        trace!(
412            instance_id = self.instance_id,
413            def_id = %id.0,
414            kind = ?info.kind,
415            "DefinitionStore::register"
416        );
417        self.definitions.insert(id, info);
418        id
419    }
420
421    /// Get definition info by `DefId`.
422    pub fn get(&self, id: DefId) -> Option<DefinitionInfo> {
423        self.definitions.get(&id).as_deref().cloned()
424    }
425
426    /// Get the binder SymbolId for a `DefId`.
427    ///
428    /// Returns the `SymbolId` (as raw u32) that this `DefId` was created from.
429    /// This is available across checker contexts because it's stored directly
430    /// in the `DefinitionInfo` (which is shared via `DefinitionStore`).
431    pub fn get_symbol_id(&self, id: DefId) -> Option<u32> {
432        self.definitions.get(&id).and_then(|info| info.symbol_id)
433    }
434
435    /// Check if a `DefId` exists.
436    pub fn contains(&self, id: DefId) -> bool {
437        self.definitions.contains_key(&id)
438    }
439
440    /// Get the kind of a definition.
441    pub fn get_kind(&self, id: DefId) -> Option<DefKind> {
442        self.definitions.get(&id).map(|r| r.kind)
443    }
444
445    /// Get type parameters for a definition.
446    pub fn get_type_params(&self, id: DefId) -> Option<Vec<TypeParamInfo>> {
447        self.definitions.get(&id).map(|r| r.type_params.clone())
448    }
449
450    /// Get the body `TypeId` for a definition.
451    pub fn get_body(&self, id: DefId) -> Option<TypeId> {
452        self.definitions.get(&id).and_then(|r| r.body)
453    }
454
455    /// Get the instance shape for a class/interface.
456    pub fn get_instance_shape(&self, id: DefId) -> Option<Arc<ObjectShape>> {
457        self.definitions
458            .get(&id)
459            .and_then(|r| r.instance_shape.clone())
460    }
461
462    /// Get the static shape for a class.
463    pub fn get_static_shape(&self, id: DefId) -> Option<Arc<ObjectShape>> {
464        self.definitions
465            .get(&id)
466            .and_then(|r| r.static_shape.clone())
467    }
468
469    /// Get parent class `DefId` for a class.
470    pub fn get_extends(&self, id: DefId) -> Option<DefId> {
471        self.definitions.get(&id).and_then(|r| r.extends)
472    }
473
474    /// Get implemented interfaces for a class/interface.
475    pub fn get_implements(&self, id: DefId) -> Option<Vec<DefId>> {
476        self.definitions.get(&id).map(|r| r.implements.clone())
477    }
478
479    /// Update the body `TypeId` for a definition (for lazy evaluation).
480    pub fn set_body(&self, id: DefId, body: TypeId) {
481        if let Some(mut entry) = self.definitions.get_mut(&id) {
482            entry.body = Some(body);
483        }
484    }
485
486    /// Update the instance shape for a type definition.
487    ///
488    /// This is used by checker code when a concrete object-like shape is computed
489    /// for an interface/class definition and should be recorded for diagnostics.
490    pub fn set_instance_shape(&self, id: DefId, shape: Arc<ObjectShape>) {
491        if let Some(mut entry) = self.definitions.get_mut(&id) {
492            entry.instance_shape = Some(shape);
493        }
494    }
495
496    /// Number of definitions.
497    pub fn len(&self) -> usize {
498        self.definitions.len()
499    }
500
501    /// Check if empty.
502    pub fn is_empty(&self) -> bool {
503        self.definitions.is_empty()
504    }
505
506    /// Clear all definitions (for testing).
507    pub fn clear(&self) {
508        self.definitions.clear();
509        self.next_id.store(DefId::FIRST_VALID, Ordering::SeqCst);
510    }
511
512    /// Get exports for a namespace/module `DefId`.
513    pub fn get_exports(&self, id: DefId) -> Option<Vec<(Atom, DefId)>> {
514        self.definitions.get(&id).map(|r| r.exports.clone())
515    }
516
517    /// Get enum members for an enum `DefId`.
518    pub fn get_enum_members(&self, id: DefId) -> Option<Vec<(Atom, EnumMemberValue)>> {
519        self.definitions.get(&id).map(|r| r.enum_members.clone())
520    }
521
522    /// Get the name of a definition.
523    pub fn get_name(&self, id: DefId) -> Option<Atom> {
524        self.definitions.get(&id).map(|r| r.name)
525    }
526
527    /// Update exports for a definition (for lazy population).
528    pub fn set_exports(&self, id: DefId, exports: Vec<(Atom, DefId)>) {
529        if let Some(mut entry) = self.definitions.get_mut(&id) {
530            entry.exports = exports;
531        }
532    }
533
534    /// Add an export to an existing definition.
535    pub fn add_export(&self, id: DefId, name: Atom, export_def: DefId) {
536        if let Some(mut entry) = self.definitions.get_mut(&id) {
537            entry.add_export(name, export_def);
538        }
539    }
540
541    /// Update enum members for a definition (for lazy population).
542    pub fn set_enum_members(&self, id: DefId, members: Vec<(Atom, EnumMemberValue)>) {
543        if let Some(mut entry) = self.definitions.get_mut(&id) {
544            entry.enum_members = members;
545        }
546    }
547
548    /// Get all `DefIds` (for debugging/testing).
549    pub fn all_ids(&self) -> Vec<DefId> {
550        self.definitions.iter().map(|r| *r.key()).collect()
551    }
552
553    /// Find a `DefId` by its instance shape.
554    ///
555    /// This is used by the `TypeFormatter` to preserve interface names in error messages.
556    /// When an Object type matches an interface's instance shape, we use the interface name
557    /// instead of expanding the object literal.
558    pub fn find_def_by_shape(&self, shape: &ObjectShape) -> Option<DefId> {
559        self.definitions
560            .iter()
561            .find(|entry| {
562                entry
563                    .value()
564                    .instance_shape
565                    .as_ref()
566                    .map(std::convert::AsRef::as_ref)
567                    == Some(shape)
568            })
569            .map(|entry| *entry.key())
570    }
571}
572
573// =============================================================================
574// Content-Addressed DefId (for LSP mode)
575// =============================================================================
576
577/// Content-addressed `DefId` generator for LSP mode.
578///
579/// Uses a hash of (name, `file_id`, span) to generate stable `DefIds`
580/// that survive file edits without changing unrelated definitions.
581pub struct ContentAddressedDefIds {
582    /// Hash -> `DefId` mapping for deduplication
583    hash_to_def: DashMap<u64, DefId>,
584
585    /// Next `DefId` for new hashes
586    next_id: AtomicU32,
587}
588
589impl Default for ContentAddressedDefIds {
590    fn default() -> Self {
591        Self::new()
592    }
593}
594
595impl ContentAddressedDefIds {
596    /// Create a new content-addressed `DefId` generator.
597    pub fn new() -> Self {
598        Self {
599            hash_to_def: DashMap::new(),
600            next_id: AtomicU32::new(DefId::FIRST_VALID),
601        }
602    }
603
604    /// Get or create a `DefId` for the given content hash.
605    ///
606    /// # Arguments
607    /// - `name`: Definition name
608    /// - `file_id`: File identifier
609    /// - `span_start`: Start offset of definition
610    pub fn get_or_create(&self, name: Atom, file_id: u32, span_start: u32) -> DefId {
611        use std::hash::{Hash, Hasher};
612
613        // Compute content hash
614        let mut hasher = rustc_hash::FxHasher::default();
615        name.hash(&mut hasher);
616        file_id.hash(&mut hasher);
617        span_start.hash(&mut hasher);
618        let hash = hasher.finish();
619
620        // Check existing
621        if let Some(existing) = self.hash_to_def.get(&hash) {
622            return *existing;
623        }
624
625        // Allocate new
626        let id = DefId(self.next_id.fetch_add(1, Ordering::SeqCst));
627        self.hash_to_def.insert(hash, id);
628        id
629    }
630
631    /// Clear all mappings (for testing).
632    pub fn clear(&self) {
633        self.hash_to_def.clear();
634        self.next_id.store(DefId::FIRST_VALID, Ordering::SeqCst);
635    }
636}
637
638// =============================================================================
639// Tests
640// =============================================================================
641
642#[cfg(test)]
643#[path = "../tests/def_tests.rs"]
644mod tests;