1#![warn(missing_docs)]
2
3use nargo_types::{NargoValue, Result};
8use std::collections::HashMap;
9
10use crate::expr::JsExpr;
11use crate::program::{IRModule, JsProgram};
12use crate::stmt::JsStmt;
13use crate::types::IRError;
14
15#[derive(Debug, Clone, PartialEq)]
19pub enum TypeInfo {
20 Number,
22 String,
24 Boolean,
26 Object,
28 Array,
30 Function,
32 Undefined,
34 Null,
36 Any,
38}
39
40pub struct TypeEnvironment {
42 variables: HashMap<String, TypeInfo>,
44}
45
46impl TypeEnvironment {
47 pub fn new() -> Self {
49 Self { variables: HashMap::new() }
50 }
51
52 pub fn add_variable(&mut self, name: String, type_info: TypeInfo) {
54 self.variables.insert(name, type_info);
55 }
56
57 pub fn get_variable(&self, name: &String) -> Option<&TypeInfo> {
59 self.variables.get(name)
60 }
61}
62
63pub struct ExprValidator {
65 env: TypeEnvironment,
67}
68
69impl ExprValidator {
70 pub fn new() -> Self {
72 Self { env: TypeEnvironment::new() }
73 }
74
75 pub fn validate(&mut self, expr: &JsExpr) -> Result<TypeInfo> {
77 match expr {
78 JsExpr::Identifier(id, _span, _) => {
79 if let Some(type_info) = self.env.get_variable(id) {
80 Ok(type_info.clone())
81 }
82 else {
83 Err(IRError::InvalidExpression(format!("Undefined variable: {}", id)).into())
84 }
85 }
86 JsExpr::Literal(value, _, _) => Ok(match value {
87 NargoValue::Number(_) => TypeInfo::Number,
88 NargoValue::String(_) => TypeInfo::String,
89 NargoValue::Bool(_) => TypeInfo::Boolean,
90 NargoValue::Object(_) => TypeInfo::Object,
91 NargoValue::Array(_) => TypeInfo::Array,
92 NargoValue::Null => TypeInfo::Null,
93 _ => TypeInfo::Any,
94 }),
95 JsExpr::Unary { op, argument, span: _, .. } => {
96 let arg_type = self.validate(argument)?;
97 match op.as_str() {
98 "!" => Ok(TypeInfo::Boolean),
99 "-" | "+" | "~" => {
100 if matches!(arg_type, TypeInfo::Number) {
101 Ok(TypeInfo::Number)
102 }
103 else {
104 Err(IRError::InvalidExpression(format!("Unary operator {} requires number type", op)).into())
105 }
106 }
107 _ => Ok(TypeInfo::Any),
108 }
109 }
110 JsExpr::Binary { left, op, right, span: _, .. } => {
111 let left_type = self.validate(left)?;
112 let right_type = self.validate(right)?;
113 match op.as_str() {
114 "+" => {
115 if matches!(left_type, TypeInfo::Number) && matches!(right_type, TypeInfo::Number) {
116 Ok(TypeInfo::Number)
117 }
118 else if matches!(left_type, TypeInfo::String) || matches!(right_type, TypeInfo::String) {
119 Ok(TypeInfo::String)
120 }
121 else {
122 Err(IRError::InvalidExpression(format!("Addition operator requires number or string types").to_string()).into())
123 }
124 }
125 "-" | "*" | "/" | "%" => {
126 if matches!(left_type, TypeInfo::Number) && matches!(right_type, TypeInfo::Number) {
127 Ok(TypeInfo::Number)
128 }
129 else {
130 Err(IRError::InvalidExpression(format!("Arithmetic operator {} requires number types", op)).into())
131 }
132 }
133 "==" | "!=" | "===" | "!==" => Ok(TypeInfo::Boolean),
134 "<" | "<=" | ">" | ">=" => {
135 if matches!(left_type, TypeInfo::Number) && matches!(right_type, TypeInfo::Number) {
136 Ok(TypeInfo::Boolean)
137 }
138 else if matches!(left_type, TypeInfo::String) && matches!(right_type, TypeInfo::String) {
139 Ok(TypeInfo::Boolean)
140 }
141 else {
142 Err(IRError::InvalidExpression(format!("Comparison operator {} requires number or string types", op)).into())
143 }
144 }
145 "&&" | "||" => Ok(TypeInfo::Boolean),
146 "&" | "|" | "^" | "<<" | ">>" | ">>>" => {
147 if matches!(left_type, TypeInfo::Number) && matches!(right_type, TypeInfo::Number) {
148 Ok(TypeInfo::Number)
149 }
150 else {
151 Err(IRError::InvalidExpression(format!("Bitwise operator {} requires number types", op)).into())
152 }
153 }
154 "=" | "+=" | "-=" | "*=" | "/=" | "%=" | "&=" | "|=" | "^=" | "<<=" | ">>=" | ">>>=" => Ok(left_type),
155 _ => Ok(TypeInfo::Any),
156 }
157 }
158 JsExpr::Call { callee, args, span: _, .. } => {
159 let callee_type = self.validate(callee)?;
160 if matches!(callee_type, TypeInfo::Function) {
161 for arg in args {
163 self.validate(arg)?;
164 }
165 Ok(TypeInfo::Any)
166 }
167 else {
168 Err(IRError::InvalidExpression("Callee must be a function".to_string()).into())
169 }
170 }
171 JsExpr::Member { object, property, .. } => {
172 self.validate(object)?;
173 self.validate(property)?;
174 Ok(TypeInfo::Any)
175 }
176 JsExpr::OptionalMember { object, property, .. } => {
177 self.validate(object)?;
178 self.validate(property)?;
179 Ok(TypeInfo::Any)
180 }
181 JsExpr::OptionalCall { callee, args, .. } => {
182 self.validate(callee)?;
183 for arg in args {
184 self.validate(arg)?;
185 }
186 Ok(TypeInfo::Any)
187 }
188 JsExpr::NullishCoalescing { left, right, .. } => {
189 self.validate(left)?;
190 self.validate(right)?;
191 Ok(TypeInfo::Any)
192 }
193 JsExpr::LogicalAssignment { left, right, .. } => {
194 self.validate(left)?;
195 self.validate(right)?;
196 Ok(TypeInfo::Any)
197 }
198 JsExpr::Array(items, ..) => {
199 for item in items {
200 self.validate(item)?;
201 }
202 Ok(TypeInfo::Array)
203 }
204 JsExpr::Object(props, ..) => {
205 for (_, value) in props {
206 self.validate(value)?;
207 }
208 Ok(TypeInfo::Object)
209 }
210 JsExpr::ArrowFunction { params, body, .. } => {
211 for param in params {
213 self.env.add_variable(param.clone(), TypeInfo::Any);
214 }
215 self.validate(body)?;
216 Ok(TypeInfo::Function)
217 }
218 JsExpr::TseElement { children, .. } => {
219 for child in children {
220 self.validate(child)?;
221 }
222 Ok(TypeInfo::Any)
223 }
224 JsExpr::Conditional { test, consequent, alternate, .. } => {
225 let test_type = self.validate(test)?;
226 if !matches!(test_type, TypeInfo::Boolean) {
227 return Err(IRError::InvalidExpression("Condition must be a boolean".to_string()).into());
228 }
229 let _consequent_type = self.validate(consequent)?;
230 let _alternate_type = self.validate(alternate)?;
231 Ok(TypeInfo::Any)
233 }
234 JsExpr::TemplateLiteral { expressions, .. } => {
235 for expr in expressions {
236 self.validate(expr)?;
237 }
238 Ok(TypeInfo::String)
239 }
240 JsExpr::Spread(expr, ..) => {
241 self.validate(expr)?;
242 Ok(TypeInfo::Any)
243 }
244 JsExpr::TypeOf(expr, ..) => {
245 self.validate(expr)?;
246 Ok(TypeInfo::String)
247 }
248 JsExpr::InstanceOf { left, right, .. } => {
249 self.validate(left)?;
250 self.validate(right)?;
251 Ok(TypeInfo::Boolean)
252 }
253 JsExpr::Other(_, _, _) => Ok(TypeInfo::Any),
254 }
255 }
256}
257
258pub struct StmtValidator {
260 expr_validator: ExprValidator,
262}
263
264impl StmtValidator {
265 pub fn new() -> Self {
267 Self { expr_validator: ExprValidator::new() }
268 }
269
270 pub fn validate(&mut self, stmt: &JsStmt) -> Result<()> {
272 match stmt {
273 JsStmt::Expr(expr, ..) => {
274 self.expr_validator.validate(expr)?;
275 Ok(())
276 }
277 JsStmt::VariableDecl { kind: _, id, init, .. } => {
278 if let Some(expr) = init {
279 let type_info = self.expr_validator.validate(expr)?;
280 self.expr_validator.env.add_variable(id.clone(), type_info);
281 }
282 else {
283 self.expr_validator.env.add_variable(id.clone(), TypeInfo::Any);
285 }
286 Ok(())
287 }
288 JsStmt::Import { .. } => Ok(()),
289 JsStmt::Export { declaration, .. } => self.validate(declaration),
290 JsStmt::ExportAll { .. } => Ok(()),
291 JsStmt::ExportNamed { .. } => Ok(()),
292 JsStmt::FunctionDecl { id, params, body, .. } => {
293 self.expr_validator.env.add_variable(id.clone(), TypeInfo::Function);
295 for param in params {
297 self.expr_validator.env.add_variable(param.clone(), TypeInfo::Any);
298 }
299 for stmt in body {
301 self.validate(stmt)?;
302 }
303 Ok(())
304 }
305 JsStmt::Return(expr, ..) => {
306 if let Some(expr) = expr {
307 self.expr_validator.validate(expr)?;
308 }
309 Ok(())
310 }
311 JsStmt::If { test, consequent, alternate, .. } => {
312 let test_type = self.expr_validator.validate(test)?;
313 if !matches!(test_type, TypeInfo::Boolean) {
314 return Err(IRError::InvalidExpression("Condition must be a boolean".to_string()).into());
315 }
316 self.validate(consequent)?;
317 if let Some(alt) = alternate {
318 self.validate(alt)?;
319 }
320 Ok(())
321 }
322 JsStmt::While { test, body, .. } => {
323 let test_type = self.expr_validator.validate(test)?;
324 if !matches!(test_type, TypeInfo::Boolean) {
325 return Err(IRError::InvalidExpression("Condition must be a boolean".to_string()).into());
326 }
327 self.validate(body)?;
328 Ok(())
329 }
330 JsStmt::For { init, test, update, body, .. } => {
331 if let Some(init_stmt) = init {
332 self.validate(init_stmt)?;
333 }
334 if let Some(test_expr) = test {
335 let test_type = self.expr_validator.validate(test_expr)?;
336 if !matches!(test_type, TypeInfo::Boolean) {
337 return Err(IRError::InvalidExpression("Condition must be a boolean".to_string()).into());
338 }
339 }
340 if let Some(update_expr) = update {
341 self.expr_validator.validate(update_expr)?;
342 }
343 self.validate(body)?;
344 Ok(())
345 }
346 JsStmt::ForIn { left, right, body, .. } => {
347 self.validate(left)?;
348 self.expr_validator.validate(right)?;
349 self.validate(body)?;
350 Ok(())
351 }
352 JsStmt::ForOf { left, right, body, .. } => {
353 self.validate(left)?;
354 self.expr_validator.validate(right)?;
355 self.validate(body)?;
356 Ok(())
357 }
358 JsStmt::Try { block, handler, finalizer, .. } => {
359 self.validate(block)?;
360 if let Some((id, body)) = handler {
361 self.expr_validator.env.add_variable(id.clone(), TypeInfo::Any);
363 self.validate(body)?;
364 }
365 if let Some(body) = finalizer {
366 self.validate(body)?;
367 }
368 Ok(())
369 }
370 JsStmt::Switch { discriminant, cases, .. } => {
371 self.expr_validator.validate(discriminant)?;
372 for (test, stmts) in cases {
373 if let Some(test_expr) = test {
374 self.expr_validator.validate(test_expr)?;
375 }
376 for stmt in stmts {
377 self.validate(stmt)?;
378 }
379 }
380 Ok(())
381 }
382 JsStmt::Throw(expr, ..) => {
383 self.expr_validator.validate(expr)?;
384 Ok(())
385 }
386 JsStmt::Block(stmts, ..) => {
387 for stmt in stmts {
388 self.validate(stmt)?;
389 }
390 Ok(())
391 }
392 JsStmt::Break(_, _) => Ok(()),
393 JsStmt::Continue(_, _) => Ok(()),
394 JsStmt::Other(_, _, _) => Ok(()),
395 }
396 }
397}
398
399pub struct ProgramValidator {
401 stmt_validator: StmtValidator,
403}
404
405impl ProgramValidator {
406 pub fn new() -> Self {
408 Self { stmt_validator: StmtValidator::new() }
409 }
410
411 pub fn validate(&mut self, program: &JsProgram) -> Result<()> {
413 for stmt in &program.body {
414 self.stmt_validator.validate(stmt)?;
415 }
416 Ok(())
417 }
418}
419
420pub struct IRValidator {
422 program_validator: ProgramValidator,
424}
425
426impl IRValidator {
427 pub fn new() -> Self {
429 Self { program_validator: ProgramValidator::new() }
430 }
431
432 pub fn validate(&mut self, module: &IRModule) -> Result<()> {
434 module.validate()?;
436
437 if let Some(script) = &module.script {
439 self.program_validator.validate(script)?;
440 }
441 if let Some(script_server) = &module.script_server {
442 self.program_validator.validate(script_server)?;
443 }
444 if let Some(script_client) = &module.script_client {
445 self.program_validator.validate(script_client)?;
446 }
447
448 Ok(())
449 }
450}