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.path_all(
55 sp,
56 true,
57 cx.std_path(&[sym::option, sym::Option, sym::None]),
58 ::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(
59 sp,
60 cx.ty_ident(sp, Ident::new(sym::str, sp)),
61 Some(lt),
62 Mutability::Not,
63 ))],
64 ))
65 }
66 Err(VarError::NotUnicode(_)) => {
67 let escaped_var = var.as_str().escape_debug().to_string();
68 let guar = cx.dcx().emit_err(diagnostics::EnvNotUnicode { span: sp, var: escaped_var });
69 return ExpandResult::Ready(DummyResult::any(sp, guar));
70 }
71 Ok(value) => cx.expr_call_global(
72 sp,
73 cx.std_path(&[sym::option, sym::Option, sym::Some]),
74 {
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)],
75 ),
76 };
77 ExpandResult::Ready(MacEager::expr(e))
78}
79
80pub(crate) fn expand_env<'cx>(
81 cx: &'cx mut ExtCtxt<'_>,
82 sp: Span,
83 tts: TokenStream,
84) -> MacroExpanderResult<'cx> {
85 let ExpandResult::Ready(mac) = get_exprs_from_tts(cx, tts) else {
86 return ExpandResult::Retry(());
87 };
88 let mut exprs = match mac {
89 Ok(exprs) if exprs.is_empty() || exprs.len() > 2 => {
90 let guar = cx.dcx().emit_err(diagnostics::EnvTakesArgs { span: sp });
91 return ExpandResult::Ready(DummyResult::any(sp, guar));
92 }
93 Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
94 Ok(exprs) => exprs.into_iter(),
95 };
96
97 let var_expr = exprs.next().unwrap();
98 let ExpandResult::Ready(mac) = expr_to_string(cx, var_expr.clone(), "expected string literal")
100 else {
101 return ExpandResult::Retry(());
102 };
103 let var = match mac {
104 Ok((var, _)) => var,
105 Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
106 };
107
108 let custom_msg = match exprs.next() {
109 None => None,
110 Some(second) => {
111 let ExpandResult::Ready(mac) = expr_to_string(cx, second, "expected string literal")
112 else {
113 return ExpandResult::Retry(());
114 };
115 match mac {
116 Ok((s, _)) => Some(s),
117 Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
118 }
119 }
120 };
121
122 let span = cx.with_def_site_ctxt(sp);
123 let value = lookup_env(var);
124 cx.sess.env_depinfo.borrow_mut().insert((var, value.as_ref().ok().copied()));
125 let e = match value {
126 Err(VarError::NotPresent) => {
127 let var_str = var.as_str();
128 let escaped_var = var_str.escape_debug().to_string();
129 let guar = if let Some(msg_from_user) = custom_msg {
130 cx.dcx().emit_err(diagnostics::EnvNotDefinedWithUserMessage { span, msg_from_user })
131 } else if let Some(suggested_var) = find_similar_cargo_var(var_str)
132 && suggested_var != var_str
133 {
134 cx.dcx().emit_err(diagnostics::EnvNotDefined::CargoEnvVarTypo {
135 span,
136 var: escaped_var,
137 suggested_var: Symbol::intern(suggested_var),
138 })
139 } else if is_cargo_env_var(var_str) {
140 cx.dcx().emit_err(diagnostics::EnvNotDefined::CargoEnvVar {
141 span,
142 var: escaped_var,
143 var_expr: pprust::expr_to_string(&var_expr),
144 })
145 } else {
146 cx.dcx().emit_err(diagnostics::EnvNotDefined::CustomEnvVar {
147 span,
148 var: escaped_var,
149 var_expr: pprust::expr_to_string(&var_expr),
150 })
151 };
152 return ExpandResult::Ready(DummyResult::any(sp, guar));
153 }
154 Err(VarError::NotUnicode(_)) => {
155 let escaped_var = var.as_str().escape_debug().to_string();
156 let guar = cx.dcx().emit_err(diagnostics::EnvNotUnicode { span, var: escaped_var });
157 return ExpandResult::Ready(DummyResult::any(sp, guar));
158 }
159 Ok(value) => cx.expr_str(span, value),
160 };
161 ExpandResult::Ready(MacEager::expr(e))
162}
163
164fn is_cargo_env_var(var: &str) -> bool {
166 var.starts_with("CARGO_")
167 || var.starts_with("DEP_")
168 || #[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")
169}
170
171const KNOWN_CARGO_VARS: &[&str] = &[
172 "CARGO_BIN_NAME",
176 "CARGO_CRATE_NAME",
177 "CARGO_MANIFEST_DIR",
178 "CARGO_MANIFEST_PATH",
179 "CARGO_PKG_AUTHORS",
180 "CARGO_PKG_DESCRIPTION",
181 "CARGO_PKG_HOMEPAGE",
182 "CARGO_PKG_LICENSE",
183 "CARGO_PKG_LICENSE_FILE",
184 "CARGO_PKG_NAME",
185 "CARGO_PKG_README",
186 "CARGO_PKG_REPOSITORY",
187 "CARGO_PKG_RUST_VERSION",
188 "CARGO_PKG_VERSION",
189 "CARGO_PKG_VERSION_MAJOR",
190 "CARGO_PKG_VERSION_MINOR",
191 "CARGO_PKG_VERSION_PATCH",
192 "CARGO_PKG_VERSION_PRE",
193 "CARGO_PRIMARY_PACKAGE",
194 "CARGO_TARGET_TMPDIR",
195 ];
197
198fn find_similar_cargo_var(var: &str) -> Option<&'static str> {
199 if !var.starts_with("CARGO_") {
200 return None;
201 }
202
203 let lookup_len = var.chars().count();
204 let max_dist = std::cmp::max(lookup_len, 3) / 3;
205 let mut best_match = None;
206 let mut best_distance = usize::MAX;
207
208 for &known_var in KNOWN_CARGO_VARS {
209 if let Some(mut distance) = edit_distance(var, known_var, max_dist) {
210 if var.contains("PACKAGE") && known_var.contains("PKG") {
213 distance = distance.saturating_sub(const { "PACKAGE".len() - "PKG".len() }) }
215
216 if distance < best_distance {
217 best_distance = distance;
218 best_match = Some(known_var);
219 }
220 }
221 }
222
223 best_match
224}