Code Monkey home page Code Monkey logo

duplicate's People

Contributors

bash avatar elihunter173 avatar emoun avatar eupn avatar laarmen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

duplicate's Issues

How to use with conditional compilation (cfg-macro)?

I use the duplicate macro to generate a sync and a async version of a function. I would like to include the async version only if the feature async is enabled. Is there any way to achieve that?

The following code, does not work. I guess this is because the cfg macro has a "higher" priority as the duplicate_item macro and therefore it just says "code is inactive due to #[cfg] directives: cfg_filter is disabled"

#[duplicate_item(
    fn_name             cfg_filter        async;
    [my_function]       [all()]           [];
    [my_function_async] [feature="async"] [async];
)]
#[cfg(cfg_filter)]
async fn fn_name() {
    todo!()
}

Thanks a lot in advance!

Add inline version of `duplicate`

Introduce a function-like procedural macro with the same functionality as duplicate.

duplicate can only be used where attributes are allowed. Notably, attributes are currently not allowed on statements. This is a significant limitation. Therefore, we can alleviate this by introducing a function-like version of duplicate that can be used where the original is not allowed.

Example:

fn main() {
  duplicate_inline!{
    #[
      name    value; 
      [var1]  [1usize]; 
      [var2]  [2usize]; 
      [var3]  [2usize]
    ]
    let name = value;
  }
}

Expands to:

fn main() {
    let var1 = 1usize;
    let var2 = 2usize;
    let var3 = 3usize;
}

MSRV policy

Short Description:

Decide on a "Minimum Supported Rust Version" (MSRV).

Motivation:

#17 proposes changes to the implementation to allow the crate to work on rust 1.34.
This is a clear indication of a need for an official MSRV from a direct user of the crate.
The reason for using 1.34 specifically is that apparently the current Debian stable has this version installed.
This could be a good starting point for an MSRV as its backwards compatible with the current implementation, and it can be lowered even more in the future, without having to bump the major version. And from #17 it doesn't seem like we depend on important features of newer rust versions.

Design

Commit to an official MSRV of 1.34

Misc:

  • This crate currently has no MSRV, however, the CI setup does test against rust 1.45 specifically. So, in a way 1.45 is our current MSRV.

Unresolved Questions

  • - Do all our dependencies have MSRVs that are compatible with this proposal
  • - What features have been introduced since 1.34 and are we sure we can live without them in the future.

Confusing error message when not using parametric substitution correctly

Situation:

When trying to use parametric substitution but forgetting to encapsulate the argument to a parametrized identifier in [], the resulting error message is confusing.

Reproduction:

A simple example is to try and create a duplicate with and without &:

#[duplicate(refs(T); [& T]; [T])]
fn from(x: refs(Bits<1, false>)) -> bool {
    x.value == 1
}

Expected Behavior:

An error message pointing out that the argument to refs is not encapsulated in [].

Actual Behavior:

The error message:

message: Error substituting identifier with arguments: ()

Affected Versions:

Only tested on 0.3.0

Local Environment:

Using default features

Miscellaneous:

Build failure: crate `proc_macro` is private

When I try to build the project on any of machines, I get the following error. My guess is that an import was misplaced but I don't know why the CI builds wouldn't catch it.

  Downloaded version_check v0.9.2
  Downloaded proc-macro-error v1.0.3
  Downloaded proc-macro-error-attr v1.0.3
  Downloaded quote v1.0.7
  Downloaded syn-mid v0.5.0
  Downloaded unicode-xid v0.2.1
  Downloaded syn v1.0.33
  Downloaded 7 crates (301.2 KB) in 0.66s
   Compiling proc-macro2 v1.0.18
   Compiling unicode-xid v0.2.1
   Compiling version_check v0.9.2
   Compiling syn v1.0.33
   Compiling proc-macro-error-attr v1.0.3
   Compiling proc-macro-error v1.0.3
   Compiling quote v1.0.7
   Compiling syn-mid v0.5.0
   Compiling duplicate v0.0.0 (/tmp/duplicate)
error[E0603]: crate `proc_macro` is private
   --> src/parse_utils.rs:3:2
    |
3   |     proc_macro::{Punct, Spacing},
    |     ^^^^^^^^^^ private crate
    |
note: the crate `proc_macro` is defined here
   --> /home/eli/.cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro-error-1.0.3/src/lib.rs:256:1
    |
256 | extern crate proc_macro;
    | ^^^^^^^^^^^^^^^^^^^^^^^^

warning: unused import: `proc_macro_error::*`
 --> src/parse.rs:6:5
  |
6 | use proc_macro_error::*;
  |     ^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: unused import: `*`
 --> src/parse_utils.rs:4:2
  |
4 |     *,
  |     ^

error: aborting due to previous error; 2 warnings emitted

For more information about this error, try `rustc --explain E0603`.
error: could not compile `duplicate`.

