Skip to content

Commit 5692047

Browse files
committed
Fix bug where option_env! would return None when env var is present but not valid Unicode
1 parent ebd08d8 commit 5692047

File tree

5 files changed

+72
-23
lines changed

5 files changed

+72
-23
lines changed

compiler/rustc_builtin_macros/src/env.rs

+27-7
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use rustc_span::Span;
1515
use thin_vec::thin_vec;
1616

1717
use crate::errors;
18-
use crate::util::{expr_to_string, get_exprs_from_tts, get_single_str_from_tts};
18+
use crate::util::{expr_to_string, get_exprs_from_tts, get_single_expr_from_tts};
1919

2020
fn lookup_env<'cx>(cx: &'cx ExtCtxt<'_>, var: Symbol) -> Result<Symbol, VarError> {
2121
let var = var.as_str();
@@ -32,19 +32,28 @@ pub(crate) fn expand_option_env<'cx>(
3232
sp: Span,
3333
tts: TokenStream,
3434
) -> MacroExpanderResult<'cx> {
35-
let ExpandResult::Ready(mac) = get_single_str_from_tts(cx, sp, tts, "option_env!") else {
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 {
3645
return ExpandResult::Retry(());
3746
};
3847
let var = match mac {
39-
Ok(var) => var,
48+
Ok((var, _)) => var,
4049
Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
4150
};
4251

4352
let sp = cx.with_def_site_ctxt(sp);
44-
let value = lookup_env(cx, var).ok();
45-
cx.sess.psess.env_depinfo.borrow_mut().insert((var, value));
53+
let value = lookup_env(cx, var);
54+
cx.sess.psess.env_depinfo.borrow_mut().insert((var, value.as_ref().ok().copied()));
4655
let e = match value {
47-
None => {
56+
Err(VarError::NotPresent) => {
4857
let lt = cx.lifetime(sp, Ident::new(kw::StaticLifetime, sp));
4958
cx.expr_path(cx.path_all(
5059
sp,
@@ -58,7 +67,18 @@ pub(crate) fn expand_option_env<'cx>(
5867
))],
5968
))
6069
}
61-
Some(value) => cx.expr_call_global(
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(
6282
sp,
6383
cx.std_path(&[sym::option, sym::Option, sym::Some]),
6484
thin_vec![cx.expr_str(sp, value)],

compiler/rustc_builtin_macros/src/util.rs

+25-7
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,30 @@ pub(crate) fn get_single_str_spanned_from_tts(
172172
tts: TokenStream,
173173
name: &str,
174174
) -> ExpandResult<Result<(Symbol, Span), ErrorGuaranteed>, ()> {
175+
let ExpandResult::Ready(ret) = get_single_expr_from_tts(cx, span, tts, name) else {
176+
return ExpandResult::Retry(());
177+
};
178+
let ret = match ret {
179+
Ok(ret) => ret,
180+
Err(e) => return ExpandResult::Ready(Err(e)),
181+
};
182+
expr_to_spanned_string(cx, ret, "argument must be a string literal").map(|res| {
183+
res.map_err(|err| match err {
184+
Ok((err, _)) => err.emit(),
185+
Err(guar) => guar,
186+
})
187+
.map(|(symbol, _style, span)| (symbol, span))
188+
})
189+
}
190+
191+
/// Interpreting `tts` as a comma-separated sequence of expressions,
192+
/// expect exactly one expression, or emit an error and return `Err`.
193+
pub(crate) fn get_single_expr_from_tts(
194+
cx: &mut ExtCtxt<'_>,
195+
span: Span,
196+
tts: TokenStream,
197+
name: &str,
198+
) -> ExpandResult<Result<P<ast::Expr>, ErrorGuaranteed>, ()> {
175199
let mut p = cx.new_parser_from_tts(tts);
176200
if p.token == token::Eof {
177201
let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
@@ -186,13 +210,7 @@ pub(crate) fn get_single_str_spanned_from_tts(
186210
if p.token != token::Eof {
187211
cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
188212
}
189-
expr_to_spanned_string(cx, ret, "argument must be a string literal").map(|res| {
190-
res.map_err(|err| match err {
191-
Ok((err, _)) => err.emit(),
192-
Err(guar) => guar,
193-
})
194-
.map(|(symbol, _style, span)| (symbol, span))
195-
})
213+
ExpandResult::Ready(Ok(ret))
196214
}
197215

198216
/// Extracts comma-separated expressions from `tts`.

library/core/src/macros/mod.rs

+10-8
Original file line numberDiff line numberDiff line change
@@ -1107,17 +1107,19 @@ pub(crate) mod builtin {
11071107
///
11081108
/// If the named environment variable is present at compile time, this will
11091109
/// expand into an expression of type `Option<&'static str>` whose value is
1110-
/// `Some` of the value of the environment variable. If the environment
1111-
/// variable is not present, then this will expand to `None`. See
1112-
/// [`Option<T>`][Option] for more information on this type. Use
1113-
/// [`std::env::var`] instead if you want to read the value at runtime.
1110+
/// `Some` of the value of the environment variable (a compilation error
1111+
/// will be emitted if the environment variable is not a valid Unicode
1112+
/// string). If the environment variable is not present, then this will
1113+
/// expand to `None`. See [`Option<T>`][Option] for more information on this
1114+
/// type. Use [`std::env::var`] instead if you want to read the value at
1115+
/// runtime.
11141116
///
11151117
/// [`std::env::var`]: ../std/env/fn.var.html
11161118
///
1117-
/// A compile time error is never emitted when using this macro regardless
1118-
/// of whether the environment variable is present or not.
1119-
/// To emit a compile error if the environment variable is not present,
1120-
/// use the [`env!`] macro instead.
1119+
/// A compile time error is only emitted when using this macro if the
1120+
/// environment variable exists and is not a valid Unicode string. To also
1121+
/// emit a compile error if the environment variable is not present, use the
1122+
/// [`env!`] macro instead.
11211123
///
11221124
/// # Examples
11231125
///
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
fn main() {
22
let _ = env!("NON_UNICODE_VAR");
3+
let _ = option_env!("NON_UNICODE_VAR");
34
}

tests/run-make/non-unicode-env/non_unicode_env.stderr

+9-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,13 @@ error: environment variable `NON_UNICODE_VAR` is not a valid Unicode string
66
|
77
= note: this error originates in the macro `env` (in Nightly builds, run with -Z macro-backtrace for more info)
88

9-
error: aborting due to 1 previous error
9+
error: environment variable `NON_UNICODE_VAR` is not a valid Unicode string
10+
--> non_unicode_env.rs:3:13
11+
|
12+
3 | let _ = option_env!("NON_UNICODE_VAR");
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
|
15+
= note: this error originates in the macro `option_env` (in Nightly builds, run with -Z macro-backtrace for more info)
16+
17+
error: aborting due to 2 previous errors
1018

0 commit comments

Comments
 (0)