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