Rollup merge of #92569 - George-lewis:87181, r=estebank

Improve Error Messaging for Unconstructed Structs and Enum Variants in Generic Contexts

Improves error messaging for empty-tuple structs and enum variants in certain generic contexts. See new ui tests for examples.

Closes #87181
This commit is contained in:
Dylan DPC 2022-04-27 02:47:07 +02:00 committed by GitHub
commit ac46e17c02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 234 additions and 30 deletions

View File

@ -3314,6 +3314,12 @@ impl<'hir> Node<'hir> {
_ => None,
}
}
/// Get the fields for the tuple-constructor,
/// if this node is a tuple constructor, otherwise None
pub fn tuple_fields(&self) -> Option<&'hir [FieldDef<'hir>]> {
if let Node::Ctor(&VariantData::Tuple(fields, _)) = self { Some(fields) } else { None }
}
}
// Some nodes are used a lot. Make sure they don't unintentionally get bigger.

View File

@ -21,11 +21,13 @@ use crate::errors::{
};
use crate::type_error_struct;
use super::suggest_call_constructor;
use crate::errors::{AddressOfTemporaryTaken, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive};
use rustc_ast as ast;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_errors::Diagnostic;
use rustc_errors::EmissionGuarantee;
use rustc_errors::ErrorGuaranteed;
use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder, DiagnosticId};
use rustc_hir as hir;
@ -1986,6 +1988,26 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.tcx().ty_error()
}
fn check_call_constructor<G: EmissionGuarantee>(
&self,
err: &mut DiagnosticBuilder<'_, G>,
base: &'tcx hir::Expr<'tcx>,
def_id: DefId,
) {
let local_id = def_id.expect_local();
let hir_id = self.tcx.hir().local_def_id_to_hir_id(local_id);
let node = self.tcx.hir().get(hir_id);
if let Some(fields) = node.tuple_fields() {
let kind = match self.tcx.opt_def_kind(local_id) {
Some(DefKind::Ctor(of, _)) => of,
_ => return,
};
suggest_call_constructor(base.span, kind, fields.len(), err);
}
}
fn suggest_await_on_field_access(
&self,
err: &mut Diagnostic,
@ -2055,6 +2077,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
ty::Opaque(_, _) => {
self.suggest_await_on_field_access(&mut err, field, base, expr_t.peel_refs());
}
ty::FnDef(def_id, _) => {
self.check_call_constructor(&mut err, base, def_id);
}
_ => {}
}

View File

@ -8,6 +8,7 @@ use rustc_errors::{
MultiSpan,
};
use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_hir::lang_items::LangItem;
use rustc_hir::{ExprKind, Node, QPath};
@ -29,7 +30,7 @@ use std::cmp::Ordering;
use std::iter;
use super::probe::{Mode, ProbeScope};
use super::{CandidateSource, MethodError, NoMatchData};
use super::{super::suggest_call_constructor, CandidateSource, MethodError, NoMatchData};
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
fn is_fn_ty(&self, ty: Ty<'tcx>, span: Span) -> bool {
@ -488,19 +489,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
if self.is_fn_ty(rcvr_ty, span) {
fn report_function<T: std::fmt::Display>(err: &mut Diagnostic, name: T) {
err.note(
&format!("`{}` is a function, perhaps you wish to call it", name,),
);
}
if let SelfSource::MethodCall(expr) = source {
if let Ok(expr_string) = tcx.sess.source_map().span_to_snippet(expr.span) {
report_function(&mut err, expr_string);
} else if let ExprKind::Path(QPath::Resolved(_, path)) = expr.kind {
if let Some(segment) = path.segments.last() {
report_function(&mut err, segment.ident);
let suggest = if let ty::FnDef(def_id, _) = rcvr_ty.kind() {
let local_id = def_id.expect_local();
let hir_id = tcx.hir().local_def_id_to_hir_id(local_id);
let node = tcx.hir().get(hir_id);
let fields = node.tuple_fields();
if let Some(fields) = fields
&& let Some(DefKind::Ctor(of, _)) = self.tcx.opt_def_kind(local_id) {
Some((fields, of))
} else {
None
}
} else {
None
};
// If the function is a tuple constructor, we recommend that they call it
if let Some((fields, kind)) = suggest {
suggest_call_constructor(expr.span, kind, fields.len(), &mut err);
} else {
// General case
err.span_label(
expr.span,
"this is a function, perhaps you wish to call it",
);
}
}
}

View File

@ -98,12 +98,15 @@ pub use check::{check_item_type, check_wf_new};
pub use diverges::Diverges;
pub use expectation::Expectation;
pub use fn_ctxt::*;
use hir::def::CtorOf;
pub use inherited::{Inherited, InheritedBuilder};
use crate::astconv::AstConv;
use crate::check::gather_locals::GatherLocalsVisitor;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::{pluralize, struct_span_err, Applicability, MultiSpan};
use rustc_errors::{
pluralize, struct_span_err, Applicability, DiagnosticBuilder, EmissionGuarantee, MultiSpan,
};
use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_hir::def_id::{DefId, LocalDefId};
@ -988,3 +991,36 @@ fn has_expected_num_generic_args<'tcx>(
generics.count() == expected + if generics.has_self { 1 } else { 0 }
})
}
/// Suggests calling the constructor of a tuple struct or enum variant
///
/// * `snippet` - The snippet of code that references the constructor
/// * `span` - The span of the snippet
/// * `params` - The number of parameters the constructor accepts
/// * `err` - A mutable diagnostic builder to add the suggestion to
fn suggest_call_constructor<G: EmissionGuarantee>(
span: Span,
kind: CtorOf,
params: usize,
err: &mut DiagnosticBuilder<'_, G>,
) {
// Note: tuple-structs don't have named fields, so just use placeholders
let args = vec!["_"; params].join(", ");
let applicable = if params > 0 {
Applicability::HasPlaceholders
} else {
// When n = 0, it's an empty-tuple struct/enum variant
// so we trivially know how to construct it
Applicability::MachineApplicable
};
let kind = match kind {
CtorOf::Struct => "a struct",
CtorOf::Variant => "an enum variant",
};
err.span_label(span, &format!("this is the constructor of {kind}"));
err.multipart_suggestion(
"call the constructor",
vec![(span.shrink_to_lo(), "(".to_string()), (span.shrink_to_hi(), format!(")({args})"))],
applicable,
);
}

