use std::collections::{HashMap, VecDeque};
use crate::error::CompilerError;
use crate::ir::{GenericBase, IrModule, ResolvedType, StructId};
use crate::location::Span;
use crate::pipeline::IrPass;
mod collect;
mod compact;
mod expr_walk;
mod external;
mod functions;
mod leftover;
mod rewrite;
mod specialise;
pub(super) mod walkers;
use collect::collect_all_instantiations;
use compact::{
apply_impl_index_remap, apply_remaps, build_enum_remap, build_struct_remap, build_trait_remap,
drop_specialised_generic_impls,
};
use external::{
detect_import_cycle, inline_imported_functions, inline_imported_impls, inline_imported_lets,
merge_imported_module_trees, qualify_imported_paths, remap_imported_body_ids,
remap_imported_file_ids, rewrite_external_references, specialise_external_instantiations,
};
use functions::specialise_generic_functions;
use leftover::LeftoverScanner;
use rewrite::{
devirtualise_concrete_receivers, rewrite_dispatch_impl_ids, rewrite_module, specialise_impls,
};
use specialise::{specialise, Instantiation};
#[expect(
clippy::exhaustive_structs,
reason = "single optional field for imported module IRs; no further fields planned"
)]
#[derive(Debug, Clone, Default)]
pub struct MonomorphisePass {
pub imported_modules: HashMap<Vec<String>, IrModule>,
}
impl MonomorphisePass {
#[must_use]
pub fn with_imports(mut self, imported_modules: HashMap<Vec<String>, IrModule>) -> Self {
self.imported_modules = imported_modules;
self
}
}
impl IrPass for MonomorphisePass {
fn name(&self) -> &'static str {
"monomorphise"
}
#[expect(
clippy::too_many_lines,
reason = "the pass is a single ordered phase pipeline; phase boundaries are commented inline"
)]
#[expect(
clippy::items_after_statements,
reason = "the inline `args_have_type_param` / `contains_type_param` helpers belong next to the closure that uses them"
)]
fn run(&mut self, mut module: IrModule) -> Result<IrModule, Vec<CompilerError>> {
let mut errors = Vec::new();
let mut impl_clone_remap: HashMap<(Vec<String>, u32), u32> = HashMap::new();
if !self.imported_modules.is_empty() {
if let Some(cycle_node) = detect_import_cycle(&self.imported_modules) {
return Err(vec![CompilerError::InternalError {
detail: format!(
"monomorphise: cyclic import involving module {cycle_node:?} reached the IR layer; semantic analysis should have rejected this"
),
span: Span::default(),
}]);
}
}
let mut external_mapping = if self.imported_modules.is_empty() {
HashMap::new()
} else {
match specialise_external_instantiations(&mut module, &self.imported_modules) {
Ok(map) => map,
Err(mut e) => {
errors.append(&mut e);
HashMap::new()
}
}
};
if !self.imported_modules.is_empty() {
if let Err(mut e) = inline_imported_functions(&mut module, &self.imported_modules) {
errors.append(&mut e);
}
inline_imported_impls(&mut module, &self.imported_modules, &mut impl_clone_remap);
inline_imported_lets(&mut module, &self.imported_modules);
match specialise_external_instantiations(&mut module, &self.imported_modules) {
Ok(more) => external_mapping.extend(more),
Err(mut e) => errors.append(&mut e),
}
remap_imported_body_ids(&mut module, &self.imported_modules, &impl_clone_remap);
qualify_imported_paths(&mut module, &self.imported_modules);
merge_imported_module_trees(&mut module, &self.imported_modules);
remap_imported_file_ids(&mut module, &self.imported_modules);
}
let prelude_skip: std::collections::HashSet<GenericBase> = [
module.prelude_array_id().map(GenericBase::Struct),
module.prelude_dictionary_id().map(GenericBase::Struct),
module.prelude_range_id().map(GenericBase::Struct),
module.prelude_optional_id().map(GenericBase::Enum),
]
.into_iter()
.flatten()
.collect();
fn args_have_type_param(args: &[ResolvedType]) -> bool {
args.iter().any(contains_type_param)
}
fn contains_type_param(ty: &ResolvedType) -> bool {
match ty {
ResolvedType::TypeParam(_) => true,
ResolvedType::Tuple(fields) => fields.iter().any(|(_, t)| contains_type_param(t)),
ResolvedType::Closure {
param_tys,
return_ty,
} => {
param_tys.iter().any(|(_, t)| contains_type_param(t))
|| contains_type_param(return_ty)
}
ResolvedType::Generic { args, .. } => args.iter().any(contains_type_param),
ResolvedType::External { type_args, .. } => {
type_args.iter().any(contains_type_param)
}
ResolvedType::Primitive(_)
| ResolvedType::Struct(_)
| ResolvedType::Trait(_)
| ResolvedType::Enum(_)
| ResolvedType::Error => false,
}
}
let initial = collect_all_instantiations(&module);
let mut worklist: VecDeque<Instantiation> = initial
.into_iter()
.filter(|(base, args)| {
!prelude_skip.contains(base) && !args_have_type_param(args)
})
.collect();
let mut mapping: HashMap<Instantiation, GenericBase> = HashMap::new();
while let Some(inst) = worklist.pop_front() {
if mapping.contains_key(&inst) {
continue;
}
match specialise(&mut module, &inst) {
Ok((spec_base, more)) => {
mapping.insert(inst, spec_base);
worklist.extend(more);
}
Err(e) => {
errors.push(e);
mapping.insert(inst, GenericBase::Struct(StructId(u32::MAX)));
}
}
}
if !errors.is_empty() {
return Err(errors);
}
rewrite_module(&mut module, &mapping);
if !external_mapping.is_empty() {
rewrite_external_references(&mut module, &external_mapping);
}
let impl_remap = specialise_impls(&mut module, &mapping);
rewrite_dispatch_impl_ids(&mut module, &impl_remap);
if let Err(e) = specialise_generic_functions(&mut module) {
errors.extend(e);
}
if !errors.is_empty() {
return Err(errors);
}
let post_clone_initial = collect_all_instantiations(&module);
let mut post_worklist: VecDeque<Instantiation> = post_clone_initial
.into_iter()
.filter(|(base, args)| {
!prelude_skip.contains(base)
&& !args_have_type_param(args)
&& !mapping.contains_key(&(*base, args.clone()))
})
.collect();
while let Some(inst) = post_worklist.pop_front() {
if mapping.contains_key(&inst) {
continue;
}
match specialise(&mut module, &inst) {
Ok((spec_base, more)) => {
mapping.insert(inst, spec_base);
post_worklist.extend(
more.into_iter()
.filter(|(b, a)| !prelude_skip.contains(b) && !args_have_type_param(a)),
);
}
Err(e) => {
errors.push(e);
mapping.insert(inst, GenericBase::Struct(StructId(u32::MAX)));
}
}
}
if !errors.is_empty() {
return Err(errors);
}
rewrite_module(&mut module, &mapping);
let post_impl_remap = specialise_impls(&mut module, &mapping);
rewrite_dispatch_impl_ids(&mut module, &post_impl_remap);
devirtualise_concrete_receivers(&mut module);
let struct_remap = build_struct_remap(&module);
let enum_remap = build_enum_remap(&module);
let trait_remap = build_trait_remap(&module);
let impl_index_remap =
drop_specialised_generic_impls(&mut module, &struct_remap, &enum_remap);
apply_remaps(&mut module, &struct_remap, &enum_remap, &trait_remap)?;
apply_impl_index_remap(&mut module, &impl_index_remap);
let is_prelude_builtin = |name: &str| {
matches!(name, "Array" | "Dictionary" | "Range" | "Optional")
};
module
.structs
.retain(|s| s.generic_params.is_empty() || is_prelude_builtin(&s.name));
module
.enums
.retain(|e| e.generic_params.is_empty() || is_prelude_builtin(&e.name));
module.traits.retain(|t| t.generic_params.is_empty());
module.functions.retain(|f| f.generic_params.is_empty());
module.rebuild_indices();
let mut leftovers = LeftoverScanner::default();
leftovers.scan(&module);
if let Some(detail) = leftovers.first_error() {
return Err(vec![CompilerError::InternalError {
detail,
span: Span::default(),
}]);
}
Ok(module)
}
}