Skip to content

Commit

Permalink
Perform own detection for proc_macro_span (fixes #39) (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
udoprog authored Sep 25, 2023
1 parent f9b4f73 commit 34dd6b9
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 67 deletions.
7 changes: 7 additions & 0 deletions examples/java_string.rs
Original file line number Diff line number Diff line change
@@ -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>(())
}
35 changes: 35 additions & 0 deletions genco-macros/build.rs
Original file line number Diff line number Diff line change
@@ -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<RustcVersion> {
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 })
}
122 changes: 68 additions & 54 deletions genco-macros/src/fake.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::cell::{RefCell, RefMut};
use std::fmt::Arguments;

use proc_macro2::Span;
Expand All @@ -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<Self> {
let span = span.unwrap().start();

Some(Self {
line: span.line(),
column: span.column(),
})
}

#[cfg(proc_macro_span)]
pub(crate) fn end(span: Span) -> Option<Self> {
let span = span.unwrap().end();

Some(Self {
line: span.line(),
column: span.column(),
})
}

#[cfg(not(proc_macro_span))]
pub(crate) fn start(_: Span) -> Option<Self> {
None
}

#[cfg(not(proc_macro_span))]
pub(crate) fn end(_: Span) -> Option<Self> {
None
}
}

#[derive(Default)]
pub(crate) struct Buf {
buf: Option<String>,
buf: RefCell<String>,
}

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<Cursor> {
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<Cursor> {
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<LineColumn> {
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<LineColumn> {
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.
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions genco-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#![recursion_limit = "256"]
#![allow(clippy::type_complexity)]
#![cfg_attr(proc_macro_span, feature(proc_macro_span))]

extern crate proc_macro;

Expand Down
2 changes: 1 addition & 1 deletion genco-macros/src/quote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
40 changes: 28 additions & 12 deletions genco-macros/src/string_parser.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<Self> {
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)> {
Expand All @@ -220,23 +226,28 @@ impl<'a> StringParser<'a> {
if input.peek(syn::Token![$]) && input.peek2(syn::Token![$]) {
let start = input.parse::<syn::Token![$]>()?;
let escape = input.parse::<syn::Token![$]>()?;
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;
}

if input.peek(syn::Token![$]) {
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::<syn::LitStr>()?;
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::<syn::Expr>()?;
encoder.raw_expr(&expr, start.start(), Some(end.end()))?;
encoder.raw_expr(&expr, start.start, Some(end.end))?;
}
}
(literal_name, _) => {
Expand All @@ -254,7 +265,9 @@ impl<'a> StringParser<'a> {

if !input.peek(token::Paren) {
let ident = input.parse::<syn::Ident>()?;
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;
}

Expand All @@ -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::<TokenTree>()?;
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)?;
Expand Down

0 comments on commit 34dd6b9

Please sign in to comment.