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::{AllowConstBlockItems, 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_span::RemapPathScopeComponents;
72 ExpandResult::Ready(MacEager::expr(cx.expr_str(
73 topmost,
74 Symbol::intern(&loc.file.name.display(RemapPathScopeComponents::MACRO).to_string_lossy()),
75 )))
76}
77
78pub(crate) fn expand_stringify(
80 cx: &mut ExtCtxt<'_>,
81 sp: Span,
82 tts: TokenStream,
83) -> MacroExpanderResult<'static> {
84 let sp = cx.with_def_site_ctxt(sp);
85 let s = pprust::tts_to_string(&tts);
86 ExpandResult::Ready(MacEager::expr(cx.expr_str(sp, Symbol::intern(&s))))
87}
88
89pub(crate) fn expand_mod(
91 cx: &mut ExtCtxt<'_>,
92 sp: Span,
93 tts: TokenStream,
94) -> MacroExpanderResult<'static> {
95 let sp = cx.with_def_site_ctxt(sp);
96 check_zero_tts(cx, sp, tts, "module_path!");
97 let mod_path = &cx.current_expansion.module.mod_path;
98 let string = join_path_idents(mod_path);
99
100 ExpandResult::Ready(MacEager::expr(cx.expr_str(sp, Symbol::intern(&string))))
101}
102
103pub(crate) fn expand_include<'cx>(
107 cx: &'cx mut ExtCtxt<'_>,
108 sp: Span,
109 tts: TokenStream,
110) -> MacroExpanderResult<'cx> {
111 let sp = cx.with_def_site_ctxt(sp);
112 let ExpandResult::Ready(mac) = get_single_str_from_tts(cx, sp, tts, "include!") else {
113 return ExpandResult::Retry(());
114 };
115 let path = match mac {
116 Ok(path) => path,
117 Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
118 };
119 let path = match resolve_path(&cx.sess, path.as_str(), sp) {
121 Ok(path) => path,
122 Err(err) => {
123 let guar = err.emit();
124 return ExpandResult::Ready(DummyResult::any(sp, guar));
125 }
126 };
127
128 let dir_path = path.parent().unwrap_or(&path).to_owned();
133 cx.current_expansion.module = Rc::new(cx.current_expansion.module.with_dir_path(dir_path));
134 cx.current_expansion.dir_ownership = DirOwnership::Owned { relative: None };
135
136 struct ExpandInclude<'a> {
137 psess: &'a ParseSess,
138 path: PathBuf,
139 node_id: ast::NodeId,
140 span: Span,
141 }
142 impl<'a> MacResult for ExpandInclude<'a> {
143 fn make_expr(self: Box<ExpandInclude<'a>>) -> Option<Box<ast::Expr>> {
144 let mut p = unwrap_or_emit_fatal(new_parser_from_file(
145 self.psess,
146 &self.path,
147 StripTokens::Nothing,
148 Some(self.span),
149 ));
150 let expr = parse_expr(&mut p).ok()?;
151 if p.token != token::Eof {
152 p.psess.buffer_lint(
153 INCOMPLETE_INCLUDE,
154 p.token.span,
155 self.node_id,
156 errors::IncompleteInclude,
157 );
158 }
159 Some(expr)
160 }
161
162 fn make_items(self: Box<ExpandInclude<'a>>) -> Option<SmallVec<[Box<ast::Item>; 1]>> {
163 let mut p = unwrap_or_emit_fatal(new_parser_from_file(
164 self.psess,
165 &self.path,
166 StripTokens::ShebangAndFrontmatter,
167 Some(self.span),
168 ));
169 let mut ret = SmallVec::new();
170 loop {
171 match p.parse_item(ForceCollect::No, AllowConstBlockItems::Yes) {
172 Err(err) => {
173 err.emit();
174 break;
175 }
176 Ok(Some(item)) => ret.push(item),
177 Ok(None) => {
178 if p.token != token::Eof {
179 p.dcx().emit_err(errors::ExpectedItem {
180 span: p.token.span,
181 token: &pprust::token_to_string(&p.token),
182 });
183 }
184
185 break;
186 }
187 }
188 }
189 Some(ret)
190 }
191 }
192
193 ExpandResult::Ready(Box::new(ExpandInclude {
194 psess: cx.psess(),
195 path,
196 node_id: cx.current_expansion.lint_node_id,
197 span: sp,
198 }))
199}
200
201pub(crate) fn expand_include_str(
205 cx: &mut ExtCtxt<'_>,
206 sp: Span,
207 tts: TokenStream,
208) -> MacroExpanderResult<'static> {
209 let sp = cx.with_def_site_ctxt(sp);
210 let ExpandResult::Ready(mac) = get_single_str_spanned_from_tts(cx, sp, tts, "include_str!")
211 else {
212 return ExpandResult::Retry(());
213 };
214 let (path, path_span) = match mac {
215 Ok(res) => res,
216 Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
217 };
218 ExpandResult::Ready(match load_binary_file(cx, path.as_str().as_ref(), sp, path_span) {
219 Ok((bytes, bsp)) => match std::str::from_utf8(&bytes) {
220 Ok(src) => {
221 let interned_src = Symbol::intern(src);
222 MacEager::expr(cx.expr_str(cx.with_def_site_ctxt(bsp), interned_src))
224 }
225 Err(utf8err) => {
226 let mut err = cx.dcx().struct_span_err(sp, ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("`{0}` wasn\'t a utf-8 file", path))
})format!("`{path}` wasn't a utf-8 file"));
227 utf8_error(cx.source_map(), path.as_str(), None, &mut err, utf8err, &bytes[..]);
228 DummyResult::any(sp, err.emit())
229 }
230 },
231 Err(dummy) => dummy,
232 })
233}
234
235pub(crate) fn expand_include_bytes(
239 cx: &mut ExtCtxt<'_>,
240 sp: Span,
241 tts: TokenStream,
242) -> MacroExpanderResult<'static> {
243 let sp = cx.with_def_site_ctxt(sp);
244 let ExpandResult::Ready(mac) = get_single_str_spanned_from_tts(cx, sp, tts, "include_bytes!")
245 else {
246 return ExpandResult::Retry(());
247 };
248 let (path, path_span) = match mac {
249 Ok(res) => res,
250 Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
251 };
252 ExpandResult::Ready(match load_binary_file(cx, path.as_str().as_ref(), sp, path_span) {
253 Ok((bytes, _bsp)) => {
254 let expr = cx.expr(sp, ast::ExprKind::IncludedBytes(ByteSymbol::intern(&bytes)));
257 MacEager::expr(expr)
259 }
260 Err(dummy) => dummy,
261 })
262}
263
264fn load_binary_file(
265 cx: &ExtCtxt<'_>,
266 original_path: &Path,
267 macro_span: Span,
268 path_span: Span,
269) -> Result<(Arc<[u8]>, Span), Box<dyn MacResult>> {
270 let resolved_path = match resolve_path(&cx.sess, original_path, macro_span) {
271 Ok(path) => path,
272 Err(err) => {
273 let guar = err.emit();
274 return Err(DummyResult::any(macro_span, guar));
275 }
276 };
277 match cx.source_map().load_binary_file(&resolved_path) {
278 Ok(data) => {
279 cx.sess
280 .psess
281 .file_depinfo
282 .borrow_mut()
283 .insert(Symbol::intern(&resolved_path.to_string_lossy()));
284
285 Ok(data)
286 }
287 Err(io_err) => {
288 let mut err = cx.dcx().struct_span_err(
289 macro_span,
290 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("couldn\'t read `{0}`: {1}",
resolved_path.display(), io_err))
})format!("couldn't read `{}`: {io_err}", resolved_path.display()),
291 );
292
293 if original_path.is_relative() {
294 let source_map = cx.sess.source_map();
295 let new_path = source_map
296 .span_to_filename(macro_span.source_callsite())
297 .into_local_path()
298 .and_then(|src| find_path_suggestion(source_map, src.parent()?, original_path))
299 .and_then(|path| path.into_os_string().into_string().ok());
300
301 if let Some(new_path) = new_path {
302 err.span_suggestion_verbose(
303 path_span,
304 "there is a file with the same name in a different directory",
305 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("\"{0}\"",
new_path.replace('\\', "/").escape_debug()))
})format!("\"{}\"", new_path.replace('\\', "/").escape_debug()),
306 rustc_lint_defs::Applicability::MachineApplicable,
307 );
308 }
309 }
310 let guar = err.emit();
311 Err(DummyResult::any(macro_span, guar))
312 }
313 }
314}
315
316fn find_path_suggestion(
317 source_map: &SourceMap,
318 base_dir: &Path,
319 wanted_path: &Path,
320) -> Option<PathBuf> {
321 let mut base_c = base_dir.components();
323 let mut wanted_c = wanted_path.components();
324 let mut without_base = None;
325 while let Some(wanted_next) = wanted_c.next() {
326 if wanted_c.as_path().file_name().is_none() {
327 break;
328 }
329 while let Some(base_next) = base_c.next() {
331 if base_next == wanted_next {
332 without_base = Some(wanted_c.as_path());
333 break;
334 }
335 }
336 }
337 let root_absolute = without_base.into_iter().map(PathBuf::from);
338
339 let base_dir_components = base_dir.components().count();
340 let max_parent_components = if base_dir.is_relative() {
342 base_dir_components + 1
343 } else {
344 base_dir_components.saturating_sub(1)
345 };
346
347 let mut prefix = PathBuf::new();
349 let add = std::iter::from_fn(|| {
350 prefix.push("..");
351 Some(prefix.join(wanted_path))
352 })
353 .take(max_parent_components.min(3));
354
355 let mut trimmed_path = wanted_path;
357 let remove = std::iter::from_fn(|| {
358 let mut components = trimmed_path.components();
359 let removed = components.next()?;
360 trimmed_path = components.as_path();
361 let _ = trimmed_path.file_name()?; Some([
363 Some(trimmed_path.to_path_buf()),
364 (removed != std::path::Component::ParentDir)
365 .then(|| Path::new("..").join(trimmed_path)),
366 ])
367 })
368 .flatten()
369 .flatten()
370 .take(4);
371
372 root_absolute
373 .chain(add)
374 .chain(remove)
375 .find(|new_path| source_map.file_exists(&base_dir.join(&new_path)))
376}