1mod builtins;
11mod deprecation;
12mod error;
13mod node_helpers;
14mod scope;
15mod scope_builder;
16mod scope_extents;
17mod signature_loader;
18mod type_checker;
19mod type_expr;
20mod validator;
21
22pub use error::AnalysisError;
23pub use node_helpers::{
24 binary_expr_rhs, call_argument_count, call_argument_node, class_decl_info, class_field_info,
25 class_member_visibility, function_decl_info, member_expr_member_name,
26 member_expr_receiver_name, param_name, primary_expr_new_constructor,
27 primary_expr_resolvable_name, var_decl_info, ClassDeclInfo, FunctionDeclInfo, VarDeclInfo,
28 VarDeclKind,
29};
30pub use scope::{
31 complexity_display_string, MemberVisibility, ResolvedSymbol, Scope, ScopeId, ScopeKind,
32 ScopeStore, SigMeta, VariableInfo, VariableKind,
33};
34pub use scope_builder::{seed_scope_from_program, ScopeBuilder};
35pub use scope_extents::{build_scope_extents, scope_at_offset};
36pub use type_checker::{TypeChecker, TypeMapKey};
37pub use type_expr::{find_type_expr_child, parse_type_expr, TypeExprResult};
38pub use validator::Validator;
39
40use sipha::error::SemanticDiagnostic;
41use sipha::red::SyntaxNode;
42use sipha::types::Span;
43use sipha::walk::WalkOptions;
44use std::collections::HashMap;
45
46use leekscript_core::Type;
47
48#[derive(Default)]
50pub struct AnalyzeOptions<'a> {
51 pub include_tree: Option<&'a leekscript_core::IncludeTree>,
53 pub signature_roots: Option<&'a [SyntaxNode]>,
55}
56
57#[must_use]
59pub fn analyze_with_options(
60 program_root: &SyntaxNode,
61 options: &AnalyzeOptions<'_>,
62) -> AnalysisResult {
63 if let Some(tree) = options.include_tree {
64 let sigs = options.signature_roots.unwrap_or(&[]);
65 return analyze_with_include_tree(tree, sigs);
66 }
67 if let Some(sigs) = options.signature_roots {
68 return analyze_with_signatures(program_root, sigs);
69 }
70 analyze(program_root)
71}
72
73#[derive(Debug)]
75pub struct AnalysisResult {
76 pub diagnostics: Vec<SemanticDiagnostic>,
77 pub scope_store: ScopeStore,
78 pub type_map: std::collections::HashMap<TypeMapKey, Type>,
80 pub scope_id_sequence: Vec<ScopeId>,
82}
83
84impl AnalysisResult {
85 #[must_use]
86 pub fn has_errors(&self) -> bool {
87 self.diagnostics
88 .iter()
89 .any(|d| d.severity == sipha::error::Severity::Error)
90 }
91
92 #[must_use]
93 pub fn is_valid(&self) -> bool {
94 !self.has_errors()
95 }
96}
97
98fn run_pipeline(
100 program_root: &SyntaxNode,
101 store: &ScopeStore,
102 scope_id_sequence: &[ScopeId],
103 options: &WalkOptions,
104) -> (Vec<SemanticDiagnostic>, HashMap<TypeMapKey, Type>) {
105 let mut validator = Validator::new(store, scope_id_sequence);
106 let _ = program_root.walk(&mut validator, options);
107
108 let mut type_checker = TypeChecker::new(store, program_root);
109 let _ = program_root.walk(&mut type_checker, options);
110
111 let mut deprecation_checker = deprecation::DeprecationChecker::new();
112 let _ = program_root.walk(&mut deprecation_checker, options);
113
114 let mut diagnostics = validator.diagnostics;
115 diagnostics.extend(type_checker.diagnostics);
116 diagnostics.extend(deprecation_checker.diagnostics);
117
118 (diagnostics, type_checker.type_map)
119}
120
121#[must_use]
126pub fn analyze(root: &SyntaxNode) -> AnalysisResult {
127 let options = WalkOptions::nodes_only();
128 let mut builder = ScopeBuilder::new();
129 let _ = root.walk(&mut builder, &options);
130
131 for name in builtins::BUILTIN_CLASS_NAMES {
132 builder
133 .store
134 .add_root_class((*name).to_string(), Span::new(0, 0));
135 }
136
137 let (diagnostics, type_map) =
138 run_pipeline(root, &builder.store, &builder.scope_id_sequence, &options);
139
140 AnalysisResult {
141 diagnostics,
142 scope_store: builder.store,
143 type_map,
144 scope_id_sequence: builder.scope_id_sequence,
145 }
146}
147
148pub fn seed_scope_from_signatures(store: &mut ScopeStore, signature_roots: &[SyntaxNode]) {
151 signature_loader::seed_scope_from_signatures(store, signature_roots);
152}
153
154#[must_use]
156pub fn analyze_with_include_tree(
157 tree: &leekscript_core::IncludeTree,
158 signature_roots: &[SyntaxNode],
159) -> AnalysisResult {
160 let program_root = match &tree.root {
161 Some(r) => r.clone(),
162 None => {
163 return AnalysisResult {
164 diagnostics: Vec::new(),
165 scope_store: ScopeStore::new(),
166 type_map: std::collections::HashMap::new(),
167 scope_id_sequence: Vec::new(),
168 };
169 }
170 };
171 let options = WalkOptions::nodes_only();
172 let mut store = ScopeStore::new();
173 seed_scope_from_signatures(&mut store, signature_roots);
174 for (_, child) in &tree.includes {
175 if let Some(ref root) = child.root {
176 seed_scope_from_program(&mut store, root);
177 }
178 }
179 for name in builtins::BUILTIN_CLASS_NAMES {
180 store.add_root_class((*name).to_string(), Span::new(0, 0));
181 }
182 let mut builder = ScopeBuilder::with_store(store);
183 let _ = program_root.walk(&mut builder, &options);
184
185 let mut validator = Validator::new(&builder.store, &builder.scope_id_sequence);
186 let _ = program_root.walk(&mut validator, &options);
187
188 let mut type_checker = TypeChecker::new(&builder.store, &program_root);
189 let _ = program_root.walk(&mut type_checker, &options);
190
191 let mut deprecation_checker = deprecation::DeprecationChecker::new();
192 let _ = program_root.walk(&mut deprecation_checker, &options);
193
194 let mut diagnostics = validator.diagnostics;
195 diagnostics.extend(type_checker.diagnostics);
196 diagnostics.extend(deprecation_checker.diagnostics);
197
198 let type_map = type_checker.type_map.clone();
199 AnalysisResult {
200 diagnostics,
201 scope_store: builder.store,
202 type_map,
203 scope_id_sequence: builder.scope_id_sequence,
204 }
205}
206
207#[must_use]
211pub fn analyze_with_signatures(
212 program_root: &SyntaxNode,
213 signature_roots: &[SyntaxNode],
214) -> AnalysisResult {
215 let options = WalkOptions::nodes_only();
216 let mut store = ScopeStore::new();
217 seed_scope_from_signatures(&mut store, signature_roots);
218 for name in builtins::BUILTIN_CLASS_NAMES {
219 store.add_root_class((*name).to_string(), Span::new(0, 0));
220 }
221 let mut builder = ScopeBuilder::with_store(store);
222 let _ = program_root.walk(&mut builder, &options);
223
224 let (diagnostics, type_map) = run_pipeline(
225 program_root,
226 &builder.store,
227 &builder.scope_id_sequence,
228 &options,
229 );
230
231 AnalysisResult {
232 diagnostics,
233 scope_store: builder.store,
234 type_map,
235 scope_id_sequence: builder.scope_id_sequence,
236 }
237}
238
239#[cfg(test)]
240mod tests;