To learn more, run the command again with --verbose.

This patch should fix it. I'll submit a PR after I test it and also replace the wildcard imports.

index 6a7fd06..96061a7 100644
--- a/src/parse_utils.rs
+++ b/src/parse_utils.rs
@@ -1,8 +1,5 @@
-use proc_macro::{Delimiter, Group, Span, TokenTree};
-use proc_macro_error::{
-	proc_macro::{Punct, Spacing},
-	*,
-};
+use proc_macro::{Delimiter, Group, Punct, Spacing, Span, TokenTree};
+use proc_macro_error::*;
 
 /// Tries to parse a valid group from the given token stream iterator, returning
 /// the group if successful.

Global substitutions: substitute variables without adding duplicates

Short Description:

Make it possible to add substitution variables without adding more duplication of the code.

Motivation:

A useful feature of this crate is the ability to reduce repetition without adding duplications:

#[duplicate(
  typ; [i32]
)]
fn some_func(in: &typ) -> typ{
  ..
}

Here we see how by only defining 1 duplication, we can reuse typ in the function.
This can be very useful where some piece of code is repeated many times in the same item.
Duplication is not needed, but we want to be able to insert some piece of code in many places.

While the above is currently possible, it cannot be used if duplication is also wanted.
In that case we would either need multiple calls to duplicate or to repeat the body of the substitution variable for each duplication.

This proposal introduces first class support for global substitutions, which are substitutions that don't require duplication.

Design

An insertion variable is a substitution variable that is only used for global substitution .
We declare an insertion variable using first an identifier, possibly followed by a parameter list, followed by a substitution and ended with semicolon:

#[duplicate(
  reference(type) [ & type];
)]
fn some_func(in: reference([i32])) -> reference([i64]){
  ..
}

This syntax is identical to how a substitution is declared in the verbose syntax and should therefore be natural to use and understand.
To increase the distinction between global substitution and duplication, we could enforce that all insertions must come at the beginning followed by the duplication declarations. If deemed unnecessary, this could be relaxed in the future without needing a major version bump.

Misc:

  • Should insertion variables be substituted in the bodies of succeeding insertion variables and the bodies of duplication substitution variables?

While this seems like a useful feature, allowing the use of insertion variables directly in the duplication declarations, it could also lead to confusion if the user doesn't intend to do it. Additionally, it also disallows the use of insertion variable identifiers in the duplication substitutions where insertions aren't wanted.
If we don't have this, reusing insertion variables would still be possible by putting all the needed insertions in a preceding call. This way the user can choose themselves whether they want to reuse the identifiers or the insertions.
Conclusion: No.

  • Does this feature overlap with the use cases for nested invocation to a point where we could omit one of them?

While my original use case for nested invocations could be make use of insertions instead, nested invocations do have a power that cannot be replicated. Nested invocations allow exponential increases in duplication numbers by having one nested substitution group expand to multiple substitution groups in the parent.

Expanded code using edition 2018 instead of 2021

I've found out that because this crate uses rust edition 2018, the code that we write inside of duplicate! {} macro is also forced to use edition 2018.

For example the following code panics with the message {a} instead of using the format args interpolation that was added in edition 2021:

fn main() {
    duplicate::duplicate! {
        [foo; [];]

        let a = 42;

        panic!("{a}");
    }
}

Also, the compiler warns about this when compiling:

warning: panic message contains an unused formatting placeholder
 --> crates/scratch/src/main.rs:7:17
  |
7 |         panic!("{a}");
  |                 ^^^
  |
  = note: `#[warn(non_fmt_panics)]` on by default
  = note: this message is not used as a format string when given without arguments, but will be in Rust 2021
help: add the missing argument
  |
7 |         panic!("{a}", ...);
  |                     +++++
help: or add a "{}" format string to use the message literally
  |
7 |         panic!("{}", "{a}");
  |                +++++

I've stumbled with this behavior and had to think really hard to deduce where the 2018 edition comes from because my crate where I use this macro is of edition 2021 ๐Ÿคจ.

Anyway, I suppose this is a good reason to migration to the new edition.

Lock down brackets

Short Description:

Change the syntax to lock down which bracket types are allowed in various syntactic positions.

Motivation:

The current syntax doesn't distinguish between bracket types anywhere. This was meant to allow users to choose their preferred brackets in various positions.

After doing a survey of project on Github using duplicate, it seems no one makes use of the bracket flexibility. They all simply seem to follow the examples of the documentation.

