Skip to main content

rustc_parse/
lib.rs

1//! The main parser interface.
2
3// tidy-alphabetical-start
4#![cfg_attr(test, feature(iter_order_by))]
5#![feature(debug_closure_helpers)]
6#![feature(default_field_values)]
7#![feature(deref_patterns)]
8#![feature(iter_intersperse)]
9#![recursion_limit = "256"]
10// tidy-alphabetical-end
11
12use std::path::{Path, PathBuf};
13use std::str::Utf8Error;
14use std::sync::Arc;
15
16use rustc_ast as ast;
17use rustc_ast::token;
18use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree};
19use rustc_ast_pretty::pprust;
20use rustc_errors::{Diag, EmissionGuarantee, FatalError, PResult, pluralize};
21pub use rustc_lexer::UNICODE_VERSION;
22use rustc_session::parse::ParseSess;
23use rustc_span::edit_distance::find_best_match_for_name;
24use rustc_span::source_map::SourceMap;
25use rustc_span::{FileName, SourceFile, Span, Symbol};
26
27pub const MACRO_ARGUMENTS: Option<&str> = Some("macro arguments");
28
29#[macro_use]
30pub mod parser;
31use parser::Parser;
32
33use crate::lexer::StripTokens;
34
35pub mod lexer;
36
37mod diagnostics;
38
39// Make sure that the Unicode version of the dependencies is the same.
40const _: () = {
41    let rustc_lexer = rustc_lexer::UNICODE_VERSION;
42    let rustc_span = rustc_span::UNICODE_VERSION;
43    let normalization = unicode_normalization::UNICODE_VERSION;
44    let width = unicode_width::UNICODE_VERSION;
45
46    if rustc_lexer.0 != rustc_span.0
47        || rustc_lexer.1 != rustc_span.1
48        || rustc_lexer.2 != rustc_span.2
49    {
50        {
    ::core::panicking::panic_fmt(format_args!("rustc_lexer and rustc_span must use the same Unicode version, `rustc_lexer::UNICODE_VERSION` and `rustc_span::UNICODE_VERSION` are different."));
};panic!(
51            "rustc_lexer and rustc_span must use the same Unicode version, \
52            `rustc_lexer::UNICODE_VERSION` and `rustc_span::UNICODE_VERSION` are \
53            different."
54        );
55    }
56
57    if rustc_lexer.0 != normalization.0
58        || rustc_lexer.1 != normalization.1
59        || rustc_lexer.2 != normalization.2
60    {
61        {
    ::core::panicking::panic_fmt(format_args!("rustc_lexer and unicode-normalization must use the same Unicode version, `rustc_lexer::UNICODE_VERSION` and `unicode_normalization::UNICODE_VERSION` are different."));
};panic!(
62            "rustc_lexer and unicode-normalization must use the same Unicode version, \
63            `rustc_lexer::UNICODE_VERSION` and `unicode_normalization::UNICODE_VERSION` are \
64            different."
65        );
66    }
67
68    if rustc_lexer.0 != width.0 || rustc_lexer.1 != width.1 || rustc_lexer.2 != width.2 {
69        {
    ::core::panicking::panic_fmt(format_args!("rustc_lexer and unicode-width must use the same Unicode version, `rustc_lexer::UNICODE_VERSION` and `unicode_width::UNICODE_VERSION` are different."));
};panic!(
70            "rustc_lexer and unicode-width must use the same Unicode version, \
71            `rustc_lexer::UNICODE_VERSION` and `unicode_width::UNICODE_VERSION` are \
72            different."
73        );
74    }
75};
76
77// Unwrap the result if `Ok`, otherwise emit the diagnostics and abort.
78pub fn unwrap_or_emit_fatal<T>(expr: Result<T, Vec<Diag<'_>>>) -> T {
79    match expr {
80        Ok(expr) => expr,
81        Err(errs) => {
82            for err in errs {
83                err.emit();
84            }
85            FatalError.raise()
86        }
87    }
88}
89
90/// Creates a new parser from a source string.
91///
92/// On failure, the errors must be consumed via `unwrap_or_emit_fatal`, `emit`, `cancel`,
93/// etc., otherwise a panic will occur when they are dropped.
94pub fn new_parser_from_source_str(
95    psess: &ParseSess,
96    name: FileName,
97    source: String,
98    strip_tokens: StripTokens,
99) -> Result<Parser<'_>, Vec<Diag<'_>>> {
100    let source_file = psess.source_map().new_source_file(name, source);
101    new_parser_from_source_file(psess, source_file, strip_tokens)
102}
103
104/// Creates a new parser from a filename. On failure, the errors must be consumed via
105/// `unwrap_or_emit_fatal`, `emit`, `cancel`, etc., otherwise a panic will occur when they are
106/// dropped.
107///
108/// If a span is given, that is used on an error as the source of the problem.
109///
110/// Error messages are tailored to the specific error kind.
111pub fn new_parser_from_file<'a>(
112    psess: &'a ParseSess,
113    path: &Path,
114    strip_tokens: StripTokens,
115    sp: Option<Span>,
116) -> Result<Parser<'a>, Vec<Diag<'a>>> {
117    let sm = psess.source_map();
118    let source_file = sm.load_file(path).unwrap_or_else(|e| {
119        use std::io::ErrorKind;
120
121        let msg = match e.kind() {
122            ErrorKind::NotFound => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("couldn\'t find file `{0}`",
                path.display()))
    })format!("couldn't find file `{}`", path.display()),
123            ErrorKind::PermissionDenied => {
124                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("permission denied when opening file `{0}`",
                path.display()))
    })format!("permission denied when opening file `{}`", path.display())
