Skip to content

Commit a0ee414

Browse files
feat(json-abi): support enum definitions (#1155)
* feat(json-abi): support enum definitions * refactor: move enum definitions into ToSolConfig * refactor(json-abi): simplify enum definitions API
1 parent fbc9b04 commit a0ee414

3 files changed

Lines changed: 329 additions & 35 deletions

File tree

crates/json-abi/src/to_sol.rs

Lines changed: 127 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use core::{
1313
ops::{Deref, DerefMut},
1414
};
1515

16+
type EnumDefinitions = BTreeMap<String, Vec<String>>;
17+
1618
/// Configuration for [`JsonAbi::to_sol`].
1719
#[derive(Clone, Debug)]
1820
#[allow(missing_copy_implementations)] // Future-proofing
@@ -21,6 +23,7 @@ pub struct ToSolConfig {
2123
enums_as_udvt: bool,
2224
for_sol_macro: bool,
2325
one_contract: bool,
26+
enum_definitions: EnumDefinitions,
2427
}
2528

2629
impl Default for ToSolConfig {
@@ -39,6 +42,7 @@ impl ToSolConfig {
3942
enums_as_udvt: true,
4043
for_sol_macro: false,
4144
one_contract: false,
45+
enum_definitions: EnumDefinitions::new(),
4246
}
4347
}
4448

@@ -49,14 +53,25 @@ impl ToSolConfig {
4953
self
5054
}
5155

52-
/// Sets whether to print `enum`s as user-defined value types (UDVTs) instead of `uint8`.
53-
/// Default: `true`.
56+
/// Sets whether to preserve enum types instead of printing them as `uint8`. Default: `true`.
57+
///
58+
/// Enums with a supplied definition are printed as enum declarations; otherwise they are
59+
/// printed as `uint8` user-defined value types. If disabled, supplied definitions are ignored.
5460
#[inline]
5561
pub const fn enums_as_udvt(mut self, yes: bool) -> Self {
5662
self.enums_as_udvt = yes;
5763
self
5864
}
5965

66+
/// Sets supplemental enum definitions used when formatting the ABI.
67+
pub fn enum_definitions(
68+
mut self,
69+
definitions: impl IntoIterator<Item = (String, Vec<String>)>,
70+
) -> Self {
71+
self.enum_definitions = definitions.into_iter().collect();
72+
self
73+
}
74+
6075
/// Sets whether to normalize the output for the [`sol!`] macro. Default: `false`.
6176
///
6277
/// [`sol!`]: https://docs.rs/alloy-sol-macro/latest/alloy_sol_macro/macro.sol.html
@@ -163,7 +178,13 @@ impl JsonAbi {
163178
};
164179
}
165180

166-
let mut its = InternalTypes::new(out.name, out.config.enums_as_udvt);
181+
let enum_definitions = core::mem::take(&mut out.config.enum_definitions);
182+
let mut its = InternalTypes::new(
183+
out.name,
184+
out.config.enums_as_udvt,
185+
out.config.one_contract,
186+
&enum_definitions,
187+
);
167188
its.visit_abi(self);
168189

169190
let one_contract = out.config.one_contract;
@@ -224,17 +245,31 @@ impl JsonAbi {
224245
}
225246

226247
/// Recursively collects internal structs, enums, and UDVTs from an ABI's items.
227-
struct InternalTypes<'a> {
248+
struct InternalTypes<'a, 'e> {
228249
name: &'a str,
229-
this_its: BTreeSet<It<'a>>,
230-
other: BTreeMap<&'a String, BTreeSet<It<'a>>>,
250+
this_its: BTreeSet<It<'a, 'e>>,
251+
other: BTreeMap<&'a String, BTreeSet<It<'a, 'e>>>,
231252
enums_as_udvt: bool,
253+
one_contract: bool,
254+
enums: &'e EnumDefinitions,
232255
}
233256

