solar-sema 0.1.8

Solidity and Yul semantic analysis
Documentation
use crate::ty::Gcx;
use serde::Serialize;
use solar_interface::config::CompilerOutput;
use std::{
    collections::BTreeMap,
    io::{self, Write},
    path::Path,
};

#[derive(Default, Serialize)]
struct CombinedJson {
    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
    contracts: BTreeMap<String, CombinedJsonContract>,
    version: &'static str,
}

#[derive(Default, Serialize)]
struct CombinedJsonContract {
    #[serde(skip_serializing_if = "Option::is_none")]
    abi: Option<Abi>,
    #[serde(skip_serializing_if = "Option::is_none")]
    hashes: Option<Hashes>,
}

type Abi = Vec<alloy_json_abi::AbiItem<'static>>;
type Hashes = BTreeMap<String, String>;

pub(crate) fn emit(gcx: Gcx<'_>) {
    let mut output = CombinedJson {
        contracts: Default::default(),
        version: solar_interface::config::version::SEMVER_VERSION,
    };
    for id in gcx.hir.contract_ids() {
        let name = gcx.contract_fully_qualified_name(id).to_string();
        let contract_output = output.contracts.entry(name).or_default();
        for &emit in &gcx.sess.opts.emit {
            match emit {
                CompilerOutput::Abi => contract_output.abi = Some(gcx.contract_abi(id)),
                CompilerOutput::Hashes => {
                    let mut hashes = Hashes::default();
                    for f in gcx.interface_functions(id) {
                        hashes.insert(
                            gcx.item_signature(f.id.into()).to_string(),
                            alloy_primitives::hex::encode(f.selector),
                        );
                    }
                    contract_output.hashes = Some(hashes);
                }
                emit => todo!("{emit:?}"),
            }
        }
    }
    let _ = (|| {
        let out_path = gcx.sess.opts.out_dir.as_deref().map(|dir| dir.join("combined.json"));
        let mut writer = out_writer(out_path.as_deref())?;
        to_json(&mut writer, &output, gcx.sess.opts.pretty_json)?;
        writer.flush()?;
        Ok::<_, io::Error>(())
    })()
    .map_err(|e| gcx.dcx().err(format!("failed to write to output: {e}")).emit());
}

fn out_writer(path: Option<&Path>) -> io::Result<impl io::Write> {
    let out: Box<dyn io::Write> = if let Some(path) = path {
        Box::new(std::fs::File::create(path)?)
    } else {
        Box::new(std::io::stdout())
    };
    Ok(io::BufWriter::new(out))
}

fn to_json<W: io::Write, T: Serialize>(
    writer: W,
    value: &T,
    pretty: bool,
) -> serde_json::Result<()> {
    if pretty {
        serde_json::to_writer_pretty(writer, value)
    } else {
        serde_json::to_writer(writer, value)
    }
}