about summary refs log tree commit diff
diff options
context:
space:
mode:
authorDylan DPC <99973273+Dylan-DPC@users.noreply.github.com>2022-04-27 02:47:07 +0200
committerGitHub <noreply@github.com>2022-04-27 02:47:07 +0200
commitac46e17c02c7be374827c2f9df899cd053659dff (patch)
tree9e843d91da7a750b8d1d43c6170aa3750aabc3c6
parent082e4ca49770ebc9cb0ee616f3726a67471be8cb (diff)
parenta6b570b209d4806cf0f6e424145ab33bc42da6be (diff)
downloadrust-ac46e17c02c7be374827c2f9df899cd053659dff.tar.gz
rust-ac46e17c02c7be374827c2f9df899cd053659dff.zip
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
-rw-r--r--compiler/rustc_hir/src/hir.rs6
-rw-r--r--compiler/rustc_typeck/src/check/expr.rs25
-rw-r--r--compiler/rustc_typeck/src/check/method/suggest.rs38
-rw-r--r--compiler/rustc_typeck/src/check/mod.rs38
-rw-r--r--src/test/ui/functions-closures/fn-help-with-err.rs6
-rw-r--r--src/test/ui/functions-closures/fn-help-with-err.stderr6
-rw-r--r--src/test/ui/issues/issue-29124.stderr12
-rw-r--r--src/test/ui/issues/issue-57362-1.stderr5
-rw-r--r--src/test/ui/typeck/issue-87181/empty-tuple-method.rs14
-rw-r--r--src/test/ui/typeck/issue-87181/empty-tuple-method.stderr16
-rw-r--r--src/test/ui/typeck/issue-87181/enum-variant.rs16
-rw-r--r--src/test/ui/typeck/issue-87181/enum-variant.stderr16
-rw-r--r--src/test/ui/typeck/issue-87181/tuple-field.rs14
-rw-r--r--src/test/ui/typeck/issue-87181/tuple-field.stderr16
-rw-r--r--src/test/ui/typeck/issue-87181/tuple-method.rs14
-rw-r--r--src/test/ui/typeck/issue-87181/tuple-method.stderr16
-rw-r--r--src/test/ui/unboxed-closures/unboxed-closures-static-call-wrong-trait.stderr6
17 files changed, 234 insertions, 30 deletions
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index ef56093bed8..2d01673b61d 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -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.
diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs
index 480a5512249..47cb1ea48cb 100644
--- a/compiler/rustc_typeck/src/check/expr.rs
+++ b/compiler/rustc_typeck/src/check/expr.rs
@@ -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);
+            }
             _ => {}
         }
 
diff --git a/compiler/rustc_typeck/src/check/method/suggest.rs b/compiler/rustc_typeck/src/check/method/suggest.rs
index 88e0a4bada8..95c9c775e37 100644
--- a/compiler/rustc_typeck/src/check/method/suggest.rs
+++ b/compiler/rustc_typeck/src/check/method/suggest.rs
@@ -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",
+                            );
                         }
                     }
                 }
diff --git a/compiler/rustc_typeck/src/check/mod.rs b/compiler/rustc_typeck/src/check/mod.rs
index 0d5e7b28a4e..f7bb30cd13e 100644
--- a/compiler/rustc_typeck/src/check/mod.rs
+++ b/compiler/rustc_typeck/src/check/mod.rs
@@ -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,
+    );
+}
diff --git a/src/test/ui/functions-closures/fn-help-with-err.rs b/src/test/ui/functions-closures/fn-help-with-err.rs
index f8a81af786f..3d2bcb8ad35 100644
--- a/src/test/ui/functions-closures/fn-help-with-err.rs
+++ b/src/test/ui/functions-closures/fn-help-with-err.rs
@@ -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
 }
diff --git a/src/test/ui/functions-closures/fn-help-with-err.stderr b/src/test/ui/functions-closures/fn-help-with-err.stderr
index 4d6b3282ad9..3e42cb1fb6e 100644
--- a/src/test/ui/functions-closures/fn-help-with-err.stderr
+++ b/src/test/ui/functions-closures/fn-help-with-err.stderr
@@ -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
 
