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.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    // FIXME: `get_exprs_from_tts()` already performed macro expansion...
99    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
164/// Returns `true` if an environment variable from `env!` could be one used by Cargo.
165fn 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    // List of known Cargo environment variables that are set for crates (not build scripts, OUT_DIR etc).
173    // See: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
174    // tidy-alphabetical-start
175    "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    // tidy-alphabetical-end
196];
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            // assume `PACKAGE` to equals `PKG`
211            // (otherwise, `d("CARGO_PACKAGE_NAME", "CARGO_PKG_NAME") == d("CARGO_PACKAGE_NAME", "CARGO_CRATE_NAME") == 4`)
212            if var.contains("PACKAGE") && known_var.contains("PKG") {
213                distance = distance.saturating_sub(const { "PACKAGE".len() - "PKG".len() }) // == d("PACKAGE", "PKG")
214            }
215
216            if distance < best_distance {
217                best_distance = distance;
218                best_match = Some(known_var);
219            }
220        }
221    }
222
223    best_match
224}