]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_builtin_macros/src/source_util.rs
2817fce463b91c5a870c71ae2d140a86dbcb7875
[rust.git] / compiler / rustc_builtin_macros / src / source_util.rs
1 use rustc_ast as ast;
2 use rustc_ast::ptr::P;
3 use rustc_ast::token;
4 use rustc_ast::tokenstream::TokenStream;
5 use rustc_ast_pretty::pprust;
6 use rustc_errors::PResult;
7 use rustc_expand::base::{self, *};
8 use rustc_expand::module::DirOwnership;
9 use rustc_parse::parser::{ForceCollect, Parser};
10 use rustc_parse::{self, new_parser_from_file};
11 use rustc_session::lint::builtin::INCOMPLETE_INCLUDE;
12 use rustc_span::symbol::Symbol;
13 use rustc_span::{self, FileName, Pos, Span};
14
15 use smallvec::SmallVec;
16 use std::path::PathBuf;
17 use std::rc::Rc;
18
19 // These macros all relate to the file system; they either return
20 // the column/row/filename of the expression, or they include
21 // a given file into the current one.
22
23 /// line!(): expands to the current line number
24 pub fn expand_line(
25     cx: &mut ExtCtxt<'_>,
26     sp: Span,
27     tts: TokenStream,
28 ) -> Box<dyn base::MacResult + 'static> {
29     let sp = cx.with_def_site_ctxt(sp);
30     base::check_zero_tts(cx, sp, tts, "line!");
31
32     let topmost = cx.expansion_cause().unwrap_or(sp);
33     let loc = cx.source_map().lookup_char_pos(topmost.lo());
34
35     base::MacEager::expr(cx.expr_u32(topmost, loc.line as u32))
36 }
37
38 /* column!(): expands to the current column number */
39 pub fn expand_column(
40     cx: &mut ExtCtxt<'_>,
41     sp: Span,
42     tts: TokenStream,
43 ) -> Box<dyn base::MacResult + 'static> {
44     let sp = cx.with_def_site_ctxt(sp);
45     base::check_zero_tts(cx, sp, tts, "column!");
46
47     let topmost = cx.expansion_cause().unwrap_or(sp);
48     let loc = cx.source_map().lookup_char_pos(topmost.lo());
49
50     base::MacEager::expr(cx.expr_u32(topmost, loc.col.to_usize() as u32 + 1))
51 }
52
53 /// file!(): expands to the current filename */
54 /// The source_file (`loc.file`) contains a bunch more information we could spit
55 /// out if we wanted.
56 pub fn expand_file(
57     cx: &mut ExtCtxt<'_>,
58     sp: Span,
59     tts: TokenStream,
60 ) -> Box<dyn base::MacResult + 'static> {
61     let sp = cx.with_def_site_ctxt(sp);
62     base::check_zero_tts(cx, sp, tts, "file!");
63
64     let topmost = cx.expansion_cause().unwrap_or(sp);
65     let loc = cx.source_map().lookup_char_pos(topmost.lo());
66     base::MacEager::expr(
67         cx.expr_str(topmost, Symbol::intern(&loc.file.name.prefer_remapped().to_string_lossy())),
68     )
69 }
70
71 pub fn expand_stringify(
72     cx: &mut ExtCtxt<'_>,
73     sp: Span,
74     tts: TokenStream,
75 ) -> Box<dyn base::MacResult + 'static> {
76     let sp = cx.with_def_site_ctxt(sp);
77     let s = pprust::tts_to_string(&tts);
78     base::MacEager::expr(cx.expr_str(sp, Symbol::intern(&s)))
79 }
80
81 pub fn expand_mod(
82     cx: &mut ExtCtxt<'_>,
83     sp: Span,
84     tts: TokenStream,
85 ) -> Box<dyn base::MacResult + 'static> {
86     let sp = cx.with_def_site_ctxt(sp);
87     base::check_zero_tts(cx, sp, tts, "module_path!");
88     let mod_path = &cx.current_expansion.module.mod_path;
89     let string = mod_path.iter().map(|x| x.to_string()).collect::<Vec<String>>().join("::");
90
91     base::MacEager::expr(cx.expr_str(sp, Symbol::intern(&string)))
92 }
93
94 /// include! : parse the given file as an expr
95 /// This is generally a bad idea because it's going to behave
96 /// unhygienically.
97 pub fn expand_include<'cx>(
98     cx: &'cx mut ExtCtxt<'_>,
99     sp: Span,
100     tts: TokenStream,
101 ) -> Box<dyn base::MacResult + 'cx> {
102     let sp = cx.with_def_site_ctxt(sp);
103     let Some(file) = get_single_str_from_tts(cx, sp, tts, "include!") else {
104         return DummyResult::any(sp);
105     };
106     // The file will be added to the code map by the parser
107     let file = match resolve_path(cx, file.as_str(), sp) {
108         Ok(f) => f,
109         Err(mut err) => {
110             err.emit();
111             return DummyResult::any(sp);
112         }
113     };
114     let p = new_parser_from_file(cx.parse_sess(), &file, Some(sp));
115
116     // If in the included file we have e.g., `mod bar;`,
117     // then the path of `bar.rs` should be relative to the directory of `file`.
118     // See https://github.com/rust-lang/rust/pull/69838/files#r395217057 for a discussion.
119     // `MacroExpander::fully_expand_fragment` later restores, so "stack discipline" is maintained.
120     let dir_path = file.parent().unwrap_or(&file).to_owned();
121     cx.current_expansion.module = Rc::new(cx.current_expansion.module.with_dir_path(dir_path));
122     cx.current_expansion.dir_ownership = DirOwnership::Owned { relative: None };
123
124     struct ExpandResult<'a> {
125         p: Parser<'a>,
126         node_id: ast::NodeId,
127     }
128     impl<'a> base::MacResult for ExpandResult<'a> {
129         fn make_expr(mut self: Box<ExpandResult<'a>>) -> Option<P<ast::Expr>> {
130             let r = base::parse_expr(&mut self.p)?;
131             if self.p.token != token::Eof {
132                 self.p.sess.buffer_lint(
133                     &INCOMPLETE_INCLUDE,
134                     self.p.token.span,
135                     self.node_id,
136                     "include macro expected single expression in source",
137                 );
138             }
139             Some(r)
140         }
141
142         fn make_items(mut self: Box<ExpandResult<'a>>) -> Option<SmallVec<[P<ast::Item>; 1]>> {
143             let mut ret = SmallVec::new();
144             loop {
145                 match self.p.parse_item(ForceCollect::No) {
146                     Err(mut err) => {
147                         err.emit();
148                         break;
149                     }
150                     Ok(Some(item)) => ret.push(item),
151                     Ok(None) => {
152                         if self.p.token != token::Eof {
153                             let token = pprust::token_to_string(&self.p.token);
154                             let msg = format!("expected item, found `{}`", token);
155                             self.p.struct_span_err(self.p.token.span, &msg).emit();
156                         }
157
158                         break;
159                     }
160                 }
161             }
162             Some(ret)
163         }
164     }
165
166     Box::new(ExpandResult { p, node_id: cx.current_expansion.lint_node_id })
167 }
168
169 // include_str! : read the given file, insert it as a literal string expr
170 pub fn expand_include_str(
171     cx: &mut ExtCtxt<'_>,
172     sp: Span,
173     tts: TokenStream,
174 ) -> Box<dyn base::MacResult + 'static> {
175     let sp = cx.with_def_site_ctxt(sp);
176     let Some(file) = get_single_str_from_tts(cx, sp, tts, "include_str!") else {
177         return DummyResult::any(sp);
178     };
179     let file = match resolve_path(cx, file.as_str(), sp) {
180         Ok(f) => f,
181         Err(mut err) => {
182             err.emit();
183             return DummyResult::any(sp);
184         }
185     };
186     match cx.source_map().load_binary_file(&file) {
187         Ok(bytes) => match std::str::from_utf8(&bytes) {
188             Ok(src) => {
189                 let interned_src = Symbol::intern(&src);
190                 base::MacEager::expr(cx.expr_str(sp, interned_src))
191             }
192             Err(_) => {
193                 cx.span_err(sp, &format!("{} wasn't a utf-8 file", file.display()));
194                 DummyResult::any(sp)
195             }
196         },
197         Err(e) => {
198             cx.span_err(sp, &format!("couldn't read {}: {}", file.display(), e));
199             DummyResult::any(sp)
200         }
201     }
202 }
203
204 pub fn expand_include_bytes(
205     cx: &mut ExtCtxt<'_>,
206     sp: Span,
207     tts: TokenStream,
208 ) -> Box<dyn base::MacResult + 'static> {
209     let sp = cx.with_def_site_ctxt(sp);
210     let Some(file) = get_single_str_from_tts(cx, sp, tts, "include_bytes!") else {
211         return DummyResult::any(sp);
212     };
213     let file = match resolve_path(cx, file.as_str(), sp) {
214         Ok(f) => f,
215         Err(mut err) => {
216             err.emit();
217             return DummyResult::any(sp);
218         }
219     };
220     match cx.source_map().load_binary_file(&file) {
221         Ok(bytes) => base::MacEager::expr(cx.expr_lit(sp, ast::LitKind::ByteStr(bytes.into()))),
222         Err(e) => {
223             cx.span_err(sp, &format!("couldn't read {}: {}", file.display(), e));
224             DummyResult::any(sp)
225         }
226     }
227 }
228
229 /// Resolves a `path` mentioned inside Rust code, returning an absolute path.
230 ///
231 /// This unifies the logic used for resolving `include_X!`.
232 fn resolve_path<'a>(
233     cx: &mut ExtCtxt<'a>,
234     path: impl Into<PathBuf>,
235     span: Span,
236 ) -> PResult<'a, PathBuf> {
237     let path = path.into();
238
239     // Relative paths are resolved relative to the file in which they are found
240     // after macro expansion (that is, they are unhygienic).
241     if !path.is_absolute() {
242         let callsite = span.source_callsite();
243         let mut result = match cx.source_map().span_to_filename(callsite) {
244             FileName::Real(name) => name
245                 .into_local_path()
246                 .expect("attempting to resolve a file path in an external file"),
247             FileName::DocTest(path, _) => path,
248             other => {
249                 return Err(cx.struct_span_err(
250                     span,
251                     &format!(
252                         "cannot resolve relative path in non-file source `{}`",
253                         cx.source_map().filename_for_diagnostics(&other)
254                     ),
255                 ));
256             }
257         };
258         result.pop();
259         result.push(path);
260         Ok(result)
261     } else {
262         Ok(path)
263     }
264 }