Skip to content

Commit d919bf7

Browse files
committed
Fixed bug by replacing 'raw' with 'val' and added support for anonymous structs
1 parent 6da2211 commit d919bf7

6 files changed

Lines changed: 140 additions & 85 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
[package]
22
name = "compare_variables"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
edition = "2024"
55
description = "A Rust procedural macro for comparing the ordering of variables and creating useful error messages"
66
readme = "README.md"
77
license = "MIT OR Apache-2.0"
88
repository = "https://github.com/StefanMathis/cart_lin.git"
99

1010
[dependencies]
11-
compare_variables_macro = { version = "0.1", path = "compare_variables_macro", optional = true}
11+
compare_variables_macro = { version = "0.2.0", path = "compare_variables_macro", optional = true}
1212

1313
[features]
1414
default = ["proc_macro"]

README.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@ compare_variables
33

44
A library for comparing the ordering of variables and producing useful error messages.
55

6-
[`ComparisonError`]: https://docs.rs/compare_variables/0.1.0/compare_variables/struct.ComparisonError.html
6+
[`ComparisonError`]: https://docs.rs/compare_variables/0.2.0/compare_variables/struct.ComparisonError.html
77
[`PartialOrd`]: https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html
8-
[`compare_variables`]: https://docs.rs/compare_variables/0.1.0/compare_variables/macro.compare_variables.html
8+
[`compare_variables`]: https://docs.rs/compare_variables/0.2.0/compare_variables/macro.compare_variables.html
99

1010
This library is based on the struct [`ComparisonError`], which can be used to compare the partial ordering
1111
of two to three variables of any type `T` which implements the [`PartialOrd`] trait. If the comparison evaluates
1212
to false, [`ComparisonError`] can be formatted into a nice error message. To simplify the usage, the procedural
1313
macro [`compare_variables`] is provided via the feature flag **proc_macro** (enabled by default).
1414

