rustc_parse/
lib.rs

1//! The main parser interface.
2
3// tidy-alphabetical-start
4#![allow(rustc::diagnostic_outside_of_impl)]
5#![allow(rustc::untranslatable_diagnostic)]
6#![feature(assert_matches)]
7#![feature(box_patterns)]
8#![feature(debug_closure_helpers)]
9#![feature(default_field_values)]
10#![feature(if_let_guard)]
11#![feature(iter_intersperse)]
12#![recursion_limit = "256"]
13// tidy-alphabetical-end
14
15use std::path::{Path, PathBuf};
16use std::str::Utf8Error;
17use std::sync::Arc;
18
19use rustc_ast as ast;
20use rustc_ast::tokenstream::{DelimSpan, TokenStream};
21use rustc_ast::{AttrItem, Attribute, MetaItemInner, token};
22use rustc_ast_pretty::pprust;
23use rustc_errors::{Diag, EmissionGuarantee, FatalError, PResult, pluralize};
24use rustc_lexer::FrontmatterAllowed;
25use rustc_session::parse::ParseSess;
26use rustc_span::source_map::SourceMap;
27use rustc_span::{FileName, SourceFile, Span};
28pub use unicode_normalization::UNICODE_VERSION as UNICODE_NORMALIZATION_VERSION;
29
30pub const MACRO_ARGUMENTS: Option<&str> = Some("macro arguments");
31
32#[macro_use]
33pub mod parser;
34use parser::Parser;
35use rustc_ast::token::Delimiter;
36
37pub mod lexer;
38
39mod errors;
40
41rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
42
43// Unwrap the result if `Ok`, otherwise emit the diagnostics and abort.
44pub fn unwrap_or_emit_fatal<T>(expr: Result<T, Vec<Diag<'_>>>) -> T {
45    match expr {
46        Ok(expr) => expr,
47        Err(errs) => {
48            for err in errs {
49                err.emit();
50            }
51            FatalError.raise()
52        }
53    }
54}
55
56/// Creates a new parser from a source string. On failure, the errors must be consumed via
57/// `unwrap_or_emit_fatal`, `emit`, `cancel`, etc., otherwise a panic will occur when they are
58/// dropped.
59pub fn new_parser_from_source_str(
60    psess: &ParseSess,
61    name: FileName,
62    source: String,
63) -> Result<Parser<'_>, Vec<Diag<'_>>> {
64    let source_file = psess.source_map().new_source_file(name, source);
65    new_parser_from_source_file(psess, source_file, FrontmatterAllowed::Yes)
66}
67
68/// Creates a new parser from a simple (no frontmatter) source string.
69///
70/// On failure, the errors must be consumed via `unwrap_or_emit_fatal`, `emit`, `cancel`,
71/// etc., otherwise a panic will occur when they are dropped.
72pub fn new_parser_from_simple_source_str(
73    psess: &ParseSess,
74    name: FileName,
75    source: String,
76) -> Result<Parser<'_>, Vec<Diag<'_>>> {
77    let source_file = psess.source_map().new_source_file(name, source);
78    new_parser_from_source_file(psess, source_file, FrontmatterAllowed::No)
79}
80
81/// Creates a new parser from a filename. On failure, the errors must be consumed via
82/// `unwrap_or_emit_fatal`, `emit`, `cancel`, etc., otherwise a panic will occur when they are
83/// dropped.
84///
85/// If a span is given, that is used on an error as the source of the problem.
86pub fn new_parser_from_file<'a>(
87    psess: &'a ParseSess,
88    path: &Path,
89    sp: Option<Span>,
90) -> Result<Parser<'a>, Vec<Diag<'a>>> {
91    let sm = psess.source_map();
92    let source_file = sm.load_file(path).unwrap_or_else(|e| {
93        let msg = format!("couldn't read `{}`: {}", path.display(), e);
94        let mut err = psess.dcx().struct_fatal(msg);
95        if let Ok(contents) = std::fs::read(path)
96            && let Err(utf8err) = String::from_utf8(contents.clone())
97        {
98            utf8_error(
99                sm,
100                &path.display().to_string(),
101                sp,
102                &mut err,
103                utf8err.utf8_error(),
104                &contents,
105            );
106        }
107        if let Some(sp) = sp {
108            err.span(sp);
109        }
110        err.emit();
111    });
112    new_parser_from_source_file(psess, source_file, FrontmatterAllowed::Yes)
113}
114
115pub fn utf8_error<E: EmissionGuarantee>(
116    sm: &SourceMap,
117    path: &str,
118    sp: Option<Span>,
119    err: &mut Diag<'_, E>,
120    utf8err: Utf8Error,
121    contents: &[u8],
122) {
123    // The file exists, but it wasn't valid UTF-8.
124    let start = utf8err.valid_up_to();
125    let note = format!("invalid utf-8 at byte `{start}`");
126    let msg = if let Some(len) = utf8err.error_len() {
127        format!(
128            "byte{s} `{bytes}` {are} not valid utf-8",
129            bytes = if len == 1 {
130                format!("{:?}", contents[start])
131            } else {
132                format!("{:?}", &contents[start..start + len])
133            },
134            s = pluralize!(len),
135            are = if len == 1 { "is" } else { "are" },
136        )
137    } else {
138        note.clone()
139    };
140    let contents = String::from_utf8_lossy(contents).to_string();
141    let source = sm.new_source_file(PathBuf::from(path).into(), contents);
142    let span = Span::with_root_ctxt(
143        source.normalized_byte_pos(start as u32),
144        source.normalized_byte_pos(start as u32),
145    );
146    if span.is_dummy() {
147        err.note(note);
148    } else {
149        if sp.is_some() {
150            err.span_note(span, msg);
151        } else {
152            err.span(span);
153            err.span_label(span, msg);
154        }
155    }
156}
157
158/// Given a session and a `source_file`, return a parser. Returns any buffered errors from lexing
159/// the initial token stream.
160fn new_parser_from_source_file(
161    psess: &ParseSess,
162    source_file: Arc<SourceFile>,
163    frontmatter_allowed: FrontmatterAllowed,
164) -> Result<Parser<'_>, Vec<Diag<'_>>> {
165    let end_pos = source_file.end_position();
166    let stream = source_file_to_stream(psess, source_file, None, frontmatter_allowed)?;
167    let mut parser = Parser::new(psess, stream, None);
168    if parser.token == token::Eof {
169        parser.token.span = Span::new(end_pos, end_pos, parser.token.span.ctxt(), None);
170    }
171    Ok(parser)
172}
173
174pub fn source_str_to_stream(
175    psess: &ParseSess,
176    name: FileName,
177    source: String,
178    override_span: Option<Span>,
179) -> Result<TokenStream, Vec<Diag<'_>>> {
180    let source_file = psess.source_map().new_source_file(name, source);
181    // used mainly for `proc_macro` and the likes, not for our parsing purposes, so don't parse
182    // frontmatters as frontmatters.
183    source_file_to_stream(psess, source_file, override_span, FrontmatterAllowed::No)
184}
185
186/// Given a source file, produces a sequence of token trees. Returns any buffered errors from
187/// parsing the token stream.
188fn source_file_to_stream<'psess>(
189    psess: &'psess ParseSess,
190    source_file: Arc<SourceFile>,
191    override_span: Option<Span>,
192    frontmatter_allowed: FrontmatterAllowed,
193) -> Result<TokenStream, Vec<Diag<'psess>>> {
194    let src = source_file.src.as_ref().unwrap_or_else(|| {
195        psess.dcx().bug(format!(
196            "cannot lex `source_file` without source: {}",
197            psess.source_map().filename_for_diagnostics(&source_file.name)
198        ));
199    });
200
201    lexer::lex_token_trees(
202        psess,
203        src.as_str(),
204        source_file.start_pos,
205        override_span,
206        frontmatter_allowed,
207    )
208}
209
210/// Runs the given subparser `f` on the tokens of the given `attr`'s item.
211pub fn parse_in<'a, T>(
212    psess: &'a ParseSess,
213    tts: TokenStream,
214    name: &'static str,
215    mut f: impl FnMut(&mut Parser<'a>) -> PResult<'a, T>,
216) -> PResult<'a, T> {
217    let mut parser = Parser::new(psess, tts, Some(name));
218    let result = f(&mut parser)?;
219    if parser.token != token::Eof {
220        parser.unexpected()?;
221    }
222    Ok(result)
223}
224
225pub fn fake_token_stream_for_item(psess: &ParseSess, item: &ast::Item) -> TokenStream {
226    let source = pprust::item_to_string(item);
227    let filename = FileName::macro_expansion_source_code(&source);
228    unwrap_or_emit_fatal(source_str_to_stream(psess, filename, source, Some(item.span)))
229}
230
231pub fn fake_token_stream_for_crate(psess: &ParseSess, krate: &ast::Crate) -> TokenStream {
232    let source = pprust::crate_to_string_for_macros(krate);
233    let filename = FileName::macro_expansion_source_code(&source);
234    unwrap_or_emit_fatal(source_str_to_stream(
235        psess,
236        filename,
237        source,
238        Some(krate.spans.inner_span),
239    ))
240}
241
242pub fn parse_cfg_attr(
243    cfg_attr: &Attribute,
244    psess: &ParseSess,
245) -> Option<(MetaItemInner, Vec<(AttrItem, Span)>)> {
246    const CFG_ATTR_GRAMMAR_HELP: &str = "#[cfg_attr(condition, attribute, other_attribute, ...)]";
247    const CFG_ATTR_NOTE_REF: &str = "for more information, visit \
248        <https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute>";
249
250    match cfg_attr.get_normal_item().args {
251        ast::AttrArgs::Delimited(ast::DelimArgs { dspan, delim, ref tokens })
252            if !tokens.is_empty() =>
253        {
254            check_cfg_attr_bad_delim(psess, dspan, delim);
255            match parse_in(psess, tokens.clone(), "`cfg_attr` input", |p| p.parse_cfg_attr()) {
256                Ok(r) => return Some(r),
257                Err(e) => {
258                    e.with_help(format!("the valid syntax is `{CFG_ATTR_GRAMMAR_HELP}`"))
259                        .with_note(CFG_ATTR_NOTE_REF)
260                        .emit();
261                }
262            }
263        }
264        _ => {
265            psess.dcx().emit_err(errors::MalformedCfgAttr {
266                span: cfg_attr.span,
267                sugg: CFG_ATTR_GRAMMAR_HELP,
268            });
269        }
270    }
271    None
272}
273
274fn check_cfg_attr_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) {
275    if let Delimiter::Parenthesis = delim {
276        return;
277    }
278    psess.dcx().emit_err(errors::CfgAttrBadDelim {
279        span: span.entire(),
280        sugg: errors::MetaBadDelimSugg { open: span.open, close: span.close },
281    });
282}