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

Perform own detection for proc_macro_span (fixes #39) #43

Merged
merged 1 commit into from
Sep 25, 2023
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
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