diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs index 720d7b1e..4d54aea2 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs @@ -558,7 +558,7 @@ fn expand_function_name(function: &Function, alias: Option<&MethodAlias>) -> Ide if let Some(alias) = alias { alias.function_name.clone() } else { - util::safe_ident(&function.name.to_snake_case()) + util::safe_ident(&util::safe_snake_case(&function.name)) } } @@ -567,7 +567,7 @@ fn expand_call_struct_name(function: &Function, alias: Option<&MethodAlias>) -> let name = if let Some(alias) = alias { format!("{}Call", alias.struct_name) } else { - format!("{}Call", function.name.to_pascal_case()) + format!("{}Call", util::safe_pascal_case(&function.name)) }; util::ident(&name) } @@ -577,7 +577,7 @@ fn expand_call_struct_variant_name(function: &Function, alias: Option<&MethodAli if let Some(alias) = alias { alias.struct_name.clone() } else { - util::safe_ident(&function.name.to_pascal_case()) + util::safe_ident(&util::safe_pascal_case(&function.name)) } } diff --git a/ethers-contract/ethers-contract-abigen/src/util.rs b/ethers-contract/ethers-contract-abigen/src/util.rs index b13b5772..d9c812e0 100644 --- a/ethers-contract/ethers-contract-abigen/src/util.rs +++ b/ethers-contract/ethers-contract-abigen/src/util.rs @@ -21,6 +21,25 @@ pub fn safe_ident(name: &str) -> Ident { syn::parse_str::(name).unwrap_or_else(|_| ident(&format!("{}_", name))) } +/// Converts a `&str` to `snake_case` `String` while respecting identifier rules +pub fn safe_snake_case(ident: &str) -> String { + safe_identifier_name(ident.to_snake_case()) +} + +/// Converts a `&str` to `PascalCase` `String` while respecting identifier rules +pub fn safe_pascal_case(ident: &str) -> String { + safe_identifier_name(ident.to_pascal_case()) +} + +/// respects identifier rules, such as, an identifier must not start with a numeric char +fn safe_identifier_name(name: String) -> String { + if name.starts_with(|c: char| c.is_numeric()) { + format!("_{}", name) + } else { + name + } +} + /// Expands an identifier as snakecase and preserve any leading or trailing underscores pub fn safe_snake_case_ident(name: &str) -> Ident { let i = name.to_snake_case(); diff --git a/ethers-contract/tests/abigen.rs b/ethers-contract/tests/abigen.rs index 04570190..4afcec6f 100644 --- a/ethers-contract/tests/abigen.rs +++ b/ethers-contract/tests/abigen.rs @@ -374,6 +374,23 @@ fn can_handle_unique_underscore_functions() { let _contract_call = ConsoleLogCalls::Log2(call); } +#[test] +fn can_handle_underscore_numeric() { + abigen!( + Test, + r#"[ + _100pct(string) + ]"# + ); + let call = _100PctCall("message".to_string()); + + let provider = Arc::new(Provider::new(MockProvider::new())); + let contract = Test::new(Address::default(), Arc::clone(&provider)); + // NOTE: this seems to be weird behaviour of `Inflector::to_snake_case` which turns "100pct" -> + // "10_0pct" + let _call = contract._10_0pct("hello".to_string()); +} + #[test] fn can_handle_duplicates_with_same_name() { abigen!(