Skip to content

Commit 0a3363b

Browse files
committed
Fix bug where option_env! would return None when env var is present but not valid Unicode
1 parent 6bb6b81 commit 0a3363b

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
@@ -6,7 +6,7 @@
66
use rustc_ast::token::{self, LitKind};
77
use rustc_ast::tokenstream::TokenStream;
88
use rustc_ast::{AstDeref, ExprKind, GenericArg, Mutability};
9-
use rustc_expand::base::{expr_to_string, get_exprs_from_tts, get_single_str_from_tts};
9+
use rustc_expand::base::{expr_to_string, get_exprs_from_tts, get_single_expr_from_tts};
1010
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
1111
use rustc_span::symbol::{kw, sym, Ident, Symbol};
1212
use rustc_span::Span;
@@ -31,19 +31,28 @@ pub fn expand_option_env<'cx>(
3131
sp: Span,
3232
tts: TokenStream,
3333
) -> MacroExpanderResult<'cx> {
34-
let ExpandResult::Ready(mac) = get_single_str_from_tts(cx, sp, tts, "option_env!") else {
34+
let ExpandResult::Ready(mac_expr) = get_single_expr_from_tts(cx, sp, tts, "option_env!") else {
35+
return ExpandResult::Retry(());
36+
};
37+
let var_expr = match mac_expr {
38+
Ok(var_expr) => var_expr,
39+
Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
40+
};
41+
let ExpandResult::Ready(mac) =
42+
expr_to_string(cx, var_expr.clone(), "argument must be a string literal")
43+
else {
3544
return ExpandResult::Retry(());
3645
};
3746
let var = match mac {
38-
Ok(var) => var,
47+
Ok((var, _)) => var,
3948
Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
4049
};
4150

4251
let sp = cx.with_def_site_ctxt(sp);
43-
let value = lookup_env(cx, var).ok();
44-
cx.sess.psess.env_depinfo.borrow_mut().insert((var, value));
52+
let value = lookup_env(cx, var);
53+
cx.sess.psess.env_depinfo.borrow_mut().insert((var, value.as_ref().ok().copied()));
4554
let e = match value {
46-
None => {
55+
Err(VarError::NotPresent) => {
4756
let lt = cx.lifetime(sp, Ident::new(kw::StaticLifetime, sp));
4857
cx.expr_path(cx.path_all(
4958
sp,
@@ -57,7 +66,18 @@ pub fn expand_option_env<'cx>(
5766
))],
5867
))
5968
}
60-
Some(value) => cx.expr_call_global(
69+
Err(VarError::NotUnicode(_)) => {
70+
let ExprKind::Lit(token::Lit {
71+
kind: LitKind::Str | LitKind::StrRaw(..), symbol, ..
72+
}) = &var_expr.kind
73+
else {
74+
unreachable!("`expr_to_string` ensures this is a string lit")
75+
};
76+
77+
let guar = cx.dcx().emit_err(errors::EnvNotUnicode { span: sp, var: *symbol });
78+
return ExpandResult::Ready(DummyResult::any(sp, guar));
79+
}
80+
Ok(value) => cx.expr_call_global(
6181
sp,
6282
cx.std_path(&[sym::option, sym::Option, sym::Some]),
6383
thin_vec![cx.expr_str(sp, value)],

compiler/rustc_expand/src/base.rs

+25-7
Original file line numberDiff line numberDiff line change
@@ -1385,6 +1385,30 @@ pub fn get_single_str_spanned_from_tts(
13851385
tts: TokenStream,
13861386
name: &str,
13871387
) -> ExpandResult<Result<(Symbol, Span), ErrorGuaranteed>, ()> {
1388+
let ExpandResult::Ready(ret) = get_single_expr_from_tts(cx, span, tts, name) else {
1389+
return ExpandResult::Retry(());
1390+
};
1391+
let ret = match ret {
1392+
Ok(ret) => ret,
1393+
Err(e) => return ExpandResult::Ready(Err(e)),
1394+
};
1395+
expr_to_spanned_string(cx, ret, "argument must be a string literal").map(|res| {
1396+
res.map_err(|err| match err {
1397+
Ok((err, _)) => err.emit(),
1398+
Err(guar) => guar,
1399+
})
1400+
.map(|(symbol, _style, span)| (symbol, span))
1401+
})
1402+
}
1403+
1404+
/// Interpreting `tts` as a comma-separated sequence of expressions,
1405+
/// expect exactly one expression, or emit an error and return `Err`.
1406+
pub fn get_single_expr_from_tts(
1407+
cx: &mut ExtCtxt<'_>,
1408+
span: Span,
1409+
tts: TokenStream,
1410+
name: &str,
1411+
) -> ExpandResult<Result<P<ast::Expr>, ErrorGuaranteed>, ()> {
13881412
let mut p = cx.new_parser_from_tts(tts);
13891413
if p.token == token::Eof {
13901414
let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
@@ -1399,13 +1423,7 @@ pub fn get_single_str_spanned_from_tts(
13991423
if p.token != token::Eof {
14001424
cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
14011425
}
1402-
expr_to_spanned_string(cx, ret, "argument must be a string literal").map(|res| {
1403-
res.map_err(|err| match err {
1404-
Ok((err, _)) => err.emit(),
1405-
Err(guar) => guar,
1406-
})
1407-
.map(|(symbol, _style, span)| (symbol, span))
1408-
})
1426+
ExpandResult::Ready(Ok(ret))
14091427
}
14101428

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

library/core/src/macros/mod.rs

+10-8
Original file line numberDiff line numberDiff line change
@@ -1089,17 +1089,19 @@ pub(crate) mod builtin {
10891089
///
10901090
/// If the named environment variable is present at compile time, this will
10911091
/// expand into an expression of type `Option<&'static str>` whose value is
1092-
/// `Some` of the value of the environment variable. If the environment
1093-
/// variable is not present, then this will expand to `None`. See
1094-
/// [`Option<T>`][Option] for more information on this type. Use
1095-
/// [`std::env::var`] instead if you want to read the value at runtime.
1092+
/// `Some` of the value of the environment variable (a compilation error
1093+
/// will be emitted if the environment variable is not a valid Unicode
1094+
/// string). If the environment variable is not present, then this will
1095+
/// expand to `None`. See [`Option<T>`][Option] for more information on this
1096+
/// type. Use [`std::env::var`] instead if you want to read the value at
1097+
/// runtime.
10961098
///
10971099
/// [`std::env::var`]: ../std/env/fn.var.html
10981100
///
1099-
/// A compile time error is never emitted when using this macro regardless
1100-
/// of whether the environment variable is present or not.
1101-
/// To emit a compile error if the environment variable is not present,
1102-
/// use the [`env!`] macro instead.
1101+
/// A compile time error is only emitted when using this macro if the
1102+
/// environment variable exists and is not a valid Unicode string. To also
1103+
/// emit a compile error if the environment variable is not present, use the
1104+
/// [`env!`] macro instead.
11031105
///
11041106
/// # Examples
11051107
///
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)