1use std::path::{Path, PathBuf};
2use std::rc::Rc;
3use std::sync::Arc;
4
5use rustc_ast as ast;
6use rustc_ast::ptr::P;
7use rustc_ast::token;
8use rustc_ast::tokenstream::TokenStream;
9use rustc_ast_pretty::pprust;
10use rustc_expand::base::{
11 DummyResult, ExpandResult, ExtCtxt, MacEager, MacResult, MacroExpanderResult, resolve_path,
12};
13use rustc_expand::module::DirOwnership;
14use rustc_lint_defs::BuiltinLintDiag;
15use rustc_parse::parser::{ForceCollect, Parser};
16use rustc_parse::{new_parser_from_file, unwrap_or_emit_fatal, utf8_error};
17use rustc_session::lint::builtin::INCOMPLETE_INCLUDE;
18use rustc_span::source_map::SourceMap;
19use rustc_span::{Pos, Span, Symbol};
20use smallvec::SmallVec;
21
22use crate::errors;
23use crate::util::{
24 check_zero_tts, get_single_str_from_tts, get_single_str_spanned_from_tts, parse_expr,
25};
26
27pub(crate) fn expand_line(
33 cx: &mut ExtCtxt<'_>,
34 sp: Span,
35 tts: TokenStream,
36) -> MacroExpanderResult<'static> {
37 let sp = cx.with_def_site_ctxt(sp);
38 check_zero_tts(cx, sp, tts, "line!");
39
40 let topmost = cx.expansion_cause().unwrap_or(sp);
41 let loc = cx.source_map().lookup_char_pos(topmost.lo());
42
43 ExpandResult::Ready(MacEager::expr(cx.expr_u32(topmost, loc.line as u32)))
44}
45
46pub(crate) fn expand_column(
48 cx: &mut ExtCtxt<'_>,
49 sp: Span,
50 tts: TokenStream,
51) -> MacroExpanderResult<'static> {
52 let sp = cx.with_def_site_ctxt(sp);
53 check_zero_tts(cx, sp, tts, "column!");
54
55 let topmost = cx.expansion_cause().unwrap_or(sp);
56 let loc = cx.source_map().lookup_char_pos(topmost.lo());
57
58 ExpandResult::Ready(MacEager::expr(cx.expr_u32(topmost, loc.col.to_usize() as u32 + 1)))
59}
60
61pub(crate) fn expand_file(
65 cx: &mut ExtCtxt<'_>,
66 sp: Span,
67 tts: TokenStream,
68) -> MacroExpanderResult<'static> {
69 let sp = cx.with_def_site_ctxt(sp);
70 check_zero_tts(cx, sp, tts, "file!");
71
72 let topmost = cx.expansion_cause().unwrap_or(sp);
73 let loc = cx.source_map().lookup_char_pos(topmost.lo());
74
75 use rustc_session::RemapFileNameExt;
76 use rustc_session::config::RemapPathScopeComponents;
77 ExpandResult::Ready(MacEager::expr(cx.expr_str(
78 topmost,
79 Symbol::intern(
80 &loc.file.name.for_scope(cx.sess, RemapPathScopeComponents::MACRO).to_string_lossy(),
81 ),
82 )))
83}
84
85pub(crate) fn expand_stringify(
86 cx: &mut ExtCtxt<'_>,
87 sp: Span,
88 tts: TokenStream,
89) -> MacroExpanderResult<'static> {
90 let sp = cx.with_def_site_ctxt(sp);
91 let s = pprust::tts_to_string(&tts);
92 ExpandResult::Ready(MacEager::expr(cx.expr_str(sp, Symbol::intern(&s))))
93}
94
95pub(crate) fn expand_mod(
96 cx: &mut ExtCtxt<'_>,
97 sp: Span,
98 tts: TokenStream,
99) -> MacroExpanderResult<'static> {
100 let sp = cx.with_def_site_ctxt(sp);
101 check_zero_tts(cx, sp, tts, "module_path!");
102 let mod_path = &cx.current_expansion.module.mod_path;
103 let string = mod_path.iter().map(|x| x.to_string()).collect::<Vec<String>>().join("::");
104
105 ExpandResult::Ready(MacEager::expr(cx.expr_str(sp, Symbol::intern(&string))))
106}
107
108pub(crate) fn expand_include<'cx>(
112 cx: &'cx mut ExtCtxt<'_>,
113 sp: Span,
114 tts: TokenStream,
115) -> MacroExpanderResult<'cx> {
116 let sp = cx.with_def_site_ctxt(sp);
117 let ExpandResult::Ready(mac) = get_single_str_from_tts(cx, sp, tts, "include!") else {
118 return ExpandResult::Retry(());
119 };
120 let file = match mac {
121 Ok(file) => file,
122 Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
123 };
124 let file = match resolve_path(&cx.sess, file.as_str(), sp) {
126 Ok(f) => f,
127 Err(err) => {
128 let guar = err.emit();
129 return ExpandResult::Ready(DummyResult::any(sp, guar));
130 }
131 };
132 let p = unwrap_or_emit_fatal(new_parser_from_file(cx.psess(), &file, Some(sp)));
133
134 let dir_path = file.parent().unwrap_or(&file).to_owned();
139 cx.current_expansion.module = Rc::new(cx.current_expansion.module.with_dir_path(dir_path));
140 cx.current_expansion.dir_ownership = DirOwnership::Owned { relative: None };
141
142 struct ExpandInclude<'a> {
143 p: Parser<'a>,
144 node_id: ast::NodeId,
145 }
146 impl<'a> MacResult for ExpandInclude<'a> {
147 fn make_expr(mut self: Box<ExpandInclude<'a>>) -> Option<P<ast::Expr>> {
148 let expr = parse_expr(&mut self.p).ok()?;
149 if self.p.token != token::Eof {
150 self.p.psess.buffer_lint(
151 INCOMPLETE_INCLUDE,
152 self.p.token.span,
153 self.node_id,
154 BuiltinLintDiag::IncompleteInclude,
155 );
156 }
157 Some(expr)
158 }
159
160 fn make_items(mut self: Box<ExpandInclude<'a>>) -> Option<SmallVec<[P<ast::Item>; 1]>> {
161 let mut ret = SmallVec::new();
162 loop {
163 match self.p.parse_item(ForceCollect::No) {
164 Err(err) => {
165 err.emit();
166 break;
167 }
168 Ok(Some(item)) => ret.push(item),
169 Ok(None) => {
170 if self.p.token != token::Eof {
171 self.p
172 .dcx()
173 .create_err(errors::ExpectedItem {
174 span: self.p.token.span,
175 token: &pprust::token_to_string(&self.p.token),
176 })
177 .emit();
178 }
179
180 break;
181 }
182 }
183 }
184 Some(ret)
185 }
186 }
187
188 ExpandResult::Ready(Box::new(ExpandInclude { p, node_id: cx.current_expansion.lint_node_id }))
189}
190
191pub(crate) fn expand_include_str(
193 cx: &mut ExtCtxt<'_>,
194 sp: Span,
195 tts: TokenStream,
196) -> MacroExpanderResult<'static> {
197 let sp = cx.with_def_site_ctxt(sp);
198 let ExpandResult::Ready(mac) = get_single_str_spanned_from_tts(cx, sp, tts, "include_str!")
199 else {
200 return ExpandResult::Retry(());
201 };
202 let (path, path_span) = match mac {
203 Ok(res) => res,
204 Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
205 };
206 ExpandResult::Ready(match load_binary_file(cx, path.as_str().as_ref(), sp, path_span) {
207 Ok((bytes, bsp)) => match std::str::from_utf8(&bytes) {
208 Ok(src) => {
209 let interned_src = Symbol::intern(src);
210 MacEager::expr(cx.expr_str(cx.with_def_site_ctxt(bsp), interned_src))
211 }
212 Err(utf8err) => {
213 let mut err = cx.dcx().struct_span_err(sp, format!("`{path}` wasn't a utf-8 file"));
214 utf8_error(cx.source_map(), path.as_str(), None, &mut err, utf8err, &bytes[..]);
215 DummyResult::any(sp, err.emit())
216 }
217 },
218 Err(dummy) => dummy,
219 })
220}
221
222pub(crate) fn expand_include_bytes(
223 cx: &mut ExtCtxt<'_>,
224 sp: Span,
225 tts: TokenStream,
226) -> MacroExpanderResult<'static> {
227 let sp = cx.with_def_site_ctxt(sp);
228 let ExpandResult::Ready(mac) = get_single_str_spanned_from_tts(cx, sp, tts, "include_bytes!")
229 else {
230 return ExpandResult::Retry(());
231 };
232 let (path, path_span) = match mac {
233 Ok(res) => res,
234 Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
235 };
236 ExpandResult::Ready(match load_binary_file(cx, path.as_str().as_ref(), sp, path_span) {
237 Ok((bytes, _bsp)) => {
238 let expr = cx.expr(sp, ast::ExprKind::IncludedBytes(bytes));
241 MacEager::expr(expr)
242 }
243 Err(dummy) => dummy,
244 })
245}
246
247fn load_binary_file(
248 cx: &ExtCtxt<'_>,
249 original_path: &Path,
250 macro_span: Span,
251 path_span: Span,
252) -> Result<(Arc<[u8]>, Span), Box<dyn MacResult>> {
253 let resolved_path = match resolve_path(&cx.sess, original_path, macro_span) {
254 Ok(path) => path,
255 Err(err) => {
256 let guar = err.emit();
257 return Err(DummyResult::any(macro_span, guar));
258 }
259 };
260 match cx.source_map().load_binary_file(&resolved_path) {
261 Ok(data) => Ok(data),
262 Err(io_err) => {
263 let mut err = cx.dcx().struct_span_err(
264 macro_span,
265 format!("couldn't read `{}`: {io_err}", resolved_path.display()),
266 );
267
268 if original_path.is_relative() {
269 let source_map = cx.sess.source_map();
270 let new_path = source_map
271 .span_to_filename(macro_span.source_callsite())
272 .into_local_path()
273 .and_then(|src| find_path_suggestion(source_map, src.parent()?, original_path))
274 .and_then(|path| path.into_os_string().into_string().ok());
275
276 if let Some(new_path) = new_path {
277 err.span_suggestion_verbose(
278 path_span,
279 "there is a file with the same name in a different directory",
280 format!("\"{}\"", new_path.replace('\\', "/").escape_debug()),
281 rustc_lint_defs::Applicability::MachineApplicable,
282 );
283 }
284 }
285 let guar = err.emit();
286 Err(DummyResult::any(macro_span, guar))
287 }
288 }
289}
290
291fn find_path_suggestion(
292 source_map: &SourceMap,
293 base_dir: &Path,
294 wanted_path: &Path,
295) -> Option<PathBuf> {
296 let mut base_c = base_dir.components();
298 let mut wanted_c = wanted_path.components();
299 let mut without_base = None;
300 while let Some(wanted_next) = wanted_c.next() {
301 if wanted_c.as_path().file_name().is_none() {
302 break;
303 }
304 while let Some(base_next) = base_c.next() {
306 if base_next == wanted_next {
307 without_base = Some(wanted_c.as_path());
308 break;
309 }
310 }
311 }
312 let root_absolute = without_base.into_iter().map(PathBuf::from);
313
314 let base_dir_components = base_dir.components().count();
315 let max_parent_components = if base_dir.is_relative() {
317 base_dir_components + 1
318 } else {
319 base_dir_components.saturating_sub(1)
320 };
321
322 let mut prefix = PathBuf::new();
324 let add = std::iter::from_fn(|| {
325 prefix.push("..");
326 Some(prefix.join(wanted_path))
327 })
328 .take(max_parent_components.min(3));
329
330 let mut trimmed_path = wanted_path;
332 let remove = std::iter::from_fn(|| {
333 let mut components = trimmed_path.components();
334 let removed = components.next()?;
335 trimmed_path = components.as_path();
336 let _ = trimmed_path.file_name()?; Some([
338 Some(trimmed_path.to_path_buf()),
339 (removed != std::path::Component::ParentDir)
340 .then(|| Path::new("..").join(trimmed_path)),
341 ])
342 })
343 .flatten()
344 .flatten()
345 .take(4);
346
347 root_absolute
348 .chain(add)
349 .chain(remove)
350 .find(|new_path| source_map.file_exists(&base_dir.join(&new_path)))
351}