The flexibility does have its downsides. First, it makes future updates to the crate harder (see e.g. #23). Second, it allows fragmentation in syntax, where some code bases might use one set of brackets in a given position and another code bases could use another set of brackets in the same position. This makes it more difficult to parse for users. It also makes it more difficult for people without knowledge of this crate to figure out if the given set of brackets means anything special (which it doesn't current, which is counterintuitive).

Design

We define a specific bracket type for each position in the syntax. We will use the brackets as they are currently used in the documentation examples. This means even though the change is technically breaking, most users wont notice, as they are already using the right brackets.

Syntax Position Bracket Type Example
Substitution [] [ code to be substituted ]
Substitution Parameter List () sub_ident(param1, param2)
Substitution Application Argument [] sub_ident([arg1], [arg2])
Nested Invocation [] #[ .. ][ .. ]
Verbose syntax substitution group [] [sub1 [ .. ] sub2 [ .. ]]
duplicate_inline! invocation [] duplicate_inline!( [ .. ] .. )

Misc:

How to use either `ref` or `ref mut`?

How can I use duplicate to use either ref or ref mut for pattern matching?

This doesn't work:

#[duplicate(
  method    reference(type) ref_;
  [get]     [& type]        [ref];
  [get_mut] [&mut type]     [ref mut];
)]
fn method(v: reference([(u64, u64)])) -> reference([u64]) {
  match v {
    (ref_ a, b) => a
  }
}

Duplicating it manually works:

fn get(v: &(u64, u64)) -> &u64 {
  match v {
    (ref a, b) => a
  }
}
fn get_mut(v: &mut (u64, u64)) -> &mut u64 {
  match v {
    (ref mut a, b) => a
  }
}

I know that in this case the code actually compiles without the ref/ref mut, but in more complex cases it might not.

`duplicate` doesn't substitute in nested duplicate invocation

Situation:

Using duplicate_item, it is possible to substitute things in a nested (or lower) duplicate_item's invocation:

#[duplicate::duplicate_item(
	vals	[val; [41]; [42];];
)]
#[duplicate::duplicate_item(
	vals
)]
impl SomeType<val> for (){}

Here, vals substitutes into a valid invocation val; [41]; [42];. This is then used in the next duplicate_item as the invocation. This works fine and as expected, however, trying to do the same using duplicate doesn't work

Reproduction:

duplicate::duplicate!{
	[vals	[val; [41]; [42];];]
	duplicate!{
		[vals]
		impl SomeType<v2> for (){}
	}
}

Expected Behavior:

Expand to:

impl SomeType<41> for () {}
impl SomeType<42> for () {}

Actual Behavior:

Error is thrown:

error: Unexpected end of code. Expected substitution identifier or ';' instead.
  --> tests\tests.rs:16:1
   |
16 | / duplicate::duplicate!{
17 | |     [vals    [val; [41]; [42];];]
18 | |     duplicate!{
19 | |         [vals]
20 | |         impl SomeType<v2> for (){}
21 | |     }
22 | | }
   | |_^
   |
   = note: this error originates in the macro `duplicate::duplicate` (in Nightly builds, run with -Z macro-backtrace for more info)

Affected Versions:

0.4.1 and likely all since nested invocation was introduced.

Local Environment:

Miscellaneous:

Version 1.0.0

Short Description:

Release a version 1.0.0

Motivation:

Releasing a stable version of the crate signals to users that the crate is generally usable and can be depended upon.

Design

The following work is open and should be finished before 1.0.0:

  • Overhaul default error messaging and test the error messages.
  • The pretty_errors feature also needs to be more complete and tested.
  • #28
  • #40
  • MSRV
  • #47

Misc:

Error when substitution includes braces

Duplicate v0.2.3 has a regression where substitutions that includes any bracket type result in the wrong expansion:

#[duplicate(
	fn_name     var;
	[fn_name_1] [ Struct() ];
	[fn_name_2] [ array[42] ];
	[fn_name_3] [ Struct{} ];
)]
fn fn_name() {
	let _ = var;
}

Expands to:

fn fn_name_1() {
    let _ = ();
    Struct;
}
fn fn_name_2() {
    let _ = [42];
    array;
}
fn fn_name_3() {
    let _ = {};
    Struct;
}

Instead of:

fn fn_name_1() {
    let _ = Struct();
}
fn fn_name_2() {
    let _ = array[42];
}
fn fn_name_3() {
    let _ = Struct{};
}

Global substitutions followed by short syntax without duplicates

Situation:

Following global substitutions with a substitutions variable list for short syntax, but not following that with any substitution groups, will successfully compile.

Even though current behavior is workable, this will probably either be a mistake or lead to confusion about what happens to the substitution variable that don't have substitutions.
Giving an error also make the behavior match the old behavior before global substitutions (i.e. an error was thrown).

Reproduction:

#[duplicate::duplicate(
	ty 		[i16];
	name;
)]
pub struct name(ty);

Expected Behavior:

Fail to compile with a message about having no substitutions groups in the short syntax.

Actual Behavior:

pub struct name(ty);

Affected Versions:

0.3.0

Local Environment:

Miscellaneous:

Different short syntax

Based on this comment.

Change the short syntax so that substitution groups are on rows instead of columns:

