Fix bug where option_env! would return None when env var is present but not valid Unicode

This commit is contained in:
beetrees 2024-03-18 00:02:49 +00:00
parent 6b9676b454
commit feecfaa18d
No known key found for this signature in database
GPG Key ID: 8791BD754191EBD6
5 changed files with 72 additions and 23 deletions

View File

@ -15,7 +15,7 @@ use rustc_span::symbol::{Ident, Symbol, kw, sym};
use thin_vec::thin_vec; use thin_vec::thin_vec;
use crate::errors; use crate::errors;
use crate::util::{expr_to_string, get_exprs_from_tts, get_single_str_from_tts}; use crate::util::{expr_to_string, get_exprs_from_tts, get_single_expr_from_tts};
fn lookup_env<'cx>(cx: &'cx ExtCtxt<'_>, var: Symbol) -> Result<Symbol, VarError> { fn lookup_env<'cx>(cx: &'cx ExtCtxt<'_>, var: Symbol) -> Result<Symbol, VarError> {
let var = var.as_str(); let var = var.as_str();
@ -32,19 +32,28 @@ pub(crate) fn expand_option_env<'cx>(
sp: Span, sp: Span,
tts: TokenStream, tts: TokenStream,
) -> MacroExpanderResult<'cx> { ) -> MacroExpanderResult<'cx> {
let ExpandResult::Ready(mac) = get_single_str_from_tts(cx, sp, tts, "option_env!") else { let ExpandResult::Ready(mac_expr) = get_single_expr_from_tts(cx, sp, tts, "option_env!") else {
return ExpandResult::Retry(());
};
let var_expr = match mac_expr {
Ok(var_expr) => var_expr,
Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
};
let ExpandResult::Ready(mac) =
expr_to_string(cx, var_expr.clone(), "argument must be a string literal")
else {
return ExpandResult::Retry(()); return ExpandResult::Retry(());
}; };
let var = match mac { let var = match mac {
Ok(var) => var, Ok((var, _)) => var,
Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)), Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
}; };
let sp = cx.with_def_site_ctxt(sp); let sp = cx.with_def_site_ctxt(sp);
let value = lookup_env(cx, var).ok(); let value = lookup_env(cx, var);
cx.sess.psess.env_depinfo.borrow_mut().insert((var, value)); cx.sess.psess.env_depinfo.borrow_mut().insert((var, value.as_ref().ok().copied()));
let e = match value { let e = match value {
None => { Err(VarError::NotPresent) => {
let lt = cx.lifetime(sp, Ident::new(kw::StaticLifetime, sp)); let lt = cx.lifetime(sp, Ident::new(kw::StaticLifetime, sp));
cx.expr_path(cx.path_all( cx.expr_path(cx.path_all(
sp, sp,
@ -58,7 +67,18 @@ pub(crate) fn expand_option_env<'cx>(
))], ))],
)) ))
} }
Some(value) => { Err(VarError::NotUnicode(_)) => {
let ExprKind::Lit(token::Lit {
kind: LitKind::Str | LitKind::StrRaw(..), symbol, ..
}) = &var_expr.kind
else {
unreachable!("`expr_to_string` ensures this is a string lit")
};
let guar = cx.dcx().emit_err(errors::EnvNotUnicode { span: sp, var: *symbol });
return ExpandResult::Ready(DummyResult::any(sp, guar));
}
Ok(value) => {
cx.expr_call_global(sp, cx.std_path(&[sym::option, sym::Option, sym::Some]), thin_vec![ cx.expr_call_global(sp, cx.std_path(&[sym::option, sym::Option, sym::Some]), thin_vec![
cx.expr_str(sp, value) cx.expr_str(sp, value)
]) ])

View File

@ -171,6 +171,30 @@ pub(crate) fn get_single_str_spanned_from_tts(
tts: TokenStream, tts: TokenStream,
name: &str, name: &str,
) -> ExpandResult<Result<(Symbol, Span), ErrorGuaranteed>, ()> { ) -> ExpandResult<Result<(Symbol, Span), ErrorGuaranteed>, ()> {
let ExpandResult::Ready(ret) = get_single_expr_from_tts(cx, span, tts, name) else {
return ExpandResult::Retry(());
};
let ret = match ret {
Ok(ret) => ret,
Err(e) => return ExpandResult::Ready(Err(e)),
};
expr_to_spanned_string(cx, ret, "argument must be a string literal").map(|res| {
res.map_err(|err| match err {
Ok((err, _)) => err.emit(),
Err(guar) => guar,
})
.map(|(symbol, _style, span)| (symbol, span))
})
}
/// Interpreting `tts` as a comma-separated sequence of expressions,
/// expect exactly one expression, or emit an error and return `Err`.
pub(crate) fn get_single_expr_from_tts(
cx: &mut ExtCtxt<'_>,
span: Span,
tts: TokenStream,
name: &str,
) -> ExpandResult<Result<P<ast::Expr>, ErrorGuaranteed>, ()> {
let mut p = cx.new_parser_from_tts(tts); let mut p = cx.new_parser_from_tts(tts);
if p.token == token::Eof { if p.token == token::Eof {
let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name }); let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
@ -185,13 +209,7 @@ pub(crate) fn get_single_str_spanned_from_tts(
if p.token != token::Eof { if p.token != token::Eof {
cx.dcx().emit_err(errors::OnlyOneArgument { span, name }); cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
} }
expr_to_spanned_string(cx, ret, "argument must be a string literal").map(|res| { ExpandResult::Ready(Ok(ret))
res.map_err(|err| match err {
Ok((err, _)) => err.emit(),
Err(guar) => guar,
})
.map(|(symbol, _style, span)| (symbol, span))
})
} }
/// Extracts comma-separated expressions from `tts`. /// Extracts comma-separated expressions from `tts`.

View File

@ -1107,17 +1107,19 @@ pub(crate) mod builtin {
/// ///
/// If the named environment variable is present at compile time, this will /// If the named environment variable is present at compile time, this will
/// expand into an expression of type `Option<&'static str>` whose value is /// expand into an expression of type `Option<&'static str>` whose value is
/// `Some` of the value of the environment variable. If the environment /// `Some` of the value of the environment variable (a compilation error
/// variable is not present, then this will expand to `None`. See /// will be emitted if the environment variable is not a valid Unicode
/// [`Option<T>`][Option] for more information on this type. Use /// string). If the environment variable is not present, then this will
/// [`std::env::var`] instead if you want to read the value at runtime. /// expand to `None`. See [`Option<T>`][Option] for more information on this
/// type. Use [`std::env::var`] instead if you want to read the value at
/// runtime.
/// ///
/// [`std::env::var`]: ../std/env/fn.var.html /// [`std::env::var`]: ../std/env/fn.var.html
/// ///
/// A compile time error is never emitted when using this macro regardless /// A compile time error is only emitted when using this macro if the
/// of whether the environment variable is present or not. /// environment variable exists and is not a valid Unicode string. To also
/// To emit a compile error if the environment variable is not present, /// emit a compile error if the environment variable is not present, use the
/// use the [`env!`] macro instead. /// [`env!`] macro instead.
/// ///
/// # Examples /// # Examples
/// ///

View File

@ -1,3 +1,4 @@
fn main() { fn main() {
let _ = env!("NON_UNICODE_VAR"); let _ = env!("NON_UNICODE_VAR");
let _ = option_env!("NON_UNICODE_VAR");
} }

View File

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