From 244322f0be1296f5ac4d6fc0dcc603b5ccab2832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 20 Mar 2026 18:44:08 +0000 Subject: [PATCH 1/3] Make `:` -> `=` typo suggestion verbose --- compiler/rustc_parse/src/parser/stmt.rs | 4 ++-- compiler/rustc_resolve/src/late/diagnostics.rs | 2 +- tests/ui/parser/recover/array-type-no-semi.stderr | 7 ++++++- .../recover-colon-instead-of-eq-in-local.stderr | 7 ++++++- .../ui/suggestions/let-binding-init-expr-as-ty.stderr | 10 +++++++--- .../type/type-ascription-instead-of-initializer.stderr | 7 ++++++- 6 files changed, 28 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 3b2102484bdf..44886de52312 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -362,7 +362,7 @@ fn parse_local(&mut self, super_: Option, attrs: AttrVec) -> PResult<'a, B // init parsed, ty error // Could parse the type as if it were the initializer, it is likely there was a // typo in the code: `:` instead of `=`. Add suggestion and emit the error. - err.span_suggestion_short( + err.span_suggestion_verbose( colon_sp, "use `=` if you meant to assign", " =", @@ -1134,7 +1134,7 @@ pub fn parse_full_stmt( false }; if suggest_eq { - e.span_suggestion_short( + e.span_suggestion_verbose( colon_sp, "use `=` if you meant to assign", "=", diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 95a6b25d54e6..80e7a957c4d2 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -1271,7 +1271,7 @@ fn suggest_typo( Some((pat_sp, Some(ty_sp), None)) if ty_sp.contains(base_error.span) && base_error.could_be_expr => { - err.span_suggestion_short( + err.span_suggestion_verbose( pat_sp.between(ty_sp), "use `=` if you meant to assign", " = ", diff --git a/tests/ui/parser/recover/array-type-no-semi.stderr b/tests/ui/parser/recover/array-type-no-semi.stderr index 45f39fefe5e3..56c78b01ea39 100644 --- a/tests/ui/parser/recover/array-type-no-semi.stderr +++ b/tests/ui/parser/recover/array-type-no-semi.stderr @@ -17,7 +17,12 @@ LL | let a: [i32, ]; | - ^ expected `;` or `]` | | | while parsing the type for `a` - | help: use `=` if you meant to assign + | +help: use `=` if you meant to assign + | +LL - let a: [i32, ]; +LL + let a = [i32, ]; + | error: expected `;` or `]`, found `,` --> $DIR/array-type-no-semi.rs:12:16 diff --git a/tests/ui/parser/recover/recover-colon-instead-of-eq-in-local.stderr b/tests/ui/parser/recover/recover-colon-instead-of-eq-in-local.stderr index 15c27bb9451b..8c238c1dd307 100644 --- a/tests/ui/parser/recover/recover-colon-instead-of-eq-in-local.stderr +++ b/tests/ui/parser/recover/recover-colon-instead-of-eq-in-local.stderr @@ -13,7 +13,12 @@ LL | let _: std::env::temp_dir().join("foo"); | - ^ expected one of `!`, `+`, `->`, `::`, `;`, or `=` | | | while parsing the type for `_` - | help: use `=` if you meant to assign + | +help: use `=` if you meant to assign + | +LL - let _: std::env::temp_dir().join("foo"); +LL + let _= std::env::temp_dir().join("foo"); + | error: aborting due to 2 previous errors diff --git a/tests/ui/suggestions/let-binding-init-expr-as-ty.stderr b/tests/ui/suggestions/let-binding-init-expr-as-ty.stderr index 19a0e4b17d02..f0887f85d6ef 100644 --- a/tests/ui/suggestions/let-binding-init-expr-as-ty.stderr +++ b/tests/ui/suggestions/let-binding-init-expr-as-ty.stderr @@ -2,9 +2,13 @@ error[E0573]: expected type, found local variable `num` --> $DIR/let-binding-init-expr-as-ty.rs:2:27 | LL | let foo: i32::from_be(num); - | -- ^^^ not a type - | | - | help: use `=` if you meant to assign + | ^^^ not a type + | +help: use `=` if you meant to assign + | +LL - let foo: i32::from_be(num); +LL + let foo = i32::from_be(num); + | error: argument types not allowed with return type notation --> $DIR/let-binding-init-expr-as-ty.rs:2:26 diff --git a/tests/ui/type/type-ascription-instead-of-initializer.stderr b/tests/ui/type/type-ascription-instead-of-initializer.stderr index 630e82d254ee..edc38b0ca7b9 100644 --- a/tests/ui/type/type-ascription-instead-of-initializer.stderr +++ b/tests/ui/type/type-ascription-instead-of-initializer.stderr @@ -5,7 +5,12 @@ LL | let x: Vec::with_capacity(10, 20); | - ^^ expected type | | | while parsing the type for `x` - | help: use `=` if you meant to assign + | +help: use `=` if you meant to assign + | +LL - let x: Vec::with_capacity(10, 20); +LL + let x = Vec::with_capacity(10, 20); + | error[E0061]: this function takes 1 argument but 2 arguments were supplied --> $DIR/type-ascription-instead-of-initializer.rs:2:12 From d48e699b6cdcf76590a141ccf9fde84adb297176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 20 Mar 2026 19:33:05 +0000 Subject: [PATCH 2/3] Emit fewer errors for incorrect rtn and `=` -> `:` typos in bindings Stash parse errors when pasing rtn that doesn't use `(..)`. Emit single error on `Foo::bar(qux)` that looks like rtn in incorrect position and suggest `Foo::bar(..)`. ``` error: return type notation not allowed in this position yet --> $DIR/let-binding-init-expr-as-ty.rs:18:10 | LL | struct K(S::new(())); | ^^^^^^^^^^ | help: furthermore, argument types not allowed with return type notation | LL - struct K(S::new(())); LL + struct K(S::new(..)); | ``` On incorrect rtn in let binding type for an existing inherent associated function, suggest `:` -> `=` to turn the "type" into a call expression. ``` error: expected type, found associated function call --> $DIR/let-binding-init-expr-as-ty.rs:29:12 | LL | let x: S::new(()); | ^^^^^^^^^^ | help: use `=` if you meant to assign | LL - let x: S::new(()); LL + let x = S::new(()); | ``` --- compiler/rustc_ast_lowering/src/path.rs | 3 +- compiler/rustc_errors/src/lib.rs | 1 + compiler/rustc_hir_analysis/src/errors.rs | 7 + .../src/hir_ty_lowering/mod.rs | 93 ++++++++++- .../let-binding-init-expr-as-ty.rs | 36 ++++- .../let-binding-init-expr-as-ty.stderr | 149 ++++++++++++++++-- 6 files changed, 268 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/path.rs b/compiler/rustc_ast_lowering/src/path.rs index ec57720387c0..fe85302c2177 100644 --- a/compiler/rustc_ast_lowering/src/path.rs +++ b/compiler/rustc_ast_lowering/src/path.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use rustc_ast::{self as ast, *}; +use rustc_errors::StashKey; use rustc_hir::def::{DefKind, PartialRes, PerNS, Res}; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, GenericArg}; @@ -298,7 +299,7 @@ pub(crate) fn lower_path_segment( sym::return_type_notation, ); } - err.emit(); + err.stash(path_span, StashKey::ReturnTypeNotation); ( GenericArgsCtor { args: Default::default(), diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 8c57544c54b8..d17a4d6de42f 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -386,6 +386,7 @@ pub enum StashKey { /// it's a method call without parens. If later on in `hir_typeck` we find out that this is /// the case we suppress this message and we give a better suggestion. GenericInFieldExpr, + ReturnTypeNotation, } fn default_track_diagnostic(diag: DiagInner, f: &mut dyn FnMut(DiagInner) -> R) -> R { diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index 1c999f1ffc93..fcd4cb938bf7 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -1807,6 +1807,13 @@ pub(crate) struct CmseImplTrait { pub(crate) struct BadReturnTypeNotation { #[primary_span] pub span: Span, + #[suggestion( + "furthermore, argument types not allowed with return type notation", + applicability = "maybe-incorrect", + code = "(..)", + style = "verbose" + )] + pub suggestion: Option, } #[derive(Diagnostic)] diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index bf97bfb1ebbc..2bba1b723353 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -26,7 +26,7 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_errors::codes::*; use rustc_errors::{ - Applicability, Diag, DiagCtxtHandle, Diagnostic, ErrorGuaranteed, FatalError, Level, + Applicability, Diag, DiagCtxtHandle, Diagnostic, ErrorGuaranteed, FatalError, Level, StashKey, struct_span_code_err, }; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; @@ -3008,7 +3008,9 @@ pub fn lower_ty(&self, hir_ty: &hir::Ty<'tcx>) -> Ty<'tcx> { matches!(args.parenthesized, hir::GenericArgsParentheses::ReturnTypeNotation) }) => { - let guar = self.dcx().emit_err(BadReturnTypeNotation { span: hir_ty.span }); + let guar = self + .dcx() + .emit_err(BadReturnTypeNotation { span: hir_ty.span, suggestion: None }); Ty::new_error(tcx, guar) } hir::TyKind::Path(hir::QPath::Resolved(maybe_qself, path)) => { @@ -3070,12 +3072,95 @@ pub fn lower_ty(&self, hir_ty: &hir::Ty<'tcx>) -> Ty<'tcx> { // If we encounter a type relative path with RTN generics, then it must have // *not* gone through `lower_ty_maybe_return_type_notation`, and therefore // it's certainly in an illegal position. - hir::TyKind::Path(hir::QPath::TypeRelative(_, segment)) + hir::TyKind::Path(hir::QPath::TypeRelative(hir_self_ty, segment)) if segment.args.is_some_and(|args| { matches!(args.parenthesized, hir::GenericArgsParentheses::ReturnTypeNotation) }) => { - let guar = self.dcx().emit_err(BadReturnTypeNotation { span: hir_ty.span }); + let guar = if let hir::Node::LetStmt(stmt) = tcx.parent_hir_node(hir_ty.hir_id) + && let None = stmt.init + && let hir::TyKind::Path(hir::QPath::Resolved(_, self_ty_path)) = + hir_self_ty.kind + && let Res::Def(DefKind::Enum | DefKind::Struct | DefKind::Union, def_id) = + self_ty_path.res + && let Some(_) = tcx + .inherent_impls(def_id) + .iter() + .flat_map(|imp| { + tcx.associated_items(*imp).filter_by_name_unhygienic(segment.ident.name) + }) + .filter(|assoc| { + matches!(assoc.kind, ty::AssocKind::Fn { has_self: false, .. }) + }) + .next() + { + // `let x: S::new(valid_in_ty_ctxt);` -> `let x = S::new(valid_in_ty_ctxt);` + let err = tcx + .dcx() + .struct_span_err( + hir_ty.span, + "expected type, found associated function call", + ) + .with_span_suggestion_verbose( + stmt.pat.span.between(hir_ty.span), + "use `=` if you meant to assign", + " = ".to_string(), + Applicability::MaybeIncorrect, + ); + self.dcx().try_steal_replace_and_emit_err( + hir_ty.span, + StashKey::ReturnTypeNotation, + err, + ) + } else if let hir::Node::LetStmt(stmt) = tcx.parent_hir_node(hir_ty.hir_id) + && let None = stmt.init + && let hir::TyKind::Path(hir::QPath::Resolved(_, self_ty_path)) = + hir_self_ty.kind + && let Res::PrimTy(_) = self_ty_path.res + && self.dcx().has_stashed_diagnostic(hir_ty.span, StashKey::ReturnTypeNotation) + { + // `let x: i32::something(valid_in_ty_ctxt);` -> `let x = i32::something(valid_in_ty_ctxt);` + // FIXME: Check that `something` is a valid function in `i32`. + let err = tcx + .dcx() + .struct_span_err( + hir_ty.span, + "expected type, found associated function call", + ) + .with_span_suggestion_verbose( + stmt.pat.span.between(hir_ty.span), + "use `=` if you meant to assign", + " = ".to_string(), + Applicability::MaybeIncorrect, + ); + self.dcx().try_steal_replace_and_emit_err( + hir_ty.span, + StashKey::ReturnTypeNotation, + err, + ) + } else { + let suggestion = if self + .dcx() + .has_stashed_diagnostic(hir_ty.span, StashKey::ReturnTypeNotation) + { + // We already created a diagnostic complaining that `foo(bar)` is wrong and + // should have been `foo(..)`. Instead, emit only the current error and + // include that prior suggestion. Changes are that the problems go further, + // but keep the suggestion just in case. Either way, we want a single error + // instead of two. + Some(segment.ident.span.shrink_to_hi().with_hi(hir_ty.span.hi())) + } else { + None + }; + let err = self + .dcx() + .create_err(BadReturnTypeNotation { span: hir_ty.span, suggestion }); + self.dcx().try_steal_replace_and_emit_err( + hir_ty.span, + StashKey::ReturnTypeNotation, + err, + ) + }; Ty::new_error(tcx, guar) } hir::TyKind::Path(hir::QPath::TypeRelative(hir_self_ty, segment)) => { diff --git a/tests/ui/suggestions/let-binding-init-expr-as-ty.rs b/tests/ui/suggestions/let-binding-init-expr-as-ty.rs index 71e5a0c728d6..c108b567b18b 100644 --- a/tests/ui/suggestions/let-binding-init-expr-as-ty.rs +++ b/tests/ui/suggestions/let-binding-init-expr-as-ty.rs @@ -1,11 +1,41 @@ -pub fn foo(num: i32) -> i32 { +fn foo(num: i32) -> i32 { + // FIXME: This case doesn't really check that `from_be` is a valid function in `i32`. let foo: i32::from_be(num); //~^ ERROR expected type, found local variable `num` - //~| ERROR argument types not allowed with return type notation - //~| ERROR return type notation not allowed in this position yet + //~| ERROR expected type, found associated function call foo } +struct S; + +impl S { + fn new(_: ()) -> S { + S + } +} + +// We should still mention that it should be `S::new(..)`, even though rtn is not allowed there: +struct K(S::new(())); //~ ERROR return type notation not allowed in this position yet + +fn bar() {} + fn main() { let _ = foo(42); + // Associated functions (#134087) + let x: Vec::new(); //~ ERROR expected type, found associated function call + let x: Vec<()>::new(); //~ ERROR expected type, found associated function call + let x: S::new(..); //~ ERROR expected type, found associated function call + //~^ ERROR return type notation is experimental + let x: S::new(()); //~ ERROR expected type, found associated function call + + // Literals + let x: 42; //~ ERROR expected type, found `42` + let x: ""; //~ ERROR expected type, found `""` + + // Functions + let x: bar(); //~ ERROR expected type, found function `bar` + let x: bar; //~ ERROR expected type, found function `bar` + + // Locals + let x: x; //~ ERROR expected type, found local variable `x` } diff --git a/tests/ui/suggestions/let-binding-init-expr-as-ty.stderr b/tests/ui/suggestions/let-binding-init-expr-as-ty.stderr index f0887f85d6ef..354d11976911 100644 --- a/tests/ui/suggestions/let-binding-init-expr-as-ty.stderr +++ b/tests/ui/suggestions/let-binding-init-expr-as-ty.stderr @@ -1,5 +1,33 @@ +error: expected type, found `42` + --> $DIR/let-binding-init-expr-as-ty.rs:32:12 + | +LL | let x: 42; + | - ^^ expected type + | | + | while parsing the type for `x` + | +help: use `=` if you meant to assign + | +LL - let x: 42; +LL + let x = 42; + | + +error: expected type, found `""` + --> $DIR/let-binding-init-expr-as-ty.rs:33:12 + | +LL | let x: ""; + | - ^^ expected type + | | + | while parsing the type for `x` + | +help: use `=` if you meant to assign + | +LL - let x: ""; +LL + let x = ""; + | + error[E0573]: expected type, found local variable `num` - --> $DIR/let-binding-init-expr-as-ty.rs:2:27 + --> $DIR/let-binding-init-expr-as-ty.rs:3:27 | LL | let foo: i32::from_be(num); | ^^^ not a type @@ -10,27 +38,122 @@ LL - let foo: i32::from_be(num); LL + let foo = i32::from_be(num); | -error: argument types not allowed with return type notation - --> $DIR/let-binding-init-expr-as-ty.rs:2:26 +error[E0573]: expected type, found function `bar` + --> $DIR/let-binding-init-expr-as-ty.rs:36:12 | -LL | let foo: i32::from_be(num); - | ^^^^^ +LL | let x: bar(); + | ^^^^^ not a type + | +help: use `=` if you meant to assign + | +LL - let x: bar(); +LL + let x = bar(); + | + +error[E0573]: expected type, found function `bar` + --> $DIR/let-binding-init-expr-as-ty.rs:37:12 + | +LL | let x: bar; + | ^^^ not a type + +error[E0573]: expected type, found local variable `x` + --> $DIR/let-binding-init-expr-as-ty.rs:40:12 + | +LL | struct K(S::new(())); + | --------------------- similarly named struct `K` defined here +... +LL | let x: x; + | ^ + | +help: a struct with a similar name exists + | +LL - let x: x; +LL + let x: K; + | + +error[E0658]: return type notation is experimental + --> $DIR/let-binding-init-expr-as-ty.rs:27:18 + | +LL | let x: S::new(..); + | ^^^^ | = note: see issue #109417 for more information = help: add `#![feature(return_type_notation)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -help: remove the input types - | -LL - let foo: i32::from_be(num); -LL + let foo: i32::from_be(..); - | error: return type notation not allowed in this position yet - --> $DIR/let-binding-init-expr-as-ty.rs:2:14 + --> $DIR/let-binding-init-expr-as-ty.rs:18:10 + | +LL | struct K(S::new(())); + | ^^^^^^^^^^ + | +help: furthermore, argument types not allowed with return type notation + | +LL - struct K(S::new(())); +LL + struct K(S::new(..)); + | + +error: expected type, found associated function call + --> $DIR/let-binding-init-expr-as-ty.rs:3:14 | LL | let foo: i32::from_be(num); | ^^^^^^^^^^^^^^^^^ + | +help: use `=` if you meant to assign + | +LL - let foo: i32::from_be(num); +LL + let foo = i32::from_be(num); + | -error: aborting due to 3 previous errors +error: expected type, found associated function call + --> $DIR/let-binding-init-expr-as-ty.rs:25:12 + | +LL | let x: Vec::new(); + | ^^^^^^^^^^ + | +help: use `=` if you meant to assign + | +LL - let x: Vec::new(); +LL + let x = Vec::new(); + | -For more information about this error, try `rustc --explain E0573`. +error: expected type, found associated function call + --> $DIR/let-binding-init-expr-as-ty.rs:26:12 + | +LL | let x: Vec<()>::new(); + | ^^^^^^^^^^^^^^ + | +help: use `=` if you meant to assign + | +LL - let x: Vec<()>::new(); +LL + let x = Vec<()>::new(); + | + +error: expected type, found associated function call + --> $DIR/let-binding-init-expr-as-ty.rs:27:12 + | +LL | let x: S::new(..); + | ^^^^^^^^^^ + | +help: use `=` if you meant to assign + | +LL - let x: S::new(..); +LL + let x = S::new(..); + | + +error: expected type, found associated function call + --> $DIR/let-binding-init-expr-as-ty.rs:29:12 + | +LL | let x: S::new(()); + | ^^^^^^^^^^ + | +help: use `=` if you meant to assign + | +LL - let x: S::new(()); +LL + let x = S::new(()); + | + +error: aborting due to 13 previous errors + +Some errors have detailed explanations: E0573, E0658. +For more information about an error, try `rustc --explain E0573`. From 3b27a3660185f7644e1fbf1582637947e36036fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 20 Mar 2026 19:45:29 +0000 Subject: [PATCH 3/3] Suggest appropriate spaces around `=` in `let` binding parse error --- compiler/rustc_parse/src/parser/stmt.rs | 6 +++--- .../recover/recover-colon-instead-of-eq-in-local.stderr | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 44886de52312..5bd2ca313922 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -1133,11 +1133,11 @@ pub fn parse_full_stmt( } else { false }; - if suggest_eq { + if suggest_eq && let Some(ty) = &local.ty { e.span_suggestion_verbose( - colon_sp, + local.pat.span.between(ty.span), "use `=` if you meant to assign", - "=", + " = ", Applicability::MaybeIncorrect, ); } diff --git a/tests/ui/parser/recover/recover-colon-instead-of-eq-in-local.stderr b/tests/ui/parser/recover/recover-colon-instead-of-eq-in-local.stderr index 8c238c1dd307..94e5d44064f8 100644 --- a/tests/ui/parser/recover/recover-colon-instead-of-eq-in-local.stderr +++ b/tests/ui/parser/recover/recover-colon-instead-of-eq-in-local.stderr @@ -17,7 +17,7 @@ LL | let _: std::env::temp_dir().join("foo"); help: use `=` if you meant to assign | LL - let _: std::env::temp_dir().join("foo"); -LL + let _= std::env::temp_dir().join("foo"); +LL + let _ = std::env::temp_dir().join("foo"); | error: aborting due to 2 previous errors