View File

@ -3,14 +3,14 @@ fn main() {
let arc = std::sync::Arc::new(oops);
//~^ ERROR cannot find value `oops` in this scope
//~| NOTE not found
// The error "note: `arc` is a function, perhaps you wish to call it" MUST NOT appear.
// The error "note: this is a function, perhaps you wish to call it" MUST NOT appear.
arc.blablabla();
//~^ ERROR no method named `blablabla`
//~| NOTE method not found
let arc2 = std::sync::Arc::new(|| 1);
// The error "note: `arc2` is a function, perhaps you wish to call it" SHOULD appear
// The error "note: this is a function, perhaps you wish to call it" SHOULD appear
arc2.blablabla();
//~^ ERROR no method named `blablabla`
//~| NOTE method not found
//~| NOTE `arc2` is a function, perhaps you wish to call it
//~| NOTE this is a function, perhaps you wish to call it
}

View File

@ -14,9 +14,9 @@ error[E0599]: no method named `blablabla` found for struct `Arc<[closure@$DIR/fn
--> $DIR/fn-help-with-err.rs:12:10
|
LL | arc2.blablabla();
| ^^^^^^^^^ method not found in `Arc<[closure@$DIR/fn-help-with-err.rs:10:36: 10:40]>`
|
= note: `arc2` is a function, perhaps you wish to call it
| ---- ^^^^^^^^^ method not found in `Arc<[closure@$DIR/fn-help-with-err.rs:10:36: 10:40]>`
| |
| this is a function, perhaps you wish to call it
error: aborting due to 3 previous errors

View File

@ -2,17 +2,17 @@ error[E0599]: no method named `x` found for fn item `fn() -> Ret {Obj::func}` in
--> $DIR/issue-29124.rs:15:15
|
LL | Obj::func.x();
| ^ method not found in `fn() -> Ret {Obj::func}`
|
= note: `Obj::func` is a function, perhaps you wish to call it
| --------- ^ method not found in `fn() -> Ret {Obj::func}`
| |
| this is a function, perhaps you wish to call it
error[E0599]: no method named `x` found for fn item `fn() -> Ret {func}` in the current scope
--> $DIR/issue-29124.rs:17:10
|
LL | func.x();
| ^ method not found in `fn() -> Ret {func}`
|
= note: `func` is a function, perhaps you wish to call it
| ---- ^ method not found in `fn() -> Ret {func}`
| |
| this is a function, perhaps you wish to call it
error: aborting due to 2 previous errors

View File

@ -2,9 +2,10 @@ error[E0599]: no method named `f` found for fn pointer `fn(&u8)` in the current
--> $DIR/issue-57362-1.rs:20:7
|
LL | a.f();
| ^ method not found in `fn(&u8)`
| - ^ method not found in `fn(&u8)`
| |
| this is a function, perhaps you wish to call it
|
= note: `a` is a function, perhaps you wish to call it
= help: items from traits can only be used if the trait is implemented and in scope
note: `Trait` defines an item `f`, perhaps you need to implement it
--> $DIR/issue-57362-1.rs:8:1

View File

@ -0,0 +1,14 @@
struct Bar<T> {
bar: T
}
struct Foo();
impl Foo {
fn foo() { }
}
fn main() {
let thing = Bar { bar: Foo };
thing.bar.foo();
//~^ ERROR no method named `foo` found for fn item `fn() -> Foo {Foo}` in the current scope [E0599]
}

View File

@ -0,0 +1,16 @@
error[E0599]: no method named `foo` found for fn item `fn() -> Foo {Foo}` in the current scope
--> $DIR/empty-tuple-method.rs:12:15
|
LL | thing.bar.foo();
| --------- ^^^ method not found in `fn() -> Foo {Foo}`
| |
| this is the constructor of a struct
|
help: call the constructor
|
LL | (thing.bar)().foo();
| + +++
error: aborting due to previous error
For more information about this error, try `rustc --explain E0599`.

View File

@ -0,0 +1,16 @@
struct Bar<T> {
bar: T
}
enum Foo{
Tup()
}
impl Foo {
fn foo() { }
}
fn main() {
let thing = Bar { bar: Foo::Tup };
thing.bar.foo();
//~^ ERROR no method named `foo` found for fn item `fn() -> Foo {Foo::Tup}` in the current scope [E0599]
}

View File

@ -0,0 +1,16 @@
error[E0599]: no method named `foo` found for fn item `fn() -> Foo {Foo::Tup}` in the current scope
--> $DIR/enum-variant.rs:14:15
|
LL | thing.bar.foo();
| --------- ^^^ method not found in `fn() -> Foo {Foo::Tup}`
| |
| this is the constructor of an enum variant
|
help: call the constructor
|
LL | (thing.bar)().foo();
| + +++
error: aborting due to previous error
For more information about this error, try `rustc --explain E0599`.

View File

@ -0,0 +1,14 @@
struct Bar<T> {
bar: T
}
struct Foo(char, u16);
impl Foo {
fn foo() { }
}
fn main() {
let thing = Bar { bar: Foo };
thing.bar.0;
//~^ ERROR no field `0` on type `fn(char, u16) -> Foo {Foo}` [E0609]
}

View File

@ -0,0 +1,16 @@
error[E0609]: no field `0` on type `fn(char, u16) -> Foo {Foo}`
--> $DIR/tuple-field.rs:12:15
|
LL | thing.bar.0;
| --------- ^
| |
| this is the constructor of a struct
|
help: call the constructor
|
LL | (thing.bar)(_, _).0;
| + +++++++
error: aborting due to previous error
For more information about this error, try `rustc --explain E0609`.

View File

@ -0,0 +1,14 @@
struct Bar<T> {
bar: T
}
struct Foo(u8, i32);
impl Foo {
fn foo() { }
}
fn main() {
let thing = Bar { bar: Foo };
thing.bar.foo();
//~^ ERROR no method named `foo` found for fn item `fn(u8, i32) -> Foo {Foo}` in the current scope [E0599]
}

View File

@ -0,0 +1,16 @@
error[E0599]: no method named `foo` found for fn item `fn(u8, i32) -> Foo {Foo}` in the current scope
--> $DIR/tuple-method.rs:12:15
|
LL | thing.bar.foo();
| --------- ^^^ method not found in `fn(u8, i32) -> Foo {Foo}`
| |
| this is the constructor of a struct
|
help: call the constructor
|
LL | (thing.bar)(_, _).foo();
| + +++++++
error: aborting due to previous error
For more information about this error, try `rustc --explain E0599`.

View File

@ -2,9 +2,9 @@ error[E0599]: no method named `call` found for closure `[closure@$DIR/unboxed-cl
--> $DIR/unboxed-closures-static-call-wrong-trait.rs:7:10
|
LL | mut_.call((0, ));
| ^^^^ method not found in `[closure@$DIR/unboxed-closures-static-call-wrong-trait.rs:6:26: 6:31]`
|
= note: `mut_` is a function, perhaps you wish to call it
| ---- ^^^^ method not found in `[closure@$DIR/unboxed-closures-static-call-wrong-trait.rs:6:26: 6:31]`
| |
| this is a function, perhaps you wish to call it
error: aborting due to previous error