Skip to content

Commit

Permalink
improve error message for const ty param mismatch
Browse files Browse the repository at this point in the history
  • Loading branch information
lcnr committed Nov 16, 2020
1 parent 06c9c59 commit 69b43c2
Show file tree
Hide file tree
Showing 25 changed files with 194 additions and 275 deletions.
8 changes: 8 additions & 0 deletions compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,14 @@ impl GenericArg<'_> {
GenericArg::Const(_) => "constant",
}
}

pub fn short_descr(&self) -> &'static str {
match self {
GenericArg::Lifetime(_) => "lifetime",
GenericArg::Type(_) => "type",
GenericArg::Const(_) => "const",
}
}
}

#[derive(Debug, HashStable_Generic)]
Expand Down
146 changes: 66 additions & 80 deletions compiler/rustc_typeck/src/astconv/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
sess: &Session,
arg: &GenericArg<'_>,
kind: &'static str,
possible_ordering_error: bool,
help: Option<&str>,
) {
let mut err = struct_span_err!(
Expand All @@ -48,8 +49,23 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
GenericArg::Const(_) => ParamKindOrd::Const { unordered },
};

if matches!(arg, GenericArg::Type(hir::Ty { kind: hir::TyKind::Path { .. }, .. }))
&& matches!(kind_ord, ParamKindOrd::Const { .. })
{
let suggestions = vec![
(arg.span().shrink_to_lo(), String::from("{ ")),
(arg.span().shrink_to_hi(), String::from(" }")),
];
err.multipart_suggestion(
"if this generic argument was intended as a const parameter, \
try surrounding it with braces:",
suggestions,
Applicability::MaybeIncorrect,
);
}

// This note is only true when generic parameters are strictly ordered by their kind.
if kind_ord.cmp(&arg_ord) != core::cmp::Ordering::Equal {
if possible_ordering_error && kind_ord.cmp(&arg_ord) != core::cmp::Ordering::Equal {
let (first, last) =
if kind_ord < arg_ord { (kind, arg.descr()) } else { (arg.descr(), kind) };
err.note(&format!("{} arguments must be provided before {} arguments", first, last));
Expand Down Expand Up @@ -153,8 +169,8 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
// Check whether this segment takes generic arguments and the user has provided any.
let (generic_args, infer_args) = args_for_def_id(def_id);

let mut args =
generic_args.iter().flat_map(|generic_args| generic_args.args.iter()).peekable();
let args_iter = generic_args.iter().flat_map(|generic_args| generic_args.args.iter());
let mut args = args_iter.clone().peekable();

// If we encounter a type or const when we expect a lifetime, we infer the lifetimes.
// If we later encounter a lifetime, we know that the arguments were provided in the
Expand Down Expand Up @@ -221,8 +237,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
GenericParamDefKind::Const => {
ParamKindOrd::Const {
unordered: tcx
.sess
.features_untracked()
.features()
.const_generics,
}
}
Expand All @@ -242,6 +257,13 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
tcx.sess,
arg,
kind.descr(),
!args_iter.clone().is_sorted_by_key(|arg| match arg {
GenericArg::Lifetime(_) => ParamKindOrd::Lifetime,
GenericArg::Type(_) => ParamKindOrd::Type,
GenericArg::Const(_) => ParamKindOrd::Const {
unordered: tcx.features().const_generics,
},
}),
Some(&format!(
"reorder the arguments: {}: `<{}>`",
param_types_present
Expand Down Expand Up @@ -293,7 +315,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
assert_eq!(kind, "lifetime");
let provided =
force_infer_lt.expect("lifetimes ought to have been inferred");
Self::generic_arg_mismatch_err(tcx.sess, provided, kind, None);
Self::generic_arg_mismatch_err(tcx.sess, provided, kind, false, None);
}

break;
Expand Down Expand Up @@ -351,6 +373,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
// that lifetimes will proceed types. So it suffices to check the number of each generic
// arguments in order to validate them with respect to the generic parameters.
let param_counts = def.own_counts();
let named_type_param_count = param_counts.types - has_self as usize;
let arg_counts = args.own_counts();
let infer_lifetimes = position != GenericArgPosition::Type && arg_counts.lifetimes == 0;

Expand Down Expand Up @@ -389,11 +412,11 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
// For kinds without defaults (e.g.., lifetimes), `required == permitted`.
// For other kinds (i.e., types), `permitted` may be greater than `required`.
if required <= provided && provided <= permitted {
return Ok(());
return true;
}

if silent {
return Err((0i32, None));
return false;
}

// Unfortunately lifetime and type parameter mismatches are typically styled
Expand All @@ -409,25 +432,26 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
(required, "")
};

let (spans, label) = if provided > permitted {
let (spans, labels) = if provided > permitted {
// In the case when the user has provided too many arguments,
// we want to point to the unexpected arguments.
let spans: Vec<Span> = args.args[offset + permitted..offset + provided]
let (spans, labels): (Vec<Span>, Vec<String>) = args.args
[offset + permitted..offset + provided]
.iter()
.map(|arg| arg.span())
.collect();
.map(|arg| (arg.span(), format!("unexpected {} argument", arg.short_descr())))
.unzip();
unexpected_spans.extend(spans.clone());
(spans, format!("unexpected {} argument", kind))
(spans, labels)
} else {
(
vec![span],
format!(
vec![format!(
"expected {}{} {} argument{}",
quantifier,
bound,
kind,
pluralize!(bound),
),
)],
)
};

Expand All @@ -439,12 +463,11 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
),
DiagnosticId::Error("E0107".into()),
);
for span in spans {
for (span, label) in spans.into_iter().zip(labels) {
err.span_label(span, label.as_str());
}

assert_ne!(bound, provided);
Err((bound as i32 - provided as i32, Some(err)))
err.emit();
false
};

let mut unexpected_spans = vec![];
Expand All @@ -459,75 +482,38 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
explicit_late_bound == ExplicitLateBound::Yes,
);

// FIXME(const_generics:defaults)
let mut const_count_correct = check_kind_count(
"const",
if infer_args { 0 } else { param_counts.consts },
param_counts.consts,
arg_counts.consts,
arg_counts.lifetimes + arg_counts.types,
&mut unexpected_spans,
false,
);
let kind_str = if param_counts.consts + arg_counts.consts == 0 {
"type"
} else if named_type_param_count + arg_counts.types == 0 {
"const"
} else {
"generic"
};

// Note that type errors are currently be emitted *after* const errors.
let mut type_count_correct = check_kind_count(
"type",
if infer_args { 0 } else { param_counts.types - defaults.types - has_self as usize },
param_counts.types - has_self as usize,
arg_counts.types,
let arg_count_correct = check_kind_count(
kind_str,
if infer_args {
0
} else {
param_counts.consts + named_type_param_count - defaults.types
},
param_counts.consts + named_type_param_count,
arg_counts.consts + arg_counts.types,
arg_counts.lifetimes,
&mut unexpected_spans,
false,
);

// Emit a help message if it's possible that a type could be surrounded in braces
if let Err((c_mismatch, Some(ref mut _const_err))) = const_count_correct {
if let Err((_, Some(ref mut type_err))) = type_count_correct {
let possible_matches = args.args[arg_counts.lifetimes..]
.iter()
.filter(|arg| {
matches!(
arg,
GenericArg::Type(hir::Ty { kind: hir::TyKind::Path { .. }, .. })
)
})
.take(c_mismatch.max(0) as usize);
for arg in possible_matches {
let suggestions = vec![
(arg.span().shrink_to_lo(), String::from("{ ")),
(arg.span().shrink_to_hi(), String::from(" }")),
];
type_err.multipart_suggestion(
"If this generic argument was intended as a const parameter, \
try surrounding it with braces:",
suggestions,
Applicability::MaybeIncorrect,
);
}
}
}

let emit_correct =
|correct: Result<(), (_, Option<rustc_errors::DiagnosticBuilder<'_>>)>| match correct {
Ok(()) => Ok(()),
Err((_, None)) => Err(()),
Err((_, Some(mut err))) => {
err.emit();
Err(())
}
};

let arg_count_correct = emit_correct(lifetime_count_correct)
.and(emit_correct(const_count_correct))
.and(emit_correct(type_count_correct));

GenericArgCountResult {
explicit_late_bound,
correct: arg_count_correct.map_err(|()| GenericArgCountMismatch {
reported: Some(ErrorReported),
invalid_args: unexpected_spans,
}),
correct: if lifetime_count_correct && arg_count_correct {
Ok(())
} else {
Err(GenericArgCountMismatch {
reported: Some(ErrorReported),
invalid_args: unexpected_spans,
})
},
}
}

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_typeck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ This API is completely unstable and subject to change.
#![feature(box_syntax)]
#![feature(crate_visibility_modifier)]
#![feature(in_band_lifetimes)]
#![feature(is_sorted)]
#![feature(nll)]
#![feature(or_patterns)]
#![feature(try_blocks)]
Expand Down
9 changes: 9 additions & 0 deletions src/test/ui/const-generics/const-param-shadowing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#![feature(min_const_generics)]

type N = u32;
struct Foo<const M: usize>;
fn test<const N: usize>() -> Foo<N> { //~ ERROR type provided when
Foo
}

fn main() {}
14 changes: 14 additions & 0 deletions src/test/ui/const-generics/const-param-shadowing.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error[E0747]: type provided when a constant was expected
--> $DIR/const-param-shadowing.rs:5:34
|
LL | fn test<const N: usize>() -> Foo<N> {
| ^
|
help: if this generic argument was intended as a const parameter, try surrounding it with braces:
|
LL | fn test<const N: usize>() -> Foo<{ N }> {
| ^ ^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0747`.
3 changes: 2 additions & 1 deletion src/test/ui/const-generics/invalid-constant-in-args.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
fn main() {
let _: Vec<&str, "a"> = Vec::new(); //~ ERROR wrong number of const arguments
let _: Vec<&str, "a"> = Vec::new();
//~^ ERROR wrong number of generic arguments
}
2 changes: 1 addition & 1 deletion src/test/ui/const-generics/invalid-constant-in-args.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error[E0107]: wrong number of const arguments: expected 0, found 1
error[E0107]: wrong number of generic arguments: expected 1, found 2
--> $DIR/invalid-constant-in-args.rs:2:22
|
LL | let _: Vec<&str, "a"> = Vec::new();
Expand Down
12 changes: 4 additions & 8 deletions src/test/ui/const-generics/invalid-enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,16 @@ impl<const CF: CompileFlag, T> Example<CF, T> {
pub fn main() {
test_1::<CompileFlag::A>();
//~^ ERROR: expected type, found variant
//~| ERROR: wrong number of const arguments
//~| ERROR: wrong number of type arguments
//~| ERROR: type provided when a constant was expected

test_2::<_, CompileFlag::A>(0);
//~^ ERROR: expected type, found variant
//~| ERROR: wrong number of const arguments
//~| ERROR: wrong number of type arguments
//~| ERROR: type provided when a constant was expected

let _: Example<CompileFlag::A, _> = Example { x: 0 };
//~^ ERROR: expected type, found variant
//~| ERROR: wrong number of const arguments
//~| ERROR: wrong number of type arguments
//~| ERROR: type provided when a constant was expected

let _: Example<Example::ASSOC_FLAG, _> = Example { x: 0 };
//~^ ERROR: wrong number of const arguments
//~| ERROR: wrong number of type arguments
//~^ ERROR: type provided when a constant was expected
}
Loading

0 comments on commit 69b43c2

Please sign in to comment.