234-
impl<'a> InternalTypes<'a> {
257+
impl<'a, 'e> InternalTypes<'a, 'e> {
235258
#[allow(clippy::missing_const_for_fn)]
236-
fn new(name: &'a str, enums_as_udvt: bool) -> Self {
237-
Self { name, this_its: BTreeSet::new(), other: BTreeMap::new(), enums_as_udvt }
259+
fn new(
260+
name: &'a str,
261+
enums_as_udvt: bool,
262+
one_contract: bool,
263+
enums: &'e EnumDefinitions,
264+
) -> Self {
265+
Self {
266+
name,
267+
this_its: BTreeSet::new(),
268+
other: BTreeMap::new(),
269+
enums_as_udvt,
270+
one_contract,
271+
enums,
272+
}
238273
}
239274

240275
fn visit_abi(&mut self, abi: &'a JsonAbi) {
@@ -288,7 +323,17 @@ impl<'a> InternalTypes<'a> {
288323
}
289324
Some(InternalType::Enum { contract, ty }) => {
290325
if self.enums_as_udvt {
291-
self.extend_one(contract, It::new(ty, ItKind::Enum));
326+
let name = ty.split('[').next().unwrap();
327+
let name = name.rsplit('.').next().unwrap();
328+
let variants = if self.enums.is_empty() {
329+
None
330+
} else if let Some(contract) = contract {
331+
self.enums.get(&format!("{contract}.{name}")).map(Vec::as_slice)
332+
} else {
333+
self.enums.get(name).map(Vec::as_slice)
334+
}
335+
.filter(|variants| (1..=256).contains(&variants.len()));
336+
self.extend_one(contract, It::new(ty, ItKind::Enum(variants)));
292337
}
293338
}
294339
Some(it @ InternalType::Other { contract, ty }) => {
@@ -304,50 +349,89 @@ impl<'a> InternalTypes<'a> {
304349
}
305350
}
306351

307-
fn extend_one(&mut self, contract: &'a Option<String>, it: It<'a>) {
352+
fn extend_one(&mut self, contract: &'a Option<String>, it: It<'a, 'e>) {
353+
if self.one_contract && matches!(it.kind, ItKind::Enum(_)) {
354+
if let Some(items) = self.other.values_mut().find(|items| items.contains(&it)) {
355+
Self::insert_item(items, it);
356+
return;
357+
}
358+
if self.this_its.contains(&it) {
359+
Self::insert_item(&mut self.this_its, it);
360+
return;
361+
}
362+
}
363+
308364
let contract = contract.as_ref();
309-
if let Some(contract) = contract {
365+
let items = if let Some(contract) = contract {
310366
if contract == self.name {
311-
self.this_its.insert(it);
367+
&mut self.this_its
312368
} else {
313-
self.other.entry(contract).or_default().insert(it);
369+
self.other.entry(contract).or_default()
314370
}
315371
} else {
316-
self.this_its.insert(it);
372+
&mut self.this_its
373+
};
374+
Self::insert_item(items, it);
375+
}
376+
377+
fn insert_item(items: &mut BTreeSet<It<'a, 'e>>, it: It<'a, 'e>) {
378+
if let ItKind::Enum(variants) = it.kind {
379+
let variants = items.take(&it).map_or(variants, |existing| {
380+
let ItKind::Enum(existing) = existing.kind else { unreachable!() };
381+
match (existing, variants) {
382+
(Some(existing), Some(variants)) if existing == variants => Some(existing),
383+
_ => None,
384+
}
385+
});
386+
items.insert(It::new(it.name, ItKind::Enum(variants)));
387+
} else {
388+
items.insert(it);
317389
}
318390
}
319391
}
320392

321393
/// An internal ABI type.
322394
#[derive(PartialEq, Eq, PartialOrd, Ord)]
323-
struct It<'a> {
395+
struct It<'a, 'e> {
324396
// kind must come before name for `Ord`
325-
kind: ItKind<'a>,
397+
kind: ItKind<'a, 'e>,
326398
name: &'a str,
327399
}
328400

329-
#[derive(PartialEq, Eq)]
330-
enum ItKind<'a> {
331-
Enum,
401+
enum ItKind<'a, 'e> {
402+
Enum(Option<&'e [String]>),
332403
Udvt(&'a str),
333404
Struct(&'a Vec<Param>),
334405
}
335406

407+
impl PartialEq for ItKind<'_, '_> {
408+
fn eq(&self, other: &Self) -> bool {
409+
matches!(
410+
(self, other),
411+
(Self::Enum(_), Self::Enum(_))
412+
| (Self::Udvt(_), Self::Udvt(_))
413+
| (Self::Struct(_), Self::Struct(_))
414+
)
415+
}
416+
}
417+
418+
impl Eq for ItKind<'_, '_> {}
419+
336420
// implemented manually because `Param: !Ord`
337-
impl PartialOrd for ItKind<'_> {
421+
impl PartialOrd for ItKind<'_, '_> {
338422
#[inline]
339423
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
340424
Some(self.cmp(other))
341425
}
342426
}
343427

344-
impl Ord for ItKind<'_> {
428+
impl Ord for ItKind<'_, '_> {
345429
#[inline]
346430
fn cmp(&self, other: &Self) -> Ordering {
347431
match (self, other) {
348-
(Self::Enum, Self::Enum) => Ordering::Equal,
349-
(Self::Enum, _) => Ordering::Less,
350-
(_, Self::Enum) => Ordering::Greater,
432+
(Self::Enum(_), Self::Enum(_)) => Ordering::Equal,
433+
(Self::Enum(_), _) => Ordering::Less,
434+
(_, Self::Enum(_)) => Ordering::Greater,
351435

352436
(Self::Udvt(_), Self::Udvt(_)) => Ordering::Equal,
353437
(Self::Udvt(_), _) => Ordering::Less,
@@ -358,9 +442,9 @@ impl Ord for ItKind<'_> {
358442
}
359443
}
360444

361-
impl<'a> It<'a> {
445+
impl<'a, 'e> It<'a, 'e> {
362446
#[inline]
363-
fn new(ty_name: &'a str, kind: ItKind<'a>) -> Self {
447+
fn new(ty_name: &'a str, kind: ItKind<'a, 'e>) -> Self {
364448
Self {
365449
kind,
366450
// `ty_name` might be an array, we just want the identifier
@@ -369,10 +453,22 @@ impl<'a> It<'a> {
369453
}
370454
}
371455

372-
impl ToSol for It<'_> {
456+
impl ToSol for It<'_, '_> {
373457
fn to_sol(&self, out: &mut SolPrinter<'_>) {
374-
match self.kind {
375-
ItKind::Enum => {
458+
match &self.kind {
459+
ItKind::Enum(Some(variants)) => {
460+
out.push_str("enum ");
461+
out.push_ident(self.name);
462+
out.push_str(" { ");
463+
for (i, variant) in variants.iter().enumerate() {
464+
if i > 0 {
465+
out.push_str(", ");
466+
}
467+
out.push_ident(variant);
468+
}
469+
out.push_str(" }");
470+
}
471+
ItKind::Enum(None) => {
376472
out.push_str("type ");
377473
out.push_ident(self.name);
378474
out.push_str(" is uint8;");
@@ -388,7 +484,7 @@ impl ToSol for It<'_> {
388484
out.push_str("struct ");
389485
out.push_ident(self.name);
390486
out.push_str(" {\n");
391-
for component in components {
487+
for component in components.iter() {
392488
out.indent();
393489
out.indent();
394490
component.to_sol(out);

0 commit comments

Comments
 (0)