codegen: prevent name duplication in Rust or Rhai

This commit is contained in:
J Henry Waugh 2020-08-19 23:12:39 -05:00
parent 8efde3c7ce
commit c87da31328
7 changed files with 158 additions and 4 deletions

View File

@ -18,6 +18,7 @@ pub(crate) struct ExportedFnParams {
pub name: Option<String>,
pub return_raw: bool,
pub skip: bool,
pub span: Option<proc_macro2::Span>,
}
impl ExportedFnParams {
@ -47,6 +48,7 @@ impl Parse for ExportedFnParams {
let arg_list = args.call(
syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_separated_nonempty,
)?;
let span = arg_list.span();
let mut attrs: HashMap<proc_macro2::Ident, Option<syn::LitStr>> = HashMap::new();
for arg in arg_list {
@ -125,6 +127,7 @@ impl Parse for ExportedFnParams {
name,
return_raw,
skip,
span: Some(span),
..Default::default()
})
}

View File

@ -12,6 +12,8 @@ use std::vec as new_vec;
#[cfg(no_std)]
use core::mem;
use std::collections::HashMap;
fn inner_fn_attributes(f: &mut syn::ItemFn) -> syn::Result<ExportedFnParams> {
if let Some(rhai_fn_idx) = f.attrs.iter().position(|a| {
a.path
@ -28,6 +30,48 @@ fn inner_fn_attributes(f: &mut syn::ItemFn) -> syn::Result<ExportedFnParams> {
}
}
fn check_rename_collisions(fns: &Vec<ExportedFn>) -> Result<(), syn::Error> {
let mut renames = HashMap::<String, proc_macro2::Span>::new();
let mut names = HashMap::<String, proc_macro2::Span>::new();
for itemfn in fns.iter() {
if let Some(ref name) = itemfn.params.name {
let current_span = itemfn.params.span.as_ref().unwrap();
let key = itemfn.arg_list().fold(name.clone(), |mut argstr, fnarg| {
let type_string: String = match fnarg {
syn::FnArg::Receiver(_) => unimplemented!("receiver rhai_fns not implemented"),
syn::FnArg::Typed(syn::PatType { ref ty, .. }) =>
ty.as_ref().to_token_stream().to_string(),
};
argstr.push('.');
argstr.extend(type_string.chars());
argstr
});
if let Some(other_span) = renames.insert(key,
current_span.clone()) {
let mut err = syn::Error::new(current_span.clone(),
format!("duplicate Rhai signature for '{}'", &name));
err.combine(syn::Error::new(other_span,
format!("duplicated function renamed '{}'", &name)));
return Err(err);
}
} else {
let ident = itemfn.name();
names.insert(ident.to_string(), ident.span());
}
}
for (new_name, attr_span) in renames.drain() {
let new_name = new_name.split('.').next().unwrap();
if let Some(fn_span) = names.get(new_name) {
let mut err = syn::Error::new(attr_span,
format!("duplicate Rhai signature for '{}'", &new_name));
err.combine(syn::Error::new(fn_span.clone(),
format!("duplicated function '{}'", &new_name)));
return Err(err);
}
}
Ok(())
}
#[derive(Debug)]
pub(crate) struct Module {
mod_all: Option<syn::ItemMod>,
@ -92,7 +136,15 @@ impl Parse for Module {
impl Module {
pub fn generate(self) -> proc_macro2::TokenStream {
// Check for collisions if the "name" attribute was used on inner functions.
if let Err(e) = check_rename_collisions(&self.fns) {
return e.to_compile_error();
}
// Perform the generation of new module items.
let mod_gen = crate::rhai_module::generate_body(&self.fns, &self.consts);
// Rebuild the structure of the module, with the new content added.
let Module { mod_all, .. } = self;
let mut mod_all = mod_all.unwrap();
let mod_name = mod_all.ident.clone();

View File

@ -187,12 +187,12 @@ mod duplicate_fn_rename {
pub mod my_adds {
use rhai::{FLOAT, INT};
#[rhai_fn(name = "add_f")]
#[rhai_fn(name = "add")]
pub fn add_float(f1: FLOAT, f2: FLOAT) -> FLOAT {
f1 + f2
}
#[rhai_fn(name = "add_i")]
#[rhai_fn(name = "add")]
pub fn add_int(i1: INT, i2: INT) -> INT {
i1 + i2
}
@ -211,9 +211,9 @@ fn duplicate_fn_rename_test() -> Result<(), Box<EvalAltResult>> {
let output_array = engine.eval::<Array>(
r#"import "Math::Advanced" as math;
let fx = get_mystic_number();
let fy = math::add_f(fx, 1.0);
let fy = math::add(fx, 1.0);
let ix = 42;
let iy = math::add_i(ix, 1);
let iy = math::add(ix, 1);
[fy, iy]
"#,
)?;

View File

@ -0,0 +1,33 @@
use rhai::plugin::*;
#[derive(Clone)]
pub struct Point {
x: f32,
y: f32,
}
#[export_module]
pub mod test_module {
pub use super::Point;
#[rhai_fn(name = "foo")]
pub fn test_fn(input: Point) -> bool {
input.x > input.y
}
#[rhai_fn(name = "foo")]
pub fn test_fn_2(input: Point) -> bool {
input.x < input.y
}
}
fn main() {
let n = Point {
x: 0.0,
y: 10.0,
};
if test_module::test_fn(n) {
println!("yes");
} else {
println!("no");
}
}

View File

@ -0,0 +1,17 @@
error: duplicate Rhai signature for 'foo'
--> $DIR/rhai_fn_rename_collision.rs:17:15
|
17 | #[rhai_fn(name = "foo")]
| ^^^^^^^^^^^^
error: duplicated function renamed 'foo'
--> $DIR/rhai_fn_rename_collision.rs:12:15
|
12 | #[rhai_fn(name = "foo")]
| ^^^^^^^^^^^^
error[E0433]: failed to resolve: use of undeclared type or module `test_module`
--> $DIR/rhai_fn_rename_collision.rs:28:8
|
28 | if test_module::test_fn(n) {
| ^^^^^^^^^^^ use of undeclared type or module `test_module`

View File

@ -0,0 +1,32 @@
use rhai::plugin::*;
#[derive(Clone)]
pub struct Point {
x: f32,
y: f32,
}
#[export_module]
pub mod test_module {
pub use super::Point;
#[rhai_fn(name = "foo")]
pub fn test_fn(input: Point) -> bool {
input.x > input.y
}
pub fn foo(input: Point) -> bool {
input.x < input.y
}
}
fn main() {
let n = Point {
x: 0.0,
y: 10.0,
};
if test_module::test_fn(n) {
println!("yes");
} else {
println!("no");
}
}

View File

@ -0,0 +1,17 @@
error: duplicate Rhai signature for 'foo'
--> $DIR/rhai_fn_rename_collision_oneattr.rs:12:15
|
12 | #[rhai_fn(name = "foo")]
| ^^^^^^^^^^^^
error: duplicated function 'foo'
--> $DIR/rhai_fn_rename_collision_oneattr.rs:17:12
|
17 | pub fn foo(input: Point) -> bool {
| ^^^
error[E0433]: failed to resolve: use of undeclared type or module `test_module`
--> $DIR/rhai_fn_rename_collision_oneattr.rs:27:8
|
27 | if test_module::test_fn(n) {
| ^^^^^^^^^^^ use of undeclared type or module `test_module`