Rollup merge of #76939 - lcnr:const-evaluatable-cont, r=oli-obk

emit errors during AbstractConst building

There changes are currently still untested, so I don't expect this to pass CI 😆

It seems to me like this is the direction we want to go in, though we didn't have too much of a discussion about this.

r? @oli-obk
This commit is contained in:
Dylan DPC 2020-09-23 14:54:02 +02:00 committed by GitHub
commit 98e5ee7df0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 160 additions and 92 deletions

View file

@ -11,6 +11,7 @@ use rustc_data_structures::fingerprint::{Fingerprint, FingerprintDecoder};
use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::svh::Svh; use rustc_data_structures::svh::Svh;
use rustc_data_structures::sync::{AtomicCell, Lock, LockGuard, Lrc, OnceCell}; use rustc_data_structures::sync::{AtomicCell, Lock, LockGuard, Lrc, OnceCell};
use rustc_errors::ErrorReported;
use rustc_expand::base::{SyntaxExtension, SyntaxExtensionKind}; use rustc_expand::base::{SyntaxExtension, SyntaxExtensionKind};
use rustc_expand::proc_macro::{AttrProcMacro, BangProcMacro, ProcMacroDerive}; use rustc_expand::proc_macro::{AttrProcMacro, BangProcMacro, ProcMacroDerive};
use rustc_hir as hir; use rustc_hir as hir;
@ -1201,13 +1202,13 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
&self, &self,
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
id: DefIndex, id: DefIndex,
) -> Option<&'tcx [mir::abstract_const::Node<'tcx>]> { ) -> Result<Option<&'tcx [mir::abstract_const::Node<'tcx>]>, ErrorReported> {
self.root self.root
.tables .tables
.mir_abstract_consts .mir_abstract_consts
.get(self, id) .get(self, id)
.filter(|_| !self.is_proc_macro(id)) .filter(|_| !self.is_proc_macro(id))
.map_or(None, |v| Some(v.decode((self, tcx)))) .map_or(Ok(None), |v| Ok(Some(v.decode((self, tcx)))))
} }
fn get_unused_generic_params(&self, id: DefIndex) -> FiniteBitSet<u32> { fn get_unused_generic_params(&self, id: DefIndex) -> FiniteBitSet<u32> {

View file

@ -1117,7 +1117,7 @@ impl EncodeContext<'a, 'tcx> {
} }
let abstract_const = self.tcx.mir_abstract_const(def_id); let abstract_const = self.tcx.mir_abstract_const(def_id);
if let Some(abstract_const) = abstract_const { if let Ok(Some(abstract_const)) = abstract_const {
record!(self.tables.mir_abstract_consts[def_id.to_def_id()] <- abstract_const); record!(self.tables.mir_abstract_consts[def_id.to_def_id()] <- abstract_const);
} }
} }

View file

@ -23,6 +23,12 @@ pub enum ErrorHandled {
TooGeneric, TooGeneric,
} }
impl From<ErrorReported> for ErrorHandled {
fn from(err: ErrorReported) -> ErrorHandled {
ErrorHandled::Reported(err)
}
}
CloneTypeFoldableAndLiftImpls! { CloneTypeFoldableAndLiftImpls! {
ErrorHandled, ErrorHandled,
} }

View file

