Skip to content

Commit

Permalink
Implement custom diagnostic for ConstParamTy
Browse files Browse the repository at this point in the history
  • Loading branch information
compiler-errors committed Jun 1, 2023
1 parent a9fcb52 commit 847d504
Show file tree
Hide file tree
Showing 51 changed files with 455 additions and 152 deletions.
12 changes: 8 additions & 4 deletions compiler/rustc_error_codes/src/error_codes/E0741.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@ struct A;
struct B<const X: A>; // error!
```

Only structural-match types (that is, types that derive `PartialEq` and `Eq`)
may be used as the types of const generic parameters.
Only structural-match types, which are types that derive `PartialEq` and `Eq`
and implement `ConstParamTy`, may be used as the types of const generic
parameters.

To fix the previous code example, we derive `PartialEq` and `Eq`:
To fix the previous code example, we derive `PartialEq`, `Eq`, and
`ConstParamTy`:

```
#![feature(adt_const_params)]
#[derive(PartialEq, Eq)] // We derive both traits here.
use std::marker::ConstParamTy;
#[derive(PartialEq, Eq, ConstParamTy)] // We derive both traits here.
struct A;
struct B<const X: A>; // ok!
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir_analysis/src/check/wfcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -836,7 +836,7 @@ fn check_param_wf(tcx: TyCtxt<'_>, param: &hir::GenericParam<'_>) {
ObligationCause::new(
hir_ty.span,
param.def_id,
ObligationCauseCode::WellFormed(Some(hir_ty.span)),
ObligationCauseCode::ConstParam(ty),
),
wfcx.param_env,
ty,
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_middle/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,9 @@ pub enum ObligationCauseCode<'tcx> {
/// Obligations to prove that a `std::ops::Drop` impl is not stronger than
/// the ADT it's being implemented for.
DropImpl,

/// Requirement for a `const N: Ty` to implement `Ty: ConstParamTy`
ConstParam(Ty<'tcx>),
}

/// The 'location' at which we try to perform HIR-based wf checking.
Expand Down
110 changes: 110 additions & 0 deletions compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ pub trait TypeErrCtxtExt<'tcx> {
root_obligation: &PredicateObligation<'tcx>,
error: &SelectionError<'tcx>,
);

fn report_const_param_not_wf(
&self,
ty: Ty<'tcx>,
obligation: &PredicateObligation<'tcx>,
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed>;
}

impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> {
Expand Down Expand Up @@ -641,6 +647,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
span = obligation.cause.span;
}
}

if let ObligationCauseCode::CompareImplItemObligation {
impl_item_def_id,
trait_item_def_id,
Expand All @@ -657,6 +664,13 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
return;
}

// Report a const-param specific error
if let ObligationCauseCode::ConstParam(ty) = *obligation.cause.code().peel_derives()
{
self.report_const_param_not_wf(ty, &obligation).emit();
return;
}

