use super::*;
pub(super) fn compile_resolved_bundle(
entry: &str,
resolver: &dyn crate::SourceResolver,
options: CompileOptions,
context: &RuntimeContext,
budget: &mut crate::budget::Budget<'_>,
) -> Result<Compilation, CompileError> {
let (source_units, entry_id) =
load_source_units_with_resolver(entry, resolver, context, budget)?;
check_programs_type_depth(source_units.iter().map(|unit| &unit.program), budget)?;
let (environment, actor) = check_source_units(&source_units, &entry_id, budget)?;
lower_checked(&source_units, &environment, actor.as_ref(), options, budget)
}
fn check_source_units(
source_units: &[SourceUnit],
entry_id: &crate::SourceId,
budget: &mut crate::budget::Budget<'_>,
) -> Result<(TypeEnv, Option<Type>), CompileError> {
let entry_unit = source_units
.iter()
.find(|unit| unit.name == entry_id.as_str())
.ok_or_else(|| {
CompileError::single(
"did_source_not_found",
DiagnosticPhase::Load,
"entry source is missing from the resolved bundle",
)
})?;
budget.checkpoint().map_err(|error| {
budget_error(error, DiagnosticPhase::TypeCheck, "Candid import merging")
})?;
let mut merged = IDLMergedProg::new(clone_program(&entry_unit.program));
for unit in source_units
.iter()
.filter(|unit| unit.name != entry_id.as_str())
{
budget.checkpoint().map_err(|error| {
budget_error(error, DiagnosticPhase::TypeCheck, "Candid import merging")
})?;
merged
.merge(
unit.include_actor,
unit.name.clone(),
clone_program(&unit.program),
)
.map_err(|error| {
merged_program_error(error, Some(SourceSpan::source_only(&unit.name)))
})?;
}
let actor = merged.resolve_actor().map_err(|error| {
let span = service_import_span(&error.to_string(), source_units, entry_id);
merged_program_error(error, span)
})?;
let program = IDLProg {
decs: merged.decs(),
actor,
};
budget
.checkpoint()
.map_err(|error| budget_error(error, DiagnosticPhase::TypeCheck, "Candid type checking"))?;
let mut environment = TypeEnv::new();
let actor = check_prog(&mut environment, &program)
.map_err(|error| candid_error(error, DiagnosticPhase::TypeCheck, None))?;
budget
.checkpoint()
.map_err(|error| budget_error(error, DiagnosticPhase::TypeCheck, "Candid type checking"))?;
Ok((environment, actor))
}
fn merged_program_error(
error: impl Into<candid_parser::Error>,
span: Option<SourceSpan>,
) -> CompileError {
let mut compile_error = candid_error(error.into(), DiagnosticPhase::TypeCheck, None);
if let Some(span) = span {
for diagnostic in &mut compile_error.diagnostics {
diagnostic.span = Some(span.clone());
}
}
compile_error
}
fn service_import_span(
message: &str,
source_units: &[SourceUnit],
entry_id: &crate::SourceId,
) -> Option<SourceSpan> {
source_units
.iter()
.filter(|unit| unit.include_actor && unit.name != entry_id.as_str())
.find(|unit| {
message
== format!(
"Imported service file \"{}\" has a service constructor",
unit.name
)
})
.map(|unit| SourceSpan::source_only(&unit.name))
}
fn clone_program(program: &IDLProg) -> IDLProg {
IDLProg {
decs: program.decs.iter().map(clone_declaration).collect(),
actor: program.actor.clone(),
}
}
fn clone_declaration(declaration: &Dec) -> Dec {
match declaration {
Dec::TypD(binding) => Dec::TypD(binding.clone()),
Dec::ImportType(import) => Dec::ImportType(import.clone()),
Dec::ImportServ(import) => Dec::ImportServ(import.clone()),
}
}