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_ast_pretty::pprust;
13use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
14use rustc_span::edit_distance::edit_distance;
15use rustc_span::{Ident, Span, Symbol, kw, sym};
16use thin_vec::thin_vec;
17
18use crate::errors;
19use crate::util::{expr_to_string, get_exprs_from_tts, get_single_expr_from_tts};
20
21fn lookup_env<'cx>(cx: &'cx ExtCtxt<'_>, var: Symbol) -> Result<Symbol, VarError> {
22    let var = var.as_str();
23    if let Some(value) = cx.sess.opts.logical_env.get(var) {
24        return Ok(Symbol::intern(value));
25    }
26    // If the environment variable was not defined with the `--env-set` option, we try to retrieve it
27    // from rustc's environment.
28    Ok(Symbol::intern(&env::var(var)?))
29}
30
31pub(crate) fn expand_option_env<'cx>(
32    cx: &'cx mut ExtCtxt<'_>,
33    sp: Span,
34    tts: TokenStream,
35) -> MacroExpanderResult<'cx> {
36    let ExpandResult::Ready(mac_expr) = get_single_expr_from_tts(cx, sp, tts, "option_env!") else {
37        return ExpandResult::Retry(());
38    };
39    let var_expr = match mac_expr {
40        Ok(var_expr) => var_expr,
41        Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
42    };
43    let ExpandResult::Ready(mac) =
44        expr_to_string(cx, var_expr.clone(), "argument must be a string literal")
45    else {
46        return ExpandResult::Retry(());
47    };
48    let var = match mac {
49        Ok((var, _)) => var,
50        Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
51    };
52
53    let sp = cx.with_def_site_ctxt(sp);
54    let value = lookup_env(cx, var);
55    cx.sess.env_depinfo.borrow_mut().insert((var, value.as_ref().ok().copied()));
56    let e = match value {
57        Err(VarError::NotPresent) => {
58            let lt = cx.lifetime(sp, Ident::new(kw::StaticLifetime, sp));
59            cx.expr_path(cx.path_all(
60                sp,
61                true,
62                cx.std_path(&[sym::option, sym::Option, sym::None]),
63                ::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(
64                    sp,
65                    cx.ty_ident(sp, Ident::new(sym::str, sp)),
66                    Some(lt),
67                    Mutability::Not,
68                ))],
69            ))
70        }
71        Err(VarError::NotUnicode(_)) => {
72            let ExprKind::Lit(token::Lit {
73                kind: LitKind::Str | LitKind::StrRaw(..), symbol, ..
74            }) = &var_expr.kind
75            else {
76                {
    ::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")
77            };
78
79            let guar = cx.dcx().emit_err(errors::EnvNotUnicode { span: sp, var: *symbol });
80            return ExpandResult::Ready(DummyResult::any(sp, guar));
81        }
82        Ok(value) => cx.expr_call_global(
83            sp,
84            cx.std_path(&[sym::option, sym::Option, sym::Some]),
85            {
    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)],
86        ),
87    };
88    ExpandResult::Ready(MacEager::expr(e))
89}
90
91pub(crate) fn expand_env<'cx>(
92    cx: &'cx mut ExtCtxt<'_>,
93    sp: Span,
94    tts: TokenStream,
95) -> MacroExpanderResult<'cx> {
96    let ExpandResult::Ready(mac) = get_exprs_from_tts(cx, tts) else {
97        return ExpandResult::Retry(());
98    };
99    let mut exprs = match mac {
100        Ok(exprs) if exprs.is_empty() || exprs.len() > 2 => {
101            let guar = cx.dcx().emit_err(errors::EnvTakesArgs { span: sp });
102            return ExpandResult::Ready(DummyResult::any(sp, guar));
103        }
104        Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
105        Ok(exprs) => exprs.into_iter(),
106    };
107
108    let var_expr = exprs.next().unwrap();
109    let ExpandResult::Ready(mac) = expr_to_string(cx, var_expr.clone(), "expected string literal")
110    else {
111        return ExpandResult::Retry(());
112    };
113    let var = match mac {
114        Ok((var, _)) => var,
115        Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
116    };
117
118    let custom_msg = match exprs.next() {
119        None => None,
120        Some(second) => {
121            let ExpandResult::Ready(mac) = expr_to_string(cx, second, "expected string literal")
122            else {
123                return ExpandResult::Retry(());
124            };
125            match mac {
126                Ok((s, _)) => Some(s),
127                Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
128            }
129        }
130    };
131
132    let span = cx.with_def_site_ctxt(sp);
133    let value = lookup_env(cx, var);
134    cx.sess.env_depinfo.borrow_mut().insert((var, value.as_ref().ok().copied()));
135    let e = match value {
136        Err(err) => {
137            let ExprKind::Lit(token::Lit {
138                kind: LitKind::Str | LitKind::StrRaw(..), symbol, ..
139            }) = &var_expr.kind
140            else {
141                {
    ::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")
142            };
143
144            let var = var.as_str();
145            let guar = match err {
146                VarError::NotPresent => {
147                    if let Some(msg_from_user) = custom_msg {
148                        cx.dcx()
149                            .emit_err(errors::EnvNotDefinedWithUserMessage { span, msg_from_user })
150                    } else if let Some(suggested_var) = find_similar_cargo_var(var)
151                        && suggested_var != var
152                    {
153                        cx.dcx().emit_err(errors::EnvNotDefined::CargoEnvVarTypo {
154                            span,
155                            var: *symbol,
156                            suggested_var: Symbol::intern(suggested_var),
157                        })
158                    } else if is_cargo_env_var(var) {
159                        cx.dcx().emit_err(errors::EnvNotDefined::CargoEnvVar {
160                            span,
161                            var: *symbol,
162                            var_expr: pprust::expr_to_string(&var_expr),
163                        })
164                    } else {
165                        cx.dcx().emit_err(errors::EnvNotDefined::CustomEnvVar {
166                            span,
167                            var: *symbol,
168                            var_expr: pprust::expr_to_string(&var_expr),
169                        })
170                    }
171                }
172                VarError::NotUnicode(_) => {
173                    cx.dcx().emit_err(errors::EnvNotUnicode { span, var: *symbol })
174                }
175            };
176
177            return ExpandResult::Ready(DummyResult::any(sp, guar));
178        }
179        Ok(value) => cx.expr_str(span, value),
180    };
181    ExpandResult::Ready(MacEager::expr(e))
182}
183
184/// Returns `true` if an environment variable from `env!` could be one used by Cargo.
185fn is_cargo_env_var(var: &str) -> bool {
186    var.starts_with("CARGO_")
187        || var.starts_with("DEP_")
188        || #[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")
189}
190
191const KNOWN_CARGO_VARS: &[&str] = &[
192    // List of known Cargo environment variables that are set for crates (not build scripts, OUT_DIR etc).
193    // See: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
194    // tidy-alphabetical-start
195    "CARGO_BIN_NAME",
196    "CARGO_CRATE_NAME",
197    "CARGO_MANIFEST_DIR",
198    "CARGO_MANIFEST_PATH",
199    "CARGO_PKG_AUTHORS",
200    "CARGO_PKG_DESCRIPTION",
201    "CARGO_PKG_HOMEPAGE",
202    "CARGO_PKG_LICENSE",
203    "CARGO_PKG_LICENSE_FILE",
204    "CARGO_PKG_NAME",
205    "CARGO_PKG_README",
206    "CARGO_PKG_REPOSITORY",
207    "CARGO_PKG_RUST_VERSION",
208    "CARGO_PKG_VERSION",
209    "CARGO_PKG_VERSION_MAJOR",
210    "CARGO_PKG_VERSION_MINOR",
211    "CARGO_PKG_VERSION_PATCH",
212    "CARGO_PKG_VERSION_PRE",
213    "CARGO_PRIMARY_PACKAGE",
214    "CARGO_TARGET_TMPDIR",
215    // tidy-alphabetical-end
216];
217
218fn find_similar_cargo_var(var: &str) -> Option<&'static str> {
219    if !var.starts_with("CARGO_") {
220        return None;
221    }
222
223    let lookup_len = var.chars().count();
224    let max_dist = std::cmp::max(lookup_len, 3) / 3;
225    let mut best_match = None;
226    let mut best_distance = usize::MAX;
227
228    for &known_var in KNOWN_CARGO_VARS {
229        if let Some(mut distance) = edit_distance(var, known_var, max_dist) {
230            // assume `PACKAGE` to equals `PKG`
231            // (otherwise, `d("CARGO_PACKAGE_NAME", "CARGO_PKG_NAME") == d("CARGO_PACKAGE_NAME", "CARGO_CRATE_NAME") == 4`)
232            if var.contains("PACKAGE") && known_var.contains("PKG") {
233                distance = distance.saturating_sub(const { "PACKAGE".len() - "PKG".len() }) // == d("PACKAGE", "PKG")
234            }
235
236            if distance < best_distance {
237                best_distance = distance;
238                best_match = Some(known_var);
239            }
240        }
241    }
242
243    best_match
244}