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::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    // If the environment variable was not defined with the `--env-set` option, we try to retrieve it
26    // from rustc's environment.
27    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                ::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(
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                {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("`expr_to_string` ensures this is a string lit")));
}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            {
    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)],
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                {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("`expr_to_string` ensures this is a string lit")));
}unreachable!("`expr_to_string` ensures this is a string lit")
141            };
142
143            let var = var.as_str();
144            let guar = match err {
145                VarError::NotPresent => {
146                    if let Some(msg_from_user) = custom_msg {
147                        cx.dcx()
148                            .emit_err(errors::EnvNotDefinedWithUserMessage { span, msg_from_user })
149                    } else if let Some(suggested_var) = find_similar_cargo_var(var)
150                        && suggested_var != var
151                    {
152                        cx.dcx().emit_err(errors::EnvNotDefined::CargoEnvVarTypo {
153                            span,
154                            var: *symbol,
155                            suggested_var: Symbol::intern(suggested_var),
156                        })
157                    } else if is_cargo_env_var(var) {
158                        cx.dcx().emit_err(errors::EnvNotDefined::CargoEnvVar {
159                            span,
160                            var: *symbol,
161                            var_expr: &var_expr,
162                        })
163                    } else {
164                        cx.dcx().emit_err(errors::EnvNotDefined::CustomEnvVar {
165                            span,
166                            var: *symbol,
167                            var_expr: &var_expr,
168                        })
169                    }
170                }
171                VarError::NotUnicode(_) => {
172                    cx.dcx().emit_err(errors::EnvNotUnicode { span, var: *symbol })
173                }
174            };
175
176            return ExpandResult::Ready(DummyResult::any(sp, guar));
177        }
178        Ok(value) => cx.expr_str(span, value),
179    };
180    ExpandResult::Ready(MacEager::expr(e))
181}
182
183/// Returns `true` if an environment variable from `env!` could be one used by Cargo.
184fn is_cargo_env_var(var: &str) -> bool {
185    var.starts_with("CARGO_")
186        || var.starts_with("DEP_")
187        || #[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")
188}
189
190const KNOWN_CARGO_VARS: &[&str] = &[
191    // List of known Cargo environment variables that are set for crates (not build scripts, OUT_DIR etc).
192    // See: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
193    // tidy-alphabetical-start
194    "CARGO_BIN_NAME",
195    "CARGO_CRATE_NAME",
196    "CARGO_MANIFEST_DIR",
197    "CARGO_MANIFEST_PATH",
198    "CARGO_PKG_AUTHORS",
199    "CARGO_PKG_DESCRIPTION",
200    "CARGO_PKG_HOMEPAGE",
201    "CARGO_PKG_LICENSE",
202    "CARGO_PKG_LICENSE_FILE",
203    "CARGO_PKG_NAME",
204    "CARGO_PKG_README",
205    "CARGO_PKG_REPOSITORY",
206    "CARGO_PKG_RUST_VERSION",
207    "CARGO_PKG_VERSION",
208    "CARGO_PKG_VERSION_MAJOR",
209    "CARGO_PKG_VERSION_MINOR",
210    "CARGO_PKG_VERSION_PATCH",
211    "CARGO_PKG_VERSION_PRE",
212    "CARGO_PRIMARY_PACKAGE",
213    "CARGO_TARGET_TMPDIR",
214    // tidy-alphabetical-end
215];
216
217fn find_similar_cargo_var(var: &str) -> Option<&'static str> {
218    if !var.starts_with("CARGO_") {
219        return None;
220    }
221
222    let lookup_len = var.chars().count();
223    let max_dist = std::cmp::max(lookup_len, 3) / 3;
224    let mut best_match = None;
225    let mut best_distance = usize::MAX;
226
227    for &known_var in KNOWN_CARGO_VARS {
228        if let Some(mut distance) = edit_distance(var, known_var, max_dist) {
229            // assume `PACKAGE` to equals `PKG`
230            // (otherwise, `d("CARGO_PACKAGE_NAME", "CARGO_PKG_NAME") == d("CARGO_PACKAGE_NAME", "CARGO_CRATE_NAME") == 4`)
231            if var.contains("PACKAGE") && known_var.contains("PKG") {
232                distance = distance.saturating_sub(const { "PACKAGE".len() - "PKG".len() }) // == d("PACKAGE", "PKG")
233            }
234
235            if distance < best_distance {
236                best_distance = distance;
237                best_match = Some(known_var);
238            }
239        }
240    }
241
242    best_match
243}