#[duplicate(
  ident1          ident2; // All substitution identifiers on one line (ending with ';' )
  [ident1_subs1]  [ident2_subst1];// group1
  [ident1_subs2]  [ident2_subst2];// group2
)]

Possible to convert substitution token to static string.

Hi,
First of all, I'd like to thank you for making this great library, it saves so much time when binding to C FFI with many similar functions!

Now, one thing I'd like to do is to have access the "token" in the implementation block as a static str.

#duplicate([
_val_type [f32]
_storage [StorageType::Float]
_get [HAPI_GetAttributeFloatData]
_set [HAPI_SetAttributeFloatData]
_get_array [HAPI_GetAttributeFloatArrayData]
_set_array [HAPI_SetAttributeFloatArrayData]
]
impl AttribAccess for _val_type {
    fn set(...) {
         // be able to use `_set` ident as `&'static str` since some APIs require it.
    }
}

Maybe it's already possible today? I couldn't figure out, since those tokens are not literals, I couldn't convert them to a string.
Could duplicate provide a way to achieve this? Thanks!

Nested invocation in substitutions

Short Description:

Enable nested invocations inside substitution blocks.

Motivation:

Take #26, where we have many struct members needing to be instantiated to the same value:

impl Example {
  fn inline_new() -> Self {
    Example { one: 0, two: 0, .. }
  }

  fn attr_new() -> Self {
    Example {one: 0, two: 0, ..}
  }
}

We would like to be able to make a single invocation that inserts the instantiations many places. The closest we get today is:

#[duplicate(
  members [one: 0, two: 0, ..]
)]
impl Example {
  fn inline_new() -> Self {
    Example {members}
  }

  fn attr_new() -> Self {
    Example {members}
  }
}

However, this repeats the :0 , bit for every member.

Nested invocation would be able to remove this repetition. However, it is not available inside substitutions. Enabling it, would allow us to do:

#[duplicate(
  members [ #duplicate[
      mem; [one]; [two]
    ][ 
      mem: 0, 
  ]]
)]
impl Example {
  fn inline_new() -> Self {
    Example {members}
  }

  fn attr_new() -> Self {
    Example {members}
  }
}

Design

The first problem with enabling nested invocations inside substitutions is how to handle user attributes. Since we currently use the syntax #[..][..] to denote nested invocation, this prohibits users from using attributes inside substitutions, as they would be assumed to be nested invocations.

To alleviate this, we change the syntax of nested invocations to #duplicate[..][..]. This is generally illegal Rust syntax, so no user would write this outside specifically when nested invocations are wanted. However, it is valid syntax, allowing us to parse it.

This is, if course, a breaking change, as it will also be the syntax use for existing nested invocations.

Misc:

Syntax alternatives

  1. #[duplicate( .. )]: This mimics top-level invocations. However, it means we cannot distinguish between nested invocations and new top-level invocations that happen to be in the substitutions.

Implicit module name duplication

Since procedural macros will soon support hygiene, it would be appropriate to implement implicit module name duplication.

if duplicate is applied to a module, the user is currently required to make sure that the module has a unique name for every duplicate. This is tedious and must be done every time. To improve the user experience, duplicate could use hygiene to allow all duplicates to keep the same module name, but with different hygiene. This means the user no longer has to manually assign names to each module duplicate.

The rule could be that if duplicate is applied to a module directly, hygienic renaming will kick in, however, if the module is already renamed by a substitution identifier, the hygienic renaming won't happen. This should therefore be a non-breaking change.

allow cfg attributes on substitution groups

Short Description:

Allows the use of cfg attributes on substitution groups, such that they can be enabled and disabled at build time.

Motivation:

See this code.

Here, they use two cfg_attrs to remove one substitution group based on whether a specific feature is enabled.
This is a bit ugly, as the two invocations of duplicate are identical except for the second substitution group not being present on the second invocation.
Therefore, the first substitution group is actually copied between the two invocations.

It would be better to be able to, within the invocation itself, be able to disable a substitution group using a method akin to cfg_attr.

Design

This could be one way of designing it (this is using verbose syntax to match that of the above example):

duplicate_item(
    [
        module_type      [non_blocking]
        maybe_async_attr [maybe_async::must_be_async]
        File             [tokio::fs::File]
        HttpBody         [reqwest::Body]
        HttpClient       [reqwest::Client]
    ]
    #[cfg_attr(feature = "non_blocking")]
    [
        module_type      [blocking]
        maybe_async_attr [maybe_async::must_be_sync]
        File             [std::fs::File]
        HttpBody         [reqwest::blocking::Body]
        HttpClient       [reqwest::blocking::Client]
    ]
))]

This will disable the second substitution group when the non_blocking feature is disabled, exactly like the current semantics of the above example.

This would work the same way for the short syntax and global substitutions (where each attribute would work on just one substitution group or substitution).

