mod definitions;
mod destructuring;
mod expr;
mod let_and_module;
mod register;
#[cfg(test)]
mod tests;
mod types;
use crate::ast::{File, ParamConvention, Statement};
use crate::error::CompilerError;
use crate::semantic::{SymbolKind, SymbolTable};
use super::{ImportedKind, IrGenericParam, IrImport, IrImportItem, IrModule, ResolvedType};
use std::collections::HashMap;
pub fn lower_to_ir(ast: &File, symbols: &SymbolTable) -> Result<IrModule, Vec<CompilerError>> {
let mut lowerer = IrLowerer::new(symbols);
lowerer.lower_file(ast)?;
Ok(lowerer.module)
}
pub fn lower_to_ir_with_path(
ast: &File,
symbols: &SymbolTable,
path: std::path::PathBuf,
) -> Result<IrModule, Vec<CompilerError>> {
let mut lowerer = IrLowerer::new(symbols);
lowerer.current_file = lowerer.module.register_file(path);
lowerer.lower_file(ast)?;
Ok(lowerer.module)
}
struct IrLowerer<'a> {
pub(super) module: IrModule,
pub(super) symbols: &'a SymbolTable,
pub(super) errors: Vec<CompilerError>,
pub(super) imports_by_module: HashMap<Vec<String>, (Vec<IrImportItem>, std::path::PathBuf)>,
pub(super) current_impl_struct: Option<String>,
pub(super) current_module_prefix: String,
pub(super) current_function_return_type: Option<String>,
pub(super) local_binding_scopes: Vec<HashMap<String, (ParamConvention, ResolvedType)>>,
pub(super) current_impl_method_returns: Option<HashMap<String, Option<ResolvedType>>>,
pub(super) generic_scopes: Vec<Vec<IrGenericParam>>,
pub(super) current_span: crate::location::Span,
pub(super) current_file: crate::ir::FileId,
pub(super) expected_closure_type: Option<ResolvedType>,
pub(super) expected_value_type: Option<ResolvedType>,
pub(super) module_node_stack: Vec<crate::ir::IrModuleNode>,
pub(super) imported_source_context: Option<Vec<String>>,
}
impl<'a> IrLowerer<'a> {
pub(super) fn record_missing_id(&mut self, kind: &'static str, id: u32) {
self.errors.push(CompilerError::InternalError {
detail: format!("{kind} id {id} produced by registration lookup is no longer valid"),
span: crate::location::Span::default(),
});
}
pub(super) fn internal_error_type(&mut self, detail: String) -> ResolvedType {
self.errors.push(CompilerError::InternalError {
detail,
span: self.current_span,
});
ResolvedType::Error
}
pub(super) fn internal_error_type_if_concrete(
&mut self,
bad_ty: &ResolvedType,
detail: String,
) -> ResolvedType {
if matches!(bad_ty, ResolvedType::Error) {
ResolvedType::Error
} else {
self.internal_error_type(detail)
}
}
fn new(symbols: &'a SymbolTable) -> Self {
Self {
module: IrModule::new(),
symbols,
errors: Vec::new(),
imports_by_module: HashMap::new(),
current_impl_struct: None,
current_module_prefix: String::new(),
current_function_return_type: None,
local_binding_scopes: Vec::new(),
current_impl_method_returns: None,
generic_scopes: Vec::new(),
current_span: crate::location::Span::default(),
current_file: crate::ir::FileId::SYNTHETIC,
expected_closure_type: None,
expected_value_type: None,
module_node_stack: Vec::new(),
imported_source_context: None,
}
}
pub(super) const fn current_ir_span(&self) -> crate::ir::IrSpan {
crate::ir::IrSpan::new(self.current_span, self.current_file)
}
pub(super) fn find_function_in_scope(&self, name: &str) -> Option<crate::ir::FunctionId> {
if !self.current_module_prefix.is_empty() {
let qualified = format!("{}::{}", self.current_module_prefix, name);
if let Some(id) = self.module.function_id(&qualified) {
return Some(id);
}
}
self.module.function_id(name)
}
pub(super) fn lookup_local_binding(&self, name: &str) -> Option<&ResolvedType> {
self.lookup_local_binding_entry(name).map(|(_, ty)| ty)
}
pub(super) fn lookup_local_binding_entry(
&self,
name: &str,
) -> Option<&(ParamConvention, ResolvedType)> {
for frame in self.local_binding_scopes.iter().rev() {
if let Some(entry) = frame.get(name) {
return Some(entry);
}
}
None
}
pub(super) fn is_generic_param_in_scope(&self, name: &str) -> bool {
for frame in &self.generic_scopes {
if frame.iter().any(|p| p.name == name) {
return true;
}
}
false
}
fn lower_file(&mut self, file: &File) -> Result<(), Vec<CompilerError>> {
self.register_imported_types();
for statement in &file.statements {
if let Statement::Definition(def) = statement {
self.register_definition(def.as_ref());
}
}
for statement in &file.statements {
if let Statement::Definition(def) = statement {
self.lower_definition(def.as_ref());
}
}
for statement in &file.statements {
if let Statement::Let(let_binding) = statement {
self.lower_let_binding(let_binding);
}
}
self.module.imports = self
.imports_by_module
.drain()
.map(|(module_path, (items, source_file))| IrImport {
module_path,
items,
source_file,
})
.collect();
if self.errors.is_empty() {
Ok(())
} else {
Err(std::mem::take(&mut self.errors))
}
}
pub(super) fn try_track_imported_type(&mut self, name: &str, expected_kind: ImportedKind) {
if let Some(module_path) = self.symbols.get_module_logical_path(name) {
let import_item = IrImportItem {
name: name.to_string(),
kind: expected_kind,
};
let source_file = self
.symbols
.get_module_origin(name)
.cloned()
.unwrap_or_default();
self.imports_by_module
.entry(module_path.clone())
.or_insert_with(|| (Vec::new(), source_file))
.0
.push(import_item);
}
}
pub(super) fn try_external_type(
&mut self,
name: &str,
type_args: Vec<ResolvedType>,
) -> Option<ResolvedType> {
let module_path = self.symbols.get_module_logical_path(name)?;
let kind = self.symbols.get_symbol_kind(name)?;
let external_kind = match kind {
SymbolKind::Struct => ImportedKind::Struct,
SymbolKind::Trait => ImportedKind::Trait,
SymbolKind::Enum => ImportedKind::Enum,
SymbolKind::Impl | SymbolKind::Let | SymbolKind::Module | SymbolKind::Function => {
return None
}
};
let import_item = IrImportItem {
name: name.to_string(),
kind: external_kind.clone(),
};
let source_file = self
.symbols
.get_module_origin(name)
.cloned()
.unwrap_or_default();
self.imports_by_module
.entry(module_path.clone())
.or_insert_with(|| (Vec::new(), source_file))
.0
.push(import_item);
Some(ResolvedType::External {
module_path: module_path.clone(),
name: name.to_string(),
kind: external_kind,
type_args,
})
}
}