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