125            }
126            ErrorKind::IsADirectory => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` is a directory",
                path.display()))
    })format!("`{}` is a directory", path.display()),
127            _ => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("couldn\'t read `{0}`: {1}",
                path.display(), e))
    })format!("couldn't read `{}`: {}", path.display(), e),
128        };
129
130        let mut err = psess.dcx().struct_fatal(msg);
131
132        if e.kind() == ErrorKind::NotFound {
133            if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
134                let parent = match path.parent() {
135                    Some(p) if !p.as_os_str().is_empty() => p,
136                    _ => Path::new("."),
137                };
138                if let Ok(entries) = std::fs::read_dir(parent) {
139                    let candidates: Vec<Symbol> = entries
140                        .flatten()
141                        .filter_map(|entry| entry.file_name().to_str().map(Symbol::intern))
142                        .collect();
143                    let lookup = Symbol::intern(file_name);
144                    if let Some(suggestion) = find_best_match_for_name(&candidates, lookup, None) {
145                        let suggested_path = if parent == Path::new(".") {
146                            suggestion.as_str().to_string()
147                        } else {
148                            parent.join(suggestion.as_str()).display().to_string()
149                        };
150                        err.help(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("you might have meant to open `{0}`",
                suggested_path))
    })format!("you might have meant to open `{}`", suggested_path));
151                    }
152                }
153            }
154        }
155        if let Ok(contents) = std::fs::read(path)
156            && let Err(utf8err) = std::str::from_utf8(&contents)
157        {
158            utf8_error(sm, &path.display().to_string(), sp, &mut err, utf8err, &contents);
159        }
160        if let Some(sp) = sp {
161            err.span(sp);
162        }
163        err.emit()
164    });
165    new_parser_from_source_file(psess, source_file, strip_tokens)
166}
167
168pub fn utf8_error<E: EmissionGuarantee>(
169    sm: &SourceMap,
170    path: &str,
171    sp: Option<Span>,
172    err: &mut Diag<'_, E>,
173    utf8err: Utf8Error,
174    contents: &[u8],
175) {
176    // The file exists, but it wasn't valid UTF-8.
177    let start = utf8err.valid_up_to();
178    let note = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("invalid utf-8 at byte `{0}`",
                start))
    })format!("invalid utf-8 at byte `{start}`");
179    let msg = if let Some(len) = utf8err.error_len() {
180        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("byte{1} `{0}` {2} not valid utf-8",
                if len == 1 {
                    ::alloc::__export::must_use({
                            ::alloc::fmt::format(format_args!("{0:?}", contents[start]))
                        })
                } else {
                    ::alloc::__export::must_use({
                            ::alloc::fmt::format(format_args!("{0:?}",
                                    &contents[start..start + len]))
                        })
                }, if len == 1 { "" } else { "s" },
                if len == 1 { "is" } else { "are" }))
    })format!(
181            "byte{s} `{bytes}` {are} not valid utf-8",
182            bytes = if len == 1 {
183                format!("{:?}", contents[start])
184            } else {
185                format!("{:?}", &contents[start..start + len])
186            },
187            s = pluralize!(len),
188            are = if len == 1 { "is" } else { "are" },
189        )
190    } else {
191        note.clone()
192    };
193    let contents = String::from_utf8_lossy(contents).to_string();
194
195    // We only emit this error for files in the current session
196    // so the working directory can only be the current working directory
197    let filename = FileName::Real(
198        sm.path_mapping().to_real_filename(sm.working_dir(), PathBuf::from(path).as_path()),
199    );
200    let source = sm.new_source_file(filename, contents);
201
202    // Avoid out-of-bounds span from lossy UTF-8 conversion.
203    if start as u32 > source.normalized_source_len.0 {
204        err.note(note);
205        return;
206    }
207
208    let span = Span::with_root_ctxt(
209        source.normalized_byte_pos(start as u32),
210        source.normalized_byte_pos(start as u32),
211    );
212    if span.is_dummy() {
213        err.note(note);
214    } else {
215        if sp.is_some() {
216            err.span_note(span, msg);
217        } else {
218            err.span(span);
219            err.span_label(span, msg);
220        }
221    }
222}
223
224/// Given a session and a `source_file`, return a parser. Returns any buffered errors from lexing
225/// the initial token stream.
226fn new_parser_from_source_file(
227    psess: &ParseSess,
228    source_file: Arc<SourceFile>,
229    strip_tokens: StripTokens,
230) -> Result<Parser<'_>, Vec<Diag<'_>>> {
231    let end_pos = source_file.end_position();
232    let stream = source_file_to_stream(psess, source_file, None, strip_tokens)?;
233    let mut parser = Parser::new(psess, stream, None);
234    if parser.token == token::Eof {
235        parser.token.span = Span::new(end_pos, end_pos, parser.token.span.ctxt(), None);
236    }
237    Ok(parser)
238}
239
240/// Given a source string, produces a sequence of token trees.
241///
242/// NOTE: This only strips shebangs, not frontmatter!
243pub fn source_str_to_stream(
244    psess: &ParseSess,
245    name: FileName,
246    source: String,
247    override_span: Option<Span>,
248) -> Result<TokenStream, Vec<Diag<'_>>> {
249    let source_file = psess.source_map().new_source_file(name, source);
250    // FIXME(frontmatter): Consider stripping frontmatter in a future edition. We can't strip them
251    // in the current edition since that would be breaking.
252    // See also <https://github.com/rust-lang/rust/issues/145520>.
253    // Alternatively, stop stripping shebangs here, too, if T-lang and crater approve.
254    source_file_to_stream(psess, source_file, override_span, StripTokens::Shebang)
255}
256
257/// Given a source file, produces a sequence of token trees.
258///
259/// Returns any buffered errors from parsing the token stream.
260fn source_file_to_stream<'psess>(
261    psess: &'psess ParseSess,
262    source_file: Arc<SourceFile>,
263    override_span: Option<Span>,
264    strip_tokens: StripTokens,
265) -> Result<TokenStream, Vec<Diag<'psess>>> {
266    let src = source_file.src.as_ref().unwrap_or_else(|| {
267        psess.dcx().bug(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("cannot lex `source_file` without source: {0}",
                psess.source_map().filename_for_diagnostics(&source_file.name)))
    })format!(
268            "cannot lex `source_file` without source: {}",
269            psess.source_map().filename_for_diagnostics(&source_file.name)
270        ));
271    });
272
273    lexer::lex_token_trees(psess, src.as_str(), source_file.start_pos, override_span, strip_tokens)
274}
275
276/// Runs the given subparser `f` on the tokens of the given `attr`'s item.
277pub fn parse_in<'a, T>(
278    psess: &'a ParseSess,
279    tts: TokenStream,
280    name: &'static str,
281    mut f: impl FnMut(&mut Parser<'a>) -> PResult<'a, T>,
282) -> PResult<'a, T> {
283    let mut parser = Parser::new(psess, tts, Some(name));
284    let result = f(&mut parser)?;
285    if parser.token != token::Eof {
286        parser.unexpected()?;
287    }
288    Ok(result)
289}
290
291pub fn fake_token_stream_for_item(
292    psess: &ParseSess,
293    item: &ast::Item,
294    attr_to_exclude: Option<&ast::Attribute>,
295) -> TokenStream {
296    if let Some(tokens) = fake_token_stream_for_file_mod(psess, item, attr_to_exclude) {
297        return tokens;
298    }
299
300    let source = pprust::item_to_string(item);
301    let filename = FileName::macro_expansion_source_code(&source);
302    unwrap_or_emit_fatal(source_str_to_stream(psess, filename, source, Some(item.span)))
303}
304
305fn fake_token_stream_for_file_mod(
306    psess: &ParseSess,
307    item: &ast::Item,
308    attr_to_exclude: Option<&ast::Attribute>,
309) -> Option<TokenStream> {
310    let ast::ItemKind::Mod(_, _, ast::ModKind::Loaded(_, ast::Inline::No { .. }, spans)) =
311        &item.kind
312    else {
313        return None;
314    };
315
316    let attr = attr_to_exclude.expect("file modules must have an attribute to exclude");
317    {
    match (&attr.style, &ast::AttrStyle::Inner) {
        (left_val, right_val) => {
            if !(*left_val == *right_val) {
                let kind = ::core::panicking::AssertKind::Eq;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val, ::core::option::Option::None);
            }
        }
    }
};assert_eq!(attr.style, ast::AttrStyle::Inner);
318
319    let mut body_tts = Vec::new();
320    body_tts.extend(lex_token_trees_for_span(psess, spans.inner_span.until(attr.span))?);
321    body_tts.extend(lex_token_trees_for_span(
322        psess,
323        attr.span.between(spans.inner_span.shrink_to_hi()),
324    )?);
325
326    let mut wrapper_tts = Vec::new();
327    for attr in item.attrs.iter().filter(|attr| attr.style == ast::AttrStyle::Outer) {
328        wrapper_tts.extend(attr.token_trees());
329    }
330    wrapper_tts.extend(lex_token_trees_for_span(psess, item.span)?);
331    let Some(TokenTree::Token(semi, _)) = wrapper_tts.pop() else {
332        return None;
333    };
334    if semi.kind != token::Semi {
335        return None;
336    }
337    wrapper_tts.push(TokenTree::Delimited(
338        DelimSpan::from_single(semi.span),
339        DelimSpacing::new(Spacing::Alone, Spacing::Alone),
340        token::Delimiter::Brace,
341        TokenStream::new(body_tts),
342    ));
343
344    Some(TokenStream::new(wrapper_tts))
345}
346
347fn lex_token_trees_for_span(
348    psess: &ParseSess,
349    span: Span,
350) -> Option<impl Iterator<Item = TokenTree>> {
351    let src = psess.source_map().span_to_snippet(span).ok()?;
352    let stream = match lexer::lex_token_trees(psess, &src, span.lo(), None, StripTokens::Nothing) {
353        Ok(stream) => stream,
354        Err(errs) => {
355            errs.into_iter().for_each(|err| err.cancel());
356            return None;
357        }
358    };
359    Some((0..).map_while(move |index| stream.get(index).cloned()))
360}
361
362pub fn fake_token_stream_for_foreign_item(
363    psess: &ParseSess,
364    item: &ast::ForeignItem,
365) -> TokenStream {
366    let source = pprust::foreign_item_to_string(item);
367    let filename = FileName::macro_expansion_source_code(&source);
368    unwrap_or_emit_fatal(source_str_to_stream(psess, filename, source, Some(item.span)))
369}
370
371pub fn fake_token_stream_for_crate(psess: &ParseSess, krate: &ast::Crate) -> TokenStream {
372    let source = pprust::crate_to_string_for_macros(krate);
373    let filename = FileName::macro_expansion_source_code(&source);
374    unwrap_or_emit_fatal(source_str_to_stream(
375        psess,
376        filename,
377        source,
378        Some(krate.spans.inner_span),
379    ))
380}