diff --git a/src/test/ui/issues/issue-29124.stderr b/src/test/ui/issues/issue-29124.stderr
index 42d89cd01a4..c5d2ec08409 100644
--- a/src/test/ui/issues/issue-29124.stderr
+++ b/src/test/ui/issues/issue-29124.stderr
@@ -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
 
diff --git a/src/test/ui/issues/issue-57362-1.stderr b/src/test/ui/issues/issue-57362-1.stderr
index 5c611cd43d3..8e19f14009a 100644
--- a/src/test/ui/issues/issue-57362-1.stderr
+++ b/src/test/ui/issues/issue-57362-1.stderr
@@ -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
diff --git a/src/test/ui/typeck/issue-87181/empty-tuple-method.rs b/src/test/ui/typeck/issue-87181/empty-tuple-method.rs
new file mode 100644
index 00000000000..1875d8280cb
--- /dev/null
+++ b/src/test/ui/typeck/issue-87181/empty-tuple-method.rs
@@ -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]
+}
diff --git a/src/test/ui/typeck/issue-87181/empty-tuple-method.stderr b/src/test/ui/typeck/issue-87181/empty-tuple-method.stderr
new file mode 100644
index 00000000000..6ed70b301e4
--- /dev/null
+++ b/src/test/ui/typeck/issue-87181/empty-tuple-method.stderr
@@ -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`.
diff --git a/src/test/ui/typeck/issue-87181/enum-variant.rs b/src/test/ui/typeck/issue-87181/enum-variant.rs
new file mode 100644
index 00000000000..3b926b90f10
--- /dev/null
+++ b/src/test/ui/typeck/issue-87181/enum-variant.rs
@@ -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]
+}
diff --git a/src/test/ui/typeck/issue-87181/enum-variant.stderr b/src/test/ui/typeck/issue-87181/enum-variant.stderr
new file mode 100644
index 00000000000..a3a818696ab
--- /dev/null
+++ b/src/test/ui/typeck/issue-87181/enum-variant.stderr
@@ -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`.
diff --git a/src/test/ui/typeck/issue-87181/tuple-field.rs b/src/test/ui/typeck/issue-87181/tuple-field.rs
new file mode 100644
index 00000000000..00e3b460ecf
--- /dev/null
+++ b/src/test/ui/typeck/issue-87181/tuple-field.rs
@@ -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]
+}
diff --git a/src/test/ui/typeck/issue-87181/tuple-field.stderr b/src/test/ui/typeck/issue-87181/tuple-field.stderr
new file mode 100644
index 00000000000..4d22ada0247
--- /dev/null
+++ b/src/test/ui/typeck/issue-87181/tuple-field.stderr
@@ -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`.
diff --git a/src/test/ui/typeck/issue-87181/tuple-method.rs b/src/test/ui/typeck/issue-87181/tuple-method.rs
new file mode 100644
index 00000000000..e88f642b070
--- /dev/null
+++ b/src/test/ui/typeck/issue-87181/tuple-method.rs
@@ -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]
+}
diff --git a/src/test/ui/typeck/issue-87181/tuple-method.stderr b/src/test/ui/typeck/issue-87181/tuple-method.stderr
new file mode 100644
index 00000000000..1e392e17984
--- /dev/null
+++ b/src/test/ui/typeck/issue-87181/tuple-method.stderr
@@ -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`.
diff --git a/src/test/ui/unboxed-closures/unboxed-closures-static-call-wrong-trait.stderr b/src/test/ui/unboxed-closures/unboxed-closures-static-call-wrong-trait.stderr
index 0b6d94e71f0..e9883903674 100644
--- a/src/test/ui/unboxed-closures/unboxed-closures-static-call-wrong-trait.stderr
+++ b/src/test/ui/unboxed-closures/unboxed-closures-static-call-wrong-trait.stderr
@@ -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