let bound_predicate = obligation.predicate.kind();
match bound_predicate.skip_binder() {
ty::PredicateKind::Clause(ty::Clause::Trait(trait_predicate)) => {
Expand Down Expand Up @@ -1163,6 +1177,102 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
self.point_at_returns_when_relevant(&mut err, &obligation);
err.emit();
}

fn report_const_param_not_wf(
&self,
ty: Ty<'tcx>,
obligation: &PredicateObligation<'tcx>,
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
let span = obligation.cause.span;

let mut diag = match ty.kind() {
_ if ty.has_param() => {
span_bug!(span, "const param tys cannot mention other generic parameters");
}
ty::Float(_) => {
struct_span_err!(
self.tcx.sess,
span,
E0741,
"`{ty}` is forbidden as the type of a const generic parameter",
)
}
ty::FnPtr(_) => {
struct_span_err!(
self.tcx.sess,
span,
E0741,
"using function pointers as const generic parameters is forbidden",
)
}
ty::RawPtr(_) => {
struct_span_err!(
self.tcx.sess,
span,
E0741,
"using raw pointers as const generic parameters is forbidden",
)
}
ty::Adt(def, _) => {
// We should probably see if we're *allowed* to derive `ConstParamTy` on the type...
let mut diag = struct_span_err!(
self.tcx.sess,
span,
E0741,
"`{ty}` must implement `ConstParamTy` to be used as the type of a const generic parameter",
);
// Only suggest derive if this isn't a derived obligation,
// and the struct is local.
if let Some(span) = self.tcx.hir().span_if_local(def.did())
&& obligation.cause.code().parent().is_none()
{
if ty.is_structural_eq_shallow(self.tcx) {
diag.span_suggestion(
span,
"add `#[derive(ConstParamTy)]` to the struct",
"#[derive(ConstParamTy)]\n",
Applicability::MachineApplicable,
);
} else {
// FIXME(adt_const_params): We should check there's not already an
// overlapping `Eq`/`PartialEq` impl.
diag.span_suggestion(
span,
"add `#[derive(ConstParamTy, PartialEq, Eq)]` to the struct",
"#[derive(ConstParamTy, PartialEq, Eq)]\n",
Applicability::MachineApplicable,
);
}
}
diag
}
_ => {
struct_span_err!(
self.tcx.sess,
span,
E0741,
"`{ty}` can't be used as a const parameter type",
)
}
};

let mut code = obligation.cause.code();
let mut pred = obligation.predicate.to_opt_poly_trait_pred();
while let Some((next_code, next_pred)) = code.parent() {
if let Some(pred) = pred {
let pred = self.instantiate_binder_with_placeholders(pred);
diag.note(format!(
"`{}` must implement `{}`, but it does not",
pred.self_ty(),
pred.print_modifiers_and_trait_path()
));
}
code = next_code;
pred = next_pred;
}

diag
}
}

trait InferCtxtPrivExt<'tcx> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2655,7 +2655,8 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
| ObligationCauseCode::BinOp { .. }
| ObligationCauseCode::AscribeUserTypeProvePredicate(..)
| ObligationCauseCode::RustCall
| ObligationCauseCode::DropImpl => {}
| ObligationCauseCode::DropImpl
| ObligationCauseCode::ConstParam(_) => {}
ObligationCauseCode::SliceOrArrayElem => {
err.note("slice and array elements must have `Sized` type");
}
Expand Down
4 changes: 3 additions & 1 deletion src/tools/clippy/tests/ui/same_functions_in_if_condition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
clippy::uninlined_format_args
)]

use std::marker::ConstParamTy;

fn function() -> bool {
true
}
Expand Down Expand Up @@ -96,7 +98,7 @@ fn main() {
};
println!("{}", os);

