1use std::collections::HashSet;
2use std::fmt;
3
4use lexer::token::Span;
5
6use crate::ast::*;
7
8#[derive(Clone, Debug, Eq, PartialEq)]
9pub struct ValidationError {
10 pub message: String,
11 pub span: Span,
12}
13
14impl fmt::Display for ValidationError {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 write!(f, "{}", self.message)
17 }
18}
19
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21enum CallableKind {
22 Function,
23 Method,
24 Constructor,
25}
26
27struct Validator {
28 scopes: Vec<HashSet<String>>,
29 callable_kinds: Vec<CallableKind>,
30 receiver_available: bool,
31 context: Vec<String>,
32}
33
34pub fn validate_program(
35 program: &Program,
36 predefined_globals: &[&str],
37) -> Result<(), ValidationError> {
38 let mut globals = HashSet::new();
39 globals.extend(predefined_globals.iter().map(|name| (*name).to_string()));
40 let mut validator = Validator {
41 scopes: vec![globals],
42 callable_kinds: Vec::new(),
43 receiver_available: false,
44 context: Vec::new(),
45 };
46 validator.validate_statements(&program.body)
47}
48
49impl Validator {
50 fn validate_statements(&mut self, statements: &[Statement]) -> Result<(), ValidationError> {
51 for statement in statements {
52 self.validate_statement(statement)?;
53 }
54 Ok(())
55 }
56
57 fn validate_statement(&mut self, statement: &Statement) -> Result<(), ValidationError> {
58 match statement {
59 Statement::Let(statement) => {
60 self.validate_expression(&statement.expr)?;
61 self.scopes
62 .last_mut()
63 .unwrap()
64 .insert(statement.identifier.name.clone());
65 Ok(())
66 }
67 Statement::Return(statement) => {
68 if self.callable_kinds.last() == Some(&CallableKind::Constructor) {
69 return Err(ValidationError {
70 message: "constructor cannot return a value".to_string(),
71 span: statement.span.clone(),
72 });
73 }
74 self.validate_expression(&statement.argument)
75 }
76 Statement::Class(class) => self.validate_class(class),
77 Statement::SetProperty(statement) => {
78 self.validate_expression(&statement.object)?;
79 self.validate_expression(&statement.value)
80 }
81 Statement::Debugger(_) => Ok(()),
82 Statement::Expr(expression) => self.validate_expression(expression),
83 }
84 }
85
86 fn validate_class(&mut self, class: &ClassDeclaration) -> Result<(), ValidationError> {
87 self.scopes
88 .last_mut()
89 .unwrap()
90 .insert(class.name.name.clone());
91 self.context.push(format!("class {}", class.name.name));
92 for method in &class.methods {
93 self.validate_method(method)?;
94 }
95 self.context.pop();
96 Ok(())
97 }
98
99 fn validate_method(&mut self, method: &MethodDefinition) -> Result<(), ValidationError> {
100 let callable_kind = match method.kind {
101 MethodKind::Constructor => CallableKind::Constructor,
102 MethodKind::Method => CallableKind::Method,
103 };
104 self.context.push(method.name.name.clone());
105 self.callable_kinds.push(callable_kind);
106 let old_receiver_available = self.receiver_available;
107 self.receiver_available = true;
108 self.scopes.push(
109 method
110 .params
111 .iter()
112 .map(|parameter| parameter.identifier.name.clone())
113 .collect(),
114 );
115
116 let result = self.validate_statements(&method.body.body);
117
118 self.scopes.pop();
119 self.receiver_available = old_receiver_available;
120 self.callable_kinds.pop();
121 self.context.pop();
122 result
123 }
124
125 fn validate_function(&mut self, function: &FunctionDeclaration) -> Result<(), ValidationError> {
126 self.callable_kinds.push(CallableKind::Function);
127 let mut scope = function
128 .params
129 .iter()
130 .map(|parameter| parameter.identifier.name.clone())
131 .collect::<HashSet<_>>();
132 if !function.name.is_empty() {
133 scope.insert(function.name.clone());
138 }
139 self.scopes.push(scope);
140 let result = self.validate_statements(&function.body.body);
141 self.scopes.pop();
142 self.callable_kinds.pop();
143 result
144 }
145
146 fn validate_expression(&mut self, expression: &Expression) -> Result<(), ValidationError> {
147 match expression {
148 Expression::IDENTIFIER(identifier) => self.validate_identifier(identifier),
149 Expression::LITERAL(literal) => self.validate_literal(literal),
150 Expression::PREFIX(expression) => self.validate_expression(&expression.operand),
151 Expression::INFIX(expression) => {
152 self.validate_expression(&expression.left)?;
153 self.validate_expression(&expression.right)
154 }
155 Expression::IF(expression) => {
156 self.validate_expression(&expression.condition)?;
157 self.validate_statements(&expression.consequent.body)?;
158 if let Some(alternate) = &expression.alternate {
159 self.validate_statements(&alternate.body)?;
160 }
161 Ok(())
162 }
163 Expression::FUNCTION(function) => self.validate_function(function),
164 Expression::FunctionCall(call) => {
165 self.validate_expression(&call.callee)?;
166 self.validate_expressions(&call.arguments)
167 }
168 Expression::Index(index) => {
169 self.validate_expression(&index.object)?;
170 self.validate_expression(&index.index)
171 }
172 Expression::This(this) => {
173 if self.receiver_available {
174 Ok(())
175 } else {
176 Err(ValidationError {
177 message: "this is only available inside a method".to_string(),
178 span: this.span.clone(),
179 })
180 }
181 }
182 Expression::Property(property) => self.validate_expression(&property.object),
183 Expression::New(new_expression) => {
184 self.validate_identifier(&new_expression.callee)?;
185 self.validate_expressions(&new_expression.arguments)
186 }
187 }
188 }
189
190 fn validate_literal(&mut self, literal: &Literal) -> Result<(), ValidationError> {
191 match literal {
192 Literal::Array(array) => self.validate_expressions(&array.elements),
193 Literal::Hash(hash) => {
194 for (key, value) in &hash.elements {
195 self.validate_expression(key)?;
196 self.validate_expression(value)?;
197 }
198 Ok(())
199 }
200 _ => Ok(()),
201 }
202 }
203
204 fn validate_expressions(&mut self, expressions: &[Expression]) -> Result<(), ValidationError> {
205 for expression in expressions {
206 self.validate_expression(expression)?;
207 }
208 Ok(())
209 }
210
211 fn validate_identifier(&self, identifier: &IDENTIFIER) -> Result<(), ValidationError> {
212 if self
213 .scopes
214 .iter()
215 .rev()
216 .any(|scope| scope.contains(&identifier.name))
217 {
218 return Ok(());
219 }
220
221 let context = if self.context.is_empty() {
222 String::new()
223 } else {
224 format!(" in {}", self.context.join("."))
225 };
226 Err(ValidationError {
227 message: format!("undefined variable '{}'{}", identifier.name, context),
228 span: identifier.span.clone(),
229 })
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236 use crate::{ast::Node, parse};
237
238 fn validate(input: &str) -> Result<(), ValidationError> {
239 let Node::Program(program) = parse(input).unwrap() else { panic!("expected program") };
240 validate_program(&program, &["len"])
241 }
242
243 #[test]
244 fn validates_source_order_constructor_return_and_lexical_this() {
245 validate(
246 r#"class Box {
247 constructor(value) { this.value = value; }
248 reader() { fn() { fn() { this.value } } }
249}
250let box = new Box(1);"#,
251 )
252 .unwrap();
253
254 assert!(validate("this")
255 .unwrap_err()
256 .message
257 .contains("only available"));
258 assert!(validate("let f = fn() { this };")
259 .unwrap_err()
260 .message
261 .contains("only available"));
262 assert!(validate("class A { constructor() { return 1; } }")
263 .unwrap_err()
264 .message
265 .contains("cannot return"));
266 assert!(validate("class A { make() { new B(); } } class B {}")
267 .unwrap_err()
268 .message
269 .contains("undefined variable 'B'"));
270 validate("class A { make() { new A(); } }").unwrap();
271 validate("len([])").unwrap();
272 }
273
274 #[test]
275 fn let_initializer_sees_only_previous_bindings_and_named_function_self() {
276 assert!(validate("let x = x;")
277 .unwrap_err()
278 .message
279 .contains("undefined variable 'x'"));
280
281 validate("let x = 1; let x = x + 1; x;").unwrap();
282 validate("let f = fn(n) { if (n == 0) { 0 } else { f(n - 1) } }; f(1);").unwrap();
283
284 assert!(validate("let f = if (true) { fn() { f(); } };")
285 .unwrap_err()
286 .message
287 .contains("undefined variable 'f'"));
288 }
289}