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, _ => 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. // 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 crate::type_error_struct;
use super::suggest_call_constructor;
use crate::errors::{AddressOfTemporaryTaken, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive}; use crate::errors::{AddressOfTemporaryTaken, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive};
use rustc_ast as ast; use rustc_ast as ast;
use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_errors::Diagnostic; use rustc_errors::Diagnostic;
use rustc_errors::EmissionGuarantee;
use rustc_errors::ErrorGuaranteed; use rustc_errors::ErrorGuaranteed;
use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder, DiagnosticId}; use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder, DiagnosticId};
use rustc_hir as hir; use rustc_hir as hir;
@ -1986,6 +1988,26 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.tcx().ty_error() 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( fn suggest_await_on_field_access(
&self, &self,
err: &mut Diagnostic, err: &mut Diagnostic,
@ -2055,6 +2077,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
ty::Opaque(_, _) => { ty::Opaque(_, _) => {
self.suggest_await_on_field_access(&mut err, field, base, expr_t.peel_refs()); 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, MultiSpan,
}; };
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_hir::lang_items::LangItem; use rustc_hir::lang_items::LangItem;
use rustc_hir::{ExprKind, Node, QPath}; use rustc_hir::{ExprKind, Node, QPath};
@ -29,7 +30,7 @@ use std::cmp::Ordering;
use std::iter; use std::iter;
use super::probe::{Mode, ProbeScope}; 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> { impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
fn is_fn_ty(&self, ty: Ty<'tcx>, span: Span) -> bool { 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) { 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 SelfSource::MethodCall(expr) = source {
if let Ok(expr_string) = tcx.sess.source_map().span_to_snippet(expr.span) { let suggest = if let ty::FnDef(def_id, _) = rcvr_ty.kind() {
report_function(&mut err, expr_string); let local_id = def_id.expect_local();
} else if let ExprKind::Path(QPath::Resolved(_, path)) = expr.kind { let hir_id = tcx.hir().local_def_id_to_hir_id(local_id);
if let Some(segment) = path.segments.last() { let node = tcx.hir().get(hir_id);
report_function(&mut err, segment.ident); 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 diverges::Diverges;
pub use expectation::Expectation; pub use expectation::Expectation;
pub use fn_ctxt::*; pub use fn_ctxt::*;
use hir::def::CtorOf;
pub use inherited::{Inherited, InheritedBuilder}; pub use inherited::{Inherited, InheritedBuilder};
use crate::astconv::AstConv; use crate::astconv::AstConv;
use crate::check::gather_locals::GatherLocalsVisitor; use crate::check::gather_locals::GatherLocalsVisitor;
use rustc_data_structures::fx::{FxHashMap, FxHashSet}; 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 as hir;
use rustc_hir::def::Res; use rustc_hir::def::Res;
use rustc_hir::def_id::{DefId, LocalDefId}; 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 } 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); let arc = std::sync::Arc::new(oops);
//~^ ERROR cannot find value `oops` in this scope //~^ ERROR cannot find value `oops` in this scope
//~| NOTE not found //~| 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(); arc.blablabla();
//~^ ERROR no method named `blablabla` //~^ ERROR no method named `blablabla`
//~| NOTE method not found //~| NOTE method not found
let arc2 = std::sync::Arc::new(|| 1); 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(); arc2.blablabla();
//~^ ERROR no method named `blablabla` //~^ ERROR no method named `blablabla`
//~| NOTE method not found //~| 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 --> $DIR/fn-help-with-err.rs:12:10
| |
LL | arc2.blablabla(); LL | arc2.blablabla();
| ^^^^^^^^^ method not found in `Arc<[closure@$DIR/fn-help-with-err.rs:10:36: 10:40]>` | ---- ^^^^^^^^^ 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 | this is a function, perhaps you wish to call it
error: aborting due to 3 previous errors 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 --> $DIR/issue-29124.rs:15:15
| |
LL | Obj::func.x(); LL | Obj::func.x();
| ^ method not found in `fn() -> Ret {Obj::func}` | --------- ^ method not found in `fn() -> Ret {Obj::func}`
| | |
= note: `Obj::func` is a function, perhaps you wish to call it | 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 error[E0599]: no method named `x` found for fn item `fn() -> Ret {func}` in the current scope
--> $DIR/issue-29124.rs:17:10 --> $DIR/issue-29124.rs:17:10
| |
LL | func.x(); LL | func.x();
| ^ method not found in `fn() -> Ret {func}` | ---- ^ method not found in `fn() -> Ret {func}`
| | |
= note: `func` is a function, perhaps you wish to call it | this is a function, perhaps you wish to call it
error: aborting due to 2 previous errors 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 --> $DIR/issue-57362-1.rs:20:7
| |
LL | a.f(); 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 = 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 note: `Trait` defines an item `f`, perhaps you need to implement it
--> $DIR/issue-57362-1.rs:8:1 --> $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 --> $DIR/unboxed-closures-static-call-wrong-trait.rs:7:10
| |
LL | mut_.call((0, )); LL | mut_.call((0, ));
| ^^^^ method not found in `[closure@$DIR/unboxed-closures-static-call-wrong-trait.rs:6:26: 6:31]` | ---- ^^^^ 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 | this is a function, perhaps you wish to call it
error: aborting due to previous error error: aborting due to previous error