mirror of
https://github.com/rust-lang/rust.git
synced 2025-11-23 11:17:20 +00:00
487 lines
18 KiB
Rust
487 lines
18 KiB
Rust
//! # Quasiquoter
|
|
//! This file contains the implementation internals of the quasiquoter provided by `quote!`.
|
|
|
|
//! This quasiquoter uses macros 2.0 hygiene to reliably access
|
|
//! items from `proc_macro`, to build a `proc_macro::TokenStream`.
|
|
|
|
use crate::{
|
|
BitOr, Delimiter, Group, Ident, Literal, Punct, Spacing, Span, ToTokens, TokenStream, TokenTree,
|
|
};
|
|
|
|
#[doc(hidden)]
|
|
pub struct HasIterator; // True
|
|
#[doc(hidden)]
|
|
pub struct ThereIsNoIteratorInRepetition; // False
|
|
|
|
impl BitOr<ThereIsNoIteratorInRepetition> for ThereIsNoIteratorInRepetition {
|
|
type Output = ThereIsNoIteratorInRepetition;
|
|
fn bitor(self, _rhs: ThereIsNoIteratorInRepetition) -> ThereIsNoIteratorInRepetition {
|
|
ThereIsNoIteratorInRepetition
|
|
}
|
|
}
|
|
|
|
impl BitOr<ThereIsNoIteratorInRepetition> for HasIterator {
|
|
type Output = HasIterator;
|
|
fn bitor(self, _rhs: ThereIsNoIteratorInRepetition) -> HasIterator {
|
|
HasIterator
|
|
}
|
|
}
|
|
|
|
impl BitOr<HasIterator> for ThereIsNoIteratorInRepetition {
|
|
type Output = HasIterator;
|
|
fn bitor(self, _rhs: HasIterator) -> HasIterator {
|
|
HasIterator
|
|
}
|
|
}
|
|
|
|
impl BitOr<HasIterator> for HasIterator {
|
|
type Output = HasIterator;
|
|
fn bitor(self, _rhs: HasIterator) -> HasIterator {
|
|
HasIterator
|
|
}
|
|
}
|
|
|
|
/// Extension traits used by the implementation of `quote!`. These are defined
|
|
/// in separate traits, rather than as a single trait due to ambiguity issues.
|
|
///
|
|
/// These traits expose a `quote_into_iter` method which should allow calling
|
|
/// whichever impl happens to be applicable. Calling that method repeatedly on
|
|
/// the returned value should be idempotent.
|
|
#[doc(hidden)]
|
|
pub mod ext {
|
|
use core::slice;
|
|
use std::collections::btree_set::{self, BTreeSet};
|
|
|
|
use super::{
|
|
HasIterator as HasIter, RepInterp, ThereIsNoIteratorInRepetition as DoesNotHaveIter,
|
|
};
|
|
use crate::ToTokens;
|
|
|
|
/// Extension trait providing the `quote_into_iter` method on iterators.
|
|
#[doc(hidden)]
|
|
pub trait RepIteratorExt: Iterator + Sized {
|
|
fn quote_into_iter(self) -> (Self, HasIter) {
|
|
(self, HasIter)
|
|
}
|
|
}
|
|
|
|
impl<T: Iterator> RepIteratorExt for T {}
|
|
|
|
/// Extension trait providing the `quote_into_iter` method for
|
|
/// non-iterable types. These types interpolate the same value in each
|
|
/// iteration of the repetition.
|
|
#[doc(hidden)]
|
|
pub trait RepToTokensExt {
|
|
/// Pretend to be an iterator for the purposes of `quote_into_iter`.
|
|
/// This allows repeated calls to `quote_into_iter` to continue
|
|
/// correctly returning DoesNotHaveIter.
|
|
fn next(&self) -> Option<&Self> {
|
|
Some(self)
|
|
}
|
|
|
|
fn quote_into_iter(&self) -> (&Self, DoesNotHaveIter) {
|
|
(self, DoesNotHaveIter)
|
|
}
|
|
}
|
|
|
|
impl<T: ToTokens + ?Sized> RepToTokensExt for T {}
|
|
|
|
/// Extension trait providing the `quote_into_iter` method for types that
|
|
/// can be referenced as an iterator.
|
|
#[doc(hidden)]
|
|
pub trait RepAsIteratorExt<'q> {
|
|
type Iter: Iterator;
|
|
|
|
fn quote_into_iter(&'q self) -> (Self::Iter, HasIter);
|
|
}
|
|
|
|
impl<'q, T: RepAsIteratorExt<'q> + ?Sized> RepAsIteratorExt<'q> for &T {
|
|
type Iter = T::Iter;
|
|
|
|
fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
|
|
<T as RepAsIteratorExt>::quote_into_iter(*self)
|
|
}
|
|
}
|
|
|
|
impl<'q, T: RepAsIteratorExt<'q> + ?Sized> RepAsIteratorExt<'q> for &mut T {
|
|
type Iter = T::Iter;
|
|
|
|
fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
|
|
<T as RepAsIteratorExt>::quote_into_iter(*self)
|
|
}
|
|
}
|
|
|
|
impl<'q, T: 'q> RepAsIteratorExt<'q> for [T] {
|
|
type Iter = slice::Iter<'q, T>;
|
|
|
|
fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
|
|
(self.iter(), HasIter)
|
|
}
|
|
}
|
|
|
|
impl<'q, T: 'q, const N: usize> RepAsIteratorExt<'q> for [T; N] {
|
|
type Iter = slice::Iter<'q, T>;
|
|
|
|
fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
|
|
(self.iter(), HasIter)
|
|
}
|
|
}
|
|
|
|
impl<'q, T: 'q> RepAsIteratorExt<'q> for Vec<T> {
|
|
type Iter = slice::Iter<'q, T>;
|
|
|
|
fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
|
|
(self.iter(), HasIter)
|
|
}
|
|
}
|
|
|
|
impl<'q, T: 'q> RepAsIteratorExt<'q> for BTreeSet<T> {
|
|
type Iter = btree_set::Iter<'q, T>;
|
|
|
|
fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
|
|
(self.iter(), HasIter)
|
|
}
|
|
}
|
|
|
|
impl<'q, T: RepAsIteratorExt<'q>> RepAsIteratorExt<'q> for RepInterp<T> {
|
|
type Iter = T::Iter;
|
|
|
|
fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
|
|
self.0.quote_into_iter()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper type used within interpolations to allow for repeated binding names.
|
|
// Implements the relevant traits, and exports a dummy `next()` method.
|
|
#[derive(Copy, Clone)]
|
|
#[doc(hidden)]
|
|
pub struct RepInterp<T>(pub T);
|
|
|
|
impl<T> RepInterp<T> {
|
|
// This method is intended to look like `Iterator::next`, and is called when
|
|
// a name is bound multiple times, as the previous binding will shadow the
|
|
// original `Iterator` object. This allows us to avoid advancing the
|
|
// iterator multiple times per iteration.
|
|
pub fn next(self) -> Option<T> {
|
|
Some(self.0)
|
|
}
|
|
}
|
|
|
|
impl<T: Iterator> Iterator for RepInterp<T> {
|
|
type Item = T::Item;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
self.0.next()
|
|
}
|
|
}
|
|
|
|
impl<T: ToTokens> ToTokens for RepInterp<T> {
|
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
|
self.0.to_tokens(tokens);
|
|
}
|
|
}
|
|
|
|
macro_rules! minimal_quote_tt {
|
|
(($($t:tt)*)) => { Group::new(Delimiter::Parenthesis, minimal_quote!($($t)*)) };
|
|
([$($t:tt)*]) => { Group::new(Delimiter::Bracket, minimal_quote!($($t)*)) };
|
|
({$($t:tt)*}) => { Group::new(Delimiter::Brace, minimal_quote!($($t)*)) };
|
|
(,) => { Punct::new(',', Spacing::Alone) };
|
|
(.) => { Punct::new('.', Spacing::Alone) };
|
|
(;) => { Punct::new(';', Spacing::Alone) };
|
|
(!) => { Punct::new('!', Spacing::Alone) };
|
|
(<) => { Punct::new('<', Spacing::Alone) };
|
|
(>) => { Punct::new('>', Spacing::Alone) };
|
|
(&) => { Punct::new('&', Spacing::Alone) };
|
|
(=) => { Punct::new('=', Spacing::Alone) };
|
|
(#) => { Punct::new('#', Spacing::Alone) };
|
|
(|) => { Punct::new('|', Spacing::Alone) };
|
|
(:) => { Punct::new(':', Spacing::Alone) };
|
|
(*) => { Punct::new('*', Spacing::Alone) };
|
|
(_) => { Ident::new("_", Span::def_site()) };
|
|
($i:ident) => { Ident::new(stringify!($i), Span::def_site()) };
|
|
($lit:literal) => { stringify!($lit).parse::<Literal>().unwrap() };
|
|
}
|
|
|
|
macro_rules! minimal_quote_ts {
|
|
((@ $($t:tt)*)) => { $($t)* };
|
|
(::) => {
|
|
{
|
|
let mut c = (
|
|
TokenTree::from(Punct::new(':', Spacing::Joint)),
|
|
TokenTree::from(Punct::new(':', Spacing::Alone))
|
|
);
|
|
c.0.set_span(Span::def_site());
|
|
c.1.set_span(Span::def_site());
|
|
[c.0, c.1].into_iter().collect::<TokenStream>()
|
|
}
|
|
};
|
|
(=>) => {
|
|
{
|
|
let mut c = (
|
|
TokenTree::from(Punct::new('=', Spacing::Joint)),
|
|
TokenTree::from(Punct::new('>', Spacing::Alone))
|
|
);
|
|
c.0.set_span(Span::def_site());
|
|
c.1.set_span(Span::def_site());
|
|
[c.0, c.1].into_iter().collect::<TokenStream>()
|
|
}
|
|
};
|
|
(+=) => {
|
|
{
|
|
let mut c = (
|
|
TokenTree::from(Punct::new('+', Spacing::Joint)),
|
|
TokenTree::from(Punct::new('=', Spacing::Alone))
|
|
);
|
|
c.0.set_span(Span::def_site());
|
|
c.1.set_span(Span::def_site());
|
|
[c.0, c.1].into_iter().collect::<TokenStream>()
|
|
}
|
|
};
|
|
(!=) => {
|
|
{
|
|
let mut c = (
|
|
TokenTree::from(Punct::new('!', Spacing::Joint)),
|
|
TokenTree::from(Punct::new('=', Spacing::Alone))
|
|
);
|
|
c.0.set_span(Span::def_site());
|
|
c.1.set_span(Span::def_site());
|
|
[c.0, c.1].into_iter().collect::<TokenStream>()
|
|
}
|
|
};
|
|
($t:tt) => { TokenTree::from(minimal_quote_tt!($t)) };
|
|
}
|
|
|
|
/// Simpler version of the real `quote!` macro, implemented solely
|
|
/// through `macro_rules`, for bootstrapping the real implementation
|
|
/// (see the `quote` function), which does not have access to the
|
|
/// real `quote!` macro due to the `proc_macro` crate not being
|
|
/// able to depend on itself.
|
|
///
|
|
/// Note: supported tokens are a subset of the real `quote!`, but
|
|
/// unquoting is different: instead of `$x`, this uses `(@ expr)`.
|
|
macro_rules! minimal_quote {
|
|
($($t:tt)*) => {
|
|
{
|
|
#[allow(unused_mut)] // In case the expansion is empty
|
|
let mut ts = TokenStream::new();
|
|
$(ToTokens::to_tokens(&minimal_quote_ts!($t), &mut ts);)*
|
|
ts
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Quote a `TokenStream` into a `TokenStream`.
|
|
/// This is the actual implementation of the `quote!()` proc macro.
|
|
///
|
|
/// It is loaded by the compiler in `register_builtin_macros`.
|
|
#[unstable(feature = "proc_macro_quote", issue = "54722")]
|
|
pub fn quote(stream: TokenStream) -> TokenStream {
|
|
if stream.is_empty() {
|
|
return minimal_quote!(crate::TokenStream::new());
|
|
}
|
|
let proc_macro_crate = minimal_quote!(crate);
|
|
let mut after_dollar = false;
|
|
|
|
let mut tokens = crate::TokenStream::new();
|
|
let mut iter = stream.into_iter().peekable();
|
|
while let Some(tree) = iter.next() {
|
|
if after_dollar {
|
|
after_dollar = false;
|
|
match tree {
|
|
TokenTree::Group(tt) => {
|
|
// Handles repetition by expanding `$( CONTENTS ) SEP_OPT *` to `{ REP_EXPANDED }`.
|
|
let contents = tt.stream();
|
|
|
|
// The `*` token is also consumed here.
|
|
let sep_opt: Option<Punct> = match (iter.next(), iter.peek()) {
|
|
(Some(TokenTree::Punct(sep)), Some(TokenTree::Punct(star)))
|
|
if sep.spacing() == Spacing::Joint && star.as_char() == '*' =>
|
|
{
|
|
iter.next();
|
|
Some(sep)
|
|
}
|
|
(Some(TokenTree::Punct(star)), _) if star.as_char() == '*' => None,
|
|
_ => panic!("`$(...)` must be followed by `*` in `quote!`"),
|
|
};
|
|
|
|
let mut rep_expanded = TokenStream::new();
|
|
|
|
// Append setup code for a `while`, where recursively quoted `CONTENTS`
|
|
// and `SEP_OPT` are repeatedly processed, to `REP_EXPANDED`.
|
|
let meta_vars = collect_meta_vars(contents.clone());
|
|
minimal_quote!(
|
|
use crate::ext::*;
|
|
(@ if sep_opt.is_some() {
|
|
minimal_quote!(let mut _i = 0usize;)
|
|
} else {
|
|
minimal_quote!(();)
|
|
})
|
|
let has_iter = crate::ThereIsNoIteratorInRepetition;
|
|
)
|
|
.to_tokens(&mut rep_expanded);
|
|
for meta_var in &meta_vars {
|
|
minimal_quote!(
|
|
#[allow(unused_mut)]
|
|
let (mut (@ meta_var), i) = (@ meta_var).quote_into_iter();
|
|
let has_iter = has_iter | i;
|
|
)
|
|
.to_tokens(&mut rep_expanded);
|
|
}
|
|
minimal_quote!(let _: crate::HasIterator = has_iter;)
|
|
.to_tokens(&mut rep_expanded);
|
|
|
|
// Append the `while` to `REP_EXPANDED`.
|
|
let mut while_body = TokenStream::new();
|
|
for meta_var in &meta_vars {
|
|
minimal_quote!(
|
|
let (@ meta_var) = match (@ meta_var).next() {
|
|
Some(_x) => crate::RepInterp(_x),
|
|
None => break,
|
|
};
|
|
)
|
|
.to_tokens(&mut while_body);
|
|
}
|
|
minimal_quote!(
|
|
(@ if let Some(sep) = sep_opt {
|
|
minimal_quote!(
|
|
if _i > 0 {
|
|
(@ minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Punct(crate::Punct::new(
|
|
(@ TokenTree::from(Literal::character(sep.as_char()))),
|
|
(@ minimal_quote!(crate::Spacing::Alone)),
|
|
)), &mut ts);))
|
|
}
|
|
_i += 1;
|
|
)
|
|
} else {
|
|
minimal_quote!(();)
|
|
})
|
|
(@ quote(contents.clone())).to_tokens(&mut ts);
|
|
)
|
|
.to_tokens(&mut while_body);
|
|
rep_expanded.extend(vec![
|
|
TokenTree::Ident(Ident::new("while", Span::call_site())),
|
|
TokenTree::Ident(Ident::new("true", Span::call_site())),
|
|
TokenTree::Group(Group::new(Delimiter::Brace, while_body)),
|
|
]);
|
|
|
|
minimal_quote!((@ TokenTree::Group(Group::new(Delimiter::Brace, rep_expanded)))).to_tokens(&mut tokens);
|
|
continue;
|
|
}
|
|
TokenTree::Ident(_) => {
|
|
minimal_quote!(crate::ToTokens::to_tokens(&(@ tree), &mut ts);)
|
|
.to_tokens(&mut tokens);
|
|
continue;
|
|
}
|
|
TokenTree::Punct(ref tt) if tt.as_char() == '$' => {}
|
|
_ => panic!(
|
|
"`$` must be followed by an ident or `$` or a repetition group in `quote!`"
|
|
),
|
|
}
|
|
} else if let TokenTree::Punct(ref tt) = tree {
|
|
if tt.as_char() == '$' {
|
|
after_dollar = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
match tree {
|
|
TokenTree::Punct(tt) => {
|
|
minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Punct(crate::Punct::new(
|
|
(@ TokenTree::from(Literal::character(tt.as_char()))),
|
|
(@ match tt.spacing() {
|
|
Spacing::Alone => minimal_quote!(crate::Spacing::Alone),
|
|
Spacing::Joint => minimal_quote!(crate::Spacing::Joint),
|
|
}),
|
|
)), &mut ts);)
|
|
}
|
|
TokenTree::Group(tt) => {
|
|
minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Group(crate::Group::new(
|
|
(@ match tt.delimiter() {
|
|
Delimiter::Parenthesis => minimal_quote!(crate::Delimiter::Parenthesis),
|
|
Delimiter::Brace => minimal_quote!(crate::Delimiter::Brace),
|
|
Delimiter::Bracket => minimal_quote!(crate::Delimiter::Bracket),
|
|
Delimiter::None => minimal_quote!(crate::Delimiter::None),
|
|
}),
|
|
(@ quote(tt.stream())),
|
|
)), &mut ts);)
|
|
}
|
|
TokenTree::Ident(tt) => {
|
|
let literal = tt.to_string();
|
|
let (literal, ctor) = if let Some(stripped) = literal.strip_prefix("r#") {
|
|
(stripped, minimal_quote!(crate::Ident::new_raw))
|
|
} else {
|
|
(literal.as_str(), minimal_quote!(crate::Ident::new))
|
|
};
|
|
minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Ident((@ ctor)(
|
|
(@ TokenTree::from(Literal::string(literal))),
|
|
(@ quote_span(proc_macro_crate.clone(), tt.span())),
|
|
)), &mut ts);)
|
|
}
|
|
TokenTree::Literal(tt) => {
|
|
minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Literal({
|
|
let mut iter = (@ TokenTree::from(Literal::string(&tt.to_string())))
|
|
.parse::<crate::TokenStream>()
|
|
.unwrap()
|
|
.into_iter();
|
|
if let (Some(crate::TokenTree::Literal(mut lit)), None) =
|
|
(iter.next(), iter.next())
|
|
{
|
|
lit.set_span((@ quote_span(proc_macro_crate.clone(), tt.span())));
|
|
lit
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
}), &mut ts);)
|
|
}
|
|
}
|
|
.to_tokens(&mut tokens);
|
|
}
|
|
if after_dollar {
|
|
panic!("unexpected trailing `$` in `quote!`");
|
|
}
|
|
|
|
minimal_quote! {
|
|
{
|
|
let mut ts = crate::TokenStream::new();
|
|
(@ tokens)
|
|
ts
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Helper function to support macro repetitions like `$( CONTENTS ) SEP_OPT *` in `quote!`.
|
|
/// Recursively collects all `Ident`s (meta-variables) that follow a `$`
|
|
/// from the given `CONTENTS` stream, preserving their order of appearance.
|
|
fn collect_meta_vars(content_stream: TokenStream) -> Vec<Ident> {
|
|
fn helper(stream: TokenStream, out: &mut Vec<Ident>) {
|
|
let mut iter = stream.into_iter().peekable();
|
|
while let Some(tree) = iter.next() {
|
|
match &tree {
|
|
TokenTree::Punct(tt) if tt.as_char() == '$' => {
|
|
if let Some(TokenTree::Ident(id)) = iter.peek() {
|
|
out.push(id.clone());
|
|
iter.next();
|
|
}
|
|
}
|
|
TokenTree::Group(tt) => {
|
|
helper(tt.stream(), out);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut vars = Vec::new();
|
|
helper(content_stream, &mut vars);
|
|
vars
|
|
}
|
|
|
|
/// Quote a `Span` into a `TokenStream`.
|
|
/// This is needed to implement a custom quoter.
|
|
#[unstable(feature = "proc_macro_quote", issue = "54722")]
|
|
pub fn quote_span(proc_macro_crate: TokenStream, span: Span) -> TokenStream {
|
|
let id = span.save_span();
|
|
minimal_quote!((@ proc_macro_crate ) ::Span::recover_proc_macro_span((@ TokenTree::from(Literal::usize_unsuffixed(id)))))
|
|
}
|