@ -247,7 +247,7 @@ rustc_queries! {
/// Try to build an abstract representation of the given constant. /// Try to build an abstract representation of the given constant.
query mir_abstract_const( query mir_abstract_const(
key: DefId key: DefId
) -> Option<&'tcx [mir::abstract_const::Node<'tcx>]> { ) -> Result<Option<&'tcx [mir::abstract_const::Node<'tcx>]>, ErrorReported> {
desc { desc {
|tcx| "building an abstract representation for {}", tcx.def_path_str(key), |tcx| "building an abstract representation for {}", tcx.def_path_str(key),
} }
@ -255,7 +255,7 @@ rustc_queries! {
/// Try to build an abstract representation of the given constant. /// Try to build an abstract representation of the given constant.
query mir_abstract_const_of_const_arg( query mir_abstract_const_of_const_arg(
key: (LocalDefId, DefId) key: (LocalDefId, DefId)
) -> Option<&'tcx [mir::abstract_const::Node<'tcx>]> { ) -> Result<Option<&'tcx [mir::abstract_const::Node<'tcx>]>, ErrorReported> {
desc { desc {
|tcx| |tcx|
"building an abstract representation for the const argument {}", "building an abstract representation for the const argument {}",

View file

@ -15,6 +15,7 @@
#![feature(box_patterns)] #![feature(box_patterns)]
#![feature(drain_filter)] #![feature(drain_filter)]
#![feature(in_band_lifetimes)] #![feature(in_band_lifetimes)]
#![feature(never_type)]
#![feature(crate_visibility_modifier)] #![feature(crate_visibility_modifier)]
#![feature(or_patterns)] #![feature(or_patterns)]
#![recursion_limit = "512"] // For rustdoc #![recursion_limit = "512"] // For rustdoc

View file

@ -8,6 +8,7 @@
//! In this case we try to build an abstract representation of this constant using //! In this case we try to build an abstract representation of this constant using
//! `mir_abstract_const` which can then be checked for structural equality with other //! `mir_abstract_const` which can then be checked for structural equality with other
//! generic constants mentioned in the `caller_bounds` of the current environment. //! generic constants mentioned in the `caller_bounds` of the current environment.
use rustc_errors::ErrorReported;
use rustc_hir::def::DefKind; use rustc_hir::def::DefKind;
use rustc_index::bit_set::BitSet; use rustc_index::bit_set::BitSet;
use rustc_index::vec::IndexVec; use rustc_index::vec::IndexVec;
@ -31,7 +32,7 @@ pub fn is_const_evaluatable<'cx, 'tcx>(
) -> Result<(), ErrorHandled> { ) -> Result<(), ErrorHandled> {
debug!("is_const_evaluatable({:?}, {:?})", def, substs); debug!("is_const_evaluatable({:?}, {:?})", def, substs);
if infcx.tcx.features().const_evaluatable_checked { if infcx.tcx.features().const_evaluatable_checked {
if let Some(ct) = AbstractConst::new(infcx.tcx, def, substs) { if let Some(ct) = AbstractConst::new(infcx.tcx, def, substs)? {
for pred in param_env.caller_bounds() { for pred in param_env.caller_bounds() {
match pred.skip_binders() { match pred.skip_binders() {
ty::PredicateAtom::ConstEvaluatable(b_def, b_substs) => { ty::PredicateAtom::ConstEvaluatable(b_def, b_substs) => {
@ -39,7 +40,7 @@ pub fn is_const_evaluatable<'cx, 'tcx>(
if b_def == def && b_substs == substs { if b_def == def && b_substs == substs {
debug!("is_const_evaluatable: caller_bound ~~> ok"); debug!("is_const_evaluatable: caller_bound ~~> ok");
return Ok(()); return Ok(());
} else if AbstractConst::new(infcx.tcx, b_def, b_substs) } else if AbstractConst::new(infcx.tcx, b_def, b_substs)?
.map_or(false, |b_ct| try_unify(infcx.tcx, ct, b_ct)) .map_or(false, |b_ct| try_unify(infcx.tcx, ct, b_ct))
{ {
debug!("is_const_evaluatable: abstract_const ~~> ok"); debug!("is_const_evaluatable: abstract_const ~~> ok");
@ -114,7 +115,7 @@ impl AbstractConst<'tcx> {
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
def: ty::WithOptConstParam<DefId>, def: ty::WithOptConstParam<DefId>,
substs: SubstsRef<'tcx>, substs: SubstsRef<'tcx>,
) -> Option<AbstractConst<'tcx>> { ) -> Result<Option<AbstractConst<'tcx>>, ErrorReported> {
let inner = match (def.did.as_local(), def.const_param_did) { let inner = match (def.did.as_local(), def.const_param_did) {
(Some(did), Some(param_did)) => { (Some(did), Some(param_did)) => {
tcx.mir_abstract_const_of_const_arg((did, param_did))? tcx.mir_abstract_const_of_const_arg((did, param_did))?
@ -122,7 +123,7 @@ impl AbstractConst<'tcx> {
_ => tcx.mir_abstract_const(def.did)?, _ => tcx.mir_abstract_const(def.did)?,
}; };
Some(AbstractConst { inner, substs }) Ok(inner.map(|inner| AbstractConst { inner, substs }))
} }
#[inline] #[inline]
@ -148,53 +149,83 @@ struct AbstractConstBuilder<'a, 'tcx> {
} }
impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
fn new(tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>) -> Option<AbstractConstBuilder<'a, 'tcx>> { fn error(&mut self, span: Option<Span>, msg: &str) -> Result<!, ErrorReported> {
// We only allow consts without control flow, so self.tcx
// we check for cycles here which simplifies the .sess
// rest of this implementation. .struct_span_err(self.body.span, "overly complex generic constant")
if body.is_cfg_cyclic() { .span_label(span.unwrap_or(self.body.span), msg)
return None; .help("consider moving this anonymous constant into a `const` function")
.emit();
Err(ErrorReported)
} }
// We don't have to look at concrete constants, as we fn new(
// can just evaluate them. tcx: TyCtxt<'tcx>,
if !body.is_polymorphic { body: &'a mir::Body<'tcx>,
return None; ) -> Result<Option<AbstractConstBuilder<'a, 'tcx>>, ErrorReported> {
} let mut builder = AbstractConstBuilder {
Some(AbstractConstBuilder {
tcx, tcx,
body, body,
nodes: IndexVec::new(), nodes: IndexVec::new(),
locals: IndexVec::from_elem(NodeId::MAX, &body.local_decls), locals: IndexVec::from_elem(NodeId::MAX, &body.local_decls),
checked_op_locals: BitSet::new_empty(body.local_decls.len()), checked_op_locals: BitSet::new_empty(body.local_decls.len()),
}) };
// We don't have to look at concrete constants, as we
// can just evaluate them.
if !body.is_polymorphic {
return Ok(None);
} }
fn operand_to_node(&mut self, op: &mir::Operand<'tcx>) -> Option<NodeId> {
debug!("operand_to_node: op={:?}", op); // We only allow consts without control flow, so
// we check for cycles here which simplifies the
// rest of this implementation.
if body.is_cfg_cyclic() {
builder.error(None, "cyclic anonymous constants are forbidden")?;
}
Ok(Some(builder))
}
fn place_to_local(
&mut self,
span: Span,
p: &mir::Place<'tcx>,
) -> Result<mir::Local, ErrorReported> {
const ZERO_FIELD: mir::Field = mir::Field::from_usize(0); const ZERO_FIELD: mir::Field = mir::Field::from_usize(0);
match op {
mir::Operand::Copy(p) | mir::Operand::Move(p) => {
// Do not allow any projections. // Do not allow any projections.
// //
// One exception are field accesses on the result of checked operations, // One exception are field accesses on the result of checked operations,
// which are required to support things like `1 + 2`. // which are required to support things like `1 + 2`.
if let Some(p) = p.as_local() { if let Some(p) = p.as_local() {
debug_assert!(!self.checked_op_locals.contains(p)); debug_assert!(!self.checked_op_locals.contains(p));
Some(self.locals[p]) Ok(p)
} else if let &[mir::ProjectionElem::Field(ZERO_FIELD, _)] = p.projection.as_ref() { } else if let &[mir::ProjectionElem::Field(ZERO_FIELD, _)] = p.projection.as_ref() {
// Only allow field accesses if the given local // Only allow field accesses if the given local
// contains the result of a checked operation. // contains the result of a checked operation.
if self.checked_op_locals.contains(p.local) { if self.checked_op_locals.contains(p.local) {
Some(self.locals[p.local]) Ok(p.local)
} else { } else {
None self.error(Some(span), "unsupported projection")?;
} }
} else { } else {
None self.error(Some(span), "unsupported projection")?;
} }
} }
mir::Operand::Constant(ct) => Some(self.nodes.push(Node::Leaf(ct.literal))),
fn operand_to_node(
&mut self,
span: Span,
op: &mir::Operand<'tcx>,
) -> Result<NodeId, ErrorReported> {
debug!("operand_to_node: op={:?}", op);
match op {
mir::Operand::Copy(p) | mir::Operand::Move(p) => {
let local = self.place_to_local(span, p)?;
Ok(self.locals[local])
}
mir::Operand::Constant(ct) => Ok(self.nodes.push(Node::Leaf(ct.literal))),
} }
} }
@ -217,44 +248,45 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
} }
} }
fn build_statement(&mut self, stmt: &mir::Statement<'tcx>) -> Option<()> { fn build_statement(&mut self, stmt: &mir::Statement<'tcx>) -> Result<(), ErrorReported> {
debug!("AbstractConstBuilder: stmt={:?}", stmt); debug!("AbstractConstBuilder: stmt={:?}", stmt);
match stmt.kind { match stmt.kind {
StatementKind::Assign(box (ref place, ref rvalue)) => { StatementKind::Assign(box (ref place, ref rvalue)) => {
let local = place.as_local()?; let local = self.place_to_local(stmt.source_info.span, place)?;
match *rvalue { match *rvalue {
Rvalue::Use(ref operand) => { Rvalue::Use(ref operand) => {
self.locals[local] = self.operand_to_node(operand)?; self.locals[local] =
Some(()) self.operand_to_node(stmt.source_info.span, operand)?;
Ok(())
} }
Rvalue::BinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => { Rvalue::BinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => {
let lhs = self.operand_to_node(lhs)?; let lhs = self.operand_to_node(stmt.source_info.span, lhs)?;
let rhs = self.operand_to_node(rhs)?; let rhs = self.operand_to_node(stmt.source_info.span, rhs)?;
self.locals[local] = self.nodes.push(Node::Binop(op, lhs, rhs)); self.locals[local] = self.nodes.push(Node::Binop(op, lhs, rhs));
if op.is_checkable() { if op.is_checkable() {
bug!("unexpected unchecked checkable binary operation"); bug!("unexpected unchecked checkable binary operation");
} else { } else {
Some(()) Ok(())
} }
} }
Rvalue::CheckedBinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => { Rvalue::CheckedBinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => {
let lhs = self.operand_to_node(lhs)?; let lhs = self.operand_to_node(stmt.source_info.span, lhs)?;
let rhs = self.operand_to_node(rhs)?; let rhs = self.operand_to_node(stmt.source_info.span, rhs)?;
self.locals[local] = self.nodes.push(Node::Binop(op, lhs, rhs)); self.locals[local] = self.nodes.push(Node::Binop(op, lhs, rhs));
self.checked_op_locals.insert(local); self.checked_op_locals.insert(local);
Some(()) Ok(())
} }
Rvalue::UnaryOp(op, ref operand) if Self::check_unop(op) => { Rvalue::UnaryOp(op, ref operand) if Self::check_unop(op) => {
let operand = self.operand_to_node(operand)?; let operand = self.operand_to_node(stmt.source_info.span, operand)?;
self.locals[local] = self.nodes.push(Node::UnaryOp(op, operand)); self.locals[local] = self.nodes.push(Node::UnaryOp(op, operand));
Some(()) Ok(())
} }
_ => None, _ => self.error(Some(stmt.source_info.span), "unsupported rvalue")?,
} }
} }
// These are not actually relevant for us here, so we can ignore them. // These are not actually relevant for us here, so we can ignore them.
StatementKind::StorageLive(_) | StatementKind::StorageDead(_) => Some(()), StatementKind::StorageLive(_) | StatementKind::StorageDead(_) => Ok(()),
_ => None, _ => self.error(Some(stmt.source_info.span), "unsupported statement")?,
} }
} }
@ -266,11 +298,11 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
fn build_terminator( fn build_terminator(
&mut self, &mut self,
terminator: &mir::Terminator<'tcx>, terminator: &mir::Terminator<'tcx>,
) -> Option<Option<mir::BasicBlock>> { ) -> Result<Option<mir::BasicBlock>, ErrorReported> {
debug!("AbstractConstBuilder: terminator={:?}", terminator); debug!("AbstractConstBuilder: terminator={:?}", terminator);
match terminator.kind { match terminator.kind {
TerminatorKind::Goto { target } => Some(Some(target)), TerminatorKind::Goto { target } => Ok(Some(target)),
TerminatorKind::Return => Some(None), TerminatorKind::Return => Ok(None),
TerminatorKind::Call { TerminatorKind::Call {
ref func, ref func,
ref args, ref args,
@ -288,17 +320,17 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
// //
// This is currently fairly irrelevant as it requires `const Trait`s. // This is currently fairly irrelevant as it requires `const Trait`s.
from_hir_call: true, from_hir_call: true,
fn_span: _, fn_span,
} => { } => {
let local = place.as_local()?; let local = self.place_to_local(fn_span, place)?;
let func = self.operand_to_node(func)?; let func = self.operand_to_node(fn_span, func)?;
let args = self.tcx.arena.alloc_from_iter( let args = self.tcx.arena.alloc_from_iter(
args.iter() args.iter()
.map(|arg| self.operand_to_node(arg)) .map(|arg| self.operand_to_node(terminator.source_info.span, arg))
.collect::<Option<Vec<NodeId>>>()?, .collect::<Result<Vec<NodeId>, _>>()?,
); );
self.locals[local] = self.nodes.push(Node::FunctionCall(func, args)); self.locals[local] = self.nodes.push(Node::FunctionCall(func, args));
Some(Some(target)) Ok(Some(target))
} }
// We only allow asserts for checked operations. // We only allow asserts for checked operations.
// //
@ -315,19 +347,19 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
if let &[mir::ProjectionElem::Field(ONE_FIELD, _)] = p.projection.as_ref() { if let &[mir::ProjectionElem::Field(ONE_FIELD, _)] = p.projection.as_ref() {
// Only allow asserts checking the result of a checked operation. // Only allow asserts checking the result of a checked operation.
if self.checked_op_locals.contains(p.local) { if self.checked_op_locals.contains(p.local) {
return Some(Some(target)); return Ok(Some(target));
} }
} }
None self.error(Some(terminator.source_info.span), "unsupported assertion")?;
} }
_ => None, _ => self.error(Some(terminator.source_info.span), "unsupported terminator")?,
} }
} }
/// Builds the abstract const by walking the mir from start to finish /// Builds the abstract const by walking the mir from start to finish
/// and bailing out when encountering an unsupported operation. /// and bailing out when encountering an unsupported operation.
fn build(mut self) -> Option<&'tcx [Node<'tcx>]> { fn build(mut self) -> Result<&'tcx [Node<'tcx>], ErrorReported> {
let mut block = &self.body.basic_blocks()[mir::START_BLOCK]; let mut block = &self.body.basic_blocks()[mir::START_BLOCK];
// We checked for a cyclic cfg above, so this should terminate. // We checked for a cyclic cfg above, so this should terminate.
loop { loop {
@ -339,7 +371,7 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
if let Some(next) = self.build_terminator(block.terminator())? { if let Some(next) = self.build_terminator(block.terminator())? {
block = &self.body.basic_blocks()[next]; block = &self.body.basic_blocks()[next];
} else { } else {
return Some(self.tcx.arena.alloc_from_iter(self.nodes)); return Ok(self.tcx.arena.alloc_from_iter(self.nodes));
} }
} }
} }
@ -349,7 +381,7 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
pub(super) fn mir_abstract_const<'tcx>( pub(super) fn mir_abstract_const<'tcx>(
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
def: ty::WithOptConstParam<LocalDefId>, def: ty::WithOptConstParam<LocalDefId>,
) -> Option<&'tcx [Node<'tcx>]> { ) -> Result<Option<&'tcx [mir::abstract_const::Node<'tcx>]>, ErrorReported> {
if tcx.features().const_evaluatable_checked { if tcx.features().const_evaluatable_checked {
match tcx.def_kind(def.did) { match tcx.def_kind(def.did) {
// FIXME(const_evaluatable_checked): We currently only do this for anonymous constants, // FIXME(const_evaluatable_checked): We currently only do this for anonymous constants,
@ -358,12 +390,12 @@ pub(super) fn mir_abstract_const<'tcx>(
// //
// Right now we do neither of that and simply always fail to unify them. // Right now we do neither of that and simply always fail to unify them.
DefKind::AnonConst => (), DefKind::AnonConst => (),
_ => return None, _ => return Ok(None),
} }
let body = tcx.mir_const(def).borrow(); let body = tcx.mir_const(def).borrow();
AbstractConstBuilder::new(tcx, &body)?.build() AbstractConstBuilder::new(tcx, &body)?.map(AbstractConstBuilder::build).transpose()
} else { } else {
None Ok(None)
} }
} }
@ -374,13 +406,19 @@ pub(super) fn try_unify_abstract_consts<'tcx>(
(ty::WithOptConstParam<DefId>, SubstsRef<'tcx>), (ty::WithOptConstParam<DefId>, SubstsRef<'tcx>),
), ),
) -> bool { ) -> bool {
if let Some(a) = AbstractConst::new(tcx, a, a_substs) { (|| {
if let Some(b) = AbstractConst::new(tcx, b, b_substs) { if let Some(a) = AbstractConst::new(tcx, a, a_substs)? {
return try_unify(tcx, a, b); if let Some(b) = AbstractConst::new(tcx, b, b_substs)? {
return Ok(try_unify(tcx, a, b));
} }
} }
false Ok(false)
})()
.unwrap_or_else(|ErrorReported| true)
// FIXME(const_evaluatable_checked): We should instead have this
// method return the resulting `ty::Const` and return `ConstKind::Error`
// on `ErrorReported`.
} }
/// Tries to unify two abstract constants using structural equality. /// Tries to unify two abstract constants using structural equality.

