From 39441bb2c1030884d0f1d200de0a65b146ba6b6d Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Thu, 29 Apr 2021 11:10:14 -0700 Subject: [PATCH 1/5] Make a ui test to take the role of libproc_macro #[test] tests --- src/test/ui/proc-macro/auxiliary/api/mod.rs | 16 ++++++++++++++++ src/test/ui/proc-macro/test.rs | 12 ++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/test/ui/proc-macro/auxiliary/api/mod.rs create mode 100644 src/test/ui/proc-macro/test.rs diff --git a/src/test/ui/proc-macro/auxiliary/api/mod.rs b/src/test/ui/proc-macro/auxiliary/api/mod.rs new file mode 100644 index 0000000000000..72b02ad554e52 --- /dev/null +++ b/src/test/ui/proc-macro/auxiliary/api/mod.rs @@ -0,0 +1,16 @@ +// force-host +// no-prefer-dynamic + +#![crate_type = "proc-macro"] +#![crate_name = "proc_macro_api_tests"] +#![deny(dead_code)] // catch if a test function is never called + +extern crate proc_macro; + +use proc_macro::TokenStream; + +#[proc_macro] +pub fn run(input: TokenStream) -> TokenStream { + assert!(input.is_empty()); + TokenStream::new() +} diff --git a/src/test/ui/proc-macro/test.rs b/src/test/ui/proc-macro/test.rs new file mode 100644 index 0000000000000..c96aa73175f2c --- /dev/null +++ b/src/test/ui/proc-macro/test.rs @@ -0,0 +1,12 @@ +// check-pass +// aux-build:api/mod.rs + +//! This is for everything that *would* be a #[test] inside of libproc_macro, +//! except for the fact that proc_macro objects are not capable of existing +//! inside of an ordinary Rust test execution, only inside a macro. + +extern crate proc_macro_api_tests; + +proc_macro_api_tests::run!(); + +fn main() {} From 3c16c0e1df61755db2267897392529eb9451aa62 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Thu, 29 Apr 2021 11:17:44 -0700 Subject: [PATCH 2/5] Move proc_macro tests to ui test --- .../test/ui/proc-macro/auxiliary/api/cmp.rs | 9 +++++---- src/test/ui/proc-macro/auxiliary/api/mod.rs | 4 ++++ 2 files changed, 9 insertions(+), 4 deletions(-) rename library/proc_macro/tests/test.rs => src/test/ui/proc-macro/auxiliary/api/cmp.rs (88%) diff --git a/library/proc_macro/tests/test.rs b/src/test/ui/proc-macro/auxiliary/api/cmp.rs similarity index 88% rename from library/proc_macro/tests/test.rs rename to src/test/ui/proc-macro/auxiliary/api/cmp.rs index d2e6b0bb8093b..3d17e9e350e64 100644 --- a/library/proc_macro/tests/test.rs +++ b/src/test/ui/proc-macro/auxiliary/api/cmp.rs @@ -1,8 +1,10 @@ -#![feature(proc_macro_span)] - use proc_macro::{LineColumn, Punct}; -#[test] +pub fn test() { + test_line_column_ord(); + test_punct_eq(); +} + fn test_line_column_ord() { let line0_column0 = LineColumn { line: 0, column: 0 }; let line0_column1 = LineColumn { line: 0, column: 1 }; @@ -11,7 +13,6 @@ fn test_line_column_ord() { assert!(line0_column1 < line1_column0); } -#[test] fn test_punct_eq() { // Good enough if it typechecks, since proc_macro::Punct can't exist in a test. fn _check(punct: Punct) { diff --git a/src/test/ui/proc-macro/auxiliary/api/mod.rs b/src/test/ui/proc-macro/auxiliary/api/mod.rs index 72b02ad554e52..019fb2e7ec874 100644 --- a/src/test/ui/proc-macro/auxiliary/api/mod.rs +++ b/src/test/ui/proc-macro/auxiliary/api/mod.rs @@ -3,14 +3,18 @@ #![crate_type = "proc-macro"] #![crate_name = "proc_macro_api_tests"] +#![feature(proc_macro_span)] #![deny(dead_code)] // catch if a test function is never called extern crate proc_macro; +mod cmp; + use proc_macro::TokenStream; #[proc_macro] pub fn run(input: TokenStream) -> TokenStream { assert!(input.is_empty()); + cmp::test(); TokenStream::new() } From faad7e209deee6d09d335ca00c06d9f41bc040b5 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Thu, 29 Apr 2021 11:20:27 -0700 Subject: [PATCH 3/5] Make a more meaningful test for Punct eq --- src/test/ui/proc-macro/auxiliary/api/cmp.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/ui/proc-macro/auxiliary/api/cmp.rs b/src/test/ui/proc-macro/auxiliary/api/cmp.rs index 3d17e9e350e64..5784a6e5d94db 100644 --- a/src/test/ui/proc-macro/auxiliary/api/cmp.rs +++ b/src/test/ui/proc-macro/auxiliary/api/cmp.rs @@ -1,4 +1,4 @@ -use proc_macro::{LineColumn, Punct}; +use proc_macro::{LineColumn, Punct, Spacing}; pub fn test() { test_line_column_ord(); @@ -14,8 +14,8 @@ fn test_line_column_ord() { } fn test_punct_eq() { - // Good enough if it typechecks, since proc_macro::Punct can't exist in a test. - fn _check(punct: Punct) { - let _ = punct == ':'; - } + let colon_alone = Punct::new(':', Spacing::Alone); + assert_eq!(colon_alone, ':'); + let colon_joint = Punct::new(':', Spacing::Joint); + assert_eq!(colon_joint, ':'); } From 965bce48348bbcc3c86898bdb5e18d4c57c35d00 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Thu, 29 Apr 2021 12:03:35 -0700 Subject: [PATCH 4/5] Add proc macro Literal parse test --- src/test/ui/proc-macro/auxiliary/api/mod.rs | 4 ++++ src/test/ui/proc-macro/auxiliary/api/parse.rs | 23 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/test/ui/proc-macro/auxiliary/api/parse.rs diff --git a/src/test/ui/proc-macro/auxiliary/api/mod.rs b/src/test/ui/proc-macro/auxiliary/api/mod.rs index 019fb2e7ec874..739c25132e77b 100644 --- a/src/test/ui/proc-macro/auxiliary/api/mod.rs +++ b/src/test/ui/proc-macro/auxiliary/api/mod.rs @@ -9,12 +9,16 @@ extern crate proc_macro; mod cmp; +mod parse; use proc_macro::TokenStream; #[proc_macro] pub fn run(input: TokenStream) -> TokenStream { assert!(input.is_empty()); + cmp::test(); + parse::test(); + TokenStream::new() } diff --git a/src/test/ui/proc-macro/auxiliary/api/parse.rs b/src/test/ui/proc-macro/auxiliary/api/parse.rs new file mode 100644 index 0000000000000..4105236b7f2d3 --- /dev/null +++ b/src/test/ui/proc-macro/auxiliary/api/parse.rs @@ -0,0 +1,23 @@ +use proc_macro::Literal; + +pub fn test() { + test_parse_literal(); +} + +fn test_parse_literal() { + assert_eq!("1".parse::().unwrap().to_string(), "1"); + assert_eq!("1.0".parse::().unwrap().to_string(), "1.0"); + assert_eq!("'a'".parse::().unwrap().to_string(), "'a'"); + assert_eq!("\"\n\"".parse::().unwrap().to_string(), "\"\n\""); + assert_eq!("b\"\"".parse::().unwrap().to_string(), "b\"\""); + assert_eq!("r##\"\"##".parse::().unwrap().to_string(), "r##\"\"##"); + assert_eq!("10ulong".parse::().unwrap().to_string(), "10ulong"); + + assert!("0 1".parse::().is_err()); + assert!("'a".parse::().is_err()); + assert!(" 0".parse::().is_err()); + assert!("0 ".parse::().is_err()); + assert!("/* comment */0".parse::().is_err()); + assert!("0/* comment */".parse::().is_err()); + assert!("0// comment".parse::().is_err()); +} From 34585cb678bc492be7e48ff48a2633f4ce1dc5ae Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Thu, 29 Apr 2021 12:08:35 -0700 Subject: [PATCH 5/5] impl FromStr for proc_macro::Literal --- .../rustc_expand/src/proc_macro_server.rs | 31 +++++++++++++++++-- library/proc_macro/src/bridge/mod.rs | 14 +++++++++ library/proc_macro/src/lib.rs | 28 +++++++++++++++++ 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs index 7bf6502c976ca..92315c4d4f6c7 100644 --- a/compiler/rustc_expand/src/proc_macro_server.rs +++ b/compiler/rustc_expand/src/proc_macro_server.rs @@ -1,9 +1,7 @@ use crate::base::{ExtCtxt, ResolverExpand}; use rustc_ast as ast; -use rustc_ast::token; -use rustc_ast::token::Nonterminal; -use rustc_ast::token::NtIdent; +use rustc_ast::token::{self, Nonterminal, NtIdent, TokenKind}; use rustc_ast::tokenstream::{self, CanSynthesizeMissingTokens}; use rustc_ast::tokenstream::{DelimSpan, Spacing::*, TokenStream, TreeAndSpacing}; use rustc_ast_pretty::pprust; @@ -541,6 +539,33 @@ impl server::Ident for Rustc<'_> { } impl server::Literal for Rustc<'_> { + fn from_str(&mut self, s: &str) -> Result { + let override_span = None; + let stream = parse_stream_from_source_str( + FileName::proc_macro_source_code(s), + s.to_owned(), + self.sess, + override_span, + ); + if stream.len() != 1 { + return Err(()); + } + let tree = stream.into_trees().next().unwrap(); + let token = match tree { + tokenstream::TokenTree::Token(token) => token, + tokenstream::TokenTree::Delimited { .. } => return Err(()), + }; + let span_data = token.span.data(); + if (span_data.hi.0 - span_data.lo.0) as usize != s.len() { + // There is a comment or whitespace adjacent to the literal. + return Err(()); + } + let lit = match token.kind { + TokenKind::Literal(lit) => lit, + _ => return Err(()), + }; + Ok(Literal { lit, span: self.call_site }) + } fn debug_kind(&mut self, literal: &Self::Literal) -> String { format!("{:?}", literal.lit.kind) } diff --git a/library/proc_macro/src/bridge/mod.rs b/library/proc_macro/src/bridge/mod.rs index 355ad1f9f881c..a2953b68564a8 100644 --- a/library/proc_macro/src/bridge/mod.rs +++ b/library/proc_macro/src/bridge/mod.rs @@ -107,6 +107,7 @@ macro_rules! with_api { Literal { fn drop($self: $S::Literal); fn clone($self: &$S::Literal) -> $S::Literal; + fn from_str(s: &str) -> Result<$S::Literal, ()>; fn debug_kind($self: &$S::Literal) -> String; fn symbol($self: &$S::Literal) -> String; fn suffix($self: &$S::Literal) -> Option; @@ -315,6 +316,19 @@ impl Unmark for Option { } } +impl Mark for Result { + type Unmarked = Result; + fn mark(unmarked: Self::Unmarked) -> Self { + unmarked.map(T::mark).map_err(E::mark) + } +} +impl Unmark for Result { + type Unmarked = Result; + fn unmark(self) -> Self::Unmarked { + self.map(T::unmark).map_err(E::unmark) + } +} + macro_rules! mark_noop { ($($ty:ty),* $(,)?) => { $( diff --git a/library/proc_macro/src/lib.rs b/library/proc_macro/src/lib.rs index 525fd0fbe3431..281999fe71588 100644 --- a/library/proc_macro/src/lib.rs +++ b/library/proc_macro/src/lib.rs @@ -91,6 +91,12 @@ pub struct LexError { _inner: (), } +impl LexError { + fn new() -> Self { + LexError { _inner: () } + } +} + #[stable(feature = "proc_macro_lexerror_impls", since = "1.44.0")] impl fmt::Display for LexError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -1171,6 +1177,28 @@ impl Literal { } } +/// Parse a single literal from its stringified representation. +/// +/// In order to parse successfully, the input string must not contain anything +/// but the literal token. Specifically, it must not contain whitespace or +/// comments in addition to the literal. +/// +/// The resulting literal token will have a `Span::call_site()` span. +/// +/// NOTE: some errors may cause panics instead of returning `LexError`. We +/// reserve the right to change these errors into `LexError`s later. +#[stable(feature = "proc_macro_literal_parse", since = "1.54.0")] +impl FromStr for Literal { + type Err = LexError; + + fn from_str(src: &str) -> Result { + match bridge::client::Literal::from_str(src) { + Ok(literal) => Ok(Literal(literal)), + Err(()) => Err(LexError::new()), + } + } +} + // N.B., the bridge only provides `to_string`, implement `fmt::Display` // based on it (the reverse of the usual relationship between the two). #[stable(feature = "proc_macro_lib", since = "1.15.0")]