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