Misc:

Open questions:

  • Could this be implemented by calling cfg_attr directly from duplicate, such that it can return an empty string when the cfg is false?
  • Can this be extended to any attribute macro and not just cfg or cfg_attr?

duplicate! as a macro-by-example

Introduction

In the last days I made this repo https://github.com/Rimpampa/variants which I later found out it does the same thing as this crate but instead of using a procedural macro it uses a macro-by-example (or declarative macro).

Now instead of making a new crate I was wondering if there was any interest in adding this version of the macro to this crate.

Differences

There are some differences in the syntax, to give an example here is the examples in the docs made with my macro:

// 1st example
variants!([$] int_type | max_value: u8 | 255, u16 | 65_535, u32 | 4_294_967_295 => {
    impl IsMax for $int_type {
        fn is_max(&self) -> bool {
            *self == $max_value
        }
    }
});

// 2nd example
impl<T> VecWrap<T> {
    variants!([$] method: get, get_mut => {
        pub fn $method<'a>(
            self: select!(get: {&'a Self}, get_mut: {&'a mut Self}),
            idx: usize,
        ) -> Option<select!(get: {&'a T}, get_mut: {&'a mut T}> {
            self.0.$method(idx)
        }
    });
}

The main difference is the use of $var syntax for substitution instead of directly using the declared identifier and the usage of the select! macro.
Naturally it's not as clean as using a procedural macro and it currently doesn't support nested invocation but by making the syntax similar to the one used by your macro, it could be possible to make it more flexible.

New Look

Using your syntax and adding nesting, the 2nd and 3rd examples could be written like this:

// 2nd example
impl<T> VecWrap<T> {
    variants!(
        [$] [method] [ref];
        [get] [&'a];
        [get_mut] [&'a mut];
        {
            pub fn $method<'a>(self: $ref Self, idx: usize) -> Option<$ref T> {
                self.0.$method(idx)
            }
        }
    );
}

// 3rd example
variants!(
    [d:$] [types] [impl];
    [[u8]; [u16]; [u32]] [false];
    [[i8]; [i16]; [i32]] [*self < 0];
    {
        variants!([$d] [type]; $types; {
            impl IsNegative for $d type {
                fn is_negative(&self) -> bool {
                    $impl
                }
            }
        });
    }
);

That's it

This is my proposal, I don't have a strong point as to why this should be included so I understand if you don't find this feature useful, but if there is an interest in adding this to the crate, let me know your opinions about it and I'll be willing to work on this to make it suitable for this crate.

Allow identifiers to take arguments

Increase the flexibility of substitution by allowing identifiers to take "arguments" that change what is substituted.

Example:

#[duplicate(
  let_var( var_name );  [ let var_name = Default::default();]
)]
fn func(){
  let_var(some_name);
  let_var(some_other_name);
}

Will expand to:

fn func(){
  let some_name = Default::default();
  let some_other_name = Default::default();
}

This issue will manage the feature for the short syntax only. A later issue will manage the same for the verbose syntax.

Transitive dependencies violate MSRV

Situation:

As reported in #42, transitive dependencies were not adequately vetted for compliance.

Reproduction:

Expected Behavior:

Specific versions of transitive dependencies should be used where not compliant with duplicate's MSRV

Actual Behavior:

Even though directly dependencies are pinned, transitives are not.

Affected Versions:

0.2.9, 0.3.0

Local Environment:

Miscellaneous:

How to handle doc comments and examples?

I want to use duplicate to create an asynchronous and a non-asynchronous version of a public method. Since this is a public API, I would like to have a proper doc comment and example. Currently both functions get the same comment, but I would like to have some small differences (especially in the example). Is there any way to achieve this?

Allow bracket omission

Based on this comment.

Maybe allow the omission of brackets in some situations, like if the substitution is just an identifier or punctuation. Should be possible the new short syntax and existing verbose.

module_disambiguation: module identifier is also substituted in body

Situation:

When using module disambiguation, the module identifier is also substituted if found anywhere in the module body.
This is a problems, since that might not be the desired behavior all the time.
E.g. we might have a struct using the same identifier that we wouldn't want to substitute.

In essence, the feature should disambiguate the module declaration and nothing else.
If users want to use the final modules' names, then they should need to use explicit substitutions.

Reproduction:

#[duplicate(
	value; [value1]; [value2]
)]
mod StructIdent {
	fn some_fn() -> StructIdent {
		StructIdent(value)
	}
}

Expected Behavior:

mod StructIdent_value1 {
    fn some_fn() -> StructIdent_value1 {
        StructIdent(value1)
    }
}
mod StructIdent_value2 {
    fn some_fn() -> StructIdent_value2 {
        StructIdent(value2)
    }
}

Actual Behavior:

