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