Add type-checking to classes/fields/constructors.
This commit is contained in:
parent
3c0bf948ac
commit
8082f4c2e6
@ -5,6 +5,7 @@ use crate::diagnostic::Diagnostic;
|
||||
use crate::source_range::SourceRange;
|
||||
use crate::symbol::class_symbol::ClassSymbol;
|
||||
use crate::symbol_table::{SymbolInsertError, SymbolTable};
|
||||
use std::collections::HashSet;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct Class {
|
||||
@ -138,10 +139,6 @@ impl Class {
|
||||
}
|
||||
|
||||
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
|
||||
// We need to determine if fields are initialized or not (the latter is an error).
|
||||
// First phase: check all fields, then check constructor, leaving pending those things that
|
||||
// are fields <- initialized by constructor. Then circle back to fields and check all are
|
||||
// initialized
|
||||
let mut diagnostics: Vec<Diagnostic> = vec![];
|
||||
|
||||
self.fields
|
||||
@ -151,7 +148,58 @@ impl Class {
|
||||
.flatten()
|
||||
.for_each(|diagnostic| diagnostics.push(diagnostic));
|
||||
|
||||
// todo check constructor and functions and re-check fields
|
||||
if let Some(constructor) = &mut self.constructor {
|
||||
match constructor.type_check(symbol_table) {
|
||||
Ok(_) => {}
|
||||
Err(mut constructor_diagnostics) => {
|
||||
diagnostics.append(&mut constructor_diagnostics);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.functions
|
||||
.iter_mut()
|
||||
.map(|function| function.type_check(symbol_table))
|
||||
.filter_map(Result::err)
|
||||
.flatten()
|
||||
.for_each(|diagnostic| diagnostics.push(diagnostic));
|
||||
|
||||
// We need to determine if fields are initialized or not (the latter is an error).
|
||||
// First phase: check all fields, then check constructor, leaving pending those things that
|
||||
// are fields <- initialized by constructor. Then circle back to fields and check all are
|
||||
// initialized
|
||||
let mut initialized_field_names: HashSet<&str> = HashSet::new();
|
||||
|
||||
for field in &self.fields {
|
||||
if field.initializer().is_some() {
|
||||
initialized_field_names.insert(field.declared_name());
|
||||
}
|
||||
}
|
||||
|
||||
// check constructor
|
||||
if let Some(constructor) = &self.constructor {
|
||||
for statement in constructor.statements() {
|
||||
match statement {
|
||||
_ => {
|
||||
// no-op for now, because we don't yet have assign statements
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check that all fields are present in the hash set
|
||||
for field in &self.fields {
|
||||
if !initialized_field_names.contains(field.declared_name()) {
|
||||
diagnostics.push(
|
||||
Diagnostic::new(
|
||||
&format!("Field {} is not initialized.", field.declared_name()),
|
||||
field.declared_name_source_range().start(),
|
||||
field.declared_name_source_range().end(),
|
||||
)
|
||||
.with_reporter(file!(), line!()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if diagnostics.is_empty() {
|
||||
Ok(())
|
||||
@ -167,11 +215,11 @@ mod tests {
|
||||
use crate::parser::parse_compilation_unit;
|
||||
|
||||
#[test]
|
||||
fn name_analysis_no_diagnostics() {
|
||||
fn complete_example_no_diagnostics() {
|
||||
let parse_result = parse_compilation_unit(
|
||||
"
|
||||
class Foo
|
||||
mut bar: Int
|
||||
mut bar: Int = 42
|
||||
|
||||
ctor(_bar: Int)
|
||||
end
|
||||
@ -210,5 +258,12 @@ mod tests {
|
||||
panic!("{:?}", diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
match compilation_unit.type_check(&symbol_table) {
|
||||
Ok(_) => {}
|
||||
Err(diagnostics) => {
|
||||
panic!("{:?}", diagnostics);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +30,10 @@ impl Constructor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn statements(&self) -> &[Statement] {
|
||||
&self.statements
|
||||
}
|
||||
|
||||
pub fn gather_declared_names(
|
||||
&mut self,
|
||||
symbol_table: &mut SymbolTable,
|
||||
@ -124,4 +128,32 @@ impl Constructor {
|
||||
Err(statements_diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
|
||||
let parameters_diagnostics: Vec<Diagnostic> = self
|
||||
.parameters
|
||||
.iter_mut()
|
||||
.map(|param| param.type_check(symbol_table))
|
||||
.filter_map(Result::err)
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
if !parameters_diagnostics.is_empty() {
|
||||
return Err(parameters_diagnostics);
|
||||
}
|
||||
|
||||
let statements_diagnostics: Vec<Diagnostic> = self
|
||||
.statements
|
||||
.iter_mut()
|
||||
.map(|statement| statement.type_check(symbol_table, None))
|
||||
.filter_map(Result::err)
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
if statements_diagnostics.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(statements_diagnostics)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,10 +3,8 @@ use crate::ast::ir_builder::IrBuilder;
|
||||
use crate::diagnostic::Diagnostic;
|
||||
use crate::ir::ir_return::IrReturn;
|
||||
use crate::ir::ir_statement::IrStatement;
|
||||
use crate::symbol::function_symbol::FunctionSymbol;
|
||||
use crate::symbol_table::SymbolTable;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use crate::type_info::TypeInfo;
|
||||
|
||||
pub struct ExpressionStatement {
|
||||
expression: Box<Expression>,
|
||||
@ -37,15 +35,13 @@ impl ExpressionStatement {
|
||||
pub fn type_check(
|
||||
&mut self,
|
||||
symbol_table: &SymbolTable,
|
||||
is_last: bool,
|
||||
function_symbol: &Rc<RefCell<FunctionSymbol>>,
|
||||
must_return_type_info: Option<&TypeInfo>,
|
||||
) -> Result<(), Vec<Diagnostic>> {
|
||||
self.expression.type_check(symbol_table)?;
|
||||
|
||||
if is_last {
|
||||
if must_return_type_info.is_some() {
|
||||
let expression_type = self.expression.type_info();
|
||||
let borrowed_symbol = function_symbol.borrow();
|
||||
let return_type = borrowed_symbol.return_type_info();
|
||||
let return_type = must_return_type_info.unwrap();
|
||||
if !return_type.is_assignable_from(expression_type) {
|
||||
return Err(vec![Diagnostic::new(
|
||||
&format!(
|
||||
|
||||
@ -37,6 +37,18 @@ impl Field {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn declared_name(&self) -> &str {
|
||||
&self.declared_name
|
||||
}
|
||||
|
||||
pub fn declared_name_source_range(&self) -> &SourceRange {
|
||||
&self.declared_name_source_range
|
||||
}
|
||||
|
||||
pub fn initializer(&self) -> Option<&Expression> {
|
||||
self.initializer.as_ref().map(Box::as_ref)
|
||||
}
|
||||
|
||||
pub fn gather_declared_names(
|
||||
&mut self,
|
||||
symbol_table: &mut SymbolTable,
|
||||
|
||||
@ -208,6 +208,7 @@ impl Function {
|
||||
);
|
||||
|
||||
let function_symbol = self.function_symbol.as_ref().unwrap();
|
||||
let return_type_info = function_symbol.borrow().return_type_info().clone();
|
||||
let statements_len = self.statements.len();
|
||||
|
||||
// statements
|
||||
@ -218,7 +219,11 @@ impl Function {
|
||||
.enumerate()
|
||||
.map(|(i, statement)| {
|
||||
let is_last = i == statements_len - 1;
|
||||
statement.type_check(symbol_table, is_last, function_symbol)
|
||||
if is_last {
|
||||
statement.type_check(symbol_table, Some(&return_type_info))
|
||||
} else {
|
||||
statement.type_check(symbol_table, None)
|
||||
}
|
||||
})
|
||||
.filter_map(Result::err)
|
||||
.flatten()
|
||||
|
||||
@ -36,7 +36,7 @@ impl Parameter {
|
||||
let insert_result = symbol_table.insert_parameter_symbol(ParameterSymbol::new(
|
||||
&self.declared_name,
|
||||
self.declared_name_source_range.clone(),
|
||||
TypeInfo::from_declared_name(self.type_use.declared_name()),
|
||||
TypeInfo::from_declared_name(self.type_use.declared_name()), // todo: this will blow up if type is a Class
|
||||
));
|
||||
match insert_result {
|
||||
Ok(parameter_symbol) => {
|
||||
|
||||
@ -2,10 +2,8 @@ use crate::ast::expression_statement::ExpressionStatement;
|
||||
use crate::ast::ir_builder::IrBuilder;
|
||||
use crate::ast::let_statement::LetStatement;
|
||||
use crate::diagnostic::Diagnostic;
|
||||
use crate::symbol::function_symbol::FunctionSymbol;
|
||||
use crate::symbol_table::SymbolTable;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use crate::type_info::TypeInfo;
|
||||
|
||||
pub enum Statement {
|
||||
Let(LetStatement),
|
||||
@ -37,13 +35,12 @@ impl Statement {
|
||||
pub fn type_check(
|
||||
&mut self,
|
||||
symbol_table: &SymbolTable,
|
||||
is_last: bool,
|
||||
function_symbol: &Rc<RefCell<FunctionSymbol>>,
|
||||
must_return_type_info: Option<&TypeInfo>,
|
||||
) -> Result<(), Vec<Diagnostic>> {
|
||||
match self {
|
||||
Statement::Let(let_statement) => let_statement.type_check(symbol_table),
|
||||
Statement::Expression(expression_statement) => {
|
||||
expression_statement.type_check(symbol_table, is_last, function_symbol)
|
||||
expression_statement.type_check(symbol_table, must_return_type_info)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ impl TypeUse {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
|
||||
pub fn type_check(&mut self, _symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
|
||||
Ok(()) // no-op, for now
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user