mod StructIdent_value1 {
    fn some_fn() -> StructIdent_value1 {
        StructIdent_value1(value1)
    }
}
mod StructIdent_value2 {
    fn some_fn() -> StructIdent_value2 {
        StructIdent_value2(value2)
    }
}

Affected Versions:

Local Environment:

Miscellaneous:

Substitution in strings

Short Description:

Enable substitution within string literals.

Motivation:

Substitution within the contents of string literals fits well with the overall functionality of duplicate.
E.g. it could be used to change the wording on some strings depending on the duplicate.

Additionally, it enables the substitution of documentation comments. Currently, to do so can only be done using the doc attribute and substituting the whole string. Not being able to substitute in docs means our examples of generating different versions of functions for e.g. mutability or async don't work well if you need the documentation to be changed too. See #54. Enabling substitution within strings should (hopefully, needs more investigation) enable substitution within /// comments directly.

Lastly, it's not immediately intuitive that substitution wouldn't work within string literals, making it a pain point for users.

Design

During substition, if a string literal is encountered, run the substitutions within it too.
To parse the contents of strings, the litrs crate can be used. See also this stackoverflow answer.

Misc:

Open questions:

  • Parsing the contents of string is not straight-forward as its would be given as &str and not TokenStream (if using littrs), meaning the normal method of identifying substitution identifiers wouldn't work. This is especially difficult with parametric substitution.
  • Should this be behind a feature flag or treated as a first-class feature?
  • Could this lead to confusion, where users wouldn't have expected substitution within strings?

Change case conversion crate to heck

Change the crate we use for case conversions to heck.

The reason for changing is because heck is a more established and widely used crate that is also being maintained by a know, respected figure. This makes this crate a safer choice.

This change should not be seen as being caused by problems with the convert_case crate or their maintainers.

Substitution/concatenation of token names

Short Description:

I was hoping to have a way to substitute a value in to the name of a token. For example:

#[duplicate_item(
  int_type  max_value;
  [ u8 ]    [ 255 ];
  [ u16 ]   [ 65_535 ];
  [ u32 ]   [ 4_294_967_295 ];
)]
fn is_[int_type]_max(&self) -> bool {
  *self == max_value
}

// Generates...
fn is_u8_max(&self) -> bool {
  *self == 255
}

fn is_u16_max(&self) -> bool {
  *self == 65_535
}
// etc.

I've read through the docs and can't seem to find a way to do so without restoring to using something like paste!. Is that possible?

Need help with syntax

I have a use case exactly for this crate, I was happy I found it, but I can' make it work.

For each numeric type I need to implement a custom trait with calls to different ffi functions:
I can't figure out a correct syntax. Should the methods be each in a [] or all in one [] ? The docs examples all use a single substitution for a single type.

use duplicate::duplicate;

#[duplicate(
    val_type storage get set;
    [i32]  [StorageType::Int] [get_int_data] [set_int_data];
)]
impl AttribDataType for val_type {
    fn read(...)  {
        crate::ffi::get(..)
    }

     fn read(...)  {
        crate::ffi::set(..)
    }

    fn storage() -> StorageType {
        storage
    }
}
error: expected one of `(` or `<`, found `::`
   --> ...\src\attribute.rs:248:26
    |
246 | / #[duplicate(
247 | |     val_type  storage           get                      set                      array;
248 | |     [i32]    [StorageType::Int] [get_int_data] [set_int_data];
    | |                          ^^ expected one of `(` or `<`
249 | | )]
    | |  -
    | |  |
    | |__while parsing this item list starting here
    |    the item list ends here

Then I tried this:

#[duplicate(
    [
        val_type [i32]
        storage [StorageType::Int]
        get [get_int_data]
        set [set_int_data]
    ]
)]

And get this error:

error: expected one of `(` or `<`, found `::`
   --> ...\src\attribute.rs:254:29
    |
251 | / #[duplicate(
252 | |     [
253 | |         val_type [i32]
254 | |         storage [StorageType::Int]
    | |                             ^^ expected one of `(` or `<`
...   |
258 | |     ]
259 | | )]

Would appreciate any help, thanks!

Allow nested invocation for short syntax

Now that #1 has changed the short syntax, it should be possible to allow nested invocation in it:

#[duplicate(
  int_type  implementation;
  [i8]      [ *self < 0 ];
  #[
    int_type  ; [u8]; [u16]; [u32]
  ][ 
      [ int_type  ] [ false ]; 
  ]  
)]

Test bug report

Description:

This is a test description of a bug, using the bug report template.

Affected Versions:

All of them or none, we'll see.

Local Environment:

Not bad.

Instantiating structs using the macro

Awesome crate! Part of my needs include a situation where a number of structs have a large number of fields all instantiated the same way. The struct is simply the glue between two libraries so it's all fairly repetitive and I want to keep it as dry as possible.

I can't seem to figure out how to/if it's possible to instantiate structs using the macro though. Take this highly contrived example:

struct Example {
  one: usize,
  two: usize,
}

