1use crate::{
24 collect_from_abi_storage_type,
25 collect_from_function_input,
26 collect_from_function_output,
27 collect_from_plaintext,
28 collect_transitive_from_records,
29 collect_transitive_from_structs,
30 convert_mode,
31 convert_plaintext,
32 convert_record,
33 convert_record_field,
34 convert_storage_type,
35 convert_struct,
36 interface_ref_from_type,
37};
38
39use leo_abi_types as abi;
40use leo_ast::{self as ast};
41use leo_span::Symbol;
42
43use indexmap::IndexMap;
44use itertools::{Either, Itertools as _};
45use std::collections::HashSet;
46
47pub struct CompiledInterface {
53 pub owner: InterfaceOwner,
54 pub abi: abi::Interface,
55}
56
57pub enum InterfaceOwner {
59 Local,
62 External { owner_program: String },
65}
66
67pub fn generate_program_interfaces(ast: &ast::Program) -> Vec<CompiledInterface> {
77 let scope = ast.program_scopes.values().next().unwrap();
78 let program_str = scope.program_id.to_string();
79 let program_sym = scope.program_id.as_symbol();
80 let cs = CompositeSource::Program { scope, modules: &ast.modules, stubs: &ast.stubs };
81
82 let mut result = Vec::new();
83 let mut seen: HashSet<(Option<String>, Vec<String>)> = HashSet::new();
84
85 for (_, iface) in &scope.interfaces {
87 let abi = build_interface(iface, program_sym, &[], &cs);
88 let key = (None, abi.path.clone());
89 if seen.insert(key) {
90 result.push(CompiledInterface { owner: InterfaceOwner::Local, abi });
91 }
92 }
93
94 for (module_path, module) in &ast.modules {
96 for (_, iface) in &module.interfaces {
97 let abi = build_interface(iface, program_sym, module_path, &cs);
98 let key = (None, abi.path.clone());
99 if seen.insert(key) {
100 result.push(CompiledInterface { owner: InterfaceOwner::Local, abi });
101 }
102 }
103 }
104
105 let mut worklist: Vec<(Symbol, Vec<Symbol>)> = Vec::new();
111
112 for (_, ty) in &scope.parents {
114 if let ast::Type::Composite(ct) = ty
115 && let Some(loc) = ct.path.try_global_location()
116 {
117 worklist.push((loc.program, loc.path.clone()));
118 }
119 }
120
121 let local_ifaces = scope
123 .interfaces
124 .iter()
125 .map(|(_, i)| i)
126 .chain(ast.modules.values().flat_map(|m| m.interfaces.iter().map(|(_, i)| i)));
127 for iface in local_ifaces {
128 for (_, parent_ty) in &iface.parents {
129 if let ast::Type::Composite(ct) = parent_ty
130 && let Some(loc) = ct.path.try_global_location()
131 {
132 worklist.push((loc.program, loc.path.clone()));
133 }
134 }
135 }
136
137 while let Some((ext_program, iface_path)) = worklist.pop() {
138 let owner_str = ext_program.to_string();
139
140 if owner_str == program_str {
142 continue;
143 }
144
145 let Some(stub) = ast.stubs.get(&ext_program) else { continue };
146 let Some(iface) = find_interface_in_stub(stub, &iface_path) else { continue };
147
148 let ext_cs = composite_source_for_stub(stub);
149 let module_path: Vec<Symbol> = iface_path[..iface_path.len().saturating_sub(1)].to_vec();
150 let abi = build_interface(iface, ext_program, &module_path, &ext_cs);
151 let key = (Some(owner_str.clone()), abi.path.clone());
152 if seen.insert(key) {
153 result.push(CompiledInterface { owner: InterfaceOwner::External { owner_program: owner_str }, abi });
154 for (_, parent_ty) in &iface.parents {
156 let ast::Type::Composite(ct) = parent_ty else { continue };
157 let Some(parent_loc) = ct.path.try_global_location() else { continue };
158 worklist.push((parent_loc.program, parent_loc.path.clone()));
159 }
160 }
161 }
162
163 result
164}
165
166pub fn generate_library_interfaces(library: &ast::Library) -> Vec<CompiledInterface> {
169 let cs = CompositeSource::Library { library, stubs: &library.stubs };
170 let mut result = Vec::new();
171
172 for (_, iface) in &library.interfaces {
173 let abi = build_interface(iface, library.name, &[], &cs);
174 result.push(CompiledInterface { owner: InterfaceOwner::Local, abi });
175 }
176
177 for (module_path, module) in &library.modules {
178 for (_, iface) in &module.interfaces {
179 let abi = build_interface(iface, library.name, module_path, &cs);
180 result.push(CompiledInterface { owner: InterfaceOwner::Local, abi });
181 }
182 }
183
184 result
185}
186
187enum CompositeSource<'a> {
193 Program {
194 scope: &'a ast::ProgramScope,
195 modules: &'a IndexMap<Vec<Symbol>, ast::Module>,
196 stubs: &'a IndexMap<Symbol, ast::Stub>,
197 },
198 Library {
199 library: &'a ast::Library,
200 stubs: &'a IndexMap<Symbol, ast::Stub>,
201 },
202}
203
204impl<'a> CompositeSource<'a> {
205 fn is_record(&self, comp_ty: &ast::CompositeType) -> bool {
207 let name = comp_ty.path.identifier().name;
208
209 match self {
211 CompositeSource::Program { scope, modules, .. } => {
212 if let Some((_, c)) = scope.composites.iter().find(|(sym, _)| *sym == name) {
213 return c.is_record;
214 }
215 for module in modules.values() {
216 if let Some((_, c)) = module.composites.iter().find(|(sym, _)| *sym == name) {
217 return c.is_record;
218 }
219 }
220 }
221 CompositeSource::Library { library, .. } => {
222 if let Some((_, c)) = library.structs.iter().find(|(sym, _)| *sym == name) {
223 return c.is_record;
224 }
225 for module in library.modules.values() {
226 if let Some((_, c)) = module.composites.iter().find(|(sym, _)| *sym == name) {
227 return c.is_record;
228 }
229 }
230 }
231 }
232
233 let stubs = match self {
235 CompositeSource::Program { stubs, .. } | CompositeSource::Library { stubs, .. } => stubs,
236 };
237 if let Some(program) = comp_ty.path.program()
238 && let Some(stub) = stubs.get(&program)
239 && let Some(is_rec) = find_is_record_in_stub(stub, name)
240 {
241 return is_rec;
242 }
243
244 false
245 }
246
247 fn all_structs(&self) -> Vec<abi::Struct> {
249 let mut out = Vec::new();
250 match self {
251 CompositeSource::Program { scope, modules, .. } => {
252 out.extend(scope.composites.iter().filter(|(_, c)| !c.is_record).map(|(_, c)| convert_struct(c, &[])));
253 for (mp, module) in *modules {
254 out.extend(
255 module.composites.iter().filter(|(_, c)| !c.is_record).map(|(_, c)| convert_struct(c, mp)),
256 );
257 }
258 }
259 CompositeSource::Library { library, .. } => {
260 out.extend(library.structs.iter().filter(|(_, c)| !c.is_record).map(|(_, c)| convert_struct(c, &[])));
261 for (mp, module) in &library.modules {
262 out.extend(
263 module.composites.iter().filter(|(_, c)| !c.is_record).map(|(_, c)| convert_struct(c, mp)),
264 );
265 }
266 }
267 }
268 out
269 }
270
271 fn all_records(&self) -> Vec<abi::Record> {
273 let mut out = Vec::new();
274 match self {
275 CompositeSource::Program { scope, modules, .. } => {
276 out.extend(scope.composites.iter().filter(|(_, c)| c.is_record).map(|(_, c)| convert_record(c, &[])));
277 for (mp, module) in *modules {
278 out.extend(
279 module.composites.iter().filter(|(_, c)| c.is_record).map(|(_, c)| convert_record(c, mp)),
280 );
281 }
282 }
283 CompositeSource::Library { library, .. } => {
284 out.extend(library.structs.iter().filter(|(_, c)| c.is_record).map(|(_, c)| convert_record(c, &[])));
285 for (mp, module) in &library.modules {
286 out.extend(
287 module.composites.iter().filter(|(_, c)| c.is_record).map(|(_, c)| convert_record(c, mp)),
288 );
289 }
290 }
291 }
292 out
293 }
294}
295
296fn find_is_record_in_stub(stub: &ast::Stub, name: Symbol) -> Option<bool> {
298 match stub {
299 ast::Stub::FromAleo { program, .. } => {
300 program.composites.iter().find(|(sym, _)| *sym == name).map(|(_, c)| c.is_record)
301 }
302 ast::Stub::FromLeo { program, .. } => program
303 .program_scopes
304 .values()
305 .flat_map(|scope| scope.composites.iter())
306 .find(|(sym, _)| *sym == name)
307 .map(|(_, c)| c.is_record),
308 ast::Stub::FromLibrary { library, .. } => {
309 library.structs.iter().find(|(sym, _)| *sym == name).map(|(_, c)| c.is_record)
310 }
311 }
312}
313
314fn composite_source_for_stub(stub: &ast::Stub) -> CompositeSource<'_> {
316 match stub {
317 ast::Stub::FromLeo { program, .. } => {
318 let scope = program.program_scopes.values().next().unwrap();
319 CompositeSource::Program { scope, modules: &program.modules, stubs: &program.stubs }
320 }
321 ast::Stub::FromLibrary { library, .. } => CompositeSource::Library { library, stubs: &library.stubs },
322 ast::Stub::FromAleo { .. } => {
323 unreachable!("Aleo stubs do not contain interfaces")
326 }
327 }
328}
329
330fn find_interface_in_stub<'a>(stub: &'a ast::Stub, path: &[Symbol]) -> Option<&'a ast::Interface> {
336 let (&iface_name, module_path) = path.split_last()?;
337 match stub {
338 ast::Stub::FromLeo { program, .. } => {
339 if module_path.is_empty() {
340 program
341 .program_scopes
342 .values()
343 .flat_map(|scope| scope.interfaces.iter())
344 .find(|(name, _)| *name == iface_name)
345 .map(|(_, iface)| iface)
346 } else {
347 program.modules.iter().find(|(mp, _)| mp.as_slice() == module_path).and_then(|(_, module)| {
348 module.interfaces.iter().find(|(name, _)| *name == iface_name).map(|(_, iface)| iface)
349 })
350 }
351 }
352 ast::Stub::FromLibrary { library, .. } => {
353 if module_path.is_empty() {
354 library.interfaces.iter().find(|(name, _)| *name == iface_name).map(|(_, iface)| iface)
355 } else {
356 library.modules.iter().find(|(mp, _)| mp.as_slice() == module_path).and_then(|(_, module)| {
357 module.interfaces.iter().find(|(name, _)| *name == iface_name).map(|(_, iface)| iface)
358 })
359 }
360 }
361 ast::Stub::FromAleo { .. } => None,
362 }
363}
364
365fn build_interface(
372 iface: &ast::Interface,
373 owning_program: Symbol,
374 module_path: &[Symbol],
375 cs: &CompositeSource<'_>,
376) -> abi::Interface {
377 let name = iface.identifier.name.to_string();
378 let program = owning_program.to_string();
379
380 let mut path: Vec<String> = module_path.iter().map(|s| s.to_string()).collect();
381 path.push(name.clone());
382
383 let parents: Vec<abi::InterfaceRef> =
384 iface.parents.iter().filter_map(|(_, ty)| interface_ref_from_type(ty, &program)).collect();
385
386 let (functions, views): (Vec<abi::Function>, Vec<abi::Function>) =
389 iface.functions.iter().partition_map(|(_, proto)| {
390 let converted = convert_function_prototype(proto, iface, cs);
391 if proto.variant.is_view() { Either::Right(converted) } else { Either::Left(converted) }
392 });
393
394 let records: Vec<abi::Record> = iface.records.iter().map(|(_, proto)| convert_record_prototype(proto)).collect();
395
396 let mappings: Vec<abi::Mapping> = iface.mappings.iter().map(convert_mapping_prototype).collect();
397
398 let storage_variables: Vec<abi::StorageVariable> =
399 iface.storages.iter().map(convert_storage_variable_prototype).collect();
400
401 let structs = collect_interface_structs(
405 &program,
406 functions.iter().chain(views.iter()),
407 &records,
408 &mappings,
409 &storage_variables,
410 cs,
411 );
412
413 abi::Interface { name, program, path, parents, functions, views, records, mappings, storage_variables, structs }
414}
415
416fn convert_function_prototype(
421 proto: &ast::FunctionPrototype,
422 iface: &ast::Interface,
423 cs: &CompositeSource<'_>,
424) -> abi::Function {
425 abi::Function {
426 name: proto.identifier.name.to_string(),
427 is_final: proto.output.iter().any(|o| matches!(o.type_, ast::Type::Future(_))),
428 const_parameters: proto.const_parameters.iter().map(convert_const_parameter).collect(),
429 inputs: proto.input.iter().map(|i| convert_input(i, iface, cs)).collect(),
430 outputs: proto.output.iter().map(|o| convert_output(o, iface, cs)).collect(),
431 }
432}
433
434fn convert_record_prototype(proto: &ast::RecordPrototype) -> abi::Record {
435 abi::Record {
436 path: vec![proto.identifier.name.to_string()],
437 fields: proto.members.iter().map(convert_record_field).collect(),
438 }
439}
440
441fn convert_mapping_prototype(proto: &ast::MappingPrototype) -> abi::Mapping {
442 abi::Mapping {
443 name: proto.identifier.name.to_string(),
444 key: convert_plaintext(&proto.key_type),
445 value: convert_plaintext(&proto.value_type),
446 }
447}
448
449fn convert_storage_variable_prototype(proto: &ast::StorageVariablePrototype) -> abi::StorageVariable {
450 abi::StorageVariable { name: proto.identifier.name.to_string(), ty: convert_storage_type(&proto.type_) }
451}
452
453fn convert_const_parameter(cp: &ast::ConstParameter) -> abi::ConstParameter {
454 abi::ConstParameter { name: cp.identifier.name.to_string(), ty: convert_plaintext(&cp.type_) }
455}
456
457fn convert_input(input: &ast::Input, iface: &ast::Interface, cs: &CompositeSource<'_>) -> abi::Input {
458 abi::Input {
459 name: input.identifier.name.to_string(),
460 ty: convert_function_input(&input.type_, iface, cs),
461 mode: convert_mode(input.mode),
462 }
463}
464
465fn convert_output(output: &ast::Output, iface: &ast::Interface, cs: &CompositeSource<'_>) -> abi::Output {
466 abi::Output { ty: convert_function_output(&output.type_, iface, cs), mode: convert_mode(output.mode) }
467}
468
469fn is_record_for_interface(comp_ty: &ast::CompositeType, iface: &ast::Interface, cs: &CompositeSource<'_>) -> bool {
474 let name = comp_ty.path.identifier().name;
476 if iface.records.iter().any(|(n, _)| *n == name) {
477 return true;
478 }
479 cs.is_record(comp_ty)
480}
481
482fn convert_function_input(ty: &ast::Type, iface: &ast::Interface, cs: &CompositeSource<'_>) -> abi::FunctionInput {
483 if let ast::Type::DynRecord = ty {
484 return abi::FunctionInput::DynamicRecord;
485 }
486 if let ast::Type::Composite(comp_ty) = ty
487 && is_record_for_interface(comp_ty, iface, cs)
488 {
489 return abi::FunctionInput::Record(abi::RecordRef {
490 path: comp_ty.path.segments_iter().map(|s| s.to_string()).collect(),
491 program: comp_ty.path.program().map(|s| s.to_string()),
492 });
493 }
494 abi::FunctionInput::Plaintext(convert_plaintext(ty))
495}
496
497fn convert_function_output(ty: &ast::Type, iface: &ast::Interface, cs: &CompositeSource<'_>) -> abi::FunctionOutput {
498 match ty {
499 ast::Type::Future(_) => abi::FunctionOutput::Final,
500 ast::Type::DynRecord => abi::FunctionOutput::DynamicRecord,
501 ast::Type::Composite(comp_ty) if is_record_for_interface(comp_ty, iface, cs) => {
502 abi::FunctionOutput::Record(abi::RecordRef {
503 path: comp_ty.path.segments_iter().map(|s| s.to_string()).collect(),
504 program: comp_ty.path.program().map(|s| s.to_string()),
505 })
506 }
507 _ => abi::FunctionOutput::Plaintext(convert_plaintext(ty)),
508 }
509}
510
511fn collect_interface_structs<'a>(
520 program_name: &str,
521 functions: impl IntoIterator<Item = &'a abi::Function>,
522 records: &[abi::Record],
523 mappings: &[abi::Mapping],
524 storage_variables: &[abi::StorageVariable],
525 cs: &CompositeSource<'_>,
526) -> Vec<abi::Struct> {
527 let mut used_types: HashSet<abi::Path> = HashSet::new();
528
529 for function in functions {
531 for cp in &function.const_parameters {
532 collect_from_plaintext(&cp.ty, program_name, &mut used_types);
533 }
534 for input in &function.inputs {
535 collect_from_function_input(&input.ty, program_name, &mut used_types);
536 }
537 for output in &function.outputs {
538 collect_from_function_output(&output.ty, program_name, &mut used_types);
539 }
540 }
541
542 for record in records {
544 for field in &record.fields {
545 collect_from_plaintext(&field.ty, program_name, &mut used_types);
546 }
547 }
548
549 for mapping in mappings {
551 collect_from_plaintext(&mapping.key, program_name, &mut used_types);
552 collect_from_plaintext(&mapping.value, program_name, &mut used_types);
553 }
554
555 for sv in storage_variables {
557 collect_from_abi_storage_type(&sv.ty, program_name, &mut used_types);
558 }
559
560 let all_structs = cs.all_structs();
562 let all_records = cs.all_records();
563 collect_transitive_from_structs(&all_structs, program_name, &mut used_types);
564 collect_transitive_from_records(&all_records, program_name, &mut used_types);
565
566 all_structs.into_iter().filter(|s| used_types.contains(&s.path)).collect()
567}