View file

@ -0,0 +1,6 @@
#![feature(const_generics, const_evaluatable_checked)]
#![allow(incomplete_features)]
fn test<const N: usize>() -> [u8; N + (|| 42)()] {}
//~^ ERROR overly complex generic constant
fn main() {}

View file

@ -0,0 +1,12 @@
error: overly complex generic constant
--> $DIR/closures.rs:3:35
|
LL | fn test<const N: usize>() -> [u8; N + (|| 42)()] {}
| ^^^^-------^^
| |
| unsupported rvalue
|
= help: consider moving this anonymous constant into a `const` function
error: aborting due to previous error

View file

@ -4,8 +4,8 @@
// We do not yet want to support let-bindings in abstract consts, // We do not yet want to support let-bindings in abstract consts,
// so this test should keep failing for now. // so this test should keep failing for now.
fn test<const N: usize>() -> [u8; { let x = N; N + 1 }] where [u8; { let x = N; N + 1 }]: Default { fn test<const N: usize>() -> [u8; { let x = N; N + 1 }] where [u8; { let x = N; N + 1 }]: Default {
//~^ ERROR constant expression depends //~^ ERROR overly complex generic constant
//~| ERROR constant expression depends //~| ERROR overly complex generic constant
Default::default() Default::default()
} }

