diff options
| author | Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> | 2023-01-05 20:17:30 +0100 |
|---|---|---|
| committer | Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> | 2023-03-14 13:16:52 +0000 |
| commit | 729185338f1726f60ea2dc282fe6bbf0eb84cb17 (patch) | |
| tree | e84828d0610ea806f017a1ba76014199c180e8f0 /tests/ui/fmt/auxiliary/format-string-proc-macro.rs | |
| parent | 0058748944abb3282aba0e0a74823c6411703565 (diff) | |
| download | rust-729185338f1726f60ea2dc282fe6bbf0eb84cb17.tar.gz rust-729185338f1726f60ea2dc282fe6bbf0eb84cb17.zip | |
Properly allow macro expanded `format_args` invocations to uses captures
Originally, this was kinda half-allowed. There were some primitive checks in place that looked at the span to see whether the input was likely a literal. These "source literal" checks are needed because the spans created during `format_args` parsing only make sense when it is indeed a literal that was written in the source code directly. This is orthogonal to the restriction that the first argument must be a "direct literal", not being exanpanded from macros. This restriction was imposed by [RFC 2795] on the basis of being too confusing. But this was only concerned with the argument of the invocation being a literal, not whether it was a source literal (maybe in spirit it meant it being a source literal, this is not clear to me). Since the original check only really cared about source literals (which is good enough to deny the `format_args!(concat!())` example), macros expanding to `format_args` invocations were able to use implicit captures if they spanned the string in a way that lead back to a source string. The "source literal" checks were not strict enough and caused ICEs in certain cases (see # 106191 (the space is intended to avoid spammy backreferences)). So I tightened it up in # 106195 to really only work if it's a direct source literal. This caused the `indoc` crate to break. `indoc` transformed the source literal by removing whitespace, which made it not a "source literal" anymore (which is required to fix the ICE). But since `indoc` spanned the literal in ways that made the old check think that it's a literal, it was able to use implicit captures (which is useful and nice for the users of `indoc`). This commit properly seperates the previously introduced concepts of "source literal" and "direct literal" and therefore allows `indoc` invocations, which don't create "source literals" to use implicit captures again. [RFC 2795]: https://rust-lang.github.io/rfcs/2795-format-args-implicit-identifiers.html#macro-hygiene
Diffstat (limited to 'tests/ui/fmt/auxiliary/format-string-proc-macro.rs')
| -rw-r--r-- | tests/ui/fmt/auxiliary/format-string-proc-macro.rs | 36 |
1 files changed, 26 insertions, 10 deletions
diff --git a/tests/ui/fmt/auxiliary/format-string-proc-macro.rs b/tests/ui/fmt/auxiliary/format-string-proc-macro.rs index 1b7ef93f41d..0c39ade721f 100644 --- a/tests/ui/fmt/auxiliary/format-string-proc-macro.rs +++ b/tests/ui/fmt/auxiliary/format-string-proc-macro.rs @@ -28,25 +28,41 @@ pub fn err_with_input_span(input: TokenStream) -> TokenStream { TokenStream::from(TokenTree::Literal(lit)) } +fn build_format(args: impl Into<TokenStream>) -> TokenStream { + TokenStream::from_iter([ + TokenTree::from(Ident::new("format", Span::call_site())), + TokenTree::from(Punct::new('!', Spacing::Alone)), + TokenTree::from(Group::new(Delimiter::Parenthesis, args.into())), + ]) +} #[proc_macro] pub fn respan_to_invalid_format_literal(input: TokenStream) -> TokenStream { let mut s = Literal::string("{"); s.set_span(input.into_iter().next().unwrap().span()); - TokenStream::from_iter([ - TokenTree::from(Ident::new("format", Span::call_site())), - TokenTree::from(Punct::new('!', Spacing::Alone)), - TokenTree::from(Group::new(Delimiter::Parenthesis, TokenTree::from(s).into())), - ]) + + build_format(TokenTree::from(s)) } #[proc_macro] pub fn capture_a_with_prepended_space_preserve_span(input: TokenStream) -> TokenStream { let mut s = Literal::string(" {a}"); s.set_span(input.into_iter().next().unwrap().span()); - TokenStream::from_iter([ - TokenTree::from(Ident::new("format", Span::call_site())), - TokenTree::from(Punct::new('!', Spacing::Alone)), - TokenTree::from(Group::new(Delimiter::Parenthesis, TokenTree::from(s).into())), - ]) + + build_format(TokenTree::from(s)) +} + +#[proc_macro] +pub fn format_args_captures(_: TokenStream) -> TokenStream { + r#"{ let x = 5; format!("{x}") }"#.parse().unwrap() +} + +#[proc_macro] +pub fn bad_format_args_captures(_: TokenStream) -> TokenStream { + r#"{ let x = 5; format!(concat!("{x}")) }"#.parse().unwrap() +} + +#[proc_macro] +pub fn identity_pm(input: TokenStream) -> TokenStream { + input } |
