diff --git a/compiler/rustc_trait_selection/src/lib.rs b/compiler/rustc_trait_selection/src/lib.rs index f46e8ff0004..539e94993ae 100644 --- a/compiler/rustc_trait_selection/src/lib.rs +++ b/compiler/rustc_trait_selection/src/lib.rs @@ -20,6 +20,7 @@ #![feature(label_break_value)] #![feature(let_chains)] #![feature(let_else)] +#![feature(if_let_guard)] #![feature(never_type)] #![recursion_limit = "512"] // For rustdoc diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs index 27ce08ea045..0dea2c3d8bf 100644 --- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs +++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs @@ -39,150 +39,148 @@ pub fn is_const_evaluatable<'cx, 'tcx>( let tcx = infcx.tcx; if tcx.features().generic_const_exprs { - match AbstractConst::new(tcx, uv)? { - // We are looking at a generic abstract constant. - Some(ct) => { - if satisfied_from_param_env(tcx, ct, param_env)? { - return Ok(()); - } - - // We were unable to unify the abstract constant with - // a constant found in the caller bounds, there are - // now three possible cases here. - #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] - enum FailureKind { - /// The abstract const still references an inference - /// variable, in this case we return `TooGeneric`. - MentionsInfer, - /// The abstract const references a generic parameter, - /// this means that we emit an error here. - MentionsParam, - /// The substs are concrete enough that we can simply - /// try and evaluate the given constant. - Concrete, - } - let mut failure_kind = FailureKind::Concrete; - walk_abstract_const::(tcx, ct, |node| match node.root(tcx) { - Node::Leaf(leaf) => { - if leaf.has_infer_types_or_consts() { - failure_kind = FailureKind::MentionsInfer; - } else if leaf.has_param_types_or_consts() { - failure_kind = cmp::min(failure_kind, FailureKind::MentionsParam); - } - - ControlFlow::CONTINUE - } - Node::Cast(_, _, ty) => { - if ty.has_infer_types_or_consts() { - failure_kind = FailureKind::MentionsInfer; - } else if ty.has_param_types_or_consts() { - failure_kind = cmp::min(failure_kind, FailureKind::MentionsParam); - } - - ControlFlow::CONTINUE - } - Node::Binop(_, _, _) | Node::UnaryOp(_, _) | Node::FunctionCall(_, _) => { - ControlFlow::CONTINUE - } - }); - - match failure_kind { - FailureKind::MentionsInfer => { - return Err(NotConstEvaluatable::MentionsInfer); - } - FailureKind::MentionsParam => { - return Err(NotConstEvaluatable::MentionsParam); - } - FailureKind::Concrete => { - // Dealt with below by the same code which handles this - // without the feature gate. - } - } + if let Some(ct) = AbstractConst::new(tcx, uv)? { + if satisfied_from_param_env(tcx, ct, param_env)? { + return Ok(()); } - None => { - // If we are dealing with a concrete constant, we can - // reuse the old code path and try to evaluate - // the constant. + + // We were unable to unify the abstract constant with + // a constant found in the caller bounds, there are + // now three possible cases here. + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] + enum FailureKind { + /// The abstract const still references an inference + /// variable, in this case we return `TooGeneric`. + MentionsInfer, + /// The abstract const references a generic parameter, + /// this means that we emit an error here. + MentionsParam, + /// The substs are concrete enough that we can simply + /// try and evaluate the given constant. + Concrete, + } + let mut failure_kind = FailureKind::Concrete; + walk_abstract_const::(tcx, ct, |node| match node.root(tcx) { + Node::Leaf(leaf) => { + if leaf.has_infer_types_or_consts() { + failure_kind = FailureKind::MentionsInfer; + } else if leaf.has_param_types_or_consts() { + failure_kind = cmp::min(failure_kind, FailureKind::MentionsParam); + } + + ControlFlow::CONTINUE + } + Node::Cast(_, _, ty) => { + if ty.has_infer_types_or_consts() { + failure_kind = FailureKind::MentionsInfer; + } else if ty.has_param_types_or_consts() { + failure_kind = cmp::min(failure_kind, FailureKind::MentionsParam); + } + + ControlFlow::CONTINUE + } + Node::Binop(_, _, _) | Node::UnaryOp(_, _) | Node::FunctionCall(_, _) => { + ControlFlow::CONTINUE + } + }); + + match failure_kind { + FailureKind::MentionsInfer => { + return Err(NotConstEvaluatable::MentionsInfer); + } + FailureKind::MentionsParam => { + return Err(NotConstEvaluatable::MentionsParam); + } + // returned below + FailureKind::Concrete => {} } } - } - - let future_compat_lint = || { - if let Some(local_def_id) = uv.def.did.as_local() { - infcx.tcx.struct_span_lint_hir( - lint::builtin::CONST_EVALUATABLE_UNCHECKED, - infcx.tcx.hir().local_def_id_to_hir_id(local_def_id), - span, - |err| { - err.build("cannot use constants which depend on generic parameters in types") - .emit(); - }, - ); - } - }; - - // FIXME: We should only try to evaluate a given constant here if it is fully concrete - // as we don't want to allow things like `[u8; std::mem::size_of::<*mut T>()]`. - // - // We previously did not check this, so we only emit a future compat warning if - // const evaluation succeeds and the given constant is still polymorphic for now - // and hopefully soon change this to an error. - // - // See #74595 for more details about this. - let concrete = infcx.const_eval_resolve(param_env, uv.expand(), Some(span)); - - if concrete.is_ok() && uv.substs.has_param_types_or_consts() { - match infcx.tcx.def_kind(uv.def.did) { - DefKind::AnonConst | DefKind::InlineConst => { - let mir_body = infcx.tcx.mir_for_ctfe_opt_const_arg(uv.def); - - if mir_body.is_polymorphic { - future_compat_lint(); - } + let concrete = infcx.const_eval_resolve(param_env, uv.expand(), Some(span)); + match concrete { + Err(ErrorHandled::TooGeneric) => Err(if !uv.has_infer_types_or_consts() { + infcx + .tcx + .sess + .delay_span_bug(span, &format!("unexpected `TooGeneric` for {:?}", uv)); + NotConstEvaluatable::MentionsParam + } else { + NotConstEvaluatable::MentionsInfer + }), + Err(ErrorHandled::Linted) => { + let reported = infcx + .tcx + .sess + .delay_span_bug(span, "constant in type had error reported as lint"); + Err(NotConstEvaluatable::Error(reported)) } - _ => future_compat_lint(), + Err(ErrorHandled::Reported(e)) => Err(NotConstEvaluatable::Error(e)), + Ok(_) => Ok(()), } - } + } else { + // FIXME: We should only try to evaluate a given constant here if it is fully concrete + // as we don't want to allow things like `[u8; std::mem::size_of::<*mut T>()]`. + // + // We previously did not check this, so we only emit a future compat warning if + // const evaluation succeeds and the given constant is still polymorphic for now + // and hopefully soon change this to an error. + // + // See #74595 for more details about this. + let concrete = infcx.const_eval_resolve(param_env, uv.expand(), Some(span)); - // If we're evaluating a foreign constant, under a nightly compiler without generic - // const exprs, AND it would've passed if that expression had been evaluated with - // generic const exprs, then suggest using generic const exprs. - if concrete.is_err() - && tcx.sess.is_nightly_build() - && !uv.def.did.is_local() - && !tcx.features().generic_const_exprs - && let Ok(Some(ct)) = AbstractConst::new(tcx, uv) - && satisfied_from_param_env(tcx, ct, param_env) == Ok(true) - { - tcx.sess - .struct_span_fatal( - // Slightly better span than just using `span` alone - if span == rustc_span::DUMMY_SP { tcx.def_span(uv.def.did) } else { span }, - "failed to evaluate generic const expression", - ) - .note("the crate this constant originates from uses `#![feature(generic_const_exprs)]`") - .span_suggestion_verbose( - rustc_span::DUMMY_SP, - "consider enabling this feature", - "#![feature(generic_const_exprs)]\n".to_string(), - rustc_errors::Applicability::MaybeIncorrect, - ) - .emit() - } + match concrete { + // If we're evaluating a foreign constant, under a nightly compiler without generic + // const exprs, AND it would've passed if that expression had been evaluated with + // generic const exprs, then suggest using generic const exprs. + Err(_) if tcx.sess.is_nightly_build() + && let Ok(Some(ct)) = AbstractConst::new(tcx, uv) + && satisfied_from_param_env(tcx, ct, param_env) == Ok(true) => { + tcx.sess + .struct_span_fatal( + // Slightly better span than just using `span` alone + if span == rustc_span::DUMMY_SP { tcx.def_span(uv.def.did) } else { span }, + "failed to evaluate generic const expression", + ) + .note("the crate this constant originates from uses `#![feature(generic_const_exprs)]`") + .span_suggestion_verbose( + rustc_span::DUMMY_SP, + "consider enabling this feature", + "#![feature(generic_const_exprs)]\n".to_string(), + rustc_errors::Applicability::MaybeIncorrect, + ) + .emit() + } - debug!(?concrete, "is_const_evaluatable"); - match concrete { - Err(ErrorHandled::TooGeneric) => Err(match uv.has_infer_types_or_consts() { - true => NotConstEvaluatable::MentionsInfer, - false => NotConstEvaluatable::MentionsParam, - }), - Err(ErrorHandled::Linted) => { - let reported = - infcx.tcx.sess.delay_span_bug(span, "constant in type had error reported as lint"); - Err(NotConstEvaluatable::Error(reported)) + Err(ErrorHandled::TooGeneric) => Err(if uv.has_infer_types_or_consts() { + NotConstEvaluatable::MentionsInfer + } else { + NotConstEvaluatable::MentionsParam + }), + Err(ErrorHandled::Linted) => { + let reported = + infcx.tcx.sess.delay_span_bug(span, "constant in type had error reported as lint"); + Err(NotConstEvaluatable::Error(reported)) + } + Err(ErrorHandled::Reported(e)) => Err(NotConstEvaluatable::Error(e)), + Ok(_) => { + if uv.substs.has_param_types_or_consts() { + assert!(matches!(infcx.tcx.def_kind(uv.def.did), DefKind::AnonConst)); + let mir_body = infcx.tcx.mir_for_ctfe_opt_const_arg(uv.def); + + if mir_body.is_polymorphic { + let Some(local_def_id) = uv.def.did.as_local() else { return Ok(()) }; + tcx.struct_span_lint_hir( + lint::builtin::CONST_EVALUATABLE_UNCHECKED, + tcx.hir().local_def_id_to_hir_id(local_def_id), + span, + |err| { + err.build("cannot use constants which depend on generic parameters in types").emit(); + }) + } + } + + Ok(()) + }, } - Err(ErrorHandled::Reported(e)) => Err(NotConstEvaluatable::Error(e)), - Ok(_) => Ok(()), } } diff --git a/src/test/ui/const-generics/generic_const_exprs/dependence_lint.full.stderr b/src/test/ui/const-generics/generic_const_exprs/dependence_lint.full.stderr new file mode 100644 index 00000000000..4cd86fecd7e --- /dev/null +++ b/src/test/ui/const-generics/generic_const_exprs/dependence_lint.full.stderr @@ -0,0 +1,39 @@ +error: generic parameters may not be used in const operations + --> $DIR/dependence_lint.rs:13:32 + | +LL | let _: [u8; size_of::<*mut T>()]; // error on stable, error with gce + | ^ cannot perform const operation using `T` + | + = note: type parameters may not be used in const expressions + = help: use `#![feature(generic_const_exprs)]` to allow generic const expressions + +error: generic parameters may not be used in const operations + --> $DIR/dependence_lint.rs:20:37 + | +LL | let _: [u8; if true { size_of::() } else { 3 }]; // error on stable, error with gce + | ^ cannot perform const operation using `T` + | + = note: type parameters may not be used in const expressions + = help: use `#![feature(generic_const_exprs)]` to allow generic const expressions + +warning: cannot use constants which depend on generic parameters in types + --> $DIR/dependence_lint.rs:9:9 + | +LL | [0; size_of::<*mut T>()]; // lint on stable, error with `generic_const_exprs` + | ^^^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(const_evaluatable_unchecked)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #76200 + +warning: cannot use constants which depend on generic parameters in types + --> $DIR/dependence_lint.rs:16:9 + | +LL | [0; if false { size_of::() } else { 3 }]; // lint on stable, error with gce + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #76200 + +error: aborting due to 2 previous errors; 2 warnings emitted + diff --git a/src/test/ui/const-generics/generic_const_exprs/dependence_lint.gce.stderr b/src/test/ui/const-generics/generic_const_exprs/dependence_lint.gce.stderr new file mode 100644 index 00000000000..b13bcdb2c47 --- /dev/null +++ b/src/test/ui/const-generics/generic_const_exprs/dependence_lint.gce.stderr @@ -0,0 +1,34 @@ +error: overly complex generic constant + --> $DIR/dependence_lint.rs:16:9 + | +LL | [0; if false { size_of::() } else { 3 }]; // lint on stable, error with gce + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ control flow is not supported in generic constants + | + = help: consider moving this anonymous constant into a `const` function + +error: overly complex generic constant + --> $DIR/dependence_lint.rs:20:17 + | +LL | let _: [u8; if true { size_of::() } else { 3 }]; // error on stable, error with gce + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ control flow is not supported in generic constants + | + = help: consider moving this anonymous constant into a `const` function + +error: unconstrained generic constant + --> $DIR/dependence_lint.rs:13:12 + | +LL | let _: [u8; size_of::<*mut T>()]; // error on stable, error with gce + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: try adding a `where` bound using this expression: `where [(); size_of::<*mut T>()]:` + +error: unconstrained generic constant + --> $DIR/dependence_lint.rs:9:9 + | +LL | [0; size_of::<*mut T>()]; // lint on stable, error with `generic_const_exprs` + | ^^^^^^^^^^^^^^^^^^^ + | + = help: try adding a `where` bound using this expression: `where [(); size_of::<*mut T>()]:` + +error: aborting due to 4 previous errors + diff --git a/src/test/ui/const-generics/generic_const_exprs/dependence_lint.rs b/src/test/ui/const-generics/generic_const_exprs/dependence_lint.rs new file mode 100644 index 00000000000..dcdfd75def9 --- /dev/null +++ b/src/test/ui/const-generics/generic_const_exprs/dependence_lint.rs @@ -0,0 +1,25 @@ +// revisions: full gce + +#![cfg_attr(gce, feature(generic_const_exprs))] +#![allow(incomplete_features)] + +use std::mem::size_of; + +fn foo() { + [0; size_of::<*mut T>()]; // lint on stable, error with `generic_const_exprs` + //[gce]~^ ERROR unconstrained + //[full]~^^ WARNING cannot use constants + //[full]~| WARNING this was previously accepted + let _: [u8; size_of::<*mut T>()]; // error on stable, error with gce + //[full]~^ ERROR generic parameters may not be used + //[gce]~^^ ERROR unconstrained generic + [0; if false { size_of::() } else { 3 }]; // lint on stable, error with gce + //[gce]~^ ERROR overly complex + //[full]~^^ WARNING cannot use constants + //[full]~| WARNING this was previously accepted + let _: [u8; if true { size_of::() } else { 3 }]; // error on stable, error with gce + //[full]~^ ERROR generic parameters may not be used + //[gce]~^^ ERROR overly complex +} + +fn main() {} diff --git a/src/test/ui/const-generics/generic_const_exprs/no_dependence.rs b/src/test/ui/const-generics/generic_const_exprs/no_dependence.rs new file mode 100644 index 00000000000..db8dc6ed443 --- /dev/null +++ b/src/test/ui/const-generics/generic_const_exprs/no_dependence.rs @@ -0,0 +1,13 @@ +// check-pass +#![feature(generic_const_exprs)] +#![allow(incomplete_features)] + +fn two_args() -> [u8; M + 2] { + [0; M + 2] +} + +fn yay() -> [u8; 4] { + two_args::() // no lint +} + +fn main() {}