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                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
180/// Returns `true` if an environment variable from `env!` is one used by Cargo.
181fn 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    // List of known Cargo environment variables that are set for crates (not build scripts, OUT_DIR etc).
189    // See: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
190    "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}