Change:
I tried this code: compiler with (using Bash shell syntax, any non-Unicode environment variable value for will work). I expected to see this happen: a value, as the currently states "If the named environment variable is present at compile time, this will expand into an expression of type whose value is of the value of the environment variable". Instead, this happened: The program outputted: Currently, the documentation doesn't consider the possibility of a non-Unicode environment variable value (as only Unicode values can be expressed as a &'static str), and the implementation emits , the same as for a non-existent environment variable. There are three possibilities that I can think of: current behaviour is a bug, and a compilation error should be emitted as it isn't possibly to represent a non-Unicode value as a , and the documentation should also be updated to reflect this. Note that this would be a breaking change to the compiler itself, but would be allowed as it would be a bugfix. current behaviour is not a bug, and the documentation should be updated to reflect the current behaviour. as 2, but the behaviour is unexpected enough to warrant a warn/deny-by-default lint. Personally, I believe 1 to be the case, as the current behaviour is unexpected and leads to code patterns like silently returning incorrect results. There doesn't seem to be a "correct" value to return, as is already taken by "does not exist" and cannot represent a non-Unicode value. It seems unlikely anyone is relying on the current behaviour given Rust's very UTF-8 everywhere design, the fact the same result can be achieved much more easily by just not setting the environment variable, and the platform-specific nature of what non-Unicode values can exist (Windows UTF-16 unpaired surrogates vs. Unix invalid UTF-8 bytes). It therefore seems to be more likely to occur when someone has mistyped something in their shell or due to data corruption, which the current output would make rather confusing to track down (as it would seem to say the variable was missing, rather than set incorrectly). : I believe the decision of what to do is down to libs-api, so label +T-libs-api
I've created a draft implementation of option 1 in .

Discussion:
use thin_vec::thin_vec; use crate::errors; <del> use crate::util::{expr_to_string, get_exprs_from_tts, get_single_str_from_tts}; </del> <ins> use crate::util::{expr_to_string, get_exprs_from_tts, get_single_expr_from_tts}; </ins> fn lookup_env<'cx>(cx: &'cx ExtCtxt<'_>, var: Symbol) -> Result<Symbol, VarError> { let var = var.as_str();

sp: Span, tts: TokenStream, ) -> MacroExpanderResult<'cx> { <del> let ExpandResult::Ready(mac) = get_single_str_from_tts(cx, sp, tts, "option_env!") else { </del> <ins> 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 { </ins> return ExpandResult::Retry(()); }; let var = match mac { <del> Ok(var) => var, </del> <ins> Ok((var, _)) => var, </ins> Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)), }; let sp = cx.with_def_site_ctxt(sp); <del> let value = lookup_env(cx, var).ok(); cx.sess.psess.env_depinfo.borrow_mut().insert((var, value)); </del> <ins> let value = lookup_env(cx, var); cx.sess.psess.env_depinfo.borrow_mut().insert((var, value.as_ref().ok().copied())); </ins> let e = match value { <del> None => { </del> <ins> Err(VarError::NotPresent) => { </ins> let lt = cx.lifetime(sp, Ident::new(kw::StaticLifetime, sp)); cx.expr_path(cx.path_all( sp,

))], )) } <del> Some(value) => { </del> <ins> 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) => { </ins> cx.expr_call_global(sp, cx.std_path(&[sym::option, sym::Option, sym::Some]), thin_vec![ cx.expr_str(sp, value) ])

tts: TokenStream, name: &str, ) -> ExpandResult<Result<(Symbol, Span), ErrorGuaranteed>, ()> { <ins> 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>, ()> { </ins> let mut p = cx.new_parser_from_tts(tts); if p.token == token::Eof { let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name });

if p.token != token::Eof { cx.dcx().emit_err(errors::OnlyOneArgument { span, name }); } <del> 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)) }) </del> <ins> ExpandResult::Ready(Ok(ret)) </ins> } /// Extracts comma-separated expressions from `tts`.

/// /// If the named environment variable is present at compile time, this will /// expand into an expression of type `Option<&'static str>` whose value is <del> /// `Some` of the value of the environment variable. If the environment /// variable is not present, then this will 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. </del> <ins> /// `Some` of the value of the environment variable (a compilation error /// will be emitted if the environment variable is not a valid Unicode /// string). If the environment variable is not present, then this will /// 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. </ins> /// /// [`std::env::var`]: ../std/env/fn.var.html /// <del> /// A compile time error is never emitted when using this macro regardless /// of whether the environment variable is present or not. /// To emit a compile error if the environment variable is not present, /// use the [`env!`] macro instead. </del> <ins> /// A compile time error is only emitted when using this macro if the /// environment variable exists and is not a valid Unicode string. To also /// emit a compile error if the environment variable is not present, use the /// [`env!`] macro instead. </ins> /// /// # Examples ///

fn main() { let _ = env!("NON_UNICODE_VAR"); <ins> let _ = option_env!("NON_UNICODE_VAR"); </ins> }

| = note: this error originates in the macro `env` (in Nightly builds, run with -Z macro-backtrace for more info) <del> error: aborting due to 1 previous error </del> <ins> 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 </ins>