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::errors;
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(errors::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(errors::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()
149 .emit_err(errors::EnvNotDefinedWithUserMessage { span, msg_from_user })
150 } else if let Some(suggested_var) = find_similar_cargo_var(var)
151 && suggested_var != var
152 {
153 cx.dcx().emit_err(errors::EnvNotDefined::CargoEnvVarTypo {
154 span,
155 var: *symbol,
156 suggested_var: Symbol::intern(suggested_var),
157 })
158 } else if is_cargo_env_var(var) {
159 cx.dcx().emit_err(errors::EnvNotDefined::CargoEnvVar {
160 span,
161 var: *symbol,
162 var_expr: pprust::expr_to_string(&var_expr),
163 })
164 } else {
165 cx.dcx().emit_err(errors::EnvNotDefined::CustomEnvVar {
166 span,
167 var: *symbol,
168 var_expr: pprust::expr_to_string(&var_expr),
169 })
170 }
171 }
172 VarError::NotUnicode(_) => {
173 cx.dcx().emit_err(errors::EnvNotUnicode { span, var: *symbol })
174 }
175 };
176
177 return ExpandResult::Ready(DummyResult::any(sp, guar));
178 }
179 Ok(value) => cx.expr_str(span, value),
180 };
181 ExpandResult::Ready(MacEager::expr(e))
182}
183
184fn is_cargo_env_var(var: &str) -> bool {
186 var.starts_with("CARGO_")
187 || var.starts_with("DEP_")
188 || #[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")
189}
190
191const KNOWN_CARGO_VARS: &[&str] = &[
192 "CARGO_BIN_NAME",
196 "CARGO_CRATE_NAME",
197 "CARGO_MANIFEST_DIR",
198 "CARGO_MANIFEST_PATH",
199 "CARGO_PKG_AUTHORS",
200 "CARGO_PKG_DESCRIPTION",
201 "CARGO_PKG_HOMEPAGE",
202 "CARGO_PKG_LICENSE",
203 "CARGO_PKG_LICENSE_FILE",
204 "CARGO_PKG_NAME",
205 "CARGO_PKG_README",
206 "CARGO_PKG_REPOSITORY",
207 "CARGO_PKG_RUST_VERSION",
208 "CARGO_PKG_VERSION",
209 "CARGO_PKG_VERSION_MAJOR",
210 "CARGO_PKG_VERSION_MINOR",
211 "CARGO_PKG_VERSION_PATCH",
212 "CARGO_PKG_VERSION_PRE",
213 "CARGO_PRIMARY_PACKAGE",
214 "CARGO_TARGET_TMPDIR",
215 ];
217
218fn find_similar_cargo_var(var: &str) -> Option<&'static str> {
219 if !var.starts_with("CARGO_") {
220 return None;
221 }
222
223 let lookup_len = var.chars().count();
224 let max_dist = std::cmp::max(lookup_len, 3) / 3;
225 let mut best_match = None;
226 let mut best_distance = usize::MAX;
227
228 for &known_var in KNOWN_CARGO_VARS {
229 if let Some(mut distance) = edit_distance(var, known_var, max_dist) {
230 if var.contains("PACKAGE") && known_var.contains("PKG") {
233 distance = distance.saturating_sub(const { "PACKAGE".len() - "PKG".len() }) }
235
236 if distance < best_distance {
237 best_distance = distance;
238 best_match = Some(known_var);
239 }
240 }
241 }
242
243 best_match
244}