#![feature(bool_to_option)]
#![feature(crate_visibility_modifier)]
#![feature(bindings_after_at)]
#![feature(iter_order_by)]
#![feature(or_patterns)]
use rustc_ast as ast;
use rustc_ast::token::{self, DelimToken, Nonterminal, Token, TokenKind};
use rustc_ast::tokenstream::{self, LazyTokenStream, TokenStream, TokenTree};
use rustc_ast_pretty::pprust;
use rustc_data_structures::sync::Lrc;
use rustc_errors::{Diagnostic, FatalError, Level, PResult};
use rustc_session::parse::ParseSess;
use rustc_span::{symbol::kw, FileName, SourceFile, Span, DUMMY_SP};
use smallvec::SmallVec;
use std::mem;
use std::path::Path;
use std::str;
use tracing::{debug, info};
pub const MACRO_ARGUMENTS: Option<&str> = Some("macro arguments");
#[macro_use]
pub mod parser;
use parser::{emit_unclosed_delims, make_unclosed_delims_error, Parser};
pub mod lexer;
pub mod validate_attr;
macro_rules! panictry_buffer {
($handler:expr, $e:expr) => {{
use rustc_errors::FatalError;
use std::result::Result::{Err, Ok};
match $e {
Ok(e) => e,
Err(errs) => {
for e in errs {
$handler.emit_diagnostic(&e);
}
FatalError.raise()
}
}
}};
}
pub fn parse_crate_from_file<'a>(input: &Path, sess: &'a ParseSess) -> PResult<'a, ast::Crate> {
let mut parser = new_parser_from_file(sess, input, None);
parser.parse_crate_mod()
}
pub fn parse_crate_attrs_from_file<'a>(
input: &Path,
sess: &'a ParseSess,
) -> PResult<'a, Vec<ast::Attribute>> {
let mut parser = new_parser_from_file(sess, input, None);
parser.parse_inner_attributes()
}
pub fn parse_crate_from_source_str(
name: FileName,
source: String,
sess: &ParseSess,
) -> PResult<'_, ast::Crate> {
new_parser_from_source_str(sess, name, source).parse_crate_mod()
}
pub fn parse_crate_attrs_from_source_str(
name: FileName,
source: String,
sess: &ParseSess,
) -> PResult<'_, Vec<ast::Attribute>> {
new_parser_from_source_str(sess, name, source).parse_inner_attributes()
}
pub fn parse_stream_from_source_str(
name: FileName,
source: String,
sess: &ParseSess,
override_span: Option<Span>,
) -> TokenStream {
let (stream, mut errors) =
source_file_to_stream(sess, sess.source_map().new_source_file(name, source), override_span);
emit_unclosed_delims(&mut errors, &sess);
stream
}
pub fn new_parser_from_source_str(sess: &ParseSess, name: FileName, source: String) -> Parser<'_> {
panictry_buffer!(&sess.span_diagnostic, maybe_new_parser_from_source_str(sess, name, source))
}
pub fn maybe_new_parser_from_source_str(
sess: &ParseSess,
name: FileName,
source: String,
) -> Result<Parser<'_>, Vec<Diagnostic>> {
maybe_source_file_to_parser(sess, sess.source_map().new_source_file(name, source))
}
pub fn new_parser_from_file<'a>(sess: &'a ParseSess, path: &Path, sp: Option<Span>) -> Parser<'a> {
source_file_to_parser(sess, file_to_source_file(sess, path, sp))
}
fn source_file_to_parser(sess: &ParseSess, source_file: Lrc<SourceFile>) -> Parser<'_> {
panictry_buffer!(&sess.span_diagnostic, maybe_source_file_to_parser(sess, source_file))
}
fn maybe_source_file_to_parser(
sess: &ParseSess,
source_file: Lrc<SourceFile>,
) -> Result<Parser<'_>, Vec<Diagnostic>> {
let end_pos = source_file.end_pos;
let (stream, unclosed_delims) = maybe_file_to_stream(sess, source_file, None)?;
let mut parser = stream_to_parser(sess, stream, None);
parser.unclosed_delims = unclosed_delims;
if parser.token == token::Eof {
parser.token.span = Span::new(end_pos, end_pos, parser.token.span.ctxt());
}
Ok(parser)
}
fn try_file_to_source_file(
sess: &ParseSess,
path: &Path,
spanopt: Option<Span>,
) -> Result<Lrc<SourceFile>, Diagnostic> {
sess.source_map().load_file(path).map_err(|e| {
let msg = format!("couldn't read {}: {}", path.display(), e);
let mut diag = Diagnostic::new(Level::Fatal, &msg);
if let Some(sp) = spanopt {
diag.set_span(sp);
}
diag
})
}
fn file_to_source_file(sess: &ParseSess, path: &Path, spanopt: Option<Span>) -> Lrc<SourceFile> {
match try_file_to_source_file(sess, path, spanopt) {
Ok(source_file) => source_file,
Err(d) => {
sess.span_diagnostic.emit_diagnostic(&d);
FatalError.raise();
}
}
}
pub fn source_file_to_stream(
sess: &ParseSess,
source_file: Lrc<SourceFile>,
override_span: Option<Span>,
) -> (TokenStream, Vec<lexer::UnmatchedBrace>) {
panictry_buffer!(&sess.span_diagnostic, maybe_file_to_stream(sess, source_file, override_span))
}
pub fn maybe_file_to_stream(
sess: &ParseSess,
source_file: Lrc<SourceFile>,
override_span: Option<Span>,
) -> Result<(TokenStream, Vec<lexer::UnmatchedBrace>), Vec<Diagnostic>> {
let src = source_file.src.as_ref().unwrap_or_else(|| {
sess.span_diagnostic
.bug(&format!("cannot lex `source_file` without source: {}", source_file.name));
});
let (token_trees, unmatched_braces) =
lexer::parse_token_trees(sess, src.as_str(), source_file.start_pos, override_span);
match token_trees {
Ok(stream) => Ok((stream, unmatched_braces)),
Err(err) => {
let mut buffer = Vec::with_capacity(1);
err.buffer(&mut buffer);
for unmatched in unmatched_braces {
if let Some(err) = make_unclosed_delims_error(unmatched, &sess) {
err.buffer(&mut buffer);
}
}
Err(buffer)
}
}
}
pub fn stream_to_parser<'a>(
sess: &'a ParseSess,
stream: TokenStream,
subparser_name: Option<&'static str>,
) -> Parser<'a> {
Parser::new(sess, stream, false, subparser_name)
}
pub fn parse_in<'a, T>(
sess: &'a ParseSess,
tts: TokenStream,
name: &'static str,
mut f: impl FnMut(&mut Parser<'a>) -> PResult<'a, T>,
) -> PResult<'a, T> {
let mut parser = Parser::new(sess, tts, false, Some(name));
let result = f(&mut parser)?;
if parser.token != token::Eof {
parser.unexpected()?;
}
Ok(result)
}
pub fn nt_to_tokenstream(nt: &Nonterminal, sess: &ParseSess, span: Span) -> TokenStream {
let convert_tokens =
|tokens: &Option<LazyTokenStream>| tokens.as_ref().map(|t| t.create_token_stream());
let tokens = match *nt {
Nonterminal::NtItem(ref item) => prepend_attrs(&item.attrs, item.tokens.as_ref()),
Nonterminal::NtBlock(ref block) => convert_tokens(&block.tokens),
Nonterminal::NtStmt(ref stmt) => {
convert_tokens(&stmt.tokens)
}
Nonterminal::NtPat(ref pat) => convert_tokens(&pat.tokens),
Nonterminal::NtTy(ref ty) => convert_tokens(&ty.tokens),
Nonterminal::NtIdent(ident, is_raw) => {
Some(tokenstream::TokenTree::token(token::Ident(ident.name, is_raw), ident.span).into())
}
Nonterminal::NtLifetime(ident) => {
Some(tokenstream::TokenTree::token(token::Lifetime(ident.name), ident.span).into())
}
Nonterminal::NtMeta(ref attr) => convert_tokens(&attr.tokens),
Nonterminal::NtPath(ref path) => convert_tokens(&path.tokens),
Nonterminal::NtVis(ref vis) => convert_tokens(&vis.tokens),
Nonterminal::NtTT(ref tt) => Some(tt.clone().into()),
Nonterminal::NtExpr(ref expr) | Nonterminal::NtLiteral(ref expr) => {
if expr.tokens.is_none() {
debug!("missing tokens for expr {:?}", expr);
}
prepend_attrs(&expr.attrs, expr.tokens.as_ref())
}
};
let source = pprust::nonterminal_to_string_no_extra_parens(nt);
let filename = FileName::macro_expansion_source_code(&source);
let reparsed_tokens = parse_stream_from_source_str(filename, source, sess, Some(span));
if let Some(tokens) = tokens {
if tokenstream_probably_equal_for_proc_macro(&tokens, &reparsed_tokens, sess, false) {
return tokens;
}
let source_with_parens = pprust::nonterminal_to_string(nt);
let filename_with_parens = FileName::macro_expansion_source_code(&source_with_parens);
let reparsed_tokens_with_parens = parse_stream_from_source_str(
filename_with_parens,
source_with_parens,
sess,
Some(span),
);
if tokenstream_probably_equal_for_proc_macro(
&tokens,
&reparsed_tokens_with_parens,
sess,
true,
) {
return tokens;
}
info!(
"cached tokens found, but they're not \"probably equal\", \
going with stringified version"
);
info!("cached tokens: {}", pprust::tts_to_string(&tokens));
info!("reparsed tokens: {}", pprust::tts_to_string(&reparsed_tokens_with_parens));
info!("cached tokens debug: {:?}", tokens);
info!("reparsed tokens debug: {:?}", reparsed_tokens_with_parens);
}
reparsed_tokens
}
pub fn tokenstream_probably_equal_for_proc_macro(
tokens: &TokenStream,
reparsed_tokens: &TokenStream,
sess: &ParseSess,
relaxed_delim_match: bool,
) -> bool {
fn semantic_tree(tree: &TokenTree) -> bool {
if let TokenTree::Token(token) = tree {
if let
| token::Comma
| token::Semi
| token::BinOp(token::BinOpToken::Or)
| token::BinOp(token::BinOpToken::Plus)
| token::ModSep = token.kind {
return false;
}
}
true
}
fn break_tokens(tree: TokenTree) -> impl Iterator<Item = TokenTree> {
let mut token_trees: SmallVec<[_; 2]>;
if let TokenTree::Token(token) = &tree {
let mut out = SmallVec::<[_; 2]>::new();
out.push(token.clone());
loop {
let mut temp = SmallVec::<[_; 2]>::new();
let mut changed = false;
for token in out.into_iter() {
if let Some((first, second)) = token.kind.break_two_token_op() {
temp.push(Token::new(first, DUMMY_SP));
temp.push(Token::new(second, DUMMY_SP));
changed = true;
} else {
temp.push(token);
}
}
out = temp;
if !changed {
break;
}
}
token_trees = out.into_iter().map(TokenTree::Token).collect();
} else {
token_trees = SmallVec::new();
token_trees.push(tree);
}
token_trees.into_iter()
}
fn expand_token(tree: TokenTree, sess: &ParseSess) -> impl Iterator<Item = TokenTree> {
let expanded: SmallVec<[_; 1]> =
if let TokenTree::Token(Token { kind: TokenKind::Interpolated(nt), span }) = &tree {
nt_to_tokenstream(nt, sess, *span)
.into_trees()
.flat_map(|t| expand_token(t, sess))
.collect()
} else {
std::iter::once(tree)
.filter(semantic_tree)
.flat_map(break_tokens)
.filter(semantic_tree)
.collect()
};
expanded.into_iter()
}
let tokens = tokens.trees().flat_map(|t| expand_token(t, sess));
let reparsed_tokens = reparsed_tokens.trees().flat_map(|t| expand_token(t, sess));
tokens.eq_by(reparsed_tokens, |t, rt| {
tokentree_probably_equal_for_proc_macro(&t, &rt, sess, relaxed_delim_match)
})
}
pub fn tokentree_probably_equal_for_proc_macro(
token: &TokenTree,
reparsed_token: &TokenTree,
sess: &ParseSess,
relaxed_delim_match: bool,
) -> bool {
match (token, reparsed_token) {
(TokenTree::Token(token), TokenTree::Token(reparsed_token)) => {
token_probably_equal_for_proc_macro(token, reparsed_token)
}
(
TokenTree::Delimited(_, delim, tokens),
TokenTree::Delimited(_, reparsed_delim, reparsed_tokens),
) if delim == reparsed_delim => tokenstream_probably_equal_for_proc_macro(
tokens,
reparsed_tokens,
sess,
relaxed_delim_match,
),
(TokenTree::Delimited(_, DelimToken::NoDelim, tokens), reparsed_token) => {
if relaxed_delim_match {
if let TokenTree::Delimited(_, DelimToken::Paren, reparsed_tokens) = reparsed_token
{
if tokenstream_probably_equal_for_proc_macro(
tokens,
reparsed_tokens,
sess,
relaxed_delim_match,
) {
return true;
}
}
}
tokens.len() == 1
&& tokentree_probably_equal_for_proc_macro(
&tokens.trees().next().unwrap(),
reparsed_token,
sess,
relaxed_delim_match,
)
}
_ => false,
}
}
fn token_probably_equal_for_proc_macro(first: &Token, other: &Token) -> bool {
if mem::discriminant(&first.kind) != mem::discriminant(&other.kind) {
return false;
}
use rustc_ast::token::TokenKind::*;
match (&first.kind, &other.kind) {
(&Eq, &Eq)
| (&Lt, &Lt)
| (&Le, &Le)
| (&EqEq, &EqEq)
| (&Ne, &Ne)
| (&Ge, &Ge)
| (&Gt, &Gt)
| (&AndAnd, &AndAnd)
| (&OrOr, &OrOr)
| (&Not, &Not)
| (&Tilde, &Tilde)
| (&At, &At)
| (&Dot, &Dot)
| (&DotDot, &DotDot)
| (&DotDotDot, &DotDotDot)
| (&DotDotEq, &DotDotEq)
| (&Comma, &Comma)
| (&Semi, &Semi)
| (&Colon, &Colon)
| (&ModSep, &ModSep)
| (&RArrow, &RArrow)
| (&LArrow, &LArrow)
| (&FatArrow, &FatArrow)
| (&Pound, &Pound)
| (&Dollar, &Dollar)
| (&Question, &Question)
| (&Eof, &Eof) => true,
(&BinOp(a), &BinOp(b)) | (&BinOpEq(a), &BinOpEq(b)) => a == b,
(&OpenDelim(a), &OpenDelim(b)) | (&CloseDelim(a), &CloseDelim(b)) => a == b,
(&DocComment(a1, a2, a3), &DocComment(b1, b2, b3)) => a1 == b1 && a2 == b2 && a3 == b3,
(&Literal(a), &Literal(b)) => a == b,
(&Lifetime(a), &Lifetime(b)) => a == b,
(&Ident(a, b), &Ident(c, d)) => {
b == d && (a == c || a == kw::DollarCrate || c == kw::DollarCrate)
}
(&Interpolated(..), &Interpolated(..)) => panic!("Unexpanded Interpolated!"),
_ => panic!("forgot to add a token?"),
}
}
fn prepend_attrs(
attrs: &[ast::Attribute],
tokens: Option<&tokenstream::LazyTokenStream>,
) -> Option<tokenstream::TokenStream> {
let tokens = tokens?.create_token_stream();
if attrs.is_empty() {
return Some(tokens);
}
let mut builder = tokenstream::TokenStreamBuilder::new();
for attr in attrs {
if attr.style == ast::AttrStyle::Inner {
return None;
}
builder.push(
attr.tokens
.as_ref()
.unwrap_or_else(|| panic!("Attribute {:?} is missing tokens!", attr))
.create_token_stream(),
);
}
builder.push(tokens);
Some(builder.build())
}