use crate::model::*;
use num_bigint::BigUint;
use num_traits::One;
use std::collections::HashSet;
#[derive(Debug)]
pub enum TypeCheckError {
TypeNotDefined(QualifiedName),
TypeAddedInNewerVersion(QualifiedName, BigUint),
CouldNotFindLastVersion(QualifiedName),
ArityMismatch(usize, usize),
TypeNotFinal(QualifiedName),
DuplicateLiteral(QualifiedName),
}
struct TypeCheck<'model> {
model: &'model Verilization,
scope: Scope<'model>,
}
impl <'model> TypeCheck<'model> {
fn check_type(&self, version: &BigUint, t: &Type) -> Result<(), TypeCheckError> {
match self.scope.lookup(t.name.clone()) {
ScopeLookup::NamedType(name) => {
let named_type_def = match self.model.get_type(&name) {
Some(t) => t,
None => return Err(TypeCheckError::TypeNotDefined(name)),
};
if !named_type_def.has_version(version) {
return Err(TypeCheckError::TypeAddedInNewerVersion(name, version.clone()));
}
let arity = named_type_def.arity();
if arity != t.args.len() {
return Err(TypeCheckError::ArityMismatch(arity, t.args.len()));
}
for arg in &t.args {
self.check_type(version, &arg)?;
}
Ok(())
},
ScopeLookup::TypeParameter(_) => {
if t.args.len() != 0 {
return Err(TypeCheckError::ArityMismatch(0, t.args.len()));
}
Ok(())
}
}
}
fn check_is_final(&self, t: &Type, version: &BigUint) -> Result<bool, TypeCheckError> {
Ok(match self.scope.lookup(t.name.clone()) {
ScopeLookup::NamedType(name) => {
match self.model.get_type(&name).ok_or_else(|| TypeCheckError::TypeNotDefined(name.clone()))? {
NamedTypeDefinition::StructType(type_def) | NamedTypeDefinition::EnumType(type_def) => {
if !type_def.is_final() {
return Ok(false);
}
if !(type_def.last_explicit_version().ok_or_else(|| TypeCheckError::CouldNotFindLastVersion(name.clone()))? <= version) {
return Ok(false);
}
},
NamedTypeDefinition::ExternType(_) => (),
}
for arg in &t.args {
if !self.check_is_final(&arg, version)? {
return Ok(false);
}
}
true
},
ScopeLookup::TypeParameter(_) => true,
})
}
}
fn type_check_versioned_type<'model>(model: &'model Verilization, t: Named<'model, VersionedTypeDefinitionData>) -> Result<(), TypeCheckError> {
let tc = TypeCheck {
model: model,
scope: t.scope(),
};
for ver in t.versions() {
for (_, field) in ver.ver_type.fields() {
tc.check_type(&ver.version, &field.field_type)?;
}
}
if t.is_final() {
if let Some(last_ver) = t.versions().last() {
for (_, field) in last_ver.ver_type.fields() {
if !tc.check_is_final(&field.field_type, &last_ver.version)? {
return Err(TypeCheckError::TypeNotFinal(t.name().clone()))
}
}
}
}
Ok(())
}
fn type_check_extern_type<'model>(model: &'model Verilization, t: Named<'model, ExternTypeDefinitionData>) -> Result<(), TypeCheckError> {
let tc = TypeCheck {
model: model,
scope: t.scope(),
};
let mut has_integer = false;
let mut has_string = false;
let mut has_sequence = false;
let mut literal_cases = HashSet::new();
let mut has_record = false;
for literal in t.literals() {
match literal {
ExternLiteralSpecifier::Integer(_, _, _, _) if has_integer => return Err(TypeCheckError::DuplicateLiteral(t.name().clone())),
ExternLiteralSpecifier::Integer(_, _, _, _) => has_integer = true,
ExternLiteralSpecifier::String if has_string => return Err(TypeCheckError::DuplicateLiteral(t.name().clone())),
ExternLiteralSpecifier::String => has_string = true,
ExternLiteralSpecifier::Sequence(_) if has_sequence => return Err(TypeCheckError::DuplicateLiteral(t.name().clone())),
ExternLiteralSpecifier::Sequence(inner) => {
has_sequence = true;
tc.check_type(&BigUint::one(), inner)?;
},
ExternLiteralSpecifier::Case(name, params) => {
if !literal_cases.insert(name) {
return Err(TypeCheckError::DuplicateLiteral(t.name().clone()));
}
for param in params {
tc.check_type(&BigUint::one(), param)?;
}
},
ExternLiteralSpecifier::Record(_) if has_record => return Err(TypeCheckError::DuplicateLiteral(t.name().clone())),
ExternLiteralSpecifier::Record(fields) => {
has_record = true;
for (_, field) in fields {
tc.check_type(&BigUint::one(), &field.field_type)?;
}
},
}
}
Ok(())
}
pub fn type_check_verilization(model: &Verilization) -> Result<(), TypeCheckError> {
for t in model.types() {
match t {
NamedTypeDefinition::StructType(t) | NamedTypeDefinition::EnumType(t) => type_check_versioned_type(model, t)?,
NamedTypeDefinition::ExternType(t) => type_check_extern_type(model, t)?,
}
}
Ok(())
}