use crate::{
collect_from_abi_storage_type,
collect_from_function_input,
collect_from_function_output,
collect_from_plaintext,
collect_transitive_from_records,
collect_transitive_from_structs,
convert_mode,
convert_plaintext,
convert_record,
convert_record_field,
convert_storage_type,
convert_struct,
interface_ref_from_type,
};
use leo_abi_types as abi;
use leo_ast::{self as ast};
use leo_span::Symbol;
use indexmap::IndexMap;
use itertools::{Either, Itertools as _};
use std::collections::HashSet;
pub struct CompiledInterface {
pub owner: InterfaceOwner,
pub abi: abi::Interface,
}
pub enum InterfaceOwner {
Local,
External { owner_program: String },
}
pub fn generate_program_interfaces(ast: &ast::Program) -> Vec<CompiledInterface> {
let scope = ast.program_scopes.values().next().unwrap();
let program_str = scope.program_id.to_string();
let program_sym = scope.program_id.as_symbol();
let cs = CompositeSource::Program { scope, modules: &ast.modules, stubs: &ast.stubs };
let mut result = Vec::new();
let mut seen: HashSet<(Option<String>, Vec<String>)> = HashSet::new();
for (_, iface) in &scope.interfaces {
let abi = build_interface(iface, program_sym, &[], &cs);
let key = (None, abi.path.clone());
if seen.insert(key) {
result.push(CompiledInterface { owner: InterfaceOwner::Local, abi });
}
}
for (module_path, module) in &ast.modules {
for (_, iface) in &module.interfaces {
let abi = build_interface(iface, program_sym, module_path, &cs);
let key = (None, abi.path.clone());
if seen.insert(key) {
result.push(CompiledInterface { owner: InterfaceOwner::Local, abi });
}
}
}
let mut worklist: Vec<(Symbol, Vec<Symbol>)> = Vec::new();
for (_, ty) in &scope.parents {
if let ast::Type::Composite(ct) = ty
&& let Some(loc) = ct.path.try_global_location()
{
worklist.push((loc.program, loc.path.clone()));
}
}
let local_ifaces = scope
.interfaces
.iter()
.map(|(_, i)| i)
.chain(ast.modules.values().flat_map(|m| m.interfaces.iter().map(|(_, i)| i)));
for iface in local_ifaces {
for (_, parent_ty) in &iface.parents {
if let ast::Type::Composite(ct) = parent_ty
&& let Some(loc) = ct.path.try_global_location()
{
worklist.push((loc.program, loc.path.clone()));
}
}
}
while let Some((ext_program, iface_path)) = worklist.pop() {
let owner_str = ext_program.to_string();
if owner_str == program_str {
continue;
}
let Some(stub) = ast.stubs.get(&ext_program) else { continue };
let Some(iface) = find_interface_in_stub(stub, &iface_path) else { continue };
let ext_cs = composite_source_for_stub(stub);
let module_path: Vec<Symbol> = iface_path[..iface_path.len().saturating_sub(1)].to_vec();
let abi = build_interface(iface, ext_program, &module_path, &ext_cs);
let key = (Some(owner_str.clone()), abi.path.clone());
if seen.insert(key) {
result.push(CompiledInterface { owner: InterfaceOwner::External { owner_program: owner_str }, abi });
for (_, parent_ty) in &iface.parents {
let ast::Type::Composite(ct) = parent_ty else { continue };
let Some(parent_loc) = ct.path.try_global_location() else { continue };
worklist.push((parent_loc.program, parent_loc.path.clone()));
}
}
}
result
}
pub fn generate_library_interfaces(library: &ast::Library) -> Vec<CompiledInterface> {
let cs = CompositeSource::Library { library, stubs: &library.stubs };
let mut result = Vec::new();
for (_, iface) in &library.interfaces {
let abi = build_interface(iface, library.name, &[], &cs);
result.push(CompiledInterface { owner: InterfaceOwner::Local, abi });
}
for (module_path, module) in &library.modules {
for (_, iface) in &module.interfaces {
let abi = build_interface(iface, library.name, module_path, &cs);
result.push(CompiledInterface { owner: InterfaceOwner::Local, abi });
}
}
result
}
enum CompositeSource<'a> {
Program {
scope: &'a ast::ProgramScope,
modules: &'a IndexMap<Vec<Symbol>, ast::Module>,
stubs: &'a IndexMap<Symbol, ast::Stub>,
},
Library {
library: &'a ast::Library,
stubs: &'a IndexMap<Symbol, ast::Stub>,
},
}
impl<'a> CompositeSource<'a> {
fn is_record(&self, comp_ty: &ast::CompositeType) -> bool {
let name = comp_ty.path.identifier().name;
match self {
CompositeSource::Program { scope, modules, .. } => {
if let Some((_, c)) = scope.composites.iter().find(|(sym, _)| *sym == name) {
return c.is_record;
}
for module in modules.values() {
if let Some((_, c)) = module.composites.iter().find(|(sym, _)| *sym == name) {
return c.is_record;
}
}
}
CompositeSource::Library { library, .. } => {
if let Some((_, c)) = library.structs.iter().find(|(sym, _)| *sym == name) {
return c.is_record;
}
for module in library.modules.values() {
if let Some((_, c)) = module.composites.iter().find(|(sym, _)| *sym == name) {
return c.is_record;
}
}
}
}
let stubs = match self {
CompositeSource::Program { stubs, .. } | CompositeSource::Library { stubs, .. } => stubs,
};
if let Some(program) = comp_ty.path.program()
&& let Some(stub) = stubs.get(&program)
&& let Some(is_rec) = find_is_record_in_stub(stub, name)
{
return is_rec;
}
false
}
fn all_structs(&self) -> Vec<abi::Struct> {
let mut out = Vec::new();
match self {
CompositeSource::Program { scope, modules, .. } => {
out.extend(scope.composites.iter().filter(|(_, c)| !c.is_record).map(|(_, c)| convert_struct(c, &[])));
for (mp, module) in *modules {
out.extend(
module.composites.iter().filter(|(_, c)| !c.is_record).map(|(_, c)| convert_struct(c, mp)),
);
}
}
CompositeSource::Library { library, .. } => {
out.extend(library.structs.iter().filter(|(_, c)| !c.is_record).map(|(_, c)| convert_struct(c, &[])));
for (mp, module) in &library.modules {
out.extend(
module.composites.iter().filter(|(_, c)| !c.is_record).map(|(_, c)| convert_struct(c, mp)),
);
}
}
}
out
}
fn all_records(&self) -> Vec<abi::Record> {
let mut out = Vec::new();
match self {
CompositeSource::Program { scope, modules, .. } => {
out.extend(scope.composites.iter().filter(|(_, c)| c.is_record).map(|(_, c)| convert_record(c, &[])));
for (mp, module) in *modules {
out.extend(
module.composites.iter().filter(|(_, c)| c.is_record).map(|(_, c)| convert_record(c, mp)),
);
}
}
CompositeSource::Library { library, .. } => {
out.extend(library.structs.iter().filter(|(_, c)| c.is_record).map(|(_, c)| convert_record(c, &[])));
for (mp, module) in &library.modules {
out.extend(
module.composites.iter().filter(|(_, c)| c.is_record).map(|(_, c)| convert_record(c, mp)),
);
}
}
}
out
}
}
fn find_is_record_in_stub(stub: &ast::Stub, name: Symbol) -> Option<bool> {
match stub {
ast::Stub::FromAleo { program, .. } => {
program.composites.iter().find(|(sym, _)| *sym == name).map(|(_, c)| c.is_record)
}
ast::Stub::FromLeo { program, .. } => program
.program_scopes
.values()
.flat_map(|scope| scope.composites.iter())
.find(|(sym, _)| *sym == name)
.map(|(_, c)| c.is_record),
ast::Stub::FromLibrary { library, .. } => {
library.structs.iter().find(|(sym, _)| *sym == name).map(|(_, c)| c.is_record)
}
}
}
fn composite_source_for_stub(stub: &ast::Stub) -> CompositeSource<'_> {
match stub {
ast::Stub::FromLeo { program, .. } => {
let scope = program.program_scopes.values().next().unwrap();
CompositeSource::Program { scope, modules: &program.modules, stubs: &program.stubs }
}
ast::Stub::FromLibrary { library, .. } => CompositeSource::Library { library, stubs: &library.stubs },
ast::Stub::FromAleo { .. } => {
unreachable!("Aleo stubs do not contain interfaces")
}
}
}
fn find_interface_in_stub<'a>(stub: &'a ast::Stub, path: &[Symbol]) -> Option<&'a ast::Interface> {
let (&iface_name, module_path) = path.split_last()?;
match stub {
ast::Stub::FromLeo { program, .. } => {
if module_path.is_empty() {
program
.program_scopes
.values()
.flat_map(|scope| scope.interfaces.iter())
.find(|(name, _)| *name == iface_name)
.map(|(_, iface)| iface)
} else {
program.modules.iter().find(|(mp, _)| mp.as_slice() == module_path).and_then(|(_, module)| {
module.interfaces.iter().find(|(name, _)| *name == iface_name).map(|(_, iface)| iface)
})
}
}
ast::Stub::FromLibrary { library, .. } => {
if module_path.is_empty() {
library.interfaces.iter().find(|(name, _)| *name == iface_name).map(|(_, iface)| iface)
} else {
library.modules.iter().find(|(mp, _)| mp.as_slice() == module_path).and_then(|(_, module)| {
module.interfaces.iter().find(|(name, _)| *name == iface_name).map(|(_, iface)| iface)
})
}
}
ast::Stub::FromAleo { .. } => None,
}
}
fn build_interface(
iface: &ast::Interface,
owning_program: Symbol,
module_path: &[Symbol],
cs: &CompositeSource<'_>,
) -> abi::Interface {
let name = iface.identifier.name.to_string();
let program = owning_program.to_string();
let mut path: Vec<String> = module_path.iter().map(|s| s.to_string()).collect();
path.push(name.clone());
let parents: Vec<abi::InterfaceRef> =
iface.parents.iter().filter_map(|(_, ty)| interface_ref_from_type(ty, &program)).collect();
let (functions, views): (Vec<abi::Function>, Vec<abi::Function>) =
iface.functions.iter().partition_map(|(_, proto)| {
let converted = convert_function_prototype(proto, iface, cs);
if proto.variant.is_view() { Either::Right(converted) } else { Either::Left(converted) }
});
let records: Vec<abi::Record> = iface.records.iter().map(|(_, proto)| convert_record_prototype(proto)).collect();
let mappings: Vec<abi::Mapping> = iface.mappings.iter().map(convert_mapping_prototype).collect();
let storage_variables: Vec<abi::StorageVariable> =
iface.storages.iter().map(convert_storage_variable_prototype).collect();
let structs = collect_interface_structs(
&program,
functions.iter().chain(views.iter()),
&records,
&mappings,
&storage_variables,
cs,
);
abi::Interface { name, program, path, parents, functions, views, records, mappings, storage_variables, structs }
}
fn convert_function_prototype(
proto: &ast::FunctionPrototype,
iface: &ast::Interface,
cs: &CompositeSource<'_>,
) -> abi::Function {
abi::Function {
name: proto.identifier.name.to_string(),
is_final: proto.output.iter().any(|o| matches!(o.type_, ast::Type::Future(_))),
const_parameters: proto.const_parameters.iter().map(convert_const_parameter).collect(),
inputs: proto.input.iter().map(|i| convert_input(i, iface, cs)).collect(),
outputs: proto.output.iter().map(|o| convert_output(o, iface, cs)).collect(),
}
}
fn convert_record_prototype(proto: &ast::RecordPrototype) -> abi::Record {
abi::Record {
path: vec![proto.identifier.name.to_string()],
fields: proto.members.iter().map(convert_record_field).collect(),
}
}
fn convert_mapping_prototype(proto: &ast::MappingPrototype) -> abi::Mapping {
abi::Mapping {
name: proto.identifier.name.to_string(),
key: convert_plaintext(&proto.key_type),
value: convert_plaintext(&proto.value_type),
}
}
fn convert_storage_variable_prototype(proto: &ast::StorageVariablePrototype) -> abi::StorageVariable {
abi::StorageVariable { name: proto.identifier.name.to_string(), ty: convert_storage_type(&proto.type_) }
}
fn convert_const_parameter(cp: &ast::ConstParameter) -> abi::ConstParameter {
abi::ConstParameter { name: cp.identifier.name.to_string(), ty: convert_plaintext(&cp.type_) }
}
fn convert_input(input: &ast::Input, iface: &ast::Interface, cs: &CompositeSource<'_>) -> abi::Input {
abi::Input {
name: input.identifier.name.to_string(),
ty: convert_function_input(&input.type_, iface, cs),
mode: convert_mode(input.mode),
}
}
fn convert_output(output: &ast::Output, iface: &ast::Interface, cs: &CompositeSource<'_>) -> abi::Output {
abi::Output { ty: convert_function_output(&output.type_, iface, cs), mode: convert_mode(output.mode) }
}
fn is_record_for_interface(comp_ty: &ast::CompositeType, iface: &ast::Interface, cs: &CompositeSource<'_>) -> bool {
let name = comp_ty.path.identifier().name;
if iface.records.iter().any(|(n, _)| *n == name) {
return true;
}
cs.is_record(comp_ty)
}
fn convert_function_input(ty: &ast::Type, iface: &ast::Interface, cs: &CompositeSource<'_>) -> abi::FunctionInput {
if let ast::Type::DynRecord = ty {
return abi::FunctionInput::DynamicRecord;
}
if let ast::Type::Composite(comp_ty) = ty
&& is_record_for_interface(comp_ty, iface, cs)
{
return abi::FunctionInput::Record(abi::RecordRef {
path: comp_ty.path.segments_iter().map(|s| s.to_string()).collect(),
program: comp_ty.path.program().map(|s| s.to_string()),
});
}
abi::FunctionInput::Plaintext(convert_plaintext(ty))
}
fn convert_function_output(ty: &ast::Type, iface: &ast::Interface, cs: &CompositeSource<'_>) -> abi::FunctionOutput {
match ty {
ast::Type::Future(_) => abi::FunctionOutput::Final,
ast::Type::DynRecord => abi::FunctionOutput::DynamicRecord,
ast::Type::Composite(comp_ty) if is_record_for_interface(comp_ty, iface, cs) => {
abi::FunctionOutput::Record(abi::RecordRef {
path: comp_ty.path.segments_iter().map(|s| s.to_string()).collect(),
program: comp_ty.path.program().map(|s| s.to_string()),
})
}
_ => abi::FunctionOutput::Plaintext(convert_plaintext(ty)),
}
}
fn collect_interface_structs<'a>(
program_name: &str,
functions: impl IntoIterator<Item = &'a abi::Function>,
records: &[abi::Record],
mappings: &[abi::Mapping],
storage_variables: &[abi::StorageVariable],
cs: &CompositeSource<'_>,
) -> Vec<abi::Struct> {
let mut used_types: HashSet<abi::Path> = HashSet::new();
for function in functions {
for cp in &function.const_parameters {
collect_from_plaintext(&cp.ty, program_name, &mut used_types);
}
for input in &function.inputs {
collect_from_function_input(&input.ty, program_name, &mut used_types);
}
for output in &function.outputs {
collect_from_function_output(&output.ty, program_name, &mut used_types);
}
}
for record in records {
for field in &record.fields {
collect_from_plaintext(&field.ty, program_name, &mut used_types);
}
}
for mapping in mappings {
collect_from_plaintext(&mapping.key, program_name, &mut used_types);
collect_from_plaintext(&mapping.value, program_name, &mut used_types);
}
for sv in storage_variables {
collect_from_abi_storage_type(&sv.ty, program_name, &mut used_types);
}
let all_structs = cs.all_structs();
let all_records = cs.all_records();
collect_transitive_from_structs(&all_structs, program_name, &mut used_types);
collect_transitive_from_records(&all_records, program_name, &mut used_types);
all_structs.into_iter().filter(|s| used_types.contains(&s.path)).collect()
}