View file

@ -1,18 +1,22 @@
error: constant expression depends on a generic parameter error: overly complex generic constant
--> $DIR/let-bindings.rs:6:91 --> $DIR/let-bindings.rs:6:68
| |
LL | fn test<const N: usize>() -> [u8; { let x = N; N + 1 }] where [u8; { let x = N; N + 1 }]: Default { LL | fn test<const N: usize>() -> [u8; { let x = N; N + 1 }] where [u8; { let x = N; N + 1 }]: Default {
| ^^^^^^^ required by this bound in `test::{{constant}}#0` | ^^^^^^-^^^^^^^^^^^^^
| |
| unsupported statement
| |
= note: this may fail depending on what value the parameter takes = help: consider moving this anonymous constant into a `const` function
error: constant expression depends on a generic parameter error: overly complex generic constant
--> $DIR/let-bindings.rs:6:30 --> $DIR/let-bindings.rs:6:35
| |
LL | fn test<const N: usize>() -> [u8; { let x = N; N + 1 }] where [u8; { let x = N; N + 1 }]: Default { LL | fn test<const N: usize>() -> [u8; { let x = N; N + 1 }] where [u8; { let x = N; N + 1 }]: Default {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^-^^^^^^^^^^^^^
| |
| unsupported statement
| |
= note: this may fail depending on what value the parameter takes = help: consider moving this anonymous constant into a `const` function
error: aborting due to 2 previous errors error: aborting due to 2 previous errors