fix: preserve underscores in case of collisions (#548)

* fix: overloaded with same type arguments

* fix: numerate overloaded functions

* docs: add note

* chore: update changelog

* style: rename vars

* fix: draft underscore fix

* fix: underscore aliases
This commit is contained in:
Matthias Seitz 2021-11-01 00:06:31 +01:00 committed by GitHub
parent 1cb43a3df3
commit a07838eaf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 92 additions and 14 deletions

View File

@ -1,4 +1,4 @@
use std::collections::BTreeMap; use std::collections::{btree_map::Entry, BTreeMap};
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use inflector::Inflector; use inflector::Inflector;
@ -232,7 +232,7 @@ impl Context {
/// Expands a single function with the given alias /// Expands a single function with the given alias
fn expand_function(&self, function: &Function, alias: Option<Ident>) -> Result<TokenStream> { fn expand_function(&self, function: &Function, alias: Option<Ident>) -> Result<TokenStream> {
let name = alias.unwrap_or_else(|| util::safe_ident(&function.name.to_snake_case())); let name = expand_function_name(function, alias.as_ref());
let selector = expand_selector(function.selector()); let selector = expand_selector(function.selector());
// TODO use structs // TODO use structs
@ -278,7 +278,6 @@ impl Context {
// no conflicts // no conflicts
continue continue
} }
// sort functions by number of inputs asc // sort functions by number of inputs asc
let mut functions = functions.iter().collect::<Vec<_>>(); let mut functions = functions.iter().collect::<Vec<_>>();
functions.sort_by(|f1, f2| f1.inputs.len().cmp(&f2.inputs.len())); functions.sort_by(|f1, f2| f1.inputs.len().cmp(&f2.inputs.len()));
@ -305,7 +304,7 @@ impl Context {
} }
let alias = match diff.len() { let alias = match diff.len() {
0 => { 0 => {
// this should not happen since functions with same name and input are // this should not happen since functions with same name and inputs are
// illegal // illegal
anyhow::bail!( anyhow::bail!(
"Function with same name and parameter types defined twice: {}", "Function with same name and parameter types defined twice: {}",
@ -355,6 +354,25 @@ impl Context {
aliases.insert(first.abi_signature(), util::safe_ident(&prev_alias)); aliases.insert(first.abi_signature(), util::safe_ident(&prev_alias));
} }
} }
// we have to handle the edge cases with underscore prefix and suffix that would get
// stripped by Inflector::to_snake_case/pascalCase if there is another function that
// would collide we manually add an alias for it eg. abi = ["_a(), a(), a_(),
// _a_()"] will generate identical rust functions
for (name, functions) in self.abi.functions.iter() {
if name.starts_with('_') || name.ends_with('_') {
let ident = name.trim_matches('_').trim_end_matches('_');
// check for possible collisions after Inflector would remove the underscores
if self.abi.functions.contains_key(ident) {
for function in functions {
if let Entry::Vacant(entry) = aliases.entry(function.abi_signature()) {
// use the full name as alias
entry.insert(util::ident(name.as_str()));
}
}
}
}
}
Ok(aliases) Ok(aliases)
} }
} }
@ -378,10 +396,27 @@ fn expand_selector(selector: Selector) -> TokenStream {
quote! { [#( #bytes ),*] } quote! { [#( #bytes ),*] }
} }
fn expand_function_name(function: &Function, alias: Option<&Ident>) -> Ident {
if let Some(alias) = alias {
// snake_case strips leading and trailing underscores so we simply add them back if the
// alias starts/ends with underscores
let alias = alias.to_string();
let ident = alias.to_snake_case();
util::ident(&util::preserve_underscore_delim(&ident, &alias))
} else {
util::safe_ident(&function.name.to_snake_case())
}
}
/// Expands to the name of the call struct /// Expands to the name of the call struct
fn expand_call_struct_name(function: &Function, alias: Option<&Ident>) -> Ident { fn expand_call_struct_name(function: &Function, alias: Option<&Ident>) -> Ident {
let name = if let Some(id) = alias { let name = if let Some(alias) = alias {
format!("{}Call", id.to_string().to_pascal_case()) // pascal_case strips leading and trailing underscores so we simply add them back if the
// alias starts/ends with underscores
let alias = alias.to_string();
let ident = alias.to_pascal_case();
let alias = util::preserve_underscore_delim(&ident, &alias);
format!("{}Call", alias)
} else { } else {
format!("{}Call", function.name.to_pascal_case()) format!("{}Call", function.name.to_pascal_case())
}; };
@ -390,8 +425,10 @@ fn expand_call_struct_name(function: &Function, alias: Option<&Ident>) -> Ident
/// Expands to the name of the call struct /// Expands to the name of the call struct
fn expand_call_struct_variant_name(function: &Function, alias: Option<&Ident>) -> Ident { fn expand_call_struct_variant_name(function: &Function, alias: Option<&Ident>) -> Ident {
let name = if let Some(id) = alias { let name = if let Some(alias) = alias {
id.to_string().to_pascal_case() let alias = alias.to_string();
let ident = alias.to_pascal_case();
util::preserve_underscore_delim(&ident, &alias)
} else { } else {
function.name.to_pascal_case() function.name.to_pascal_case()
}; };

View File

@ -94,6 +94,17 @@ pub fn safe_ident(name: &str) -> Ident {
syn::parse_str::<SynIdent>(name).unwrap_or_else(|_| ident(&format!("{}_", name))) syn::parse_str::<SynIdent>(name).unwrap_or_else(|_| ident(&format!("{}_", name)))
} }
/// Reapplies leading and trailing underscore chars to the ident
/// Example `ident = "pascalCase"; alias = __pascalcase__` -> `__pascalCase__`
pub fn preserve_underscore_delim(ident: &str, alias: &str) -> String {
alias
.chars()
.take_while(|c| *c == '_')
.chain(ident.chars())
.chain(alias.chars().rev().take_while(|c| *c == '_'))
.collect()
}
/// Expands a positional identifier string that may be empty. /// Expands a positional identifier string that may be empty.
/// ///
/// Note that this expands the parameter name with `safe_ident`, meaning that /// Note that this expands the parameter name with `safe_ident`, meaning that
@ -159,16 +170,18 @@ mod tests {
#[test] #[test]
fn parse_address_missing_prefix() { fn parse_address_missing_prefix() {
if parse_address("0000000000000000000000000000000000000000").is_ok() { assert!(
panic!("parsing address not starting with 0x should fail"); !parse_address("0000000000000000000000000000000000000000").is_ok(),
} "parsing address not starting with 0x should fail"
);
} }
#[test] #[test]
fn parse_address_address_too_short() { fn parse_address_address_too_short() {
if parse_address("0x00000000000000").is_ok() { assert!(
panic!("parsing address not starting with 0x should fail"); !parse_address("0x00000000000000").is_ok(),
} "parsing address not starting with 0x should fail"
);
} }
#[test] #[test]

View File

@ -321,3 +321,31 @@ async fn can_handle_underscore_functions() {
assert_eq!(res, res4); assert_eq!(res, res4);
assert_eq!(res, res5); assert_eq!(res, res5);
} }
#[test]
fn can_handle_unique_underscore_functions() {
abigen!(
ConsoleLog,
r#"[
log(string, string)
_log(string)
_log_(string)
__log__(string)
__log2__(string)
]"#
);
let call = LogCall("message".to_string(), "message".to_string());
let _contract_call = ConsoleLogCalls::Log(call);
let call = _LogCall("message".to_string());
let _contract_call = ConsoleLogCalls::_Log(call);
let call = _Log_Call("message".to_string());
let _contract_call = ConsoleLogCalls::_Log_(call);
let call = __Log__Call("message".to_string());
let _contract_call = ConsoleLogCalls::__Log__(call);
let call = Log2Call("message".to_string());
let _contract_call = ConsoleLogCalls::Log2(call);
}