diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index ef56093bed8d9..2d01673b61d31 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 480a551224929..47cb1ea48cb54 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( + &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 88e0a4bada845..95c9c775e3764 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(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 0d5e7b28a4e65..f7bb30cd13e6f 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( + 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 f8a81af786f7e..3d2bcb8ad3550 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 4d6b3282ad9e9..3e42cb1fb6ec0 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 42d89cd01a48e..c5d2ec0840996 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 5c611cd43d3cc..8e19f14009a0e 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 0000000000000..1875d8280cb62 --- /dev/null +++ b/src/test/ui/typeck/issue-87181/empty-tuple-method.rs @@ -0,0 +1,14 @@ +struct Bar { + 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 0000000000000..6ed70b301e4a4 --- /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 0000000000000..3b926b90f10bb --- /dev/null +++ b/src/test/ui/typeck/issue-87181/enum-variant.rs @@ -0,0 +1,16 @@ +struct Bar { + 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 0000000000000..a3a818696ab5b --- /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 0000000000000..00e3b460ecf32 --- /dev/null +++ b/src/test/ui/typeck/issue-87181/tuple-field.rs @@ -0,0 +1,14 @@ +struct Bar { + 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 0000000000000..4d22ada0247e9 --- /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 0000000000000..e88f642b0707b --- /dev/null +++ b/src/test/ui/typeck/issue-87181/tuple-method.rs @@ -0,0 +1,14 @@ +struct Bar { + 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 0000000000000..1e392e17984b0 --- /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 0b6d94e71f0c7..e9883903674ad 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