use crate::diagnostics::{diagnostic_codes, diagnostic_messages, format_message};
use rustc_hash::FxHashSet;
use tsz_binder::SymbolId;
use tsz_parser::parser::NodeIndex;
pub struct ClassInheritanceChecker<'a, 'ctx> {
pub ctx: &'a mut crate::CheckerContext<'ctx>,
}
impl<'a, 'ctx> ClassInheritanceChecker<'a, 'ctx> {
pub const fn new(ctx: &'a mut crate::CheckerContext<'ctx>) -> Self {
Self { ctx }
}
pub fn check_class_inheritance_cycle(
&mut self,
class_idx: NodeIndex,
class: &tsz_parser::parser::node::ClassData,
) -> bool {
use tsz_scanner::SyntaxKind;
let current_sym = match self.ctx.binder.get_node_symbol(class_idx) {
Some(sym) => sym,
None => return false, };
let mut parent_symbols = Vec::new();
if let Some(heritage_clauses) = &class.heritage_clauses {
for &clause_idx in &heritage_clauses.nodes {
let Some(heritage) = self.ctx.arena.get_heritage_clause_at(clause_idx) else {
continue;
};
if heritage.token != SyntaxKind::ExtendsKeyword as u16 {
continue;
}
let Some(&type_idx) = heritage.types.nodes.first() else {
continue;
};
let expr_idx = self
.ctx
.arena
.get_expr_type_args_at(type_idx)
.map_or(type_idx, |e| e.expression);
if let Some(parent_sym) = self.resolve_heritage_symbol(expr_idx) {
if parent_sym == current_sym {
self.error_circular_class_inheritance_for_symbol(current_sym);
return true; }
parent_symbols.push(parent_sym);
}
}
}
self.detect_and_report_cycle(current_sym, &parent_symbols)
}
pub fn check_interface_inheritance_cycle(
&mut self,
iface_idx: NodeIndex,
iface: &tsz_parser::parser::node::InterfaceData,
) -> bool {
use tsz_scanner::SyntaxKind;
let current_sym = match self.ctx.binder.get_node_symbol(iface_idx) {
Some(sym) => sym,
None => return false,
};
let mut parent_symbols = Vec::new();
if let Some(heritage_clauses) = &iface.heritage_clauses {
for &clause_idx in &heritage_clauses.nodes {
let Some(heritage) = self.ctx.arena.get_heritage_clause_at(clause_idx) else {
continue;
};
if heritage.token != SyntaxKind::ExtendsKeyword as u16 {
continue;
}
for &type_idx in &heritage.types.nodes {
let expr_idx = self
.ctx
.arena
.get_expr_type_args_at(type_idx)
.map_or(type_idx, |e| e.expression);
if let Some(parent_sym) = self.resolve_heritage_symbol(expr_idx) {
if parent_sym == current_sym {
self.error_circular_class_inheritance_for_symbol(current_sym);
return true;
}
parent_symbols.push(parent_sym);
}
}
}
}
self.detect_and_report_cycle(current_sym, &parent_symbols)
}
fn detect_and_report_cycle(
&mut self,
current_sym: SymbolId,
parent_symbols: &[SymbolId],
) -> bool {
if self.detects_cycle_dfs(current_sym, parent_symbols) {
let cycle_symbols = self.collect_cycle_symbols(current_sym, parent_symbols);
for sym in cycle_symbols {
self.error_circular_class_inheritance_for_symbol(sym);
}
return true;
}
self.ctx
.inheritance_graph
.add_inheritance(current_sym, parent_symbols);
false
}
fn detects_cycle_dfs(&self, current: SymbolId, parents: &[SymbolId]) -> bool {
let mut visited = FxHashSet::default();
for &parent in parents {
if self.would_create_cycle(parent, current, &mut visited) {
return true;
}
visited.clear();
}
false
}
fn would_create_cycle(
&self,
child: SymbolId,
target: SymbolId,
visited: &mut FxHashSet<SymbolId>,
) -> bool {
if child == target {
return true; }
if !visited.insert(child) {
return false; }
let parents = self.ctx.inheritance_graph.get_parents(child);
for &parent in &parents {
if self.would_create_cycle(parent, target, visited) {
return true;
}
}
visited.remove(&child);
false
}
fn collect_cycle_symbols(
&self,
current: SymbolId,
parents: &[SymbolId],
) -> FxHashSet<SymbolId> {
let mut cycle = FxHashSet::default();
cycle.insert(current);
for &parent in parents {
let mut visited = FxHashSet::default();
let mut path = Vec::new();
if self.find_path_to_target(parent, current, &mut visited, &mut path) {
for sym in path {
cycle.insert(sym);
}
}
}
cycle
}
fn find_path_to_target(
&self,
node: SymbolId,
target: SymbolId,
visited: &mut FxHashSet<SymbolId>,
path: &mut Vec<SymbolId>,
) -> bool {
if node == target {
path.push(node);
return true;
}
if !visited.insert(node) {
return false;
}
let parents = self.ctx.inheritance_graph.get_parents(node);
for &parent in &parents {
if self.find_path_to_target(parent, target, visited, path) {
path.push(node);
return true;
}
}
false
}
fn resolve_heritage_symbol(&self, expr_idx: NodeIndex) -> Option<SymbolId> {
use tsz_parser::parser::syntax_kind_ext;
let node = self.ctx.arena.get(expr_idx)?;
if node.kind == tsz_scanner::SyntaxKind::Identifier as u16 {
self.ctx.binder.resolve_identifier(self.ctx.arena, expr_idx)
} else if node.kind == syntax_kind_ext::QUALIFIED_NAME {
self.resolve_qualified_symbol(expr_idx)
} else if node.kind == syntax_kind_ext::PROPERTY_ACCESS_EXPRESSION {
self.resolve_heritage_symbol_access(expr_idx)
} else {
None
}
}
fn resolve_qualified_symbol(&self, expr_idx: NodeIndex) -> Option<SymbolId> {
let access = self.ctx.arena.get_access_expr_at(expr_idx)?;
let left_sym = self.resolve_heritage_symbol(access.expression)?;
let name = self
.ctx
.arena
.get_identifier_at(access.name_or_argument)
.map(|ident| ident.escaped_text.clone())?;
let left_symbol = self.ctx.binder.get_symbol(left_sym)?;
let exports = left_symbol.exports.as_ref()?;
exports.get(&name)
}
fn resolve_heritage_symbol_access(&self, expr_idx: NodeIndex) -> Option<SymbolId> {
let access = self.ctx.arena.get_access_expr_at(expr_idx)?;
let left_sym = self.resolve_heritage_symbol(access.expression)?;
let name = self
.ctx
.arena
.get_identifier_at(access.name_or_argument)
.map(|ident| ident.escaped_text.clone())?;
let left_symbol = self.ctx.binder.get_symbol(left_sym)?;
let exports = left_symbol.exports.as_ref()?;
exports.get(&name)
}
fn error_circular_class_inheritance_for_symbol(&mut self, sym_id: SymbolId) {
let Some(symbol) = self.ctx.binder.get_symbol(sym_id) else {
return;
};
let decl_info = symbol.declarations.iter().find_map(|&decl_idx| {
let node = self.ctx.arena.get(decl_idx)?;
match node.kind {
tsz_parser::parser::syntax_kind_ext::CLASS_DECLARATION => {
Some((decl_idx, true)) }
tsz_parser::parser::syntax_kind_ext::INTERFACE_DECLARATION => {
Some((decl_idx, false)) }
_ => None,
}
});
let Some((decl_idx, is_class)) = decl_info else {
return;
};
let error_node_idx = if is_class {
self.ctx
.arena
.get(decl_idx)
.and_then(|node| self.ctx.arena.get_class(node))
.map(|class| class.name)
.filter(|name| name.is_some())
.unwrap_or(decl_idx)
} else {
self.ctx
.arena
.get(decl_idx)
.and_then(|node| self.ctx.arena.get_interface(node))
.map(|iface| iface.name)
.filter(|name| name.is_some())
.unwrap_or(decl_idx)
};
let Some((start, end)) = self.ctx.get_node_span(error_node_idx) else {
return;
};
let (code, message_template) = if is_class {
(
diagnostic_codes::IS_REFERENCED_DIRECTLY_OR_INDIRECTLY_IN_ITS_OWN_BASE_EXPRESSION,
diagnostic_messages::IS_REFERENCED_DIRECTLY_OR_INDIRECTLY_IN_ITS_OWN_BASE_EXPRESSION,
)
} else {
(
diagnostic_codes::TYPE_RECURSIVELY_REFERENCES_ITSELF_AS_A_BASE_TYPE,
diagnostic_messages::TYPE_RECURSIVELY_REFERENCES_ITSELF_AS_A_BASE_TYPE,
)
};
let name = symbol.escaped_name.clone();
let message = format_message(message_template, &[&name]);
let length = end.saturating_sub(start);
self.ctx.error(start, length, message, code);
}
}