use std::hash::BuildHasher;
use crate::errors::CliError;
use clap::Args;
use fm::FileManager;
use iter_extended::btree_map;
use nargo::{
errors::CompileError, insert_all_files_for_workspace_into_file_manager,
ops::check_crate_and_report_errors, package::Package, parse_all, prepare_package,
workspace::Workspace,
};
use nargo_toml::PackageSelection;
use noir_artifact_cli::fs::artifact::write_to_file;
use noirc_abi::{AbiParameter, AbiType, MAIN_RETURN_NAME};
use noirc_driver::{CompileOptions, check_crate, compute_function_abi};
use noirc_frontend::{hir::ParsedFiles, monomorphization::monomorphize};
use super::{LockType, PackageOptions, WorkspaceCommand};
#[derive(Debug, Clone, Args)]
#[clap(visible_alias = "c")]
pub(crate) struct CheckCommand {
#[clap(flatten)]
pub(super) package_options: PackageOptions,
#[clap(long)]
overwrite: bool,
#[clap(flatten)]
compile_options: CompileOptions,
#[clap(long, hide = true)]
show_program_hash: bool,
}
impl WorkspaceCommand for CheckCommand {
fn package_selection(&self) -> PackageSelection {
self.package_options.package_selection()
}
fn lock_type(&self) -> LockType {
LockType::None
}
}
pub(crate) fn run(args: CheckCommand, workspace: Workspace) -> Result<(), CliError> {
let mut workspace_file_manager = workspace.new_file_manager();
insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager);
let parsed_files = parse_all(&workspace_file_manager);
for package in &workspace {
if args.show_program_hash {
let (mut context, crate_id) =
prepare_package(&workspace_file_manager, &parsed_files, package);
check_crate(&mut context, crate_id, &args.compile_options).unwrap();
let Some(main) = context.get_main_function(&crate_id) else {
continue;
};
let program = monomorphize(main, &mut context.def_interner, false).unwrap();
let hash = rustc_hash::FxBuildHasher.hash_one(&program);
println!("{}: {:x}", package.name, hash);
continue;
}
check_package(
&workspace_file_manager,
&parsed_files,
package,
&args.compile_options,
args.overwrite,
)?;
}
Ok(())
}
fn check_package(
file_manager: &FileManager,
parsed_files: &ParsedFiles,
package: &Package,
compile_options: &CompileOptions,
overwrite: bool,
) -> Result<(), CompileError> {
let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package);
check_crate_and_report_errors(&mut context, crate_id, compile_options)?;
if package.is_library() || package.is_contract() {
Ok(())
} else if let Some((parameters, return_type)) = compute_function_abi(&context, &crate_id) {
let path_to_prover_input = package.prover_input_path();
let should_write_prover = !path_to_prover_input.exists() || overwrite;
if should_write_prover {
let prover_toml = create_input_toml_template(parameters, return_type);
write_to_file(prover_toml.as_bytes(), &path_to_prover_input)
.expect("failed to write template");
} else {
eprintln!("Note: Prover.toml already exists. Use --overwrite to force overwrite.");
}
Ok(())
} else {
Err(CompileError::MissingMainFunction(package.name.clone()))
}
}
fn create_input_toml_template(
parameters: Vec<AbiParameter>,
return_type: Option<AbiType>,
) -> String {
fn default_value(typ: AbiType) -> toml::Value {
match typ {
AbiType::Array { length, typ } => {
let default_value_vec =
std::iter::repeat_n(default_value(*typ), length.try_into().unwrap()).collect();
toml::Value::Array(default_value_vec)
}
AbiType::Struct { fields, .. } => {
let default_value_map = toml::map::Map::from_iter(
fields.into_iter().map(|(name, typ)| (name, default_value(typ))),
);
toml::Value::Table(default_value_map)
}
AbiType::Field | AbiType::Integer { .. } => toml::Value::Integer(0),
AbiType::Boolean => toml::Value::Boolean(false),
AbiType::Tuple { fields } => {
let default_value_vec = fields.into_iter().map(default_value).collect();
toml::Value::Array(default_value_vec)
}
AbiType::String { length } => toml::Value::String("_".repeat(length as usize)),
}
}
let mut map =
btree_map(parameters, |AbiParameter { name, typ, .. }| (name, default_value(typ)));
if let Some(typ) = return_type {
map.insert(MAIN_RETURN_NAME.to_owned(), default_value(typ));
}
toml::to_string(&map).unwrap()
}
#[cfg(test)]
mod tests {
use insta::assert_snapshot;
use noirc_abi::{AbiParameter, AbiType, AbiVisibility, Sign};
use super::create_input_toml_template;
#[test]
fn valid_toml_template() {
let typed_param = |name: &str, typ: AbiType| AbiParameter {
name: name.to_string(),
typ,
visibility: AbiVisibility::Public,
};
let parameters = vec![
typed_param("a", AbiType::Field),
typed_param("b", AbiType::Integer { sign: Sign::Unsigned, width: 32 }),
typed_param("c", AbiType::Array { length: 2, typ: Box::new(AbiType::Field) }),
typed_param(
"d",
AbiType::Struct {
path: String::from("MyStruct"),
fields: vec![
(String::from("d1"), AbiType::Field),
(
String::from("d2"),
AbiType::Array { length: 3, typ: Box::new(AbiType::Field) },
),
],
},
),
typed_param("e", AbiType::Boolean),
typed_param(
"f",
AbiType::Tuple { fields: vec![AbiType::Field, AbiType::String { length: 5 }] },
),
];
let return_type = AbiType::Boolean;
let toml_str = create_input_toml_template(parameters, Some(return_type));
assert_snapshot!(toml_str, @r#"
a = 0
b = 0
c = [0, 0]
e = false
f = [0, "_____"]
return = false
[d]
d1 = 0
d2 = [0, 0, 0]
"#);
}
}