Skip to main content

rustc_builtin_macros/
env.rs

1// The compiler code necessary to support the env! extension. Eventually this
2// should all get sucked into either the compiler syntax extension plugin
3// interface.
4//
5
6use 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    // Try to retrieve the environment variable from rustc's environment.
23    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    // FIXME: `get_exprs_from_tts()` already performed macro expansion...
98    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
163/// Returns `true` if an environment variable from `env!` could be one used by Cargo.
164fn 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    // List of known Cargo environment variables that are set for crates (not build scripts, OUT_DIR etc).
172    // See: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
173    // tidy-alphabetical-start
174    "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    // tidy-alphabetical-end
195];
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            // assume `PACKAGE` to equals `PKG`
210            // (otherwise, `d("CARGO_PACKAGE_NAME", "CARGO_PKG_NAME") == d("CARGO_PACKAGE_NAME", "CARGO_CRATE_NAME") == 4`)
211            if var.contains("PACKAGE") && known_var.contains("PKG") {
212                distance = distance.saturating_sub(const { "PACKAGE".len() - "PKG".len() }) // == d("PACKAGE", "PKG")
213            }
214
215            if distance < best_distance {
216                best_distance = distance;
217                best_match = Some(known_var);
218            }
219        }
220    }
221
222    best_match
223}