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;