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