mod builtins;
mod deprecation;
mod error;
mod node_helpers;
mod scope;
mod scope_builder;
mod scope_extents;
mod signature_loader;
mod type_checker;
mod type_expr;
mod validator;
pub use error::AnalysisError;
pub use node_helpers::{
binary_expr_rhs, call_argument_count, call_argument_node, class_decl_info, class_field_info,
class_member_visibility, function_decl_info, member_expr_member_name,
member_expr_receiver_name, param_name, primary_expr_new_constructor,
primary_expr_resolvable_name, var_decl_info, ClassDeclInfo, FunctionDeclInfo, VarDeclInfo,
VarDeclKind,
};
pub use scope::{
complexity_display_string, MemberVisibility, ResolvedSymbol, Scope, ScopeId, ScopeKind,
ScopeStore, SigMeta, VariableInfo, VariableKind,
};
pub use scope_builder::{seed_scope_from_program, ScopeBuilder};
pub use scope_extents::{build_scope_extents, scope_at_offset};
pub use type_checker::{TypeChecker, TypeMapKey};
pub use type_expr::{find_type_expr_child, parse_type_expr, TypeExprResult};
pub use validator::Validator;
use sipha::error::SemanticDiagnostic;
use sipha::red::SyntaxNode;
use sipha::types::Span;
use sipha::walk::WalkOptions;
use std::collections::HashMap;
use leekscript_core::Type;
#[derive(Default)]
pub struct AnalyzeOptions<'a> {
pub include_tree: Option<&'a leekscript_core::IncludeTree>,
pub signature_roots: Option<&'a [SyntaxNode]>,
}
#[must_use]
pub fn analyze_with_options(
program_root: &SyntaxNode,
options: &AnalyzeOptions<'_>,
) -> AnalysisResult {
if let Some(tree) = options.include_tree {
let sigs = options.signature_roots.unwrap_or(&[]);
return analyze_with_include_tree(tree, sigs);
}
if let Some(sigs) = options.signature_roots {
return analyze_with_signatures(program_root, sigs);
}
analyze(program_root)
}
#[derive(Debug)]
pub struct AnalysisResult {
pub diagnostics: Vec<SemanticDiagnostic>,
pub scope_store: ScopeStore,
pub type_map: std::collections::HashMap<TypeMapKey, Type>,
pub scope_id_sequence: Vec<ScopeId>,
}
impl AnalysisResult {
#[must_use]
pub fn has_errors(&self) -> bool {
self.diagnostics
.iter()
.any(|d| d.severity == sipha::error::Severity::Error)
}
#[must_use]
pub fn is_valid(&self) -> bool {
!self.has_errors()
}
}
fn run_pipeline(
program_root: &SyntaxNode,
store: &ScopeStore,
scope_id_sequence: &[ScopeId],
options: &WalkOptions,
) -> (Vec<SemanticDiagnostic>, HashMap<TypeMapKey, Type>) {
let mut validator = Validator::new(store, scope_id_sequence);
let _ = program_root.walk(&mut validator, options);
let mut type_checker = TypeChecker::new(store, program_root);
let _ = program_root.walk(&mut type_checker, options);
let mut deprecation_checker = deprecation::DeprecationChecker::new();
let _ = program_root.walk(&mut deprecation_checker, options);
let mut diagnostics = validator.diagnostics;
diagnostics.extend(type_checker.diagnostics);
diagnostics.extend(deprecation_checker.diagnostics);
(diagnostics, type_checker.type_map)
}
#[must_use]
pub fn analyze(root: &SyntaxNode) -> AnalysisResult {
let options = WalkOptions::nodes_only();
let mut builder = ScopeBuilder::new();
let _ = root.walk(&mut builder, &options);
for name in builtins::BUILTIN_CLASS_NAMES {
builder
.store
.add_root_class((*name).to_string(), Span::new(0, 0));
}
let (diagnostics, type_map) =
run_pipeline(root, &builder.store, &builder.scope_id_sequence, &options);
AnalysisResult {
diagnostics,
scope_store: builder.store,
type_map,
scope_id_sequence: builder.scope_id_sequence,
}
}
pub fn seed_scope_from_signatures(store: &mut ScopeStore, signature_roots: &[SyntaxNode]) {
signature_loader::seed_scope_from_signatures(store, signature_roots);
}
#[must_use]
pub fn analyze_with_include_tree(
tree: &leekscript_core::IncludeTree,
signature_roots: &[SyntaxNode],
) -> AnalysisResult {
let program_root = match &tree.root {
Some(r) => r.clone(),
None => {
return AnalysisResult {
diagnostics: Vec::new(),
scope_store: ScopeStore::new(),
type_map: std::collections::HashMap::new(),
scope_id_sequence: Vec::new(),
};
}
};
let options = WalkOptions::nodes_only();
let mut store = ScopeStore::new();
seed_scope_from_signatures(&mut store, signature_roots);
for (_, child) in &tree.includes {
if let Some(ref root) = child.root {
seed_scope_from_program(&mut store, root);
}
}
for name in builtins::BUILTIN_CLASS_NAMES {
store.add_root_class((*name).to_string(), Span::new(0, 0));
}
let mut builder = ScopeBuilder::with_store(store);
let _ = program_root.walk(&mut builder, &options);
let mut validator = Validator::new(&builder.store, &builder.scope_id_sequence);
let _ = program_root.walk(&mut validator, &options);
let mut type_checker = TypeChecker::new(&builder.store, &program_root);
let _ = program_root.walk(&mut type_checker, &options);
let mut deprecation_checker = deprecation::DeprecationChecker::new();
let _ = program_root.walk(&mut deprecation_checker, &options);
let mut diagnostics = validator.diagnostics;
diagnostics.extend(type_checker.diagnostics);
diagnostics.extend(deprecation_checker.diagnostics);
let type_map = type_checker.type_map.clone();
AnalysisResult {
diagnostics,
scope_store: builder.store,
type_map,
scope_id_sequence: builder.scope_id_sequence,
}
}
#[must_use]
pub fn analyze_with_signatures(
program_root: &SyntaxNode,
signature_roots: &[SyntaxNode],
) -> AnalysisResult {
let options = WalkOptions::nodes_only();
let mut store = ScopeStore::new();
seed_scope_from_signatures(&mut store, signature_roots);
for name in builtins::BUILTIN_CLASS_NAMES {
store.add_root_class((*name).to_string(), Span::new(0, 0));
}
let mut builder = ScopeBuilder::with_store(store);
let _ = program_root.walk(&mut builder, &options);
let (diagnostics, type_map) = run_pipeline(
program_root,
&builder.store,
&builder.scope_id_sequence,
&options,
);
AnalysisResult {
diagnostics,
scope_store: builder.store,
type_map,
scope_id_sequence: builder.scope_id_sequence,
}
}
#[cfg(test)]
mod tests;