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