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