WIP type analysis.

This commit is contained in:
Jesse Brault 2026-09-23 16:36:51 -05:00
parent dae482ef78
commit 3535fed61d
4 changed files with 114 additions and 24 deletions

View File

@ -64,7 +64,7 @@ pub fn lower_to_ir_synthetic_function(
lower_to_ir_statement(statement, &session.ctx, &mut fn_ctx, true); lower_to_ir_statement(statement, &session.ctx, &mut fn_ctx, true);
fn_ctx.finish_block(); fn_ctx.finish_block();
// infer return type from statement // check return type from statement
let maybe_return_ir_type_info = match statement { let maybe_return_ir_type_info = match statement {
Statement::Let(_) | Statement::Assign(_) => None, Statement::Let(_) | Statement::Assign(_) => None,
Statement::Expression(expression_statement) => { Statement::Expression(expression_statement) => {

View File

@ -1,5 +1,6 @@
pub mod helpers; pub mod helpers;
use crate::ast::assign_statement::AssignStatement;
use crate::ast::binary_expression::{BinaryExpression, BinaryOperation}; use crate::ast::binary_expression::{BinaryExpression, BinaryOperation};
use crate::ast::call::Call; use crate::ast::call::Call;
use crate::ast::class::Class; use crate::ast::class::Class;
@ -13,26 +14,29 @@ use crate::diagnostic::{Diagnostic, Diagnostics};
use crate::error_codes::BINARY_INCOMPATIBLE_TYPES; use crate::error_codes::BINARY_INCOMPATIBLE_TYPES;
use crate::semantic_analysis::analysis_context::AnalysisContext; use crate::semantic_analysis::analysis_context::AnalysisContext;
use crate::semantic_analysis::analysis_context::helpers::get_name_for_type; use crate::semantic_analysis::analysis_context::helpers::get_name_for_type;
use crate::semantic_analysis::type_analysis::infer::helpers::{ use crate::semantic_analysis::type_analysis::check::helpers::{
are_binary_op_compatible, binary_op_result, is_negative_op_compatible, negative_op_result, are_binary_op_compatible, binary_op_result, is_negative_op_compatible, negative_op_result,
}; };
use crate::semantic_analysis::type_info::{TypeInfo, TypeInfoId}; use crate::semantic_analysis::type_info::{TypeInfo, TypeInfoId};
pub fn infer_types(compilation_unit: &CompilationUnit, ctx: &mut AnalysisContext) -> Diagnostics { pub fn check_types(compilation_unit: &CompilationUnit, ctx: &mut AnalysisContext) -> Diagnostics {
let mut diagnostics = Diagnostics::new(); let mut diagnostics = Diagnostics::new();
for function in compilation_unit.functions() {
check_types_function(function, ctx, &mut diagnostics);
}
for class in compilation_unit.classes() { for class in compilation_unit.classes() {
infer_types_class(class, ctx, &mut diagnostics); check_types_class(class, ctx, &mut diagnostics);
} }
diagnostics diagnostics
} }
fn infer_types_class(class: &Class, ctx: &mut AnalysisContext, diagnostics: &mut Diagnostics) { fn check_types_class(class: &Class, ctx: &mut AnalysisContext, diagnostics: &mut Diagnostics) {
for function in class.functions() { for function in class.functions() {
infer_types_function(function, ctx, diagnostics); check_types_function(function, ctx, diagnostics);
} }
} }
fn infer_types_function( fn check_types_function(
function: &Function, function: &Function,
ctx: &mut AnalysisContext, ctx: &mut AnalysisContext,
diagnostics: &mut Diagnostics, diagnostics: &mut Diagnostics,
@ -40,31 +44,117 @@ fn infer_types_function(
for statement in function.statements() { for statement in function.statements() {
match statement { match statement {
Statement::Let(let_statement) => { Statement::Let(let_statement) => {
infer_types_let_statement(let_statement, ctx, diagnostics); check_types_let_statement(let_statement, ctx, diagnostics);
}
Statement::Expression(expression_statement) => {
resolve_types_expression(expression_statement.expression(), ctx, diagnostics);
}
Statement::Assign(assign_statement) => {
check_types_assign_statement(assign_statement, ctx, diagnostics);
} }
Statement::Expression(expression_statement) => {}
Statement::Assign(assign_statement) => {}
} }
} }
} }
fn infer_types_let_statement( fn check_types_let_statement(
let_statement: &LetStatement, let_statement: &LetStatement,
ctx: &mut AnalysisContext, ctx: &mut AnalysisContext,
diagnostics: &mut Diagnostics, diagnostics: &mut Diagnostics,
) { ) {
let initializer_type = infer_types_expression(let_statement.initializer(), ctx, diagnostics); let initializer_type = resolve_types_expression(let_statement.initializer(), ctx, diagnostics);
// todo set symbol's type to the initializer type let symbol_id = ctx
.get_symbol_id(let_statement.node_id())
.unwrap_or_else(|| {
panic!(
"LetStatement {} has no associated symbol",
let_statement.node_id()
)
});
ctx.associate_symbol_to_type(symbol_id, initializer_type);
} }
fn infer_types_expression( fn check_types_assign_statement(
assign_statement: &AssignStatement,
ctx: &mut AnalysisContext,
diagnostics: &mut Diagnostics,
) {
let destination_type_info_id =
resolve_types_expression(assign_statement.destination(), ctx, diagnostics);
let value_type_info_id = resolve_types_expression(assign_statement.value(), ctx, diagnostics);
let destination_type_info = ctx.get_type_info_by_id(destination_type_info_id);
let value_type_info = ctx.get_type_info_by_id(value_type_info_id);
match destination_type_info {
TypeInfo::Any
| TypeInfo::Class(_)
| TypeInfo::Constructor(_)
| TypeInfo::Function(_)
| TypeInfo::Void => {
let diagnostic = Diagnostic::new(
&format!(
"Unassignable destination type: cannot assign {} to {}",
get_name_for_type(value_type_info, ctx),
get_name_for_type(destination_type_info, ctx)
),
assign_statement.destination().source_range().start(),
assign_statement.value().source_range().end(),
);
diagnostics.push(diagnostic);
}
TypeInfo::Instance(destination_instance_type_info) => {
match value_type_info {
TypeInfo::Any
| TypeInfo::Class(_)
| TypeInfo::Constructor(_)
| TypeInfo::Function(_)
| TypeInfo::Void => {
let diagnostic = Diagnostic::new(
&format!(
"Unassignable source type: cannot assign {} to {}",
get_name_for_type(value_type_info, ctx),
get_name_for_type(destination_type_info, ctx),
),
assign_statement.destination().source_range().start(),
assign_statement.value().source_range().end(),
);
diagnostics.push(diagnostic);
}
TypeInfo::Instance(value_instance_type_info) => {
if !destination_instance_type_info
.can_assign_from(value_instance_type_info, ctx)
{
let diagnostic = Diagnostic::new(
&format!(
"Incompatible types: cannot assign {} to {}",
get_name_for_type(value_type_info, ctx),
get_name_for_type(destination_type_info, ctx)
),
assign_statement.destination().source_range().start(),
assign_statement.value().source_range().end(),
);
diagnostics.push(diagnostic);
}
}
TypeInfo::__Error => {
// no-op
}
}
}
TypeInfo::__Error => {
// no-op
}
}
}
fn resolve_types_expression(
expression: &Expression, expression: &Expression,
ctx: &mut AnalysisContext, ctx: &mut AnalysisContext,
diagnostics: &mut Diagnostics, diagnostics: &mut Diagnostics,
) -> TypeInfoId { ) -> TypeInfoId {
match expression { match expression {
Expression::Binary(binary_expression) => { Expression::Binary(binary_expression) => {
infer_types_binary_expression(binary_expression, ctx, diagnostics) resolve_types_binary_expression(binary_expression, ctx, diagnostics)
} }
Expression::Negative(negative_expression) => { Expression::Negative(negative_expression) => {
resolve_types_negative_expression(negative_expression, ctx, diagnostics) resolve_types_negative_expression(negative_expression, ctx, diagnostics)
@ -93,13 +183,13 @@ fn infer_types_expression(
} }
} }
fn infer_types_binary_expression( fn resolve_types_binary_expression(
binary_expression: &BinaryExpression, binary_expression: &BinaryExpression,
ctx: &mut AnalysisContext, ctx: &mut AnalysisContext,
diagnostics: &mut Diagnostics, diagnostics: &mut Diagnostics,
) -> TypeInfoId { ) -> TypeInfoId {
let lhs_type_info_id = infer_types_expression(binary_expression.lhs(), ctx, diagnostics); let lhs_type_info_id = resolve_types_expression(binary_expression.lhs(), ctx, diagnostics);
let rhs_type_info_id = infer_types_expression(binary_expression.rhs(), ctx, diagnostics); let rhs_type_info_id = resolve_types_expression(binary_expression.rhs(), ctx, diagnostics);
let lhs_type_info = ctx.get_type_info_by_id(lhs_type_info_id); let lhs_type_info = ctx.get_type_info_by_id(lhs_type_info_id);
let rhs_type_info = ctx.get_type_info_by_id(rhs_type_info_id); let rhs_type_info = ctx.get_type_info_by_id(rhs_type_info_id);
@ -148,7 +238,7 @@ fn resolve_types_negative_expression(
diagnostics: &mut Diagnostics, diagnostics: &mut Diagnostics,
) -> TypeInfoId { ) -> TypeInfoId {
let operand_type_info_id = let operand_type_info_id =
infer_types_expression(negative_expression.operand(), ctx, diagnostics); resolve_types_expression(negative_expression.operand(), ctx, diagnostics);
let operand_type_info = ctx.get_type_info_by_id(operand_type_info_id); let operand_type_info = ctx.get_type_info_by_id(operand_type_info_id);
let result_type_info_id = if is_negative_op_compatible(operand_type_info, ctx) { let result_type_info_id = if is_negative_op_compatible(operand_type_info, ctx) {
negative_op_result(operand_type_info, ctx) negative_op_result(operand_type_info, ctx)
@ -175,7 +265,7 @@ fn resolve_types_call(
ctx: &mut AnalysisContext, ctx: &mut AnalysisContext,
diagnostics: &mut Diagnostics, diagnostics: &mut Diagnostics,
) -> TypeInfoId { ) -> TypeInfoId {
let receiver_type_info_id = infer_types_expression(call.callee(), ctx, diagnostics); let receiver_type_info_id = resolve_types_expression(call.callee(), ctx, diagnostics);
let receiver_type_info = ctx.get_type_info_by_id(receiver_type_info_id); let receiver_type_info = ctx.get_type_info_by_id(receiver_type_info_id);
match receiver_type_info { match receiver_type_info {
TypeInfo::Any | TypeInfo::Void => { TypeInfo::Any | TypeInfo::Void => {
@ -199,7 +289,7 @@ fn resolve_types_call(
ctx.instance_type(symbol) ctx.instance_type(symbol)
} }
TypeInfo::Constructor(_) => { TypeInfo::Constructor(_) => {
panic!("Cannot infer expression type directly on ConstructorTypeInfo"); panic!("Cannot check expression type directly on ConstructorTypeInfo");
} }
TypeInfo::Function(function_type_info) => function_type_info.return_type_id(), TypeInfo::Function(function_type_info) => function_type_info.return_type_id(),
TypeInfo::Instance(_) => { TypeInfo::Instance(_) => {

View File

@ -1,12 +1,12 @@
use crate::ast::compilation_unit::CompilationUnit; use crate::ast::compilation_unit::CompilationUnit;
use crate::diagnostic::Diagnostics; use crate::diagnostic::Diagnostics;
use crate::semantic_analysis::analysis_context::AnalysisContext; use crate::semantic_analysis::analysis_context::AnalysisContext;
use crate::semantic_analysis::type_analysis::check::check_types;
use crate::semantic_analysis::type_analysis::collect::collect_types; use crate::semantic_analysis::type_analysis::collect::collect_types;
use crate::semantic_analysis::type_analysis::infer::infer_types;
use crate::semantic_analysis::type_analysis::resolve::resolve_types; use crate::semantic_analysis::type_analysis::resolve::resolve_types;
mod check;
mod collect; mod collect;
mod infer;
mod resolve; mod resolve;
pub fn analyze_types( pub fn analyze_types(
@ -15,5 +15,5 @@ pub fn analyze_types(
) -> Diagnostics { ) -> Diagnostics {
collect_types(compilation_unit, analysis_context); collect_types(compilation_unit, analysis_context);
resolve_types(compilation_unit, analysis_context); resolve_types(compilation_unit, analysis_context);
infer_types(compilation_unit, analysis_context) check_types(compilation_unit, analysis_context)
} }