15-
The full API documentation is available at [https://docs.rs/compare_variables/0.1.0/compare_variables/](https://docs.rs/compare_variables/0.1.0/compare_variables/).
15+
The full API documentation is available at [https://docs.rs/compare_variables/0.2.0/compare_variables/](https://docs.rs/compare_variables/0.2.0/compare_variables/).
1616

1717
# Examples
1818

19+
The type annotation of the error can be omitted and is only written out in these examples for clarity.
20+
1921
```rust
2022
use compare_variables::{compare_variables, ComparisonError};
2123

@@ -27,21 +29,25 @@ assert_eq!(err.to_string(), "`x (value: 1.0) > 1.5` is false");
2729

2830
assert!(compare_variables!(1.0 == x < 5.0).is_ok());
2931

30-
// Named struct fields can also be used:
32+
// Named and unnamed struct fields can also be used:
3133
struct NamedField {
3234
x: usize
3335
}
34-
3536
let n = NamedField {x: 1};
3637
assert!(compare_variables!(n.x > 0).is_ok());
3738
let err: ComparisonError<usize> = compare_variables!(n.x > 1).unwrap_err();
3839
assert_eq!(err.to_string(), "`n.x (value: 1) > 1` is false");
3940

40-
// It is also possible to customize the error message via `as` (providing an alias) and `raw` (omit the variable name):
41+
struct AnonymousField(i32);
42+
let a = AnonymousField(-5);
43+
let err: ComparisonError<i32> = compare_variables!(a.0 > 1).unwrap_err();
44+
assert_eq!(err.to_string(), "`a.0 (value: -5) > 1` is false");
45+
46+
// It is also possible to customize the error message via `as` (providing an alias) and `val` (omit the variable name):
4147
let x: u16 = 1;
4248
let y: u16 = 2;
4349
let z: u16 = 3;
44-
let err: ComparisonError<u16> = compare_variables!(x as arg > raw y > z).unwrap_err();
50+
let err: ComparisonError<u16> = compare_variables!(x as arg > val y > z).unwrap_err();
4551
assert_eq!(err.to_string(), "`arg (value: 1) > 2 > z (value: 3)` is false");
4652
```
4753

compare_variables_macro/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "compare_variables_macro"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
edition = "2024"
55
description = "Procedural macro for crate `compare_variables`"
66
readme = "README.md"

compare_variables_macro/src/lib.rs

Lines changed: 89 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -55,50 +55,54 @@ assert_eq!(checked_sub(2, 2).unwrap(), 0);
5555
assert!(checked_sub(2, 3).is_err());
5656
```
5757
58-
It is also possible to use named struct fields as inputs:
58+
It is also possible to use named and anonymous struct fields as inputs:
5959
6060
```
6161
use compare_variables::compare_variables;
6262
6363
struct NamedField {
6464
x: f64
6565
}
66-
6766
let n = NamedField {x: 1.0};
6867
assert!(compare_variables!(n.x > -1.0).is_ok());
6968
assert!(compare_variables!(n.x > 1.0).is_err());
69+
70+
struct AnonymousField(i32);
71+
let a = AnonymousField(-5);
72+
assert!(compare_variables!(a.0 > -6).is_ok());
73+
assert!(compare_variables!(a.0 > 1).is_err());
7074
```
7175
7276
# Error message
7377
7478
The error message is created via the struct [`ComparisonError`](https://docs.rs/compare_variables/0.1.0/compare_variables/struct.ComparisonError.html).
75-
Please refer to its documentation for more details. The keywords `raw` and `as` allow to customize the treatment of variable names in the error message:
79+
Please refer to its documentation for more details. The keywords `val` and `as` allow to customize the treatment of variable names in the error message:
7680
7781
```
7882
use compare_variables::compare_variables;
7983
8084
// Error message with literals only
81-
let err = compare_variables!(5i32 <= -1i32);
82-
assert_eq!(err.unwrap_err().to_string(), "`5 <= -1` is false");
85+
let err = compare_variables!(5i32 <= -1i32).unwrap_err();
86+
assert_eq!(err.to_string(), "`5 <= -1` is false");
8387
8488
let x = 1;
8589
let y = 2;
8690
8791
// Default error message
88-
let err = compare_variables!(x > y);
89-
assert_eq!(err.unwrap_err().to_string(), "`x (value: 1) > y (value: 2)` is false");
92+
let err = compare_variables!(x > y).unwrap_err();
93+
assert_eq!(err.to_string(), "`x (value: 1) > y (value: 2)` is false");
9094
9195
// Rename x in the error message
92-
let err = compare_variables!(x as variable > y);
93-
assert_eq!(err.unwrap_err().to_string(), "`variable (value: 1) > y (value: 2)` is false");
96+
let err = compare_variables!(x as variable > y).unwrap_err();
97+
assert_eq!(err.to_string(), "`variable (value: 1) > y (value: 2)` is false");
9498
9599
// Only display the underlying value, not the variable name:
96-
let err = compare_variables!(raw x > y);
97-
assert_eq!(err.unwrap_err().to_string(), "`1 > y (value: 2)` is false");
100+
let err = compare_variables!(val x > y).unwrap_err();
101+
assert_eq!(err.to_string(), "`1 > y (value: 2)` is false");
98102
99-
// `as` is ignored if used together with `raw`:
100-
let err = compare_variables!(raw x as variable > y);
101-
assert_eq!(err.unwrap_err().to_string(), "`1 > y (value: 2)` is false");
103+
// `as` is ignored if used together with `val`:
104+
let err = compare_variables!(val x as variable > y).unwrap_err();
105+
assert_eq!(err.to_string(), "`1 > y (value: 2)` is false");
102106
```
103107
104108
# Examples
@@ -111,7 +115,7 @@ assert!(compare_variables!(1.5 < 2.0 == 3.0).is_err());
111115
assert!(compare_variables!(1.7f32 == 1.7f32).is_ok());
112116
let f = 2.0;
113117
assert!(compare_variables!(f < 5.2).is_ok());
114-
assert!(compare_variables!(f as f_var == raw f).is_ok());
118+
assert!(compare_variables!(f as f_var == val f).is_ok());
115119
116120
// Signed and unsigned integers
117121
assert!(compare_variables!(1i32 >= 2i32).is_err());
@@ -216,8 +220,8 @@ impl ComparisonError {
216220

217221
enum VariableOrLiteral {
218222
Other {
219-
arg_names: Vec<syn::Ident>,
220-
arg_names_display: Vec<syn::Ident>,
223+
arg_names: Vec<String>,
224+
arg_names_display: Vec<String>,
221225
},
222226
LitFloat(syn::LitFloat),
223227
LitInt(syn::LitInt),
@@ -231,11 +235,7 @@ impl VariableOrLiteral {
231235
arg_names_display,
232236
} => {
233237
// Build a token stream out of arg_name and arg_name_display, using . as a delimiter
234-
let arg_value = arg_names
235-
.into_iter()
236-
.map(|ident| ident.to_string())
237-
.collect::<Vec<String>>()
238-
.join(".");
238+
let arg_value = arg_names.join(".");
239239
let arg_value_ts: TokenStream2 = match str::parse::<TokenStream2>(&arg_value) {
240240
Ok(ts) => ts,
241241
Err(_) => abort!(
@@ -248,11 +248,7 @@ impl VariableOrLiteral {
248248
compare_variables::ComparisonValue::new(#arg_value_ts, None)
249249
}
250250
} else {
251-
let arg_name_display = arg_names_display
252-
.into_iter()
253-
.map(|ident| ident.to_string())
254-
.collect::<Vec<String>>()
255-
.join(".");
251+
let arg_name_display = arg_names_display.join(".");
256252
quote! {
257253
compare_variables::ComparisonValue::new(#arg_value_ts, Some(#arg_name_display))
258254
}
@@ -284,6 +280,30 @@ struct ComparisonErrorInfo {
284280
impl Parse for ComparisonErrorInfo {
285281
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
286282
fn parse_arg(input: &syn::parse::ParseStream) -> syn::Result<VariableOrLiteral> {
283+
fn parse_composite_varname(
284+
input: &syn::parse::ParseStream,
285+
vec: &mut Vec<String>,
286+
) -> syn::Result<()> {
287+
loop {
288+
if input.peek(syn::LitInt) {
289+
let lit = input.parse::<syn::LitInt>()?;
290+
vec.push(lit.to_string());
291+
} else {
292+
let ident: syn::Ident = input.call(Ident::parse_any)?;
293+
vec.push(ident.to_string()); // parse_any also handles stuff like self
294+
}
295+
296+
if input.peek(Token![.]) {
297+
// Throw the token away
298+
let _ = input.parse::<Token![.]>()?;
299+
} else {
300+
// Field access is done ==> Finish the loop
301+
break;
302+
}
303+
}
304+
return Ok(());
305+
} // parse_composite_varname
306+
287307
if input.peek(syn::LitFloat) {
288308
// Parse the float literal
289309
let val = input.parse::<syn::LitFloat>()?;
@@ -293,59 +313,64 @@ impl Parse for ComparisonErrorInfo {
293313
let val = input.parse::<syn::LitInt>()?;
294314
return Ok(VariableOrLiteral::LitInt(val));
295315
} else {
296-
// If the first token is "raw", do not display the variable name
297-
let no_arg_name_display = input.peek(Token![raw]);
298-
if no_arg_name_display {
299-
// Remove the "raw" token
300-
let _ = input.parse::<Token![raw]>()?;
301-
}
316+
let mut display_arg_names = true;
302317

303-
// Resolve field accesses like self.field or variable.field.field
304-
let mut arg_names: Vec<Ident> = Vec::new();
305-
loop {
306-
let arg_name: Ident = input.call(Ident::parse_any)?; // parse_any also handles stuff like self
307-
arg_names.push(arg_name);
318+
// Input is possibly a variable name.
319+
let mut arg_names: Vec<String> = Vec::new();
308320

309-
if input.peek(Token![.]) {
310-
// Throw the token away
311-
let _ = input.parse::<Token![.]>()?;
321+
// First check if the first identifier is "val":
322+
let first_ident: Ident = input.call(Ident::parse_any)?; // parse_any also handles stuff like self
323+
324+
// Next identifier is not a "." -> Check if first_ident is "val". If not, the macro is used wrong
325+
if input.peek(Token![.]) {
326+
// Throw the token away
327+
let _ = input.parse::<Token![.]>()?;
328+
329+
// Try continuing to parse and keep the first identifier
330+
arg_names.push(first_ident.to_string());
331+
parse_composite_varname(input, &mut arg_names)?;
332+
} else {
333+
if input.peek(syn::Ident) {
334+
if first_ident == "val" {
335+
display_arg_names = false;
336+
337+
// Try continuing to parse
338+
parse_composite_varname(input, &mut arg_names)?;
339+
} else {
340+
abort!(
341+
Span::call_site(),
342+
format!("found unexpected tokens behind {first_ident}")
343+
)
344+
}
312345
} else {
313-
// Field access is done ==> Finish the loop
314-
break;
346+
arg_names.push(first_ident.to_string());
315347
}
316348
}
317349

318-
let arg_names_display = if input.peek(Token![as]) {
350+
// Resolve the alias, if the variable name should be displayed
351+
let arg_names_display: Vec<String> = if input.peek(Token![as]) {
319352
input.parse::<Token![as]>()?;
320-
let mut arg_names_display: Vec<Ident> = Vec::new();
321-
loop {
322-
let arg_name: Ident = input.call(Ident::parse_any)?; // parse_any also handles stuff like self
323-
if !no_arg_name_display {
324-
arg_names_display.push(arg_name);
325-
}
326-
327-
if input.peek(Token![.]) {
328-
// Throw the token away
329-
let _ = input.parse::<Token![.]>()?;
330-
} else {
331-
// Field access is done ==> Finish the loop
332-
break;
333-
}
353+
let mut arg_names_display: Vec<String> = Vec::new();
354+
parse_composite_varname(input, &mut arg_names_display)?;
355+
if display_arg_names {
356+
arg_names_display
357+
} else {
358+
Vec::new()
334359
}
335-
arg_names_display
336360
} else {
337-
if no_arg_name_display {
338-
Vec::new()
339-
} else {
361+
if display_arg_names {
340362
arg_names.clone()
363+
} else {
364+
Vec::new()
341365
}
342366
};
367+
343368
return Ok(VariableOrLiteral::Other {
344369
arg_names,
345370
arg_names_display,
346371
});
347372
}
348-
}
373+
} // parse_arg
349374

350375
fn parse_comparison_operator(
351376
input: &syn::parse::ParseStream,
@@ -372,7 +397,7 @@ impl Parse for ComparisonErrorInfo {
372397
"no comparison operator could be identified. Valid operators are \"<\", \"<=\", \"==\", \">=\" or \">\".",
373398
))
374399
}
375-
}
400+
} // parse_comparison_operator
376401

377402
// Read the arguments
378403
let first_arg: VariableOrLiteral = parse_arg(&input)?;

tests/struct_fields.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use compare_variables::compare_variables;
22

3-
struct Struct {
3+
struct NamedFields {
44
field1: f64,
55
field2: f64,
66
}
77

8-
impl Struct {
8+
struct AnonymousField(f64);
9+
10+
impl NamedFields {
911
fn field1_larger_than_0(&self) -> bool {
1012
return compare_variables!(self.field1 > 0.0).is_ok();
1113
}
@@ -30,8 +32,8 @@ impl Struct {
3032
}
3133

3234
#[test]
33-
fn test_check_struct_fields() {
34-
let instance = Struct {
35+
fn test_named_struct_fields() {
36+
let instance = NamedFields {
3537
field1: 1.0,
3638
field2: 2.0,
3739
};
@@ -62,3 +64,11 @@ fn test_check_struct_fields() {
6264
.to_string();
6365
assert!(error_msg.contains("`field2 (value: 2.0) < value (value: 1.0)` is false"));
6466
}
67+
68+
#[test]
69+
fn test_anonymous_struct_fields() {
70+
let anon = AnonymousField(1.0);
71+
assert!(compare_variables!(0.0 < anon.0 <= 1.0).is_ok());
72+
let err = compare_variables!(0.0 > anon.0).unwrap_err();
73+
assert_eq!(err.to_string(), "`0.0 > anon.0 (value: 1.0)` is false");
74+
}

0 commit comments

Comments
 (0)