use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::rustc_ast::token;
use crate::rustc_errors::plain_emitter::PlainEmitter;
use crate::rustc_errors::{DiagCtxt, PResult};
use crate::rustc_parse::lexer::StripTokens;
use crate::rustc_parse::new_parser_from_source_str;
use crate::rustc_parse::parser::{
AllowConstBlockItems, AttemptLocalParseRecovery, CommaRecoveryMode, ForceCollect, Parser,
RecoverColon, RecoverComma,
};
use crate::rustc_session::parse::ParseSess;
use crate::rustc_span::edition::Edition;
use crate::rustc_span::fatal_error::catch_fatal_errors;
use crate::rustc_span::source_map::{FilePathMapping, SourceMap};
use crate::rustc_span::{FileName, create_session_if_not_set_then};
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Fragment {
Expr,
Block,
Stmt,
Item,
Items,
Type,
Pat,
}
pub fn parses_as(source: &str, kind: Fragment) -> Result<(), Vec<String>> {
assert!(
crate::unwind_janky::unwinding_is_enabled(),
"parses_as needs panic=unwind and a catcher installed through unwind_janky::install_catcher"
);
create_session_if_not_set_then(Edition::Edition2024, |_| parse_in_session(source, kind))
}
pub fn parses(source: &str) -> Result<(), Vec<String>> {
parses_as(source, Fragment::Items)
}
fn parse_in_session(source: &str, kind: Fragment) -> Result<(), Vec<String>> {
let text = Arc::new(eko::thread::Mutex::new(String::new()));
let sm = Arc::new(SourceMap::new(FilePathMapping::empty()));
let emitter = PlainEmitter::new()
.sm(Some(Arc::clone(&sm)))
.short_message(true)
.dst(Box::new(super::Sink(Arc::clone(&text))));
let psess = ParseSess::with_dcx(DiagCtxt::new(Box::new(emitter)), sm);
let finished = catch_fatal_errors(|| parse_all(&psess, source, kind));
let _ = psess.dcx().emit_stashed_diagnostics();
let refused = finished.is_err() || psess.dcx().has_errors().is_some();
let captured = text.lock().clone();
let mut errors = error_entries(&captured);
if !refused && errors.is_empty() {
return Ok(());
}
if errors.is_empty() {
errors.push("error: the parser stopped without a diagnostic".to_string());
}
Err(errors)
}
fn parse_all(psess: &ParseSess, source: &str, kind: Fragment) {
let strip = if kind == Fragment::Items { StripTokens::Shebang } else { StripTokens::Nothing };
let name = FileName::anon_source_code(source);
let mut parser = match new_parser_from_source_str(psess, name, source.to_string(), strip) {
Ok(parser) => parser,
Err(diags) => {
for diag in diags {
let _ = diag.emit();
}
return;
}
};
let result = match parse_fragment(&mut parser, kind) {
Ok(()) if parser.token == token::Eof => Ok(()),
Ok(()) => parser.unexpected(),
Err(diag) => Err(diag),
};
if let Err(diag) = result {
let _ = diag.emit();
}
}
fn parse_fragment<'a>(parser: &mut Parser<'a>, kind: Fragment) -> PResult<'a, ()> {
match kind {
Fragment::Expr => parser.parse_expr().map(drop),
Fragment::Block => parser.parse_block().map(drop),
Fragment::Stmt => parser.parse_full_stmt(AttemptLocalParseRecovery::No).map(drop),
Fragment::Item => match parser.parse_item(ForceCollect::No, AllowConstBlockItems::Yes)? {
Some(_) => Ok(()),
None => parser.unexpected(),
},
Fragment::Items => parser.parse_crate_mod().map(drop),
Fragment::Type => parser.parse_ty().map(drop),
Fragment::Pat => parser
.parse_pat_no_top_guard(
None,
RecoverComma::No,
RecoverColon::No,
CommaRecoveryMode::EitherTupleOrPipe,
)
.map(drop),
}
}
fn error_entries(captured: &str) -> Vec<String> {
super::split_diagnostics(captured).0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn entries_keep_location_lines_and_drop_warnings() {
let captured = "error: expected expression, found `<eof>`\n --> a.rs:1:4\n\
warning: unused\n --> a.rs:1:1\nerror: second\n";
assert_eq!(
error_entries(captured),
vec![
"error: expected expression, found `<eof>`\n --> a.rs:1:4".to_string(),
"error: second".to_string(),
]
);
}
}