Skip to main content

tsz_solver/
class_hierarchy.rs

1//! Class Hierarchy Type Construction
2//!
3//! This module implements class type construction in the Solver,
4//! following the Solver-First Architecture.
5//!
6//! Responsibilities:
7//! - Merge base class properties with derived class members
8//! - Handle property overrides and shadowing
9//! - Apply inheritance rules (nominal for classes, structural for interfaces)
10//!
11//! Note: Cycle detection for inheritance (class A extends B, B extends A)
12//! is handled by the Checker using `InheritanceGraph` BEFORE calling these functions.
13//! This module assumes the inheritance graph is acyclic.
14
15use crate::TypeDatabase;
16use crate::types::{CallSignature, CallableShape, ObjectFlags, ObjectShape, PropertyInfo, TypeId};
17use rustc_hash::FxHashMap;
18use tsz_binder::SymbolId;
19use tsz_common::interner::Atom;
20
21/// Builder for constructing class instance types.
22///
23/// This is a pure type computation - it knows nothing about AST nodes.
24/// It takes a base type and a list of member properties, and produces
25/// the merged instance type.
26pub struct ClassTypeBuilder<'a> {
27    db: &'a dyn TypeDatabase,
28}
29
30impl<'a> ClassTypeBuilder<'a> {
31    pub fn new(db: &'a dyn TypeDatabase) -> Self {
32        Self { db }
33    }
34
35    /// Creates a class instance type by merging base class properties with own members.
36    ///
37    /// # Arguments
38    /// * `base_type` - The `TypeId` of the base class (or `TypeId::ANY` if no base)
39    /// * `own_members` - Properties/methods declared directly in this class
40    /// * `symbol` - The `SymbolId` of the class (for creating Ref type if needed)
41    ///
42    /// # Returns
43    /// The `TypeId` representing the merged instance type.
44    pub fn create_instance_type(
45        &self,
46        base_type: TypeId,
47        own_members: Vec<PropertyInfo>,
48        symbol: SymbolId,
49    ) -> TypeId {
50        // If base is ERROR, just return own members as an object type
51        // This handles the case where base class has a cycle or error
52        if base_type == TypeId::ERROR {
53            return self.create_object_type(own_members, symbol);
54        }
55
56        // Get base class properties
57        let base_props = self.get_properties_of_type(base_type);
58
59        // Merge: own members override base properties
60        // Pass the class symbol to handle parent_id updates during merge
61        let merged = self.merge_properties(base_props, own_members, symbol);
62
63        self.create_object_type(merged, symbol)
64    }
65
66    /// Creates a class constructor type.
67    ///
68    /// The constructor type is a callable type with:
69    /// - A construct signature that returns the instance type
70    /// - Static properties as own properties
71    /// - Optional nominal identity via the class symbol
72    pub fn create_constructor_type(
73        &self,
74        static_members: Vec<PropertyInfo>,
75        instance_type: TypeId,
76        constructor_params: Vec<crate::types::ParamInfo>,
77        type_params: Vec<crate::types::TypeParamInfo>,
78        symbol: SymbolId,
79    ) -> TypeId {
80        let construct_sig = CallSignature {
81            type_params,
82            params: constructor_params,
83            this_type: None,
84            return_type: instance_type,
85            type_predicate: None,
86            is_method: false,
87        };
88
89        self.db.callable(CallableShape {
90            call_signatures: Vec::new(),
91            construct_signatures: vec![construct_sig],
92            properties: static_members,
93            string_index: None,
94            number_index: None,
95            symbol: Some(symbol),
96        })
97    }
98
99    /// Extract properties from a type.
100    fn get_properties_of_type(&self, type_id: TypeId) -> Vec<PropertyInfo> {
101        use crate::type_queries::get_object_shape;
102
103        match get_object_shape(self.db, type_id) {
104            Some(shape) => shape.properties.clone(),
105            None => Vec::new(),
106        }
107    }
108
109    /// Merge base properties with own members.
110    ///
111    /// Requirements:
112    /// - ALL properties (including private) are inherited.
113    /// - Private members are inherited but not accessible (Checker handles access control).
114    /// - `parent_id` is updated to the current class for all own/overriding members.
115    fn merge_properties(
116        &self,
117        base: Vec<PropertyInfo>,
118        own: Vec<PropertyInfo>,
119        current_class: SymbolId,
120    ) -> Vec<PropertyInfo> {
121        let mut result_map: FxHashMap<Atom, PropertyInfo> = FxHashMap::default();
122
123        // 1. Add ALL base properties (private members are inherited but inaccessible)
124        // This is critical for subtyping: derived class must structurally contain
125        // all base class properties for assignability to work
126        for prop in base {
127            result_map.insert(prop.name, prop);
128        }
129
130        // 2. Override with own members (last-write-wins)
131        for mut prop in own {
132            // 3. Update parent_id to the current class
133            // This stamps the property as belonging to the derived class
134            prop.parent_id = Some(current_class);
135            result_map.insert(prop.name, prop);
136        }
137
138        // Convert back to Vec
139        result_map.into_values().collect()
140    }
141
142    /// Create an object type from properties.
143    fn create_object_type(&self, properties: Vec<PropertyInfo>, symbol: SymbolId) -> TypeId {
144        // Create an object type with the class symbol for nominal discrimination
145        // The symbol field affects Hash (for interning) but NOT PartialEq (for structural comparison)
146        // This ensures that:
147        // - Different classes with identical structures get different TypeIds (via Hash in interner)
148        // - Structural type checking still works correctly (via PartialEq ignoring symbol)
149        self.db.object_with_index(ObjectShape {
150            flags: ObjectFlags::empty(),
151            properties,
152            string_index: None,
153            number_index: None,
154            symbol: Some(symbol),
155        })
156    }
157}
158
159/// Detects if adding a parent to a child would create a cycle in the inheritance graph.
160///
161/// This is a static check that should be performed by the Checker BEFORE
162/// calling the Solver to construct class types.
163///
164/// # Arguments
165/// * `child` - The `SymbolId` of the class being defined
166/// * `parent` - The `SymbolId` of the base class
167/// * `graph` - The `InheritanceGraph` to check against
168///
169/// # Returns
170/// `true` if adding child->parent would create a cycle.
171pub fn would_create_inheritance_cycle(
172    child: SymbolId,
173    parent: SymbolId,
174    graph: &crate::inheritance::InheritanceGraph,
175) -> bool {
176    // If parent is already derived from child, adding child->parent creates a cycle
177    graph.is_derived_from(parent, child)
178}
179
180#[cfg(test)]
181#[path = "../tests/class_hierarchy_tests.rs"]
182mod tests;