use std::collections::{HashMap, HashSet};
use crate::{
ast::{Expr, Literal, ObjectProperty},
lexer::Token,
};
use super::Type;
#[derive(Debug)]
pub(crate) struct ClassField<'gc> {
pub name: Token<'gc>,
pub ty: Type<'gc>,
pub required: bool,
}
#[derive(Debug)]
pub(crate) struct ClassInfo<'gc> {
pub fields: Vec<ClassField<'gc>>,
}
#[derive(Debug)]
pub(crate) enum ValidationError<'gc> {
ClassNotFound(Token<'gc>),
MissingFields(Token<'gc>, Vec<&'gc str>),
InvalidField(Token<'gc>, Token<'gc>), DuplicateField(Token<'gc>, Token<'gc>), TypeError {
class_token: Token<'gc>,
field_token: Token<'gc>,
expected_type: Type<'gc>,
},
ComputedPropertyError(Token<'gc>), }
#[derive(Debug)]
pub(crate) struct TypeResolver<'gc> {
defined_types: HashMap<&'gc str, Type<'gc>>,
pending_validations: Vec<Token<'gc>>,
class_info: HashMap<&'gc str, ClassInfo<'gc>>,
}
impl Default for TypeResolver<'_> {
fn default() -> Self {
Self::new()
}
}
impl<'gc> TypeResolver<'gc> {
pub fn new() -> Self {
let mut resolver = Self {
defined_types: HashMap::new(),
pending_validations: Vec::new(),
class_info: HashMap::new(),
};
resolver.defined_types.insert("int", Type::Int);
resolver.defined_types.insert("str", Type::Str);
resolver.defined_types.insert("bool", Type::Bool);
resolver.defined_types.insert("float", Type::Float);
resolver
}
pub fn register_class(&mut self, name: Token<'gc>) {
self.class_info
.insert(name.lexeme, ClassInfo { fields: Vec::new() });
}
pub fn add_class_field(&mut self, class_name: &'gc str, field: ClassField<'gc>) {
if let Some(info) = self.class_info.get_mut(class_name) {
info.fields.push(field);
}
}
pub fn check_class(&mut self, token: &Token<'gc>) -> bool {
self.class_info.contains_key(token.lexeme)
}
pub fn add_type_usage(&mut self, token: Token<'gc>) {
self.pending_validations.push(token);
}
pub fn validate_all_types<F>(&self, mut f: F)
where
F: FnMut(Token<'gc>, String),
{
for token in &self.pending_validations {
let ty = Type::from_token(*token);
if let Err(err) = self.validate_type(ty) {
f(*token, err);
}
}
}
pub fn register_type(&mut self, name: &'gc str, typ: Type<'gc>) {
self.defined_types.insert(name, typ);
}
fn resolve_type(&self, typ: Type<'gc>) -> Option<Type<'gc>> {
match typ {
Type::Custom(token) => self.defined_types.get(token.lexeme).copied(),
_ => Some(typ), }
}
pub fn validate_type(&self, typ: Type<'gc>) -> Result<(), String> {
match self.resolve_type(typ) {
Some(_) => Ok(()),
None => match typ {
Type::Custom(token) => Err(format!("Undefined type '{}'.", token.lexeme)),
_ => Ok(()),
},
}
}
fn check_type(&self, expr: &Expr<'gc>, expected_type: Type<'gc>) -> Result<(), String> {
match expr {
Expr::Literal { value, .. } => {
match (value, expected_type) {
(Literal::String(_), Type::Str) |
(Literal::Number(_), Type::Int) |
(Literal::Number(_), Type::Float)|
(Literal::Boolean(_), Type::Bool) |
(Literal::Nil, _) => Ok(()),
_ => Err(format!("Type mismatch: expected {:?}", expected_type)),
}
}
Expr::Variable { .. } => Ok(()),
_ => Ok(()),
}
}
pub fn validate_object_literal(
&self,
class_name: Token<'gc>,
properties: &[ObjectProperty<'gc>],
) -> Result<(), Vec<ValidationError<'gc>>> {
let class_info = self
.class_info
.get(class_name.lexeme)
.ok_or_else(|| vec![ValidationError::ClassNotFound(class_name)])?;
let mut errors = Vec::new();
let mut provided_fields = HashSet::new();
for prop in properties {
match prop {
ObjectProperty::Literal { key, value } => {
if let Some(field) = class_info
.fields
.iter()
.find(|f| f.name.lexeme == key.lexeme)
{
if !provided_fields.insert(key.lexeme) {
errors.push(ValidationError::DuplicateField(class_name, *key));
continue;
}
if self.check_type(value, field.ty).is_err() {
errors.push(ValidationError::TypeError {
class_token: class_name,
field_token: *key,
expected_type: field.ty,
});
}
} else {
errors.push(ValidationError::InvalidField(class_name, *key));
}
provided_fields.insert(key.lexeme);
}
ObjectProperty::Computed { .. } => {
errors.push(ValidationError::ComputedPropertyError(class_name));
}
}
}
let missing_fields: Vec<_> = class_info
.fields
.iter()
.filter(|field| field.required && !provided_fields.contains(field.name.lexeme))
.map(|field| field.name.lexeme)
.collect();
if !missing_fields.is_empty() {
errors.push(ValidationError::MissingFields(class_name, missing_fields));
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}