From d327fa112b8ca56e8c310a8ec9bf458909beacfe Mon Sep 17 00:00:00 2001 From: Bastian Kauschke Date: Thu, 10 Sep 2020 09:06:30 +0200 Subject: [PATCH 01/12] initial working state --- .../rustc_middle/src/mir/abstract_const.rs | 15 + compiler/rustc_middle/src/mir/mod.rs | 1 + compiler/rustc_middle/src/query/mod.rs | 19 ++ compiler/rustc_mir/src/transform/mod.rs | 6 +- compiler/rustc_trait_selection/src/lib.rs | 1 + .../src/traits/const_evaluatable.rs | 256 +++++++++++++++++- .../rustc_trait_selection/src/traits/mod.rs | 14 + .../const_evaluatable_checked/less_than.rs | 14 + 8 files changed, 313 insertions(+), 13 deletions(-) create mode 100644 compiler/rustc_middle/src/mir/abstract_const.rs create mode 100644 src/test/ui/const-generics/const_evaluatable_checked/less_than.rs diff --git a/compiler/rustc_middle/src/mir/abstract_const.rs b/compiler/rustc_middle/src/mir/abstract_const.rs new file mode 100644 index 0000000000000..48fe8dafd5343 --- /dev/null +++ b/compiler/rustc_middle/src/mir/abstract_const.rs @@ -0,0 +1,15 @@ +//! A subset of a mir body used for const evaluatability checking. +use crate::mir; +use crate::ty; + +/// An index into an `AbstractConst`. +pub type NodeId = usize; + +/// A node of an `AbstractConst`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, HashStable)] +pub enum Node<'tcx> { + Leaf(&'tcx ty::Const<'tcx>), + Binop(mir::BinOp, NodeId, NodeId), + UnaryOp(mir::UnOp, NodeId), + FunctionCall(NodeId, &'tcx [NodeId]), +} diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index 29daf7e9309aa..be61b67680750 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -40,6 +40,7 @@ use std::{iter, mem, option}; use self::predecessors::{PredecessorCache, Predecessors}; pub use self::query::*; +pub mod abstract_const; pub mod coverage; pub mod interpret; pub mod mono; diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 15e3110bc851e..41b8bb60ef5ea 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -244,6 +244,25 @@ rustc_queries! { no_hash } + /// Try to build an abstract representation of the given constant. + query mir_abstract_const( + key: DefId + ) -> Option<&'tcx [mir::abstract_const::Node<'tcx>]> { + desc { + |tcx| "building an abstract representation for {}", tcx.def_path_str(key), + } + } + /// Try to build an abstract representation of the given constant. + query mir_abstract_const_of_const_arg( + key: (LocalDefId, DefId) + ) -> Option<&'tcx [mir::abstract_const::Node<'tcx>]> { + desc { + |tcx| + "building an abstract representation for the const argument {}", + tcx.def_path_str(key.0.to_def_id()), + } + } + query mir_drops_elaborated_and_const_checked( key: ty::WithOptConstParam ) -> &'tcx Steal> { diff --git a/compiler/rustc_mir/src/transform/mod.rs b/compiler/rustc_mir/src/transform/mod.rs index 8025b7c02043d..226282fe4263c 100644 --- a/compiler/rustc_mir/src/transform/mod.rs +++ b/compiler/rustc_mir/src/transform/mod.rs @@ -329,7 +329,11 @@ fn mir_promoted( // this point, before we steal the mir-const result. // Also this means promotion can rely on all const checks having been done. let _ = tcx.mir_const_qualif_opt_const_arg(def); - + let _ = if let Some(param_did) = def.const_param_did { + tcx.mir_abstract_const_of_const_arg((def.did, param_did)) + } else { + tcx.mir_abstract_const(def.did.to_def_id()) + }; let mut body = tcx.mir_const(def).steal(); let mut required_consts = Vec::new(); diff --git a/compiler/rustc_trait_selection/src/lib.rs b/compiler/rustc_trait_selection/src/lib.rs index b5882df47294e..da1996b92a60b 100644 --- a/compiler/rustc_trait_selection/src/lib.rs +++ b/compiler/rustc_trait_selection/src/lib.rs @@ -12,6 +12,7 @@ #![doc(html_root_url = "https://doc.rust-lang.org/nightly/")] #![feature(bool_to_option)] +#![feature(box_patterns)] #![feature(drain_filter)] #![feature(in_band_lifetimes)] #![feature(crate_visibility_modifier)] diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs index fdb87c085b54e..9d74de44d171c 100644 --- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs +++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs @@ -1,10 +1,17 @@ +#![allow(warnings)] use rustc_hir::def::DefKind; +use rustc_index::bit_set::BitSet; +use rustc_index::vec::IndexVec; use rustc_infer::infer::InferCtxt; +use rustc_middle::mir::abstract_const::{Node, NodeId}; use rustc_middle::mir::interpret::ErrorHandled; +use rustc_middle::mir::visit::Visitor; +use rustc_middle::mir::{self, Rvalue, StatementKind, TerminatorKind}; +use rustc_middle::ty::subst::Subst; use rustc_middle::ty::subst::SubstsRef; -use rustc_middle::ty::{self, TypeFoldable}; +use rustc_middle::ty::{self, TyCtxt, TypeFoldable}; use rustc_session::lint; -use rustc_span::def_id::DefId; +use rustc_span::def_id::{DefId, LocalDefId}; use rustc_span::Span; pub fn is_const_evaluatable<'cx, 'tcx>( @@ -16,18 +23,23 @@ pub fn is_const_evaluatable<'cx, 'tcx>( ) -> Result<(), ErrorHandled> { debug!("is_const_evaluatable({:?}, {:?})", def, substs); if infcx.tcx.features().const_evaluatable_checked { - // FIXME(const_evaluatable_checked): Actually look into generic constants to - // implement const equality. - for pred in param_env.caller_bounds() { - match pred.skip_binders() { - ty::PredicateAtom::ConstEvaluatable(b_def, b_substs) => { - debug!("is_const_evaluatable: caller_bound={:?}, {:?}", b_def, b_substs); - if b_def == def && b_substs == substs { - debug!("is_const_evaluatable: caller_bound ~~> ok"); - return Ok(()); + if let Some(ct) = AbstractConst::new(infcx.tcx, def, substs) { + for pred in param_env.caller_bounds() { + match pred.skip_binders() { + ty::PredicateAtom::ConstEvaluatable(b_def, b_substs) => { + debug!("is_const_evaluatable: caller_bound={:?}, {:?}", b_def, b_substs); + if b_def == def && b_substs == substs { + debug!("is_const_evaluatable: caller_bound ~~> ok"); + return Ok(()); + } else if AbstractConst::new(infcx.tcx, b_def, b_substs) + .map_or(false, |b_ct| try_unify(infcx.tcx, ct, b_ct)) + { + debug!("is_const_evaluatable: abstract_const ~~> ok"); + return Ok(()); + } } + _ => {} // don't care } - _ => {} // don't care } } } @@ -76,3 +88,223 @@ pub fn is_const_evaluatable<'cx, 'tcx>( debug!(?concrete, "is_const_evaluatable"); concrete.map(drop) } + +/// A tree representing an anonymous constant. +/// +/// This is only able to represent a subset of `MIR`, +/// and should not leak any information about desugarings. +#[derive(Clone, Copy)] +pub struct AbstractConst<'tcx> { + pub inner: &'tcx [Node<'tcx>], + pub substs: SubstsRef<'tcx>, +} + +impl AbstractConst<'tcx> { + pub fn new( + tcx: TyCtxt<'tcx>, + def: ty::WithOptConstParam, + substs: SubstsRef<'tcx>, + ) -> Option> { + let inner = match (def.did.as_local(), def.const_param_did) { + (Some(did), Some(param_did)) => { + tcx.mir_abstract_const_of_const_arg((did, param_did))? + } + _ => tcx.mir_abstract_const(def.did)?, + }; + + Some(AbstractConst { inner, substs }) + } + + #[inline] + pub fn subtree(self, node: NodeId) -> AbstractConst<'tcx> { + AbstractConst { inner: &self.inner[..=node], substs: self.substs } + } + + #[inline] + pub fn root(self) -> Node<'tcx> { + self.inner.last().copied().unwrap() + } +} + +struct AbstractConstBuilder<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + body: &'a mir::Body<'tcx>, + nodes: Vec>, + locals: IndexVec, + checked_op_locals: BitSet, +} + +impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { + fn new(tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>) -> Option> { + if body.is_cfg_cyclic() { + return None; + } + + Some(AbstractConstBuilder { + tcx, + body, + nodes: vec![], + locals: IndexVec::from_elem(NodeId::MAX, &body.local_decls), + checked_op_locals: BitSet::new_empty(body.local_decls.len()), + }) + } + + fn add_node(&mut self, n: Node<'tcx>) -> NodeId { + let len = self.nodes.len(); + self.nodes.push(n); + len + } + + fn operand_to_node(&mut self, op: &mir::Operand<'tcx>) -> Option { + debug!("operand_to_node: op={:?}", op); + const ZERO_FIELD: mir::Field = mir::Field::from_usize(0); + match op { + mir::Operand::Copy(p) | mir::Operand::Move(p) => { + if let Some(p) = p.as_local() { + debug_assert!(!self.checked_op_locals.contains(p)); + Some(self.locals[p]) + } else if let &[mir::ProjectionElem::Field(ZERO_FIELD, _)] = p.projection.as_ref() { + // Only allow field accesses on the result of checked operations. + if self.checked_op_locals.contains(p.local) { + Some(self.locals[p.local]) + } else { + None + } + } else { + None + } + } + mir::Operand::Constant(ct) => Some(self.add_node(Node::Leaf(ct.literal))), + } + } + + fn check_binop(op: mir::BinOp) -> bool { + use mir::BinOp::*; + match op { + Add | Sub | Mul | Div | Rem | BitXor | BitAnd | BitOr | Shl | Shr | Eq | Lt | Le + | Ne | Ge | Gt => true, + Offset => false, + } + } + + fn build(mut self) -> Option<&'tcx [Node<'tcx>]> { + let mut block = &self.body.basic_blocks()[mir::START_BLOCK]; + loop { + debug!("AbstractConstBuilder: block={:?}", block); + for stmt in block.statements.iter() { + debug!("AbstractConstBuilder: stmt={:?}", stmt); + match stmt.kind { + StatementKind::Assign(box (ref place, ref rvalue)) => { + let local = place.as_local()?; + match *rvalue { + Rvalue::Use(ref operand) => { + self.locals[local] = self.operand_to_node(operand)?; + } + Rvalue::BinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => { + let lhs = self.operand_to_node(lhs)?; + let rhs = self.operand_to_node(rhs)?; + self.locals[local] = self.add_node(Node::Binop(op, lhs, rhs)); + if op.is_checkable() { + bug!("unexpected unchecked checkable binary operation"); + } + } + Rvalue::CheckedBinaryOp(op, ref lhs, ref rhs) + if Self::check_binop(op) => + { + let lhs = self.operand_to_node(lhs)?; + let rhs = self.operand_to_node(rhs)?; + self.locals[local] = self.add_node(Node::Binop(op, lhs, rhs)); + self.checked_op_locals.insert(local); + } + _ => return None, + } + } + _ => return None, + } + } + + debug!("AbstractConstBuilder: terminator={:?}", block.terminator()); + match block.terminator().kind { + TerminatorKind::Goto { target } => { + block = &self.body.basic_blocks()[target]; + } + TerminatorKind::Return => { + warn!(?self.nodes); + return { Some(self.tcx.arena.alloc_from_iter(self.nodes)) }; + } + TerminatorKind::Assert { ref cond, expected: false, target, .. } => { + let p = match cond { + mir::Operand::Copy(p) | mir::Operand::Move(p) => p, + mir::Operand::Constant(_) => bug!("Unexpected assert"), + }; + + const ONE_FIELD: mir::Field = mir::Field::from_usize(1); + debug!("proj: {:?}", p.projection); + if let &[mir::ProjectionElem::Field(ONE_FIELD, _)] = p.projection.as_ref() { + // Only allow asserts checking the result of a checked operation. + if self.checked_op_locals.contains(p.local) { + block = &self.body.basic_blocks()[target]; + continue; + } + } + + return None; + } + _ => return None, + } + } + } +} + +/// Builds an abstract const, do not use this directly, but use `AbstractConst::new` instead. +pub(super) fn mir_abstract_const<'tcx>( + tcx: TyCtxt<'tcx>, + def: ty::WithOptConstParam, +) -> Option<&'tcx [Node<'tcx>]> { + if !tcx.features().const_evaluatable_checked { + None + } else { + let body = tcx.mir_const(def).borrow(); + AbstractConstBuilder::new(tcx, &body)?.build() + } +} + +pub fn try_unify<'tcx>(tcx: TyCtxt<'tcx>, a: AbstractConst<'tcx>, b: AbstractConst<'tcx>) -> bool { + match (a.root(), b.root()) { + (Node::Leaf(a_ct), Node::Leaf(b_ct)) => { + let a_ct = a_ct.subst(tcx, a.substs); + let b_ct = b_ct.subst(tcx, b.substs); + match (a_ct.val, b_ct.val) { + (ty::ConstKind::Param(a_param), ty::ConstKind::Param(b_param)) => { + a_param == b_param + } + (ty::ConstKind::Value(a_val), ty::ConstKind::Value(b_val)) => a_val == b_val, + // If we have `fn a() -> [u8; N + 1]` and `fn b() -> [u8; 1 + M]` + // we do not want to use `assert_eq!(a(), b())` to infer that `N` and `M` have to be `1`. This + // means that we can't do anything with inference variables here. + (ty::ConstKind::Infer(_), _) | (_, ty::ConstKind::Infer(_)) => false, + // FIXME(const_evaluatable_checked): We may want to either actually try + // to evaluate `a_ct` and `b_ct` if they are are fully concrete or something like + // this, for now we just return false here. + _ => false, + } + } + (Node::Binop(a_op, al, ar), Node::Binop(b_op, bl, br)) if a_op == b_op => { + try_unify(tcx, a.subtree(al), b.subtree(bl)) + && try_unify(tcx, a.subtree(ar), b.subtree(br)) + } + (Node::UnaryOp(a_op, av), Node::UnaryOp(b_op, bv)) if a_op == b_op => { + try_unify(tcx, a.subtree(av), b.subtree(bv)) + } + (Node::FunctionCall(a_f, a_args), Node::FunctionCall(b_f, b_args)) + if a_args.len() == b_args.len() => + { + try_unify(tcx, a.subtree(a_f), b.subtree(b_f)) + && a_args + .iter() + .zip(b_args) + .all(|(&an, &bn)| try_unify(tcx, a.subtree(an), b.subtree(bn))) + } + _ => false, + } +} diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs index b72e86a4cbde6..2f0b66ec8c941 100644 --- a/compiler/rustc_trait_selection/src/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/mod.rs @@ -552,6 +552,20 @@ pub fn provide(providers: &mut ty::query::Providers) { vtable_methods, type_implements_trait, subst_and_check_impossible_predicates, + mir_abstract_const: |tcx, def_id| { + let def_id = def_id.as_local()?; // We do not store failed AbstractConst's. + if let Some(def) = ty::WithOptConstParam::try_lookup(def_id, tcx) { + tcx.mir_abstract_const_of_const_arg(def) + } else { + const_evaluatable::mir_abstract_const(tcx, ty::WithOptConstParam::unknown(def_id)) + } + }, + mir_abstract_const_of_const_arg: |tcx, (did, param_did)| { + const_evaluatable::mir_abstract_const( + tcx, + ty::WithOptConstParam { did, const_param_did: Some(param_did) }, + ) + }, ..*providers }; } diff --git a/src/test/ui/const-generics/const_evaluatable_checked/less_than.rs b/src/test/ui/const-generics/const_evaluatable_checked/less_than.rs new file mode 100644 index 0000000000000..907ea255abb08 --- /dev/null +++ b/src/test/ui/const-generics/const_evaluatable_checked/less_than.rs @@ -0,0 +1,14 @@ +// run-pass +#![feature(const_generics, const_evaluatable_checked)] +#![allow(incomplete_features)] + +struct Foo; + +fn test() -> Foo<{ N > 10 }> where Foo<{ N > 10 }>: Sized { + Foo +} + +fn main() { + let _: Foo = test::<12>(); + let _: Foo = test::<9>(); +} From c3a772f55f2263cd2f2709f2bb187f59a8b4a673 Mon Sep 17 00:00:00 2001 From: Bastian Kauschke Date: Thu, 10 Sep 2020 18:48:18 +0200 Subject: [PATCH 02/12] use abstract consts when unifying ConstKind::Unevaluated --- compiler/rustc_middle/src/query/mod.rs | 10 +++++++++ compiler/rustc_middle/src/ty/query/keys.rs | 16 ++++++++++++++ compiler/rustc_middle/src/ty/relate.rs | 15 ++++++++++++- .../src/traits/const_evaluatable.rs | 22 ++++++++++++++++++- .../src/traits/fulfill.rs | 19 ++++++++++++++++ .../rustc_trait_selection/src/traits/mod.rs | 1 + .../simple.min.stderr | 16 ++++++++++---- .../const_evaluatable_checked/simple.rs | 7 +++--- 8 files changed, 96 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 41b8bb60ef5ea..44d906dada5f0 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -263,6 +263,16 @@ rustc_queries! { } } + query try_unify_abstract_consts(key: ( + (ty::WithOptConstParam, SubstsRef<'tcx>), + (ty::WithOptConstParam, SubstsRef<'tcx>) + )) -> bool { + desc { + |tcx| "trying to unify the generic constants {} and {}", + tcx.def_path_str(key.0.0.did), tcx.def_path_str(key.1.0.did) + } + } + query mir_drops_elaborated_and_const_checked( key: ty::WithOptConstParam ) -> &'tcx Steal> { diff --git a/compiler/rustc_middle/src/ty/query/keys.rs b/compiler/rustc_middle/src/ty/query/keys.rs index 3f7a20bba2b9a..a005990264cf1 100644 --- a/compiler/rustc_middle/src/ty/query/keys.rs +++ b/compiler/rustc_middle/src/ty/query/keys.rs @@ -193,6 +193,22 @@ impl<'tcx> Key for (DefId, SubstsRef<'tcx>) { } } +impl<'tcx> Key + for ( + (ty::WithOptConstParam, SubstsRef<'tcx>), + (ty::WithOptConstParam, SubstsRef<'tcx>), + ) +{ + type CacheSelector = DefaultCacheSelector; + + fn query_crate(&self) -> CrateNum { + (self.0).0.did.krate + } + fn default_span(&self, tcx: TyCtxt<'_>) -> Span { + (self.0).0.did.default_span(tcx) + } +} + impl<'tcx> Key for (LocalDefId, DefId, SubstsRef<'tcx>) { type CacheSelector = DefaultCacheSelector; diff --git a/compiler/rustc_middle/src/ty/relate.rs b/compiler/rustc_middle/src/ty/relate.rs index 7d3634a75b0a7..c4df0bba726cb 100644 --- a/compiler/rustc_middle/src/ty/relate.rs +++ b/compiler/rustc_middle/src/ty/relate.rs @@ -576,7 +576,20 @@ pub fn super_relate_consts>( new_val.map(ty::ConstKind::Value) } - // FIXME(const_generics): this is wrong, as it is a projection + ( + ty::ConstKind::Unevaluated(a_def, a_substs, None), + ty::ConstKind::Unevaluated(b_def, b_substs, None), + ) if tcx.features().const_evaluatable_checked => { + if tcx.try_unify_abstract_consts(((a_def, a_substs), (b_def, b_substs))) { + Ok(a.val) + } else { + Err(TypeError::ConstMismatch(expected_found(relation, a, b))) + } + } + + // While this is slightly incorrect, it shouldn't matter for `min_const_generics` + // and is the better alternative to waiting until `const_evaluatable_checked` can + // be stabilized. ( ty::ConstKind::Unevaluated(a_def, a_substs, a_promoted), ty::ConstKind::Unevaluated(b_def, b_substs, b_promoted), diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs index 9d74de44d171c..e14af1a27ef44 100644 --- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs +++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs @@ -269,7 +269,27 @@ pub(super) fn mir_abstract_const<'tcx>( } } -pub fn try_unify<'tcx>(tcx: TyCtxt<'tcx>, a: AbstractConst<'tcx>, b: AbstractConst<'tcx>) -> bool { +pub(super) fn try_unify_abstract_consts<'tcx>( + tcx: TyCtxt<'tcx>, + ((a, a_substs), (b, b_substs)): ( + (ty::WithOptConstParam, SubstsRef<'tcx>), + (ty::WithOptConstParam, SubstsRef<'tcx>), + ), +) -> bool { + if let Some(a) = AbstractConst::new(tcx, a, a_substs) { + if let Some(b) = AbstractConst::new(tcx, b, b_substs) { + return try_unify(tcx, a, b); + } + } + + false +} + +pub(super) fn try_unify<'tcx>( + tcx: TyCtxt<'tcx>, + a: AbstractConst<'tcx>, + b: AbstractConst<'tcx>, +) -> bool { match (a.root(), b.root()) { (Node::Leaf(a_ct), Node::Leaf(b_ct)) => { let a_ct = a_ct.subst(tcx, a.substs); diff --git a/compiler/rustc_trait_selection/src/traits/fulfill.rs b/compiler/rustc_trait_selection/src/traits/fulfill.rs index 1dd50d69a2195..5b4314598deb5 100644 --- a/compiler/rustc_trait_selection/src/traits/fulfill.rs +++ b/compiler/rustc_trait_selection/src/traits/fulfill.rs @@ -476,6 +476,25 @@ impl<'a, 'b, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'b, 'tcx> { ty::PredicateAtom::ConstEquate(c1, c2) => { debug!("equating consts: c1={:?} c2={:?}", c1, c2); + if self.selcx.tcx().features().const_evaluatable_checked { + // FIXME: we probably should only try to unify abstract constants + // if the constants depend on generic parameters. + // + // Let's just see where this breaks :shrug: + if let ( + ty::ConstKind::Unevaluated(a_def, a_substs, None), + ty::ConstKind::Unevaluated(b_def, b_substs, None), + ) = (c1.val, c2.val) + { + if self + .selcx + .tcx() + .try_unify_abstract_consts(((a_def, a_substs), (b_def, b_substs))) + { + return ProcessResult::Changed(vec![]); + } + } + } let stalled_on = &mut pending_obligation.stalled_on; diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs index 2f0b66ec8c941..098336453bc69 100644 --- a/compiler/rustc_trait_selection/src/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/mod.rs @@ -566,6 +566,7 @@ pub fn provide(providers: &mut ty::query::Providers) { ty::WithOptConstParam { did, const_param_did: Some(param_did) }, ) }, + try_unify_abstract_consts: const_evaluatable::try_unify_abstract_consts, ..*providers }; } diff --git a/src/test/ui/const-generics/const_evaluatable_checked/simple.min.stderr b/src/test/ui/const-generics/const_evaluatable_checked/simple.min.stderr index da8ccdaee4146..3cac604a7b33a 100644 --- a/src/test/ui/const-generics/const_evaluatable_checked/simple.min.stderr +++ b/src/test/ui/const-generics/const_evaluatable_checked/simple.min.stderr @@ -1,10 +1,18 @@ error: generic parameters must not be used inside of non trivial constant values - --> $DIR/simple.rs:8:33 + --> $DIR/simple.rs:8:53 | -LL | type Arr = [u8; N - 1]; - | ^ non-trivial anonymous constants must not depend on the parameter `N` +LL | fn test() -> [u8; N - 1] where [u8; N - 1]: Default { + | ^ non-trivial anonymous constants must not depend on the parameter `N` | = help: it is currently only allowed to use either `N` or `{ N }` as generic constants -error: aborting due to previous error +error: generic parameters must not be used inside of non trivial constant values + --> $DIR/simple.rs:8:35 + | +LL | fn test() -> [u8; N - 1] where [u8; N - 1]: Default { + | ^ non-trivial anonymous constants must not depend on the parameter `N` + | + = help: it is currently only allowed to use either `N` or `{ N }` as generic constants + +error: aborting due to 2 previous errors diff --git a/src/test/ui/const-generics/const_evaluatable_checked/simple.rs b/src/test/ui/const-generics/const_evaluatable_checked/simple.rs index 27dc6b103200d..dcf0071cb29b6 100644 --- a/src/test/ui/const-generics/const_evaluatable_checked/simple.rs +++ b/src/test/ui/const-generics/const_evaluatable_checked/simple.rs @@ -5,10 +5,9 @@ #![feature(const_evaluatable_checked)] #![allow(incomplete_features)] -type Arr = [u8; N - 1]; -//[min]~^ ERROR generic parameters must not be used inside of non trivial constant values - -fn test() -> Arr where Arr: Default { +fn test() -> [u8; N - 1] where [u8; N - 1]: Default { + //[min]~^ ERROR generic parameters + //[min]~| ERROR generic parameters Default::default() } From f24d532749674c41940120866937860c8d4abcc8 Mon Sep 17 00:00:00 2001 From: Bastian Kauschke Date: Fri, 11 Sep 2020 09:00:21 +0200 Subject: [PATCH 03/12] refactor AbstractConstBuilder --- .../src/traits/const_evaluatable.rs | 125 ++++++++++-------- 1 file changed, 67 insertions(+), 58 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs index e14af1a27ef44..337276fd811e7 100644 --- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs +++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs @@ -187,70 +187,79 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { } } - fn build(mut self) -> Option<&'tcx [Node<'tcx>]> { - let mut block = &self.body.basic_blocks()[mir::START_BLOCK]; - loop { - debug!("AbstractConstBuilder: block={:?}", block); - for stmt in block.statements.iter() { - debug!("AbstractConstBuilder: stmt={:?}", stmt); - match stmt.kind { - StatementKind::Assign(box (ref place, ref rvalue)) => { - let local = place.as_local()?; - match *rvalue { - Rvalue::Use(ref operand) => { - self.locals[local] = self.operand_to_node(operand)?; - } - Rvalue::BinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => { - let lhs = self.operand_to_node(lhs)?; - let rhs = self.operand_to_node(rhs)?; - self.locals[local] = self.add_node(Node::Binop(op, lhs, rhs)); - if op.is_checkable() { - bug!("unexpected unchecked checkable binary operation"); - } - } - Rvalue::CheckedBinaryOp(op, ref lhs, ref rhs) - if Self::check_binop(op) => - { - let lhs = self.operand_to_node(lhs)?; - let rhs = self.operand_to_node(rhs)?; - self.locals[local] = self.add_node(Node::Binop(op, lhs, rhs)); - self.checked_op_locals.insert(local); - } - _ => return None, + fn build_statement(&mut self, stmt: &mir::Statement<'tcx>) -> Option<()> { + debug!("AbstractConstBuilder: stmt={:?}", stmt); + match stmt.kind { + StatementKind::Assign(box (ref place, ref rvalue)) => { + let local = place.as_local()?; + match *rvalue { + Rvalue::Use(ref operand) => { + self.locals[local] = self.operand_to_node(operand)?; + } + Rvalue::BinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => { + let lhs = self.operand_to_node(lhs)?; + let rhs = self.operand_to_node(rhs)?; + self.locals[local] = self.add_node(Node::Binop(op, lhs, rhs)); + if op.is_checkable() { + bug!("unexpected unchecked checkable binary operation"); } } + Rvalue::CheckedBinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => { + let lhs = self.operand_to_node(lhs)?; + let rhs = self.operand_to_node(rhs)?; + self.locals[local] = self.add_node(Node::Binop(op, lhs, rhs)); + self.checked_op_locals.insert(local); + } _ => return None, } } + _ => return None, + } - debug!("AbstractConstBuilder: terminator={:?}", block.terminator()); - match block.terminator().kind { - TerminatorKind::Goto { target } => { - block = &self.body.basic_blocks()[target]; - } - TerminatorKind::Return => { - warn!(?self.nodes); - return { Some(self.tcx.arena.alloc_from_iter(self.nodes)) }; - } - TerminatorKind::Assert { ref cond, expected: false, target, .. } => { - let p = match cond { - mir::Operand::Copy(p) | mir::Operand::Move(p) => p, - mir::Operand::Constant(_) => bug!("Unexpected assert"), - }; + Some(()) + } - const ONE_FIELD: mir::Field = mir::Field::from_usize(1); - debug!("proj: {:?}", p.projection); - if let &[mir::ProjectionElem::Field(ONE_FIELD, _)] = p.projection.as_ref() { - // Only allow asserts checking the result of a checked operation. - if self.checked_op_locals.contains(p.local) { - block = &self.body.basic_blocks()[target]; - continue; - } - } + fn build_terminator( + &mut self, + terminator: &mir::Terminator<'tcx>, + ) -> Option> { + debug!("AbstractConstBuilder: terminator={:?}", terminator); + match terminator.kind { + TerminatorKind::Goto { target } => Some(Some(target)), + TerminatorKind::Return => Some(None), + TerminatorKind::Assert { ref cond, expected: false, target, .. } => { + let p = match cond { + mir::Operand::Copy(p) | mir::Operand::Move(p) => p, + mir::Operand::Constant(_) => bug!("Unexpected assert"), + }; - return None; + const ONE_FIELD: mir::Field = mir::Field::from_usize(1); + debug!("proj: {:?}", p.projection); + if let &[mir::ProjectionElem::Field(ONE_FIELD, _)] = p.projection.as_ref() { + // Only allow asserts checking the result of a checked operation. + if self.checked_op_locals.contains(p.local) { + return Some(Some(target)); + } } - _ => return None, + + None + } + _ => None, + } + } + + fn build(mut self) -> Option<&'tcx [Node<'tcx>]> { + let mut block = &self.body.basic_blocks()[mir::START_BLOCK]; + loop { + debug!("AbstractConstBuilder: block={:?}", block); + for stmt in block.statements.iter() { + self.build_statement(stmt)?; + } + + if let Some(next) = self.build_terminator(block.terminator())? { + block = &self.body.basic_blocks()[next]; + } else { + return Some(self.tcx.arena.alloc_from_iter(self.nodes)); } } } @@ -261,11 +270,11 @@ pub(super) fn mir_abstract_const<'tcx>( tcx: TyCtxt<'tcx>, def: ty::WithOptConstParam, ) -> Option<&'tcx [Node<'tcx>]> { - if !tcx.features().const_evaluatable_checked { - None - } else { + if tcx.features().const_evaluatable_checked { let body = tcx.mir_const(def).borrow(); AbstractConstBuilder::new(tcx, &body)?.build() + } else { + None } } From 5a277822a536eff72d562e75fb6046add63d4926 Mon Sep 17 00:00:00 2001 From: Bastian Kauschke Date: Fri, 11 Sep 2020 09:18:54 +0200 Subject: [PATCH 04/12] use newtype_index for abstract_const::NodeId --- .../rustc_middle/src/mir/abstract_const.rs | 9 +++++-- .../src/traits/const_evaluatable.rs | 24 ++++++++----------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_middle/src/mir/abstract_const.rs b/compiler/rustc_middle/src/mir/abstract_const.rs index 48fe8dafd5343..8d215c5a5215a 100644 --- a/compiler/rustc_middle/src/mir/abstract_const.rs +++ b/compiler/rustc_middle/src/mir/abstract_const.rs @@ -2,8 +2,13 @@ use crate::mir; use crate::ty; -/// An index into an `AbstractConst`. -pub type NodeId = usize; +rustc_index::newtype_index! { + /// An index into an `AbstractConst`. + pub struct NodeId { + derive [HashStable] + DEBUG_FORMAT = "n{}", + } +} /// A node of an `AbstractConst`. #[derive(Debug, Clone, Copy, PartialEq, Eq, HashStable)] diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs index 337276fd811e7..56886aae06669 100644 --- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs +++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs @@ -95,8 +95,10 @@ pub fn is_const_evaluatable<'cx, 'tcx>( /// and should not leak any information about desugarings. #[derive(Clone, Copy)] pub struct AbstractConst<'tcx> { - pub inner: &'tcx [Node<'tcx>], - pub substs: SubstsRef<'tcx>, + // FIXME: Consider adding something like `IndexSlice` + // and use this here. + inner: &'tcx [Node<'tcx>], + substs: SubstsRef<'tcx>, } impl AbstractConst<'tcx> { @@ -117,7 +119,7 @@ impl AbstractConst<'tcx> { #[inline] pub fn subtree(self, node: NodeId) -> AbstractConst<'tcx> { - AbstractConst { inner: &self.inner[..=node], substs: self.substs } + AbstractConst { inner: &self.inner[..=node.index()], substs: self.substs } } #[inline] @@ -129,7 +131,7 @@ impl AbstractConst<'tcx> { struct AbstractConstBuilder<'a, 'tcx> { tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, - nodes: Vec>, + nodes: IndexVec>, locals: IndexVec, checked_op_locals: BitSet, } @@ -143,18 +145,12 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { Some(AbstractConstBuilder { tcx, body, - nodes: vec![], + nodes: IndexVec::new(), locals: IndexVec::from_elem(NodeId::MAX, &body.local_decls), checked_op_locals: BitSet::new_empty(body.local_decls.len()), }) } - fn add_node(&mut self, n: Node<'tcx>) -> NodeId { - let len = self.nodes.len(); - self.nodes.push(n); - len - } - fn operand_to_node(&mut self, op: &mir::Operand<'tcx>) -> Option { debug!("operand_to_node: op={:?}", op); const ZERO_FIELD: mir::Field = mir::Field::from_usize(0); @@ -174,7 +170,7 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { None } } - mir::Operand::Constant(ct) => Some(self.add_node(Node::Leaf(ct.literal))), + mir::Operand::Constant(ct) => Some(self.nodes.push(Node::Leaf(ct.literal))), } } @@ -199,7 +195,7 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { Rvalue::BinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => { let lhs = self.operand_to_node(lhs)?; let rhs = self.operand_to_node(rhs)?; - self.locals[local] = self.add_node(Node::Binop(op, lhs, rhs)); + self.locals[local] = self.nodes.push(Node::Binop(op, lhs, rhs)); if op.is_checkable() { bug!("unexpected unchecked checkable binary operation"); } @@ -207,7 +203,7 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { Rvalue::CheckedBinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => { let lhs = self.operand_to_node(lhs)?; let rhs = self.operand_to_node(rhs)?; - self.locals[local] = self.add_node(Node::Binop(op, lhs, rhs)); + self.locals[local] = self.nodes.push(Node::Binop(op, lhs, rhs)); self.checked_op_locals.insert(local); } _ => return None, From d1294e0ce2ce78e4a634fbfa68cb2bc4d50afc6e Mon Sep 17 00:00:00 2001 From: Bastian Kauschke Date: Fri, 11 Sep 2020 10:00:06 +0200 Subject: [PATCH 05/12] allow unary operations and ignore StorageLive/Dead stmts --- .../src/traits/const_evaluatable.rs | 27 ++++++++++++++++--- .../const_evaluatable_checked/unop.rs | 14 ++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 src/test/ui/const-generics/const_evaluatable_checked/unop.rs diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs index 56886aae06669..f0e5151173276 100644 --- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs +++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs @@ -174,6 +174,7 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { } } + /// We do not allow all binary operations in abstract consts, so filter disallowed ones. fn check_binop(op: mir::BinOp) -> bool { use mir::BinOp::*; match op { @@ -183,6 +184,15 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { } } + /// While we currently allow all unary operations, we still want to explicitly guard against + /// future changes here. + fn check_unop(op: mir::UnOp) -> bool { + use mir::UnOp::*; + match op { + Not | Neg => true, + } + } + fn build_statement(&mut self, stmt: &mir::Statement<'tcx>) -> Option<()> { debug!("AbstractConstBuilder: stmt={:?}", stmt); match stmt.kind { @@ -191,6 +201,7 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { match *rvalue { Rvalue::Use(ref operand) => { self.locals[local] = self.operand_to_node(operand)?; + Some(()) } Rvalue::BinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => { let lhs = self.operand_to_node(lhs)?; @@ -198,6 +209,8 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { self.locals[local] = self.nodes.push(Node::Binop(op, lhs, rhs)); if op.is_checkable() { bug!("unexpected unchecked checkable binary operation"); + } else { + Some(()) } } Rvalue::CheckedBinaryOp(op, ref lhs, ref rhs) if Self::check_binop(op) => { @@ -205,14 +218,20 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { let rhs = self.operand_to_node(rhs)?; self.locals[local] = self.nodes.push(Node::Binop(op, lhs, rhs)); self.checked_op_locals.insert(local); + Some(()) } - _ => return None, + Rvalue::UnaryOp(op, ref operand) if Self::check_unop(op) => { + let operand = self.operand_to_node(operand)?; + self.locals[local] = self.nodes.push(Node::UnaryOp(op, operand)); + Some(()) + } + _ => None, } } - _ => return None, + // These are not actually relevant for us here, so we can ignore them. + StatementKind::StorageLive(_) | StatementKind::StorageDead(_) => Some(()), + _ => None, } - - Some(()) } fn build_terminator( diff --git a/src/test/ui/const-generics/const_evaluatable_checked/unop.rs b/src/test/ui/const-generics/const_evaluatable_checked/unop.rs new file mode 100644 index 0000000000000..8e0768b1c9595 --- /dev/null +++ b/src/test/ui/const-generics/const_evaluatable_checked/unop.rs @@ -0,0 +1,14 @@ +// run-pass +#![feature(const_generics, const_evaluatable_checked)] +#![allow(incomplete_features)] + +struct Foo; + +fn test() -> Foo<{ !(N > 10) }> where Foo<{ !(N > 10) }>: Sized { + Foo +} + +fn main() { + let _: Foo = test::<12>(); + let _: Foo = test::<9>(); +} From c7d16df1d81934ff33d9d421ac2dc34c893ad68f Mon Sep 17 00:00:00 2001 From: Bastian Kauschke Date: Fri, 11 Sep 2020 10:35:28 +0200 Subject: [PATCH 06/12] add function calls --- .../src/traits/const_evaluatable.rs | 18 +++++++++++ .../const_evaluatable_checked/fn_call.rs | 30 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 src/test/ui/const-generics/const_evaluatable_checked/fn_call.rs diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs index f0e5151173276..50cc2afb89c80 100644 --- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs +++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs @@ -242,6 +242,24 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { match terminator.kind { TerminatorKind::Goto { target } => Some(Some(target)), TerminatorKind::Return => Some(None), + TerminatorKind::Call { + ref func, + ref args, + destination: Some((ref place, target)), + cleanup: _, + from_hir_call: true, + fn_span: _, + } => { + let local = place.as_local()?; + let func = self.operand_to_node(func)?; + let args = self.tcx.arena.alloc_from_iter( + args.iter() + .map(|arg| self.operand_to_node(arg)) + .collect::>>()?, + ); + self.locals[local] = self.nodes.push(Node::FunctionCall(func, args)); + Some(Some(target)) + } TerminatorKind::Assert { ref cond, expected: false, target, .. } => { let p = match cond { mir::Operand::Copy(p) | mir::Operand::Move(p) => p, diff --git a/src/test/ui/const-generics/const_evaluatable_checked/fn_call.rs b/src/test/ui/const-generics/const_evaluatable_checked/fn_call.rs new file mode 100644 index 0000000000000..1b9ec0108b1e7 --- /dev/null +++ b/src/test/ui/const-generics/const_evaluatable_checked/fn_call.rs @@ -0,0 +1,30 @@ +// run-pass +#![feature(const_generics, const_evaluatable_checked)] +#![allow(incomplete_features)] + +const fn test_me(a: usize, b: usize) -> usize { + if a < b { + std::mem::size_of::() + } else { + std::usize::MAX + } +} + +fn test_simple() -> [u8; std::mem::size_of::()] +where + [u8; std::mem::size_of::()]: Sized, +{ + [0; std::mem::size_of::()] +} + +fn test_with_args() -> [u8; test_me::(N, N + 1) + N] +where + [u8; test_me::(N, N + 1) + N]: Sized, +{ + [0; test_me::(N, N + 1) + N] +} + +fn main() { + assert_eq!([0; 8], test_simple::()); + assert_eq!([0; 12], test_with_args::()); +} From 82ebbd7d6b6d3f0ec1560c823320aab696463770 Mon Sep 17 00:00:00 2001 From: Bastian Kauschke Date: Fri, 11 Sep 2020 10:46:35 +0200 Subject: [PATCH 07/12] add test for let-bindings --- compiler/rustc_typeck/src/collect.rs | 12 +++++++----- .../const_evaluatable_checked/let-bindings.rs | 15 +++++++++++++++ .../let-bindings.stderr | 18 ++++++++++++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 src/test/ui/const-generics/const_evaluatable_checked/let-bindings.rs create mode 100644 src/test/ui/const-generics/const_evaluatable_checked/let-bindings.stderr diff --git a/compiler/rustc_typeck/src/collect.rs b/compiler/rustc_typeck/src/collect.rs index 9b8427a46955c..731ccfad2b44f 100644 --- a/compiler/rustc_typeck/src/collect.rs +++ b/compiler/rustc_typeck/src/collect.rs @@ -1693,25 +1693,27 @@ pub fn const_evaluatable_predicates_of<'tcx>( ) -> impl Iterator, Span)> { #[derive(Default)] struct ConstCollector<'tcx> { - ct: SmallVec<[(ty::WithOptConstParam, SubstsRef<'tcx>); 4]>, + ct: SmallVec<[(ty::WithOptConstParam, SubstsRef<'tcx>, Span); 4]>, + curr_span: Span, } impl<'tcx> TypeVisitor<'tcx> for ConstCollector<'tcx> { fn visit_const(&mut self, ct: &'tcx Const<'tcx>) -> bool { if let ty::ConstKind::Unevaluated(def, substs, None) = ct.val { - self.ct.push((def, substs)); + self.ct.push((def, substs, self.curr_span)); } false } } let mut collector = ConstCollector::default(); - for (pred, _span) in predicates.predicates.iter() { + for &(pred, span) in predicates.predicates.iter() { + collector.curr_span = span; pred.visit_with(&mut collector); } warn!("const_evaluatable_predicates_of({:?}) = {:?}", def_id, collector.ct); - collector.ct.into_iter().map(move |(def_id, subst)| { - (ty::PredicateAtom::ConstEvaluatable(def_id, subst).to_predicate(tcx), DUMMY_SP) + collector.ct.into_iter().map(move |(def_id, subst, span)| { + (ty::PredicateAtom::ConstEvaluatable(def_id, subst).to_predicate(tcx), span) }) } diff --git a/src/test/ui/const-generics/const_evaluatable_checked/let-bindings.rs b/src/test/ui/const-generics/const_evaluatable_checked/let-bindings.rs new file mode 100644 index 0000000000000..d96788f8cd100 --- /dev/null +++ b/src/test/ui/const-generics/const_evaluatable_checked/let-bindings.rs @@ -0,0 +1,15 @@ +#![feature(const_generics, const_evaluatable_checked)] +#![allow(incomplete_features)] + +// We do not yet want to support let-bindings in abstract consts, +// so this test should keep failing for now. +fn test() -> [u8; { let x = N; N + 1 }] where [u8; { let x = N; N + 1 }]: Default { + //~^ ERROR constant expression depends + //~| ERROR constant expression depends + Default::default() +} + +fn main() { + let x = test::<31>(); + assert_eq!(x, [0; 32]); +} diff --git a/src/test/ui/const-generics/const_evaluatable_checked/let-bindings.stderr b/src/test/ui/const-generics/const_evaluatable_checked/let-bindings.stderr new file mode 100644 index 0000000000000..95fb48bd43402 --- /dev/null +++ b/src/test/ui/const-generics/const_evaluatable_checked/let-bindings.stderr @@ -0,0 +1,18 @@ +error: constant expression depends on a generic parameter + --> $DIR/let-bindings.rs:6:91 + | +LL | fn test() -> [u8; { let x = N; N + 1 }] where [u8; { let x = N; N + 1 }]: Default { + | ^^^^^^^ required by this bound in `test::{{constant}}#0` + | + = note: this may fail depending on what value the parameter takes + +error: constant expression depends on a generic parameter + --> $DIR/let-bindings.rs:6:30 + | +LL | fn test() -> [u8; { let x = N; N + 1 }] where [u8; { let x = N; N + 1 }]: Default { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this may fail depending on what value the parameter takes + +error: aborting due to 2 previous errors + From 30ff1ef3d000c7f76a1906522b0efff4c8b27201 Mon Sep 17 00:00:00 2001 From: Bastian Kauschke Date: Fri, 11 Sep 2020 21:16:16 +0200 Subject: [PATCH 08/12] support const_evaluatable_checked across crate boundaries --- compiler/rustc_metadata/src/rmeta/decoder.rs | 19 ++++++++++ .../src/rmeta/decoder/cstore_impl.rs | 1 + compiler/rustc_metadata/src/rmeta/encoder.rs | 11 ++++++ compiler/rustc_metadata/src/rmeta/mod.rs | 1 + .../rustc_middle/src/mir/abstract_const.rs | 2 +- compiler/rustc_middle/src/ty/codec.rs | 20 +++++++++++ .../src/ty/query/on_disk_cache.rs | 6 ++++ compiler/rustc_privacy/src/lib.rs | 9 +++++ .../src/traits/const_evaluatable.rs | 15 ++++++++ .../rustc_trait_selection/src/traits/mod.rs | 2 +- .../auxiliary/const_evaluatable_lib.rs | 9 +++++ .../const_evaluatable_checked/cross_crate.rs | 15 ++++++++ .../cross_crate_predicate.rs | 13 +++++++ .../cross_crate_predicate.stderr | 36 +++++++++++++++++++ 14 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 src/test/ui/const-generics/const_evaluatable_checked/auxiliary/const_evaluatable_lib.rs create mode 100644 src/test/ui/const-generics/const_evaluatable_checked/cross_crate.rs create mode 100644 src/test/ui/const-generics/const_evaluatable_checked/cross_crate_predicate.rs create mode 100644 src/test/ui/const-generics/const_evaluatable_checked/cross_crate_predicate.stderr diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 43d76e9fdb4c3..a2e2cf1ca0219 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -562,6 +562,12 @@ impl<'a, 'tcx> Decodable> for Span { } } +impl<'a, 'tcx> Decodable> for &'tcx [mir::abstract_const::Node<'tcx>] { + fn decode(d: &mut DecodeContext<'a, 'tcx>) -> Result { + ty::codec::RefDecodable::decode(d) + } +} + impl<'a, 'tcx> Decodable> for &'tcx [(ty::Predicate<'tcx>, Span)] { fn decode(d: &mut DecodeContext<'a, 'tcx>) -> Result { ty::codec::RefDecodable::decode(d) @@ -1191,6 +1197,19 @@ impl<'a, 'tcx> CrateMetadataRef<'a> { .decode((self, tcx)) } + fn get_mir_abstract_const( + &self, + tcx: TyCtxt<'tcx>, + id: DefIndex, + ) -> Option<&'tcx [mir::abstract_const::Node<'tcx>]> { + self.root + .tables + .mir_abstract_consts + .get(self, id) + .filter(|_| !self.is_proc_macro(id)) + .map_or(None, |v| Some(v.decode((self, tcx)))) + } + fn get_unused_generic_params(&self, id: DefIndex) -> FiniteBitSet { self.root .tables diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 94abfac19c665..d4f577a7d1b49 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -112,6 +112,7 @@ provide! { <'tcx> tcx, def_id, other, cdata, } optimized_mir => { tcx.arena.alloc(cdata.get_optimized_mir(tcx, def_id.index)) } promoted_mir => { tcx.arena.alloc(cdata.get_promoted_mir(tcx, def_id.index)) } + mir_abstract_const => { cdata.get_mir_abstract_const(tcx, def_id.index) } unused_generic_params => { cdata.get_unused_generic_params(def_id.index) } mir_const_qualif => { cdata.mir_const_qualif(def_id.index) } fn_sig => { cdata.fn_sig(def_id.index, tcx) } diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 1e313b7edfc27..eb091d86b82c6 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -321,6 +321,12 @@ impl<'a, 'tcx> TyEncoder<'tcx> for EncodeContext<'a, 'tcx> { } } +impl<'a, 'tcx> Encodable> for &'tcx [mir::abstract_const::Node<'tcx>] { + fn encode(&self, s: &mut EncodeContext<'a, 'tcx>) -> opaque::EncodeResult { + (**self).encode(s) + } +} + impl<'a, 'tcx> Encodable> for &'tcx [(ty::Predicate<'tcx>, Span)] { fn encode(&self, s: &mut EncodeContext<'a, 'tcx>) -> opaque::EncodeResult { (**self).encode(s) @@ -1109,6 +1115,11 @@ impl EncodeContext<'a, 'tcx> { if !unused.is_empty() { record!(self.tables.unused_generic_params[def_id.to_def_id()] <- unused); } + + let abstract_const = self.tcx.mir_abstract_const(def_id); + if let Some(abstract_const) = abstract_const { + record!(self.tables.mir_abstract_consts[def_id.to_def_id()] <- abstract_const); + } } } diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 1ba5962d119e8..ba540c944117d 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -284,6 +284,7 @@ define_tables! { super_predicates: Table)>, mir: Table)>, promoted_mir: Table>)>, + mir_abstract_consts: Table])>, unused_generic_params: Table>>, // `def_keys` and `def_path_hashes` represent a lazy version of a // `DefPathTable`. This allows us to avoid deserializing an entire diff --git a/compiler/rustc_middle/src/mir/abstract_const.rs b/compiler/rustc_middle/src/mir/abstract_const.rs index 8d215c5a5215a..b85f1e6e5ded0 100644 --- a/compiler/rustc_middle/src/mir/abstract_const.rs +++ b/compiler/rustc_middle/src/mir/abstract_const.rs @@ -11,7 +11,7 @@ rustc_index::newtype_index! { } /// A node of an `AbstractConst`. -#[derive(Debug, Clone, Copy, PartialEq, Eq, HashStable)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)] pub enum Node<'tcx> { Leaf(&'tcx ty::Const<'tcx>), Binop(mir::BinOp, NodeId, NodeId), diff --git a/compiler/rustc_middle/src/ty/codec.rs b/compiler/rustc_middle/src/ty/codec.rs index e2e5f08462f72..8ea34f9161abc 100644 --- a/compiler/rustc_middle/src/ty/codec.rs +++ b/compiler/rustc_middle/src/ty/codec.rs @@ -357,6 +357,26 @@ impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for [(ty::Predicate<'tcx>, } } +impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for [mir::abstract_const::Node<'tcx>] { + fn decode(decoder: &mut D) -> Result<&'tcx Self, D::Error> { + Ok(decoder.tcx().arena.alloc_from_iter( + (0..decoder.read_usize()?) + .map(|_| Decodable::decode(decoder)) + .collect::, _>>()?, + )) + } +} + +impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for [mir::abstract_const::NodeId] { + fn decode(decoder: &mut D) -> Result<&'tcx Self, D::Error> { + Ok(decoder.tcx().arena.alloc_from_iter( + (0..decoder.read_usize()?) + .map(|_| Decodable::decode(decoder)) + .collect::, _>>()?, + )) + } +} + impl_decodable_via_ref! { &'tcx ty::TypeckResults<'tcx>, &'tcx ty::List>, diff --git a/compiler/rustc_middle/src/ty/query/on_disk_cache.rs b/compiler/rustc_middle/src/ty/query/on_disk_cache.rs index dcfb8d314300f..b0c48a860ebaf 100644 --- a/compiler/rustc_middle/src/ty/query/on_disk_cache.rs +++ b/compiler/rustc_middle/src/ty/query/on_disk_cache.rs @@ -760,6 +760,12 @@ impl<'a, 'tcx> Decodable> } } +impl<'a, 'tcx> Decodable> for &'tcx [mir::abstract_const::Node<'tcx>] { + fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Result { + RefDecodable::decode(d) + } +} + impl<'a, 'tcx> Decodable> for &'tcx [(ty::Predicate<'tcx>, Span)] { fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Result { RefDecodable::decode(d) diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs index 03cc718b8995d..1a95992ed8318 100644 --- a/compiler/rustc_privacy/src/lib.rs +++ b/compiler/rustc_privacy/src/lib.rs @@ -97,6 +97,15 @@ where ty.visit_with(self) } ty::PredicateAtom::RegionOutlives(..) => false, + ty::PredicateAtom::ConstEvaluatable(..) + if self.def_id_visitor.tcx().features().const_evaluatable_checked => + { + // FIXME(const_evaluatable_checked): If the constant used here depends on a + // private function we may have to do something here... + // + // For now, let's just pretend that everything is fine. + false + } _ => bug!("unexpected predicate: {:?}", predicate), } } diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs index 50cc2afb89c80..1a5139d34d3cc 100644 --- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs +++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs @@ -142,6 +142,12 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { return None; } + // We don't have to look at concrete constants, as we + // can just evaluate them. + if !body.is_polymorphic { + return None; + } + Some(AbstractConstBuilder { tcx, body, @@ -304,6 +310,15 @@ pub(super) fn mir_abstract_const<'tcx>( def: ty::WithOptConstParam, ) -> Option<&'tcx [Node<'tcx>]> { if tcx.features().const_evaluatable_checked { + match tcx.def_kind(def.did) { + // FIXME(const_evaluatable_checked): We currently only do this for anonymous constants, + // meaning that we do not look into associated constants. I(@lcnr) am not yet sure whether + // we want to look into them or treat them as opaque projections. + // + // Right now we do neither of that and simply always fail to unify them. + DefKind::AnonConst => (), + _ => return None, + } let body = tcx.mir_const(def).borrow(); AbstractConstBuilder::new(tcx, &body)?.build() } else { diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs index 098336453bc69..79495ba7f9b30 100644 --- a/compiler/rustc_trait_selection/src/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/mod.rs @@ -553,7 +553,7 @@ pub fn provide(providers: &mut ty::query::Providers) { type_implements_trait, subst_and_check_impossible_predicates, mir_abstract_const: |tcx, def_id| { - let def_id = def_id.as_local()?; // We do not store failed AbstractConst's. + let def_id = def_id.expect_local(); if let Some(def) = ty::WithOptConstParam::try_lookup(def_id, tcx) { tcx.mir_abstract_const_of_const_arg(def) } else { diff --git a/src/test/ui/const-generics/const_evaluatable_checked/auxiliary/const_evaluatable_lib.rs b/src/test/ui/const-generics/const_evaluatable_checked/auxiliary/const_evaluatable_lib.rs new file mode 100644 index 0000000000000..9745dfed46087 --- /dev/null +++ b/src/test/ui/const-generics/const_evaluatable_checked/auxiliary/const_evaluatable_lib.rs @@ -0,0 +1,9 @@ +#![feature(const_generics, const_evaluatable_checked)] +#![allow(incomplete_features)] + +pub fn test1() -> [u8; std::mem::size_of::() - 1] +where + [u8; std::mem::size_of::() - 1]: Sized, +{ + [0; std::mem::size_of::() - 1] +} diff --git a/src/test/ui/const-generics/const_evaluatable_checked/cross_crate.rs b/src/test/ui/const-generics/const_evaluatable_checked/cross_crate.rs new file mode 100644 index 0000000000000..53b237843871f --- /dev/null +++ b/src/test/ui/const-generics/const_evaluatable_checked/cross_crate.rs @@ -0,0 +1,15 @@ +// aux-build:const_evaluatable_lib.rs +// run-pass +#![feature(const_generics, const_evaluatable_checked)] +#![allow(incomplete_features)] +extern crate const_evaluatable_lib; + +fn user() where [u8; std::mem::size_of::() - 1]: Sized { + assert_eq!(const_evaluatable_lib::test1::(), [0; std::mem::size_of::() - 1]); +} + +fn main() { + assert_eq!(const_evaluatable_lib::test1::(), [0; 3]); + user::(); + user::(); +} diff --git a/src/test/ui/const-generics/const_evaluatable_checked/cross_crate_predicate.rs b/src/test/ui/const-generics/const_evaluatable_checked/cross_crate_predicate.rs new file mode 100644 index 0000000000000..223699233298d --- /dev/null +++ b/src/test/ui/const-generics/const_evaluatable_checked/cross_crate_predicate.rs @@ -0,0 +1,13 @@ +// aux-build:const_evaluatable_lib.rs +#![feature(const_generics, const_evaluatable_checked)] +#![allow(incomplete_features)] +extern crate const_evaluatable_lib; + +fn user() { + let _ = const_evaluatable_lib::test1::(); + //~^ ERROR constant expression depends + //~| ERROR constant expression depends + //~| ERROR constant expression depends +} + +fn main() {} diff --git a/src/test/ui/const-generics/const_evaluatable_checked/cross_crate_predicate.stderr b/src/test/ui/const-generics/const_evaluatable_checked/cross_crate_predicate.stderr new file mode 100644 index 0000000000000..63abb782b93a3 --- /dev/null +++ b/src/test/ui/const-generics/const_evaluatable_checked/cross_crate_predicate.stderr @@ -0,0 +1,36 @@ +error: constant expression depends on a generic parameter + --> $DIR/cross_crate_predicate.rs:7:13 + | +LL | let _ = const_evaluatable_lib::test1::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + ::: $DIR/auxiliary/const_evaluatable_lib.rs:6:41 + | +LL | [u8; std::mem::size_of::() - 1]: Sized, + | ----- required by this bound in `test1` + | + = note: this may fail depending on what value the parameter takes + +error: constant expression depends on a generic parameter + --> $DIR/cross_crate_predicate.rs:7:13 + | +LL | let _ = const_evaluatable_lib::test1::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + ::: $DIR/auxiliary/const_evaluatable_lib.rs:6:41 + | +LL | [u8; std::mem::size_of::() - 1]: Sized, + | ----- required by this bound in `test1::{{constant}}#1` + | + = note: this may fail depending on what value the parameter takes + +error: constant expression depends on a generic parameter + --> $DIR/cross_crate_predicate.rs:7:13 + | +LL | let _ = const_evaluatable_lib::test1::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this may fail depending on what value the parameter takes + +error: aborting due to 3 previous errors + From 7fff155d2a2b10958454e4958197dda103644ad4 Mon Sep 17 00:00:00 2001 From: Bastian Kauschke Date: Fri, 11 Sep 2020 21:19:15 +0200 Subject: [PATCH 09/12] remove allow(warnings) --- compiler/rustc_trait_selection/src/traits/const_evaluatable.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs index 1a5139d34d3cc..b2e5ad52b0126 100644 --- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs +++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs @@ -1,11 +1,9 @@ -#![allow(warnings)] use rustc_hir::def::DefKind; use rustc_index::bit_set::BitSet; use rustc_index::vec::IndexVec; use rustc_infer::infer::InferCtxt; use rustc_middle::mir::abstract_const::{Node, NodeId}; use rustc_middle::mir::interpret::ErrorHandled; -use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::{self, Rvalue, StatementKind, TerminatorKind}; use rustc_middle::ty::subst::Subst; use rustc_middle::ty::subst::SubstsRef; From 1b275d08ad6299a1f9b31650ba906a18846d5461 Mon Sep 17 00:00:00 2001 From: Bastian Kauschke Date: Fri, 11 Sep 2020 21:50:17 +0200 Subject: [PATCH 10/12] document `const_evaluatable` --- .../src/traits/const_evaluatable.rs | 50 +++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs index b2e5ad52b0126..db79de06d5e4c 100644 --- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs +++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs @@ -1,3 +1,13 @@ +//! Checking that constant values used in types can be successfully evaluated. +//! +//! For concrete constants, this is fairly simple as we can just try and evaluate it. +//! +//! When dealing with polymorphic constants, for example `std::mem::size_of::() - 1`, +//! this is not as easy. +//! +//! 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 +//! generic constants mentioned in the `caller_bounds` of the current environment. use rustc_hir::def::DefKind; use rustc_index::bit_set::BitSet; use rustc_index::vec::IndexVec; @@ -129,13 +139,19 @@ impl AbstractConst<'tcx> { struct AbstractConstBuilder<'a, 'tcx> { tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, + /// The current WIP node tree. nodes: IndexVec>, locals: IndexVec, + /// We only allow field accesses if they access + /// the result of a checked operation. checked_op_locals: BitSet, } impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { fn new(tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>) -> Option> { + // 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() { return None; } @@ -154,17 +170,21 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { checked_op_locals: BitSet::new_empty(body.local_decls.len()), }) } - fn operand_to_node(&mut self, op: &mir::Operand<'tcx>) -> Option { debug!("operand_to_node: op={:?}", op); 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. + // + // One exception are field accesses on the result of checked operations, + // which are required to support things like `1 + 2`. if let Some(p) = p.as_local() { debug_assert!(!self.checked_op_locals.contains(p)); Some(self.locals[p]) } else if let &[mir::ProjectionElem::Field(ZERO_FIELD, _)] = p.projection.as_ref() { - // Only allow field accesses on the result of checked operations. + // Only allow field accesses if the given local + // contains the result of a checked operation. if self.checked_op_locals.contains(p.local) { Some(self.locals[p.local]) } else { @@ -238,6 +258,11 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { } } + /// Possible return values: + /// + /// - `None`: unsupported terminator, stop building + /// - `Some(None)`: supported terminator, finish building + /// - `Some(Some(block))`: support terminator, build `block` next fn build_terminator( &mut self, terminator: &mir::Terminator<'tcx>, @@ -250,7 +275,18 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { ref func, ref args, destination: Some((ref place, target)), + // We do not care about `cleanup` here. Any branch which + // uses `cleanup` will fail const-eval and they therefore + // do not matter when checking for const evaluatability. + // + // Do note that even if `panic::catch_unwind` is made const, + // we still do not have to care about this, as we do not look + // into functions. cleanup: _, + // Do not allow overloaded operators for now, + // we probably do want to allow this in the future. + // + // This is currently fairly irrelevant as it requires `const Trait`s. from_hir_call: true, fn_span: _, } => { @@ -264,10 +300,14 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { self.locals[local] = self.nodes.push(Node::FunctionCall(func, args)); Some(Some(target)) } + // We only allow asserts for checked operations. + // + // These asserts seem to all have the form `!_local.0` so + // we only allow exactly that. TerminatorKind::Assert { ref cond, expected: false, target, .. } => { let p = match cond { mir::Operand::Copy(p) | mir::Operand::Move(p) => p, - mir::Operand::Constant(_) => bug!("Unexpected assert"), + mir::Operand::Constant(_) => bug!("unexpected assert"), }; const ONE_FIELD: mir::Field = mir::Field::from_usize(1); @@ -285,8 +325,11 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { } } + /// Builds the abstract const by walking the mir from start to finish + /// and bailing out when encountering an unsupported operation. fn build(mut self) -> Option<&'tcx [Node<'tcx>]> { let mut block = &self.body.basic_blocks()[mir::START_BLOCK]; + // We checked for a cyclic cfg above, so this should terminate. loop { debug!("AbstractConstBuilder: block={:?}", block); for stmt in block.statements.iter() { @@ -340,6 +383,7 @@ pub(super) fn try_unify_abstract_consts<'tcx>( false } +/// Tries to unify two abstract constants using structural equality. pub(super) fn try_unify<'tcx>( tcx: TyCtxt<'tcx>, a: AbstractConst<'tcx>, From 09e6254496e1b46a474757b8fcc66cc5981584c5 Mon Sep 17 00:00:00 2001 From: Bastian Kauschke Date: Fri, 18 Sep 2020 17:11:17 +0200 Subject: [PATCH 11/12] review, small cleanup --- .../src/traits/const_evaluatable.rs | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs index db79de06d5e4c..6abe62759b6b8 100644 --- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs +++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs @@ -30,24 +30,24 @@ pub fn is_const_evaluatable<'cx, 'tcx>( span: Span, ) -> Result<(), ErrorHandled> { debug!("is_const_evaluatable({:?}, {:?})", def, substs); - if infcx.tcx.features().const_evaluatable_checked { - if let Some(ct) = AbstractConst::new(infcx.tcx, def, substs) { - for pred in param_env.caller_bounds() { - match pred.skip_binders() { - ty::PredicateAtom::ConstEvaluatable(b_def, b_substs) => { - debug!("is_const_evaluatable: caller_bound={:?}, {:?}", b_def, b_substs); - if b_def == def && b_substs == substs { - debug!("is_const_evaluatable: caller_bound ~~> ok"); - return Ok(()); - } else if AbstractConst::new(infcx.tcx, b_def, b_substs) - .map_or(false, |b_ct| try_unify(infcx.tcx, ct, b_ct)) - { - debug!("is_const_evaluatable: abstract_const ~~> ok"); - return Ok(()); - } + // `AbstractConst::new` already returns `None` if `const_evaluatable_checked` + // is not active, so we don't have to explicitly check for this here. + if let Some(ct) = AbstractConst::new(infcx.tcx, def, substs) { + for pred in param_env.caller_bounds() { + match pred.skip_binders() { + ty::PredicateAtom::ConstEvaluatable(b_def, b_substs) => { + debug!("is_const_evaluatable: caller_bound={:?}, {:?}", b_def, b_substs); + if b_def == def && b_substs == substs { + debug!("is_const_evaluatable: caller_bound ~~> ok"); + return Ok(()); + } else if AbstractConst::new(infcx.tcx, b_def, b_substs) + .map_or(false, |b_ct| try_unify(infcx.tcx, ct, b_ct)) + { + debug!("is_const_evaluatable: abstract_const ~~> ok"); + return Ok(()); } - _ => {} // don't care } + _ => {} // don't care } } } @@ -394,14 +394,17 @@ pub(super) fn try_unify<'tcx>( let a_ct = a_ct.subst(tcx, a.substs); let b_ct = b_ct.subst(tcx, b.substs); match (a_ct.val, b_ct.val) { + // We can just unify errors with everything to reduce the amount of + // emitted errors here. + (ty::ConstKind::Error(_), _) | (_, ty::ConstKind::Error(_)) => true, (ty::ConstKind::Param(a_param), ty::ConstKind::Param(b_param)) => { a_param == b_param } (ty::ConstKind::Value(a_val), ty::ConstKind::Value(b_val)) => a_val == b_val, // If we have `fn a() -> [u8; N + 1]` and `fn b() -> [u8; 1 + M]` // we do not want to use `assert_eq!(a(), b())` to infer that `N` and `M` have to be `1`. This - // means that we can't do anything with inference variables here. - (ty::ConstKind::Infer(_), _) | (_, ty::ConstKind::Infer(_)) => false, + // means that we only allow inference variables if they are equal. + (ty::ConstKind::Infer(a_val), ty::ConstKind::Infer(b_val)) => a_val == b_val, // FIXME(const_evaluatable_checked): We may want to either actually try // to evaluate `a_ct` and `b_ct` if they are are fully concrete or something like // this, for now we just return false here. From b7641209d79860172c5bf605d3ebf1377d46db55 Mon Sep 17 00:00:00 2001 From: Bastian Kauschke Date: Fri, 18 Sep 2020 17:36:11 +0200 Subject: [PATCH 12/12] add `const-evaluatable_checked` check back in --- .../src/traits/const_evaluatable.rs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs index 6abe62759b6b8..2642358dbc54c 100644 --- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs +++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs @@ -30,24 +30,24 @@ pub fn is_const_evaluatable<'cx, 'tcx>( span: Span, ) -> Result<(), ErrorHandled> { debug!("is_const_evaluatable({:?}, {:?})", def, substs); - // `AbstractConst::new` already returns `None` if `const_evaluatable_checked` - // is not active, so we don't have to explicitly check for this here. - if let Some(ct) = AbstractConst::new(infcx.tcx, def, substs) { - for pred in param_env.caller_bounds() { - match pred.skip_binders() { - ty::PredicateAtom::ConstEvaluatable(b_def, b_substs) => { - debug!("is_const_evaluatable: caller_bound={:?}, {:?}", b_def, b_substs); - if b_def == def && b_substs == substs { - debug!("is_const_evaluatable: caller_bound ~~> ok"); - return Ok(()); - } else if AbstractConst::new(infcx.tcx, b_def, b_substs) - .map_or(false, |b_ct| try_unify(infcx.tcx, ct, b_ct)) - { - debug!("is_const_evaluatable: abstract_const ~~> ok"); - return Ok(()); + if infcx.tcx.features().const_evaluatable_checked { + if let Some(ct) = AbstractConst::new(infcx.tcx, def, substs) { + for pred in param_env.caller_bounds() { + match pred.skip_binders() { + ty::PredicateAtom::ConstEvaluatable(b_def, b_substs) => { + debug!("is_const_evaluatable: caller_bound={:?}, {:?}", b_def, b_substs); + if b_def == def && b_substs == substs { + debug!("is_const_evaluatable: caller_bound ~~> ok"); + return Ok(()); + } else if AbstractConst::new(infcx.tcx, b_def, b_substs) + .map_or(false, |b_ct| try_unify(infcx.tcx, ct, b_ct)) + { + debug!("is_const_evaluatable: abstract_const ~~> ok"); + return Ok(()); + } } + _ => {} // don't care } - _ => {} // don't care } } }