From 4a0535c03803a0e285335f338f180b11ad440845 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Mon, 25 Sep 2023 18:12:03 +0200 Subject: [PATCH] Perform own detection for proc_macro_span (fixes #39) --- examples/java_string.rs | 7 ++ genco-macros/build.rs | 35 +++++++++ genco-macros/src/fake.rs | 122 +++++++++++++++++------------- genco-macros/src/lib.rs | 1 + genco-macros/src/quote.rs | 2 +- genco-macros/src/string_parser.rs | 40 +++++++--- 6 files changed, 140 insertions(+), 67 deletions(-) create mode 100644 examples/java_string.rs create mode 100644 genco-macros/build.rs diff --git a/examples/java_string.rs b/examples/java_string.rs new file mode 100644 index 0000000..3bdde6b --- /dev/null +++ b/examples/java_string.rs @@ -0,0 +1,7 @@ +use genco::prelude::*; + +fn main() -> Result<(), genco::fmt::Error> { + let tokens: python::Tokens = quote!($[str](Hello World)); + assert_eq!("\"Hello World\"", tokens.to_string()?); + Ok::<_, genco::fmt::Error>(()) +} diff --git a/genco-macros/build.rs b/genco-macros/build.rs new file mode 100644 index 0000000..b486454 --- /dev/null +++ b/genco-macros/build.rs @@ -0,0 +1,35 @@ +use std::env; +use std::process::Command; +use std::str; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + + let version = rustc_version().unwrap_or(RustcVersion { + minor: u32::MAX, + nightly: false, + }); + + if version.nightly { + println!("cargo:rustc-cfg=proc_macro_span"); + } +} + +struct RustcVersion { + #[allow(unused)] + minor: u32, + nightly: bool, +} + +fn rustc_version() -> Option { + let rustc = env::var_os("RUSTC")?; + let output = Command::new(rustc).arg("--version").output().ok()?; + let version = str::from_utf8(&output.stdout).ok()?; + let nightly = version.contains("nightly") || version.contains("dev"); + let mut pieces = version.split('.'); + if pieces.next() != Some("rustc 1") { + return None; + } + let minor = pieces.next()?.parse().ok()?; + Some(RustcVersion { minor, nightly }) +} diff --git a/genco-macros/src/fake.rs b/genco-macros/src/fake.rs index a78ab91..30f5158 100644 --- a/genco-macros/src/fake.rs +++ b/genco-macros/src/fake.rs @@ -1,3 +1,4 @@ +use std::cell::{RefCell, RefMut}; use std::fmt::Arguments; use proc_macro2::Span; @@ -17,84 +18,97 @@ pub(crate) struct LineColumn { } impl LineColumn { - fn new(line_column: proc_macro2::LineColumn) -> Self { - Self { - line: line_column.line, - column: line_column.column, - } + #[cfg(proc_macro_span)] + pub(crate) fn start(span: Span) -> Option { + let span = span.unwrap().start(); + + Some(Self { + line: span.line(), + column: span.column(), + }) + } + + #[cfg(proc_macro_span)] + pub(crate) fn end(span: Span) -> Option { + let span = span.unwrap().end(); + + Some(Self { + line: span.line(), + column: span.column(), + }) + } + + #[cfg(not(proc_macro_span))] + pub(crate) fn start(_: Span) -> Option { + None + } + + #[cfg(not(proc_macro_span))] + pub(crate) fn end(_: Span) -> Option { + None } } #[derive(Default)] pub(crate) struct Buf { - buf: Option, + buf: RefCell, } impl Buf { /// Format the given arguments and return the associated string. - fn format(&mut self, args: Arguments<'_>) -> &str { + fn format(&self, args: Arguments<'_>) -> RefMut<'_, str> { use std::fmt::Write; - let buf = self.buf.get_or_insert_with(String::default); + let mut buf = self.buf.borrow_mut(); buf.clear(); buf.write_fmt(args).unwrap(); - buf.as_str() + RefMut::map(buf, |buf| buf.as_mut_str()) } /// Construct a cursor from a span. - pub(crate) fn cursor(&mut self, span: Span) -> syn::Result { - let start = span.start(); - let end = span.end(); - - if (start.line == 0 && start.column == 0) || (end.line == 0 && end.column == 0) { - // Try compat. - let (start, end) = self.find_line_column(span)?; - - Ok(Cursor::new( - span, - LineColumn { - line: 1, - column: start, - }, - LineColumn { - line: 1, - column: end, - }, - )) - } else { - Ok(Cursor::new( - span, - LineColumn::new(start), - LineColumn::new(end), - )) + pub(crate) fn cursor(&self, span: Span) -> syn::Result { + let start = LineColumn::start(span); + let end = LineColumn::end(span); + + if let (Some(start), Some(end)) = (start, end) { + return Ok(Cursor::new(span, start, end)); } + + // Try compat. + let (start, end) = self.find_line_column(span)?; + + Ok(Cursor::new( + span, + LineColumn { + line: 1, + column: start, + }, + LineColumn { + line: 1, + column: end, + }, + )) } /// The start of the given span. pub(crate) fn start(&mut self, span: Span) -> syn::Result { - let start = span.start(); - - // Try to use compat layer. - if start.line == 0 && start.column == 0 { - // Try compat. - let (column, _) = self.find_line_column(span)?; - Ok(LineColumn { line: 1, column }) - } else { - Ok(LineColumn::new(start)) + if let Some(start) = LineColumn::start(span) { + return Ok(start); } + + // Try compat. + let (column, _) = self.find_line_column(span)?; + Ok(LineColumn { line: 1, column }) } /// The start of the given span. pub(crate) fn end(&mut self, span: Span) -> syn::Result { - let end = span.end(); - - // Try to use compat layer. - if end.line == 0 && end.column == 0 { - // Try compat. - let (_, column) = self.find_line_column(span)?; - Ok(LineColumn { line: 1, column }) - } else { - Ok(LineColumn::new(end)) + if let Some(end) = LineColumn::end(span) { + return Ok(end); } + + // Try compat. + let (_, column) = self.find_line_column(span)?; + Ok(LineColumn { line: 1, column }) } /// Join two spans. @@ -108,14 +122,14 @@ impl Buf { /// Try to decode line and column information using the debug implementation of /// a `span` which leaks the byte offset of a thing. - fn find_line_column(&mut self, span: Span) -> syn::Result<(usize, usize)> { + fn find_line_column(&self, span: Span) -> syn::Result<(usize, usize)> { match self.find_line_column_inner(span) { Some((start, end)) => Ok((start, end)), None => Err(syn::Error::new(span, ERROR)), } } - fn find_line_column_inner(&mut self, span: Span) -> Option<(usize, usize)> { + fn find_line_column_inner(&self, span: Span) -> Option<(usize, usize)> { let text = self.format(format_args!("{:?}", span)); let start = text.find('(')?; let (start, end) = text diff --git a/genco-macros/src/lib.rs b/genco-macros/src/lib.rs index 6d7b5c5..7a9a68c 100644 --- a/genco-macros/src/lib.rs +++ b/genco-macros/src/lib.rs @@ -4,6 +4,7 @@ #![recursion_limit = "256"] #![allow(clippy::type_complexity)] +#![cfg_attr(proc_macro_span, feature(proc_macro_span))] extern crate proc_macro; diff --git a/genco-macros/src/quote.rs b/genco-macros/src/quote.rs index c738a1f..20b0a3f 100644 --- a/genco-macros/src/quote.rs +++ b/genco-macros/src/quote.rs @@ -348,7 +348,7 @@ impl<'a> Quote<'a> { )); } (LiteralName::Ident("str"), Some(content)) => { - let parser = StringParser::new(self.receiver, end); + let parser = StringParser::new(self.receiver, &self.buf, end)?; let (options, r, stream) = parser.parse(&content)?; encoder.requirements.merge_with(r); diff --git a/genco-macros/src/string_parser.rs b/genco-macros/src/string_parser.rs index 02f2e32..8e9cd6d 100644 --- a/genco-macros/src/string_parser.rs +++ b/genco-macros/src/string_parser.rs @@ -1,9 +1,11 @@ //! Helper to parse quoted strings. use crate::ast::LiteralName; +use crate::fake::{Buf, LineColumn}; use crate::quote::parse_internal_function; use crate::requirements::Requirements; -use proc_macro2::{LineColumn, Span, TokenStream, TokenTree}; + +use proc_macro2::{Span, TokenStream, TokenTree}; use syn::parse::ParseStream; use syn::spanned::Spanned; use syn::token; @@ -194,22 +196,26 @@ impl<'a> Encoder<'a> { pub struct StringParser<'a> { receiver: &'a syn::Ident, + buf: &'a Buf, start: LineColumn, end: LineColumn, span: Span, } impl<'a> StringParser<'a> { - pub(crate) fn new(receiver: &'a syn::Ident, span: Span) -> Self { - Self { + pub(crate) fn new(receiver: &'a syn::Ident, buf: &'a Buf, span: Span) -> syn::Result { + let cursor = buf.cursor(span)?; + + Ok(Self { receiver, + buf, // Note: adjusting span since we expect the quoted string to be // withing a block, where the interior span is one character pulled // in in each direction. - start: adjust_start(span.start()), - end: adjust_end(span.end()), + start: adjust_start(cursor.start), + end: adjust_end(cursor.end), span, - } + }) } pub(crate) fn parse(self, input: ParseStream) -> Result<(Options, Requirements, TokenStream)> { @@ -220,7 +226,9 @@ impl<'a> StringParser<'a> { if input.peek(syn::Token![$]) && input.peek2(syn::Token![$]) { let start = input.parse::()?; let escape = input.parse::()?; - encoder.encode_char('$', start.span().start(), escape.span().end())?; + let start = self.buf.cursor(start.span())?; + let escape = self.buf.cursor(escape.span())?; + encoder.encode_char('$', start.start, escape.end)?; continue; } @@ -228,15 +236,18 @@ impl<'a> StringParser<'a> { if let Some((name, content, [start, end])) = parse_internal_function(input)? { match (name.as_literal_name(), content) { (LiteralName::Ident("const"), Some(content)) => { + let start = self.buf.cursor(start)?; + let end = self.buf.cursor(end)?; + // Compile-time string optimization. A single, // enclosed literal string can be added to the // existing static buffer. if content.peek(syn::LitStr) && content.peek2(crate::token::Eof) { let s = content.parse::()?; - encoder.encode_str(&s.value(), start.start(), Some(end.end()))?; + encoder.encode_str(&s.value(), start.start, Some(end.end))?; } else { let expr = content.parse::()?; - encoder.raw_expr(&expr, start.start(), Some(end.end()))?; + encoder.raw_expr(&expr, start.start, Some(end.end))?; } } (literal_name, _) => { @@ -254,7 +265,9 @@ impl<'a> StringParser<'a> { if !input.peek(token::Paren) { let ident = input.parse::()?; - encoder.eval_ident(&ident, start.start(), Some(ident.span().end()))?; + let start = self.buf.cursor(start.span())?; + let end = self.buf.cursor(ident.span())?.end; + encoder.eval_ident(&ident, start.start, Some(end))?; continue; } @@ -265,14 +278,17 @@ impl<'a> StringParser<'a> { .with_span(content.span())? .parse(&content)?; requirements.merge_with(req); - encoder.eval_stream(stream, start.start(), Some(end.end()))?; + let start = self.buf.cursor(start.span())?; + let end = self.buf.cursor(end.span())?; + encoder.eval_stream(stream, start.start, Some(end.end))?; } continue; } let tt = input.parse::()?; - encoder.extend_tt(&tt, tt.span().start(), Some(tt.span().end()))?; + let cursor = self.buf.cursor(tt.span())?; + encoder.extend_tt(&tt, cursor.start, Some(cursor.end))?; } let (options, stream) = encoder.finalize(self.end)?;