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;