Check for element being const before resolving repeat count

This commit is contained in:
Boxy 2025-04-10 15:30:28 +01:00
parent 9a2856837c
commit be564703b0
3 changed files with 65 additions and 80 deletions

View File

@ -115,6 +115,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let deferred_repeat_expr_checks = deferred_repeat_expr_checks
.drain(..)
.flat_map(|(element, element_ty, count)| {
// Actual constants as the repeat element get inserted repeatedly instead of getting copied via Copy
// so we don't need to attempt to structurally resolve the repeat count which may unnecessarily error.
match &element.kind {
hir::ExprKind::ConstBlock(..) => return None,
hir::ExprKind::Path(qpath) => {
let res = self.typeck_results.borrow().qpath_res(qpath, element.hir_id);
if let Res::Def(
DefKind::Const | DefKind::AssocConst | DefKind::AnonConst,
_,
) = res
{
return None;
}
}
_ => {}
}
// We want to emit an error if the const is not structurally resolveable as otherwise
// we can find up conservatively proving `Copy` which may infer the repeat expr count
// to something that never required `Copy` in the first place.
@ -135,12 +152,40 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// expr's `Copy` check.
.collect::<Vec<_>>();
let enforce_copy_bound = |element: &hir::Expr<'_>, element_ty| {
// If someone calls a const fn or constructs a const value, they can extract that
// out into a separate constant (or a const block in the future), so we check that
// to tell them that in the diagnostic. Does not affect typeck.
let is_constable = match element.kind {
hir::ExprKind::Call(func, _args) => match *self.node_ty(func.hir_id).kind() {
ty::FnDef(def_id, _) if self.tcx.is_stable_const_fn(def_id) => {
traits::IsConstable::Fn
}
_ => traits::IsConstable::No,
},
hir::ExprKind::Path(qpath) => {
match self.typeck_results.borrow().qpath_res(&qpath, element.hir_id) {
Res::Def(DefKind::Ctor(_, CtorKind::Const), _) => traits::IsConstable::Ctor,
_ => traits::IsConstable::No,
}
}
_ => traits::IsConstable::No,
};
let lang_item = self.tcx.require_lang_item(LangItem::Copy, None);
let code = traits::ObligationCauseCode::RepeatElementCopy {
is_constable,
elt_span: element.span,
};
self.require_type_meets(element_ty, element.span, code, lang_item);
};
for (element, element_ty, count) in deferred_repeat_expr_checks {
match count.kind() {
ty::ConstKind::Value(val)
if val.try_to_target_usize(self.tcx).is_none_or(|count| count > 1) =>
{
self.enforce_repeat_element_needs_copy_bound(element, element_ty)
enforce_copy_bound(element, element_ty)
}
// If the length is 0 or 1 we don't actually copy the element, we either don't create it
// or we just use the one value.
@ -151,9 +196,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
ty::ConstKind::Param(_)
| ty::ConstKind::Expr(_)
| ty::ConstKind::Placeholder(_)
| ty::ConstKind::Unevaluated(_) => {
self.enforce_repeat_element_needs_copy_bound(element, element_ty)
}
| ty::ConstKind::Unevaluated(_) => enforce_copy_bound(element, element_ty),
ty::ConstKind::Bound(_, _) | ty::ConstKind::Infer(_) | ty::ConstKind::Error(_) => {
unreachable!()
@ -162,50 +205,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
/// Requires that `element_ty` is `Copy` (unless it's a const expression itself).
pub(super) fn enforce_repeat_element_needs_copy_bound(
&self,
element: &hir::Expr<'_>,
element_ty: Ty<'tcx>,
) {
// Actual constants as the repeat element get inserted repeatedly instead of getting copied via Copy.
match &element.kind {
hir::ExprKind::ConstBlock(..) => return,
hir::ExprKind::Path(qpath) => {
let res = self.typeck_results.borrow().qpath_res(qpath, element.hir_id);
if let Res::Def(DefKind::Const | DefKind::AssocConst | DefKind::AnonConst, _) = res
{
return;
}
}
_ => {}
}
// If someone calls a const fn or constructs a const value, they can extract that
// out into a separate constant (or a const block in the future), so we check that
// to tell them that in the diagnostic. Does not affect typeck.
let is_constable = match element.kind {
hir::ExprKind::Call(func, _args) => match *self.node_ty(func.hir_id).kind() {
ty::FnDef(def_id, _) if self.tcx.is_stable_const_fn(def_id) => {
traits::IsConstable::Fn
}
_ => traits::IsConstable::No,
},
hir::ExprKind::Path(qpath) => {
match self.typeck_results.borrow().qpath_res(&qpath, element.hir_id) {
Res::Def(DefKind::Ctor(_, CtorKind::Const), _) => traits::IsConstable::Ctor,
_ => traits::IsConstable::No,
}
}
_ => traits::IsConstable::No,
};
let lang_item = self.tcx.require_lang_item(LangItem::Copy, None);
let code =
traits::ObligationCauseCode::RepeatElementCopy { is_constable, elt_span: element.span };
self.require_type_meets(element_ty, element.span, code, lang_item);
}
pub(in super::super) fn check_method_argument_types(
&self,
sp: Span,

View File

@ -20,7 +20,6 @@ fn tie_and_make_goal<const N: usize, T: Trait<N>>(_: &T, _: &[String; N]) {}
fn const_block() {
// Deferred repeat expr `String; ?n`
let a = [const { String::new() }; _];
//~^ ERROR: type annotations needed for `[String; _]`
// `?int: Trait<?n>` goal
tie_and_make_goal(&1, &a);
@ -36,7 +35,6 @@ fn const_item() {
// Deferred repeat expr `String; ?n`
let a = [MY_CONST; _];
//~^ ERROR: type annotations needed for `[String; _]`
// `?int: Trait<?n>` goal
tie_and_make_goal(&1, &a);
@ -54,7 +52,6 @@ fn assoc_const() {
// Deferred repeat expr `String; ?n`
let a = [<() as Dummy>::ASSOC; _];
//~^ ERROR: type annotations needed for `[String; _]`
// `?int: Trait<?n>` goal
tie_and_make_goal(&1, &a);
@ -62,4 +59,14 @@ fn assoc_const() {
// ... same as `const_block`
}
fn const_block_but_uninferred() {
// Deferred repeat expr `String; ?n`
let a = [const { String::new() }; _];
//~^ ERROR: type annotations needed for `[String; _]`
// Even if we don't structurally resolve the repeat count as part of repeat expr
// checks, we still error on the repeat count being uninferred as we require all
// types/consts to be inferred by the end of type checking.
}
fn main() {}

View File

@ -1,36 +1,15 @@
error[E0282]: type annotations needed for `[String; _]`
--> $DIR/copy-check-const-element-uninferred-count.rs:22:9
error[E0284]: type annotations needed for `[String; _]`
--> $DIR/copy-check-const-element-uninferred-count.rs:64:9
|
LL | let a = [const { String::new() }; _];
| ^ ----------------------- type must be known at this point
| ^ ---------------------------- type must be known at this point
|
help: consider giving `a` an explicit type, where the value of const parameter `N` is specified
= note: the length of array `[String; _]` must be type `usize`
help: consider giving `a` an explicit type, where the placeholders `_` are specified
|
LL | let a: [_; N] = [const { String::new() }; _];
LL | let a: [_; _] = [const { String::new() }; _];
| ++++++++
error[E0282]: type annotations needed for `[String; _]`
--> $DIR/copy-check-const-element-uninferred-count.rs:38:9
|
LL | let a = [MY_CONST; _];
| ^ -------- type must be known at this point
|
help: consider giving `a` an explicit type, where the value of const parameter `N` is specified
|
LL | let a: [_; N] = [MY_CONST; _];
| ++++++++
error: aborting due to 1 previous error
error[E0282]: type annotations needed for `[String; _]`
--> $DIR/copy-check-const-element-uninferred-count.rs:56:9
|
LL | let a = [<() as Dummy>::ASSOC; _];
| ^ -------------------- type must be known at this point
|
help: consider giving `a` an explicit type, where the value of const parameter `N` is specified
|
LL | let a: [_; N] = [<() as Dummy>::ASSOC; _];
| ++++++++
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0282`.
For more information about this error, try `rustc --explain E0284`.