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 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 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 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 unreachable!("`expr_to_string` ensures this is a string lit")
141 };
142
143 let guar = match err {
144 VarError::NotPresent => {
145 if let Some(msg_from_user) = custom_msg {
146 cx.dcx()
147 .emit_err(errors::EnvNotDefinedWithUserMessage { span, msg_from_user })
148 } else if let Some(suggested_var) = find_similar_cargo_var(var.as_str()) {
149 cx.dcx().emit_err(errors::EnvNotDefined::CargoEnvVarTypo {
150 span,
151 var: *symbol,
152 suggested_var: Symbol::intern(suggested_var),
153 })
154 } else if is_cargo_env_var(var.as_str()) {
155 cx.dcx().emit_err(errors::EnvNotDefined::CargoEnvVar {
156 span,
157 var: *symbol,
158 var_expr: &var_expr,
159 })
160 } else {
161 cx.dcx().emit_err(errors::EnvNotDefined::CustomEnvVar {
162 span,
163 var: *symbol,
164 var_expr: &var_expr,
165 })
166 }
167 }
168 VarError::NotUnicode(_) => {
169 cx.dcx().emit_err(errors::EnvNotUnicode { span, var: *symbol })
170 }
171 };
172
173 return ExpandResult::Ready(DummyResult::any(sp, guar));
174 }
175 Ok(value) => cx.expr_str(span, value),
176 };
177 ExpandResult::Ready(MacEager::expr(e))
178}
179
180fn is_cargo_env_var(var: &str) -> bool {
182 var.starts_with("CARGO_")
183 || var.starts_with("DEP_")
184 || matches!(var, "OUT_DIR" | "OPT_LEVEL" | "PROFILE" | "HOST" | "TARGET")
185}
186
187const KNOWN_CARGO_VARS: &[&str] = &[
188 "CARGO_PKG_VERSION",
191 "CARGO_PKG_VERSION_MAJOR",
192 "CARGO_PKG_VERSION_MINOR",
193 "CARGO_PKG_VERSION_PATCH",
194 "CARGO_PKG_VERSION_PRE",
195 "CARGO_PKG_AUTHORS",
196 "CARGO_PKG_NAME",
197 "CARGO_PKG_DESCRIPTION",
198 "CARGO_PKG_HOMEPAGE",
199 "CARGO_PKG_REPOSITORY",
200 "CARGO_PKG_LICENSE",
201 "CARGO_PKG_LICENSE_FILE",
202 "CARGO_PKG_RUST_VERSION",
203 "CARGO_PKG_README",
204 "CARGO_MANIFEST_DIR",
205 "CARGO_MANIFEST_PATH",
206 "CARGO_CRATE_NAME",
207 "CARGO_BIN_NAME",
208 "CARGO_PRIMARY_PACKAGE",
209];
210
211fn find_similar_cargo_var(var: &str) -> Option<&'static str> {
212 if !var.starts_with("CARGO_") {
213 return None;
214 }
215
216 let lookup_len = var.chars().count();
217 let max_dist = std::cmp::max(lookup_len, 3) / 3;
218 let mut best_match = None;
219 let mut best_distance = usize::MAX;
220
221 for &known_var in KNOWN_CARGO_VARS {
222 if let Some(distance) = edit_distance(var, known_var, max_dist) {
223 if distance < best_distance {
224 best_distance = distance;
225 best_match = Some(known_var);
226 }
227 }
228 }
229
230 best_match
231}