1use std::env;
7use std::env::VarError;
8
9use rustc_ast::token::{self, LitKind};
10use rustc_ast::tokenstream::TokenStream;
11use rustc_ast::{ExprKind, GenericArg, Mutability};
12use rustc_ast_pretty::pprust;
13use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
14use rustc_span::edit_distance::edit_distance;
15use rustc_span::{Ident, Span, Symbol, kw, sym};
16use thin_vec::thin_vec;
17
18use crate::diagnostics;
19use crate::util::{expr_to_string, get_exprs_from_tts, get_single_expr_from_tts};
20
21fn lookup_env<'cx>(cx: &'cx ExtCtxt<'_>, var: Symbol) -> Result<Symbol, VarError> {
22 let var = var.as_str();
23 if let Some(value) = cx.sess.opts.logical_env.get(var) {
24 return Ok(Symbol::intern(value));
25 }
26 Ok(Symbol::intern(&env::var(var)?))
29}
30
31pub(crate) fn expand_option_env<'cx>(
32 cx: &'cx mut ExtCtxt<'_>,
33 sp: Span,
34 tts: TokenStream,
35) -> MacroExpanderResult<'cx> {
36 let ExpandResult::Ready(mac_expr) = get_single_expr_from_tts(cx, sp, tts, "option_env!") else {
37 return ExpandResult::Retry(());
38 };
39 let var_expr = match mac_expr {
40 Ok(var_expr) => var_expr,
41 Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
42 };
43 let ExpandResult::Ready(mac) =
44 expr_to_string(cx, var_expr.clone(), "argument must be a string literal")
45 else {
46 return ExpandResult::Retry(());
47 };
48 let var = match mac {
49 Ok((var, _)) => var,
50 Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
51 };
52
53 let sp = cx.with_def_site_ctxt(sp);
54 let value = lookup_env(cx, var);
55 cx.sess.env_depinfo.borrow_mut().insert((var, value.as_ref().ok().copied()));
56 let e = match value {
57 Err(VarError::NotPresent) => {
58 let lt = cx.lifetime(sp, Ident::new(kw::StaticLifetime, sp));
59 cx.expr_path(cx.path_all(
60 sp,
61 true,
62 cx.std_path(&[sym::option, sym::Option, sym::None]),
63 ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[GenericArg::Type(cx.ty_ref(sp,
cx.ty_ident(sp, Ident::new(sym::str, sp)), Some(lt),
Mutability::Not))]))vec![GenericArg::Type(cx.ty_ref(
64 sp,
65 cx.ty_ident(sp, Ident::new(sym::str, sp)),
66 Some(lt),
67 Mutability::Not,
68 ))],
69 ))
70 }
71 Err(VarError::NotUnicode(_)) => {
72 let ExprKind::Lit(token::Lit {
73 kind: LitKind::Str | LitKind::StrRaw(..), symbol, ..
74 }) = &var_expr.kind
75 else {
76 {
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("`expr_to_string` ensures this is a string lit")));
}unreachable!("`expr_to_string` ensures this is a string lit")
77 };
78
79 let guar = cx.dcx().emit_err(diagnostics::EnvNotUnicode { span: sp, var: *symbol });
80 return ExpandResult::Ready(DummyResult::any(sp, guar));
81 }
82 Ok(value) => cx.expr_call_global(
83 sp,
84 cx.std_path(&[sym::option, sym::Option, sym::Some]),
85 {
let len = [()].len();
let mut vec = ::thin_vec::ThinVec::with_capacity(len);
vec.push(cx.expr_str(sp, value));
vec
}thin_vec![cx.expr_str(sp, value)],
86 ),
87 };
88 ExpandResult::Ready(MacEager::expr(e))
89}
90
91pub(crate) fn expand_env<'cx>(
92 cx: &'cx mut ExtCtxt<'_>,
93 sp: Span,
94 tts: TokenStream,
95) -> MacroExpanderResult<'cx> {
96 let ExpandResult::Ready(mac) = get_exprs_from_tts(cx, tts) else {
97 return ExpandResult::Retry(());
98 };
99 let mut exprs = match mac {
100 Ok(exprs) if exprs.is_empty() || exprs.len() > 2 => {
101 let guar = cx.dcx().emit_err(diagnostics::EnvTakesArgs { span: sp });
102 return ExpandResult::Ready(DummyResult::any(sp, guar));
103 }
104 Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
105 Ok(exprs) => exprs.into_iter(),
106 };
107
108 let var_expr = exprs.next().unwrap();
109 let ExpandResult::Ready(mac) = expr_to_string(cx, var_expr.clone(), "expected string literal")
110 else {
111 return ExpandResult::Retry(());
112 };
113 let var = match mac {
114 Ok((var, _)) => var,
115 Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
116 };
117
118 let custom_msg = match exprs.next() {
119 None => None,
120 Some(second) => {
121 let ExpandResult::Ready(mac) = expr_to_string(cx, second, "expected string literal")
122 else {
123 return ExpandResult::Retry(());
124 };
125 match mac {
126 Ok((s, _)) => Some(s),
127 Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
128 }
129 }
130 };
131
132 let span = cx.with_def_site_ctxt(sp);
133 let value = lookup_env(cx, var);
134 cx.sess.env_depinfo.borrow_mut().insert((var, value.as_ref().ok().copied()));
135 let e = match value {
136 Err(err) => {
137 let ExprKind::Lit(token::Lit {
138 kind: LitKind::Str | LitKind::StrRaw(..), symbol, ..
139 }) = &var_expr.kind
140 else {
141 {
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("`expr_to_string` ensures this is a string lit")));
}unreachable!("`expr_to_string` ensures this is a string lit")
142 };
143
144 let var = var.as_str();
145 let guar = match err {
146 VarError::NotPresent => {
147 if let Some(msg_from_user) = custom_msg {
148 cx.dcx().emit_err(diagnostics::EnvNotDefinedWithUserMessage {
149 span,
150 msg_from_user,
151 })
152 } else if let Some(suggested_var) = find_similar_cargo_var(var)
153 && suggested_var != var
154 {
155 cx.dcx().emit_err(diagnostics::EnvNotDefined::CargoEnvVarTypo {
156 span,
157 var: *symbol,
158 suggested_var: Symbol::intern(suggested_var),
159 })
160 } else if is_cargo_env_var(var) {
161 cx.dcx().emit_err(diagnostics::EnvNotDefined::CargoEnvVar {
162 span,
163 var: *symbol,
164 var_expr: pprust::expr_to_string(&var_expr),
165 })
166 } else {
167 cx.dcx().emit_err(diagnostics::EnvNotDefined::CustomEnvVar {
168 span,
169 var: *symbol,
170 var_expr: pprust::expr_to_string(&var_expr),
171 })
172 }
173 }
174 VarError::NotUnicode(_) => {
175 cx.dcx().emit_err(diagnostics::EnvNotUnicode { span, var: *symbol })
176 }
177 };
178
179 return ExpandResult::Ready(DummyResult::any(sp, guar));
180 }
181 Ok(value) => cx.expr_str(span, value),
182 };
183 ExpandResult::Ready(MacEager::expr(e))
184}
185
186fn is_cargo_env_var(var: &str) -> bool {
188 var.starts_with("CARGO_")
189 || var.starts_with("DEP_")
190 || #[allow(non_exhaustive_omitted_patterns)] match var {
"OUT_DIR" | "OPT_LEVEL" | "PROFILE" | "HOST" | "TARGET" => true,
_ => false,
}matches!(var, "OUT_DIR" | "OPT_LEVEL" | "PROFILE" | "HOST" | "TARGET")
191}
192
193const KNOWN_CARGO_VARS: &[&str] = &[
194 "CARGO_BIN_NAME",
198 "CARGO_CRATE_NAME",
199 "CARGO_MANIFEST_DIR",
200 "CARGO_MANIFEST_PATH",
201 "CARGO_PKG_AUTHORS",
202 "CARGO_PKG_DESCRIPTION",
203 "CARGO_PKG_HOMEPAGE",
204 "CARGO_PKG_LICENSE",
205 "CARGO_PKG_LICENSE_FILE",
206 "CARGO_PKG_NAME",
207 "CARGO_PKG_README",
208 "CARGO_PKG_REPOSITORY",
209 "CARGO_PKG_RUST_VERSION",
210 "CARGO_PKG_VERSION",
211 "CARGO_PKG_VERSION_MAJOR",
212 "CARGO_PKG_VERSION_MINOR",
213 "CARGO_PKG_VERSION_PATCH",
214 "CARGO_PKG_VERSION_PRE",
215 "CARGO_PRIMARY_PACKAGE",
216 "CARGO_TARGET_TMPDIR",
217 ];
219
220fn find_similar_cargo_var(var: &str) -> Option<&'static str> {
221 if !var.starts_with("CARGO_") {
222 return None;
223 }
224
225 let lookup_len = var.chars().count();
226 let max_dist = std::cmp::max(lookup_len, 3) / 3;
227 let mut best_match = None;
228 let mut best_distance = usize::MAX;
229
230 for &known_var in KNOWN_CARGO_VARS {
231 if let Some(mut distance) = edit_distance(var, known_var, max_dist) {
232 if var.contains("PACKAGE") && known_var.contains("PKG") {
235 distance = distance.saturating_sub(const { "PACKAGE".len() - "PKG".len() }) }
237
238 if distance < best_distance {
239 best_distance = distance;
240 best_match = Some(known_var);
241 }
242 }
243 }
244
245 best_match
246}