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