#[derive(PartialEq, Eq)]
#[derive(PartialEq, Eq, ConstParamTy)]
enum E {
A,
B,
Expand Down
24 changes: 12 additions & 12 deletions src/tools/clippy/tests/ui/same_functions_in_if_condition.stderr
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
error: this `if` has the same function call as a previous `if`
--> $DIR/same_functions_in_if_condition.rs:37:15
--> $DIR/same_functions_in_if_condition.rs:39:15
|
LL | } else if function() {
| ^^^^^^^^^^
|
note: same as this
--> $DIR/same_functions_in_if_condition.rs:36:8
--> $DIR/same_functions_in_if_condition.rs:38:8
|
LL | if function() {
| ^^^^^^^^^^
Expand All @@ -16,61 +16,61 @@ LL | #![deny(clippy::same_functions_in_if_condition)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: this `if` has the same function call as a previous `if`
--> $DIR/same_functions_in_if_condition.rs:42:15
--> $DIR/same_functions_in_if_condition.rs:44:15
|
LL | } else if fn_arg(a) {
| ^^^^^^^^^
|
note: same as this
--> $DIR/same_functions_in_if_condition.rs:41:8
--> $DIR/same_functions_in_if_condition.rs:43:8
|
LL | if fn_arg(a) {
| ^^^^^^^^^

error: this `if` has the same function call as a previous `if`
--> $DIR/same_functions_in_if_condition.rs:47:15
--> $DIR/same_functions_in_if_condition.rs:49:15
|
LL | } else if obj.method() {
| ^^^^^^^^^^^^
|
note: same as this
--> $DIR/same_functions_in_if_condition.rs:46:8
--> $DIR/same_functions_in_if_condition.rs:48:8
|
LL | if obj.method() {
| ^^^^^^^^^^^^

error: this `if` has the same function call as a previous `if`
--> $DIR/same_functions_in_if_condition.rs:52:15
--> $DIR/same_functions_in_if_condition.rs:54:15
|
LL | } else if obj.method_arg(a) {
| ^^^^^^^^^^^^^^^^^
|
note: same as this
--> $DIR/same_functions_in_if_condition.rs:51:8
--> $DIR/same_functions_in_if_condition.rs:53:8
|
LL | if obj.method_arg(a) {
| ^^^^^^^^^^^^^^^^^

error: this `if` has the same function call as a previous `if`
--> $DIR/same_functions_in_if_condition.rs:59:15
--> $DIR/same_functions_in_if_condition.rs:61:15
|
LL | } else if v.pop().is_none() {
| ^^^^^^^^^^^^^^^^^
|
note: same as this
--> $DIR/same_functions_in_if_condition.rs:57:8
--> $DIR/same_functions_in_if_condition.rs:59:8
|
LL | if v.pop().is_none() {
| ^^^^^^^^^^^^^^^^^

error: this `if` has the same function call as a previous `if`
--> $DIR/same_functions_in_if_condition.rs:64:15
--> $DIR/same_functions_in_if_condition.rs:66:15
|
LL | } else if v.len() == 42 {
| ^^^^^^^^^^^^^
|
note: same as this
--> $DIR/same_functions_in_if_condition.rs:62:8
--> $DIR/same_functions_in_if_condition.rs:64:8
|
LL | if v.len() == 42 {
| ^^^^^^^^^^^^^
Expand Down
14 changes: 13 additions & 1 deletion tests/incremental/const-generics/hash-tyvid-regression-1.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
// revisions: cfail
#![feature(generic_const_exprs, adt_const_params)]
#![allow(incomplete_features)]

use std::marker::ConstParamTy;

#[derive(PartialEq, Eq, ConstParamTy)]
struct NonZeroUsize(usize);

impl NonZeroUsize {
const fn get(self) -> usize {
self.0
}
}

// regression test for #77650
fn c<T, const N: std::num::NonZeroUsize>()
fn c<T, const N: NonZeroUsize>()
where
[T; N.get()]: Sized,
{
Expand Down
16 changes: 14 additions & 2 deletions tests/incremental/const-generics/hash-tyvid-regression-2.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
// revisions: cfail
#![feature(generic_const_exprs, adt_const_params)]
#![allow(incomplete_features)]

use std::marker::ConstParamTy;

#[derive(PartialEq, Eq, ConstParamTy)]
struct NonZeroUsize(usize);

impl NonZeroUsize {
const fn get(self) -> usize {
self.0
}
}

// regression test for #77650
struct C<T, const N: core::num::NonZeroUsize>([T; N.get()])
struct C<T, const N: NonZeroUsize>([T; N.get()])
where
[T; N.get()]: Sized;
impl<'a, const N: core::num::NonZeroUsize, A, B: PartialEq<A>> PartialEq<&'a [A]> for C<B, N>
impl<'a, const N: NonZeroUsize, A, B: PartialEq<A>> PartialEq<&'a [A]> for C<B, N>
where
[B; N.get()]: Sized,
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@
#![feature(generic_const_exprs, adt_const_params)]
#![allow(incomplete_features)]

use std::{convert::TryFrom, num::NonZeroUsize};
use std::{convert::TryFrom};

use std::marker::ConstParamTy;

#[derive(PartialEq, Eq, ConstParamTy)]
struct NonZeroUsize(usize);

impl NonZeroUsize {
const fn get(self) -> usize {
self.0
}
}

struct A<const N: NonZeroUsize>([u8; N.get()])
where
Expand Down
Loading

0 comments on commit 847d504

Please sign in to comment.