impl Example {
  fn inline_new() -> Self {
    Example {
      duplicate_inline! {
        [
          key;
          [ one ];
          [ two ];
        ]
        key: 0,
      }
    }
  }

  fn attr_new() -> Self {
    Example {
      #[duplicate(
        key;
        [ one ];
        [ two ];
      )]
      key: 0,
    }
  }
}

It fails with the following:

error: expected one of `,` or `}`, found `!`
  --> src/example.rs:57:23
   |
56 |     Example {
   |     ------- while parsing this struct
57 |       duplicate_inline! {
   |                       ^ expected one of `,` or `}`

error: expected non-macro attribute, found attribute macro `duplicate`
  --> src/example.rs:70:9
   |
70 |       #[duplicate(
   |         ^^^^^^^^^ not a non-macro attribute

error[E0063]: missing fields `one` and `two` in initializer of `Example`
  --> src/example.rs:56:5
   |
56 |     Example {
   |     ^^^^^^^ missing `one` and `two`

error[E0560]: struct `Example` has no field named `key`
  --> src/example.rs:75:7
   |
75 |       key: 0,
   |       ^^^ `Example` does not have this field
   |
   = note: available fields are: `one`, `two`

It seems like the macros can't operate on struct fields, is that indeed the case? Is it simply a limitation of the macro system currently?

Replace proc-macro-error with proc-macro2-diagnostics

proc-macro-error which is used for pretty errors seems to be unmaintained and also still depends on Syn 1.x.

The latter is my main driver for opening this issue as I'm working on cutting down on duplicate dependencies in @janhohenheim's Bevy game template Foxtrot. His template is quite representative of the kinds of dependencies that you'll find in Bevy games.

I propose replacing it with proc-macro2-diagnostics. Doing so is quite easy (I have a PR ready).

Make good error reporting a feature

We use proc_macro_error to print nice errors when using duplicate. As #11 has shown, this is not without its risks.

Therefore, we should make it a feature that can be disabled, so that even if build fail when using the feature, turning the feature off can resolve the issue. This feature should be enabled by default.

This could also be a win in other areas, as published crates that use duplicate do not need this feature (as they hopefully will have no errors.)

Rename macros

Short Description:

Rename the macros:
duplicate_inline -> duplicate (new),
duplicate(old) -> duplicate_item.

Motivation:

#28 decided that duplicate_inline would be used in nested substitution.
Therefore, the current name is a bit long for something that would probably be embedded inside other code.
duplicate(old) on the other hand is often on its own, or as part of shorter lines, so giving it a longer name wouldn't be an issue.

duplicate_inline is also the more general of the macros (everywhere can use duplicate(old) you can also use duplicate_inline, but not vice versa).
Therefore, if it has the shorter name, new users trying it out (without knowing the difference) would have faster success.

Design

Simple rename.

Misc:

Initially, we will simply rename everything in the documentation. However, since duplicate_inline will become the new default macro, we might want to refocus the documentation to account for this.

Increase MSRV to 1.42

Short Description:

Increase base MSRV to 1.42

Motivation:

Significant effort must be continuously spent to keep the code working on older version.
The original reason for choosing 1.34 as MSRV came from #17, which used Rust on Debian stable, which in turn only had version 1.34 installed.

The current LTS of Debian is scheduled to reach end-of-life in June of this year, meaning the need for rust 1.34 is reduced. The next LTS uses run 1.48.
Therefore, since we are getting ready to ship version 0.4 with significant breaking changes, we might as well increase the MSRV while we are at it.

Design

Increase base MSRV to 1.42 to match the MSRV of the module_disambiguation feature.

Misc:

Missing substitution in parameter body

Situation:

When using parameterized substitution, if a substitution variable is used inside the argument to a substitution, it is not substituted.

Example:

#[duplicate::duplicate(
  refs(type)	fn_name;
  [&type] 	[fn_const];
  [&mut type]	[fn_mut];
)]
fn fn_name(arg: refs([refs([i32])])){}

Expected Behavior:

fn fn_const(arg: &&i32){}
fn fn_mut(arg: &mut &mut i32){}

Actual Behavior:

fn fn_const(arg: &refs([i32])){}
fn fn_mut(arg: &mut refs([i32])){}

Reproduction:

Affected Versions:

0.2.9 and below

Local Environment:

Miscellaneous:

Add `substitute` macro(s)

Short Description:

Provide substitute and substitute_item which only provide the global substitution feature of the main macros.

Motivation:

Sometimes, duplicate may be used only to avoid repeating something within the code, but not to duplicate the whole thing.
Providing these macro would make the intent more clear for readers of the code that no duplication is happening.

Design

Misc:

Provide substitute! and substitute_item as synonyms for duplicate! and duplicate_item respectively, where only global substitutions are allowed.

Thank you!

Just wanted to drop a big thank you for this crate! It saved me from some real nasty stuff. <3

Automatically convert between syntaxes

Short Description:

Provide a way to easily convert an invocation of duplicate from one syntax to another.

Motivation:

We currently have two syntaxes: short and verbose.
The reason to use the verbose syntax is that it looks clearer when the substitutions get complex. However, complexity usually evolves from something simple. Therefore, users would probably often start using the short syntax, but at some point realize that their use case has become too complex and would want to switch to the verbose syntax.
At that point it might be tedious to switch to the verbose syntax. Therefore, some automated way of producing a verbose version of a short syntax invocation would be nice.

Design

The firs thing to realize is that this feature is not something that "stays" in the code. I.e. as soon as the conversion is done, we wouldn't want to see that it was ever using the other syntax. Therefore, this is more of a tool than a library feature. Therefore it should be easy to take an invocation, copy-paste the short syntax and get the verbose syntax that can then be manually switched for the short one. Then the code doing the conversion is just deleted.

An alternative could also be an actual command line tool that does the conversion. However, whether creating a whole executable for such a specific task might not be worth it.

Misc:

Upgrade heck to v0.3.3 or higher

Hey guys, I was wondering when would be next release for this package and if you are going to upgrade heck to v0.3.3 or more. As of now sqlx-macros Crate has a dependency of heck v0.3.3 or more and Duplicate crate requires v0.3.2 which creates conflicts.

Reusable Invocations

Short Description:

Provide a way for reusing duplicate! invocation code for multiple invocations

Motivation:

If we want to reuse the same invocation in multiple calls to different duplicate!s:

some_invocations_code!{
  ReUsableId
  [SomeInvocations] [That are repeated]
}

#[duplicate_item(ReUsableId)]
{...}

#[duplicate_item(ReUsableId)]
{...}

Design

It should be possible to define some code that can be used as duplicate invocation repeatedly.
It should also be possible for an invocation to use some code from a different file.

Misc:

Pinning dependency versions prevents downstream users from depending on newer compatible releases

Situation:

Combine duplicate with another dependency that depends on heck ^0.3.3.

Reproduction:

$ cargo new foo
     Created binary (application) `foo` package
$ cd foo/
$ cargo add duplicate@=0.3.0 heck@=0.3.3
    Updating 'https://github.com/rust-lang/crates.io-index' index
      Adding duplicate v=0.3.0 to dependencies
      Adding heck v=0.3.3 to dependencies
$ cargo b
    Updating crates.io index
error: failed to select a version for `heck`.
    ... required by package `duplicate v0.3.0`
    ... which satisfies dependency `duplicate = "=0.3.0"` of package `foo v0.1.0 (/home/teo/junk/foo)`
versions that meet the requirements `=0.3.2` are: 0.3.2

all possible versions conflict with previously selected packages.

  previously selected package `heck v0.3.3`
    ... which satisfies dependency `heck = "=0.3.3"` of package `foo v0.1.0 (/home/teo/junk/foo)`

failed to select a version for `heck` which could resolve this conflict
$ cat Cargo.toml 
[package]
name = "foo"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
duplicate = "=0.3.0"
heck = "=0.3.3"

Expected Behavior:

duplicate is built using the semver-compatible heck 0.3.3 instead.

Actual Behavior:

The build fails because Cargo refuses to make a build graph that combines both heck 0.3.2 and heck 0.3.3.

Affected Versions:

0.3.0

Local Environment:

cargo 1.56.0 (4ed5d137b 2021-10-04)
rustc 1.56.1 (59eed8a2a 2021-11-01)

Miscellaneous:

Submitted a PR as #41.

Relax MSRV policy to exclude dependencies

Short Description:

Relax MSRV policy to exclude any guarantees about the MSRV of duplicate's dependencies.

Motivation:

#42 showed that the current MSRV policy implementation of pinning any dependencies that don't have a compatible MSRV policy puts undue limits on users who want to use the same dependencies as duplicate.
The discussion in the same issue shows the problem isn't likely to be fixed by rust tooling, meaning pinning versions must be avoided by duplicate

As such, the MSRV is practically too restrictive and should be relaxed.

Design

The original vision of the MSRV was to promise that increasing it would always follow with a major version bump.
While this can be maintained by duplicate itself, it is unlikely to be a policy that is generally used by dependencies.

Therefore, to maintain as much of the promise as we can, while avoiding having to pin dependencies, the new policy is that dependencies don't necessarily have a compatible MSRV policy.
Instead, we will promise that the lowest version used by duplicate works for the MSRV. The user must then independently pin any dependencies they need that releases a version with a higher MSRV.

Misc:

Implement `pretty_errors` feature

Currently we have a feature called pretty_errors that is intended to give better error messages than without the feature.

This is currently only haphazardly implemented and not automatically tested.
We should therefore add a setup that tests the error messages provided by this feature.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.