Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable numbered access to positional args on tuples #354

Merged
merged 2 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ pub enum DataStoreError {
```rust
#[derive(Error, Debug)]
pub enum Error {
#[error("invalid rdo_lookahead_frames {0} (expected < {})", i32::MAX)]
#[error("invalid rdo_lookahead_frames {0} (expected < {max})", max = i32::MAX)]
InvalidLookahead(u32),
}
```
Expand Down
4 changes: 2 additions & 2 deletions impl/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl<'a> Struct<'a> {
let span = attrs.span().unwrap_or_else(Span::call_site);
let fields = Field::multiple_from_syn(&data.fields, &scope, span)?;
if let Some(display) = &mut attrs.display {
display.expand_shorthand(&fields);
display.expand_shorthand(&fields)?;
}
Ok(Struct {
attrs,
Expand All @@ -85,7 +85,7 @@ impl<'a> Enum<'a> {
display.clone_from(&attrs.display);
}
if let Some(display) = &mut variant.attrs.display {
display.expand_shorthand(&variant.fields);
display.expand_shorthand(&variant.fields)?;
} else if variant.attrs.transparent.is_none() {
variant.attrs.transparent = attrs.transparent;
}
Expand Down
85 changes: 66 additions & 19 deletions impl/src/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,29 @@ use crate::ast::Field;
use crate::attr::{Display, Trait};
use crate::scan_expr::scan_expr;
use crate::unraw::{IdentUnraw, MemberUnraw};
use proc_macro2::{TokenStream, TokenTree};
use proc_macro2::{Delimiter, TokenStream, TokenTree};
use quote::{format_ident, quote, quote_spanned};
use std::collections::{BTreeSet as Set, HashMap as Map};
use std::iter;
use syn::ext::IdentExt;
use syn::parse::discouraged::Speculative;
use syn::parse::{ParseStream, Parser};
use syn::{Expr, Ident, Index, LitStr, Result, Token};
use syn::parse::{Error, ParseStream, Parser, Result};
use syn::{Expr, Ident, Index, LitStr, Token};

impl Display<'_> {
// Transform `"error {var}"` to `"error {}", var`.
pub fn expand_shorthand(&mut self, fields: &[Field]) {
pub fn expand_shorthand(&mut self, fields: &[Field]) -> Result<()> {
let raw_args = self.args.clone();
let mut named_args = explicit_named_args.parse2(raw_args).unwrap().named;
let FmtArguments {
named: mut named_args,
first_unnamed,
} = explicit_named_args.parse2(raw_args).unwrap();

let mut member_index = Map::new();
let mut extra_positional_arguments_allowed = true;
for (i, field) in fields.iter().enumerate() {
member_index.insert(&field.member, i);
extra_positional_arguments_allowed &= matches!(&field.member, MemberUnraw::Named(_));
}

let span = self.fmt.span();
Expand Down Expand Up @@ -48,14 +55,20 @@ impl Display<'_> {
}
let next = match read.chars().next() {
Some(next) => next,
None => return,
None => return Ok(()),
};
let member = match next {
'0'..='9' => {
let int = take_int(&mut read);
if !extra_positional_arguments_allowed {
if let Some(first_unnamed) = &first_unnamed {
let msg = "ambiguous reference to positional arguments by number in a tuple struct; change this to a named argument";
return Err(Error::new_spanned(first_unnamed, msg));
}
}
let member = match int.parse::<u32>() {
Ok(index) => MemberUnraw::Unnamed(Index { index, span }),
Err(_) => return,
Err(_) => return Ok(()),
};
if !member_index.contains_key(&member) {
out += int;
Expand Down Expand Up @@ -86,7 +99,7 @@ impl Display<'_> {
if let Some(&field) = member_index.get(&member) {
let end_spec = match read.find('}') {
Some(end_spec) => end_spec,
None => return,
None => return Ok(()),
};
let bound = match read[..end_spec].chars().next_back() {
Some('?') => Trait::Debug,
Expand Down Expand Up @@ -114,12 +127,13 @@ impl Display<'_> {
self.args = args;
self.has_bonus_display = has_bonus_display;
self.implied_bounds = implied_bounds;
Ok(())
}
}

struct FmtArguments {
named: Set<IdentUnraw>,
unnamed: bool,
first_unnamed: Option<TokenStream>,
}

#[allow(clippy::unnecessary_wraps)]
Expand All @@ -139,37 +153,44 @@ fn explicit_named_args(input: ParseStream) -> Result<FmtArguments> {
input.parse::<TokenStream>().unwrap();
Ok(FmtArguments {
named: Set::new(),
unnamed: false,
first_unnamed: None,
})
}

fn try_explicit_named_args(input: ParseStream) -> Result<FmtArguments> {
let mut syn_full = None;
let mut args = FmtArguments {
named: Set::new(),
unnamed: false,
first_unnamed: None,
};

while !input.is_empty() {
input.parse::<Token![,]>()?;
if input.is_empty() {
break;
}

let mut begin_unnamed = None;
if input.peek(Ident::peek_any) && input.peek2(Token![=]) && !input.peek2(Token![==]) {
let ident: IdentUnraw = input.parse()?;
input.parse::<Token![=]>()?;
args.named.insert(ident);
} else {
args.unnamed = true;
begin_unnamed = Some(input.fork());
}
if *syn_full.get_or_insert_with(is_syn_full) {
let ahead = input.fork();
if ahead.parse::<Expr>().is_ok() {
input.advance_to(&ahead);
continue;

let ahead = input.fork();
if *syn_full.get_or_insert_with(is_syn_full) && ahead.parse::<Expr>().is_ok() {
input.advance_to(&ahead);
} else {
scan_expr(input)?;
}

if let Some(begin_unnamed) = begin_unnamed {
if args.first_unnamed.is_none() {
args.first_unnamed = Some(between(&begin_unnamed, input));
}
}
scan_expr(input)?;
}

Ok(args)
Expand All @@ -178,7 +199,7 @@ fn try_explicit_named_args(input: ParseStream) -> Result<FmtArguments> {
fn fallback_explicit_named_args(input: ParseStream) -> Result<FmtArguments> {
let mut args = FmtArguments {
named: Set::new(),
unnamed: false,
first_unnamed: None,
};

while !input.is_empty() {
Expand Down Expand Up @@ -240,3 +261,29 @@ fn take_ident<'a>(read: &mut &'a str) -> &'a str {
*read = rest;
ident
}

fn between<'a>(begin: ParseStream<'a>, end: ParseStream<'a>) -> TokenStream {
let end = end.cursor();
let mut cursor = begin.cursor();
let mut tokens = TokenStream::new();

while cursor < end {
let (tt, next) = cursor.token_tree().unwrap();

if end < next {
if let Some((inside, _span, _after)) = cursor.group(Delimiter::None) {
cursor = inside;
continue;
}
if tokens.is_empty() {
tokens.extend(iter::once(tt));
}
break;
}

tokens.extend(iter::once(tt));
cursor = next;
}

tokens
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
//! #
//! #[derive(Error, Debug)]
//! pub enum Error {
//! #[error("invalid rdo_lookahead_frames {0} (expected < {})", i32::MAX)]
//! #[error("invalid rdo_lookahead_frames {0} (expected < {max})", max = i32::MAX)]
//! InvalidLookahead(u32),
//! }
//! ```
Expand Down
2 changes: 1 addition & 1 deletion tests/test_display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ fn test_nested() {
#[test]
fn test_match() {
#[derive(Error, Debug)]
#[error("{}: {0}", match .1 {
#[error("{intro}: {0}", intro = match .1 {
Some(n) => format!("error occurred with {}", n),
None => "there was an empty error".to_owned(),
})]
Expand Down
7 changes: 7 additions & 0 deletions tests/ui/numbered-positional-tuple.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use thiserror::Error;

#[derive(Error, Debug)]
#[error("invalid rdo_lookahead_frames {0} (expected < {})", i32::MAX)]
pub struct Error(u32);

fn main() {}
5 changes: 5 additions & 0 deletions tests/ui/numbered-positional-tuple.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: ambiguous reference to positional arguments by number in a tuple struct; change this to a named argument
--> tests/ui/numbered-positional-tuple.rs:4:61
|
4 | #[error("invalid rdo_lookahead_frames {0} (expected < {})", i32::MAX)]
| ^^^^^^^^
Loading