From 3c921e1f07b0390254bdc7734dd90dbf1024298e Mon Sep 17 00:00:00 2001 From: Kao-Wei Yeh Date: Sun, 17 May 2026 22:48:47 +0800 Subject: [PATCH 1/4] feat: add diagnostic for E0422 --- crates/hir-ty/src/infer.rs | 4 ++ crates/hir-ty/src/infer/expr.rs | 2 +- crates/hir/src/diagnostics.rs | 10 +++++ .../src/handlers/inactive_code.rs | 3 +- .../src/handlers/unresolved_record_expr.rs | 45 +++++++++++++++++++ crates/ide-diagnostics/src/lib.rs | 2 + 6 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 crates/ide-diagnostics/src/handlers/unresolved_record_expr.rs diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 51e9435c804d..04694f921de1 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -352,6 +352,10 @@ pub enum InferenceDiagnostic { #[type_visitable(ignore)] id: ExprOrPatId, }, + UnresolvedRecordExpr { + #[type_visitable(ignore)] + expr: ExprId, + }, // FIXME: This should be emitted in body lowering BreakOutsideOfLoop { #[type_visitable(ignore)] diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 3cff0ea89693..59b95dab32b6 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -964,7 +964,7 @@ impl<'db> InferenceContext<'_, 'db> { ) -> Ty<'db> { // Find the relevant variant let (adt_ty, Some(variant)) = self.resolve_variant(expr.into(), path, false) else { - // FIXME: Emit an error. + self.push_diagnostic(InferenceDiagnostic::UnresolvedRecordExpr { expr }); for field in fields { self.infer_expr_no_expect(field.expr, ExprIsRead::Yes); } diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index f4a29a4bcc08..2a3f2803bb88 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -160,6 +160,7 @@ diagnostics![AnyDiagnostic<'db> -> UnresolvedMethodCall<'db>, UnresolvedModule, UnresolvedIdent, + UnresolvedRecordExpr, UnusedMut, UnusedVariable, GenericArgsProhibited, @@ -362,6 +363,11 @@ pub struct UnresolvedIdent { pub node: InFile<(ExprOrPatPtr, Option)>, } +#[derive(Debug)] +pub struct UnresolvedRecordExpr { + pub expr: InFile, +} + #[derive(Debug)] pub struct PrivateField { pub expr: InFile, @@ -901,6 +907,10 @@ impl<'db> AnyDiagnostic<'db> { }; UnresolvedIdent { node }.into() } + &InferenceDiagnostic::UnresolvedRecordExpr { expr } => { + let expr = expr_syntax(expr)?; + UnresolvedRecordExpr { expr }.into() + } &InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break, bad_value_break } => { let expr = expr_syntax(expr)?; BreakOutsideOfLoop { expr, is_break, bad_value_break }.into() diff --git a/crates/ide-diagnostics/src/handlers/inactive_code.rs b/crates/ide-diagnostics/src/handlers/inactive_code.rs index 09f3e8bfb319..b48e85a2d358 100644 --- a/crates/ide-diagnostics/src/handlers/inactive_code.rs +++ b/crates/ide-diagnostics/src/handlers/inactive_code.rs @@ -71,7 +71,8 @@ fn f() { fn abc() {} abc(#[cfg(a)] 0); //^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled - let x = Struct { + struct Struct {} + let _x = Struct { #[cfg(a)] f: 0, //^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: a is disabled }; diff --git a/crates/ide-diagnostics/src/handlers/unresolved_record_expr.rs b/crates/ide-diagnostics/src/handlers/unresolved_record_expr.rs new file mode 100644 index 000000000000..5e11dbc0d704 --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/unresolved_record_expr.rs @@ -0,0 +1,45 @@ +use either::Either; +use syntax::{AstNode, ast::Expr}; + +use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; + +// Diagnostic: unresolved-record-expr +// +// This diagnostic is triggered if the struct, variant, or union type referred to by a record expression does not exist in the current scope. +pub(crate) fn unresolved_record_expr( + ctx: &DiagnosticsContext<'_, '_>, + d: &hir::UnresolvedRecordExpr, +) -> Diagnostic { + Diagnostic::new( + DiagnosticCode::RustcHardError("E0422"), + "cannot find struct, variant or union type in this scope".to_owned(), + crate::adjusted_display_range(ctx, d.expr, &|expr| match expr { + Either::Left(Expr::RecordExpr(it)) => it.path().map(|p| p.syntax().text_range()), + _ => None, + }), + ) + .stable() +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn unresolved_record_expr() { + check_diagnostics( + r#" +struct Exist { + x: i32, + y: i32, +} + +fn main() { + let _ = Exist { x: 1, y: 2 }; + let _ = DoesNotExist { x: 1, y: 2 }; + // ^^^^^^^^^^^^ error: cannot find struct, variant or union type in this scope +} +"#, + ); + } +} diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index d38780ede234..ba1d04436550 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -92,6 +92,7 @@ mod handlers { pub(crate) mod unresolved_macro_call; pub(crate) mod unresolved_method; pub(crate) mod unresolved_module; + pub(crate) mod unresolved_record_expr; pub(crate) mod unused_must_use; pub(crate) mod unused_variables; @@ -501,6 +502,7 @@ pub fn semantic_diagnostics( AnyDiagnostic::UnresolvedMacroCall(d) => handlers::unresolved_macro_call::unresolved_macro_call(&ctx, &d), AnyDiagnostic::UnresolvedMethodCall(d) => handlers::unresolved_method::unresolved_method(&ctx, &d), AnyDiagnostic::UnresolvedModule(d) => handlers::unresolved_module::unresolved_module(&ctx, &d), + AnyDiagnostic::UnresolvedRecordExpr(d) => handlers::unresolved_record_expr::unresolved_record_expr(&ctx, &d), AnyDiagnostic::UnusedMustUse(d) => handlers::unused_must_use::unused_must_use(&ctx, &d), AnyDiagnostic::UnusedMut(d) => match handlers::mutability_errors::unused_mut(&ctx, &d) { Some(it) => it, From 5387cb4636fb85c5227d330f02740f62fb7dcd9e Mon Sep 17 00:00:00 2001 From: Kao-Wei Yeh Date: Wed, 20 May 2026 22:46:22 +0800 Subject: [PATCH 2/4] Add diagnostic for `RecordPat`, `TupleStructPat` --- crates/hir-ty/src/infer.rs | 12 ++- crates/hir-ty/src/infer/expr.rs | 1 - crates/hir/src/diagnostics.rs | 12 +-- .../src/handlers/unresolved_record_expr.rs | 45 ----------- .../src/handlers/unresolved_variant.rs | 79 +++++++++++++++++++ crates/ide-diagnostics/src/lib.rs | 4 +- 6 files changed, 96 insertions(+), 57 deletions(-) delete mode 100644 crates/ide-diagnostics/src/handlers/unresolved_record_expr.rs create mode 100644 crates/ide-diagnostics/src/handlers/unresolved_variant.rs diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 04694f921de1..1bfce5c785c7 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -352,9 +352,9 @@ pub enum InferenceDiagnostic { #[type_visitable(ignore)] id: ExprOrPatId, }, - UnresolvedRecordExpr { + UnresolvedVariant { #[type_visitable(ignore)] - expr: ExprId, + id: ExprOrPatId, }, // FIXME: This should be emitted in body lowering BreakOutsideOfLoop { @@ -2278,6 +2278,8 @@ impl<'body, 'db> InferenceContext<'body, 'db> { let interner = DbInterner::conjure(); let (resolution, unresolved) = if value_ns { let Some(res) = path_ctx.resolve_path_in_value_ns(HygieneId::ROOT) else { + drop(ctx); + self.push_diagnostic(InferenceDiagnostic::UnresolvedVariant { id: node }); return (self.types.types.error, None); }; match res { @@ -2313,7 +2315,11 @@ impl<'body, 'db> InferenceContext<'body, 'db> { } else { match path_ctx.resolve_path_in_type_ns() { Some((it, idx)) => (it, idx), - None => return (self.types.types.error, None), + None => { + drop(ctx); + self.push_diagnostic(InferenceDiagnostic::UnresolvedVariant { id: node }); + return (self.types.types.error, None); + } } }; return match resolution { diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 59b95dab32b6..1ea6a431d375 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -964,7 +964,6 @@ impl<'db> InferenceContext<'_, 'db> { ) -> Ty<'db> { // Find the relevant variant let (adt_ty, Some(variant)) = self.resolve_variant(expr.into(), path, false) else { - self.push_diagnostic(InferenceDiagnostic::UnresolvedRecordExpr { expr }); for field in fields { self.infer_expr_no_expect(field.expr, ExprIsRead::Yes); } diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 2a3f2803bb88..0b6e43f52172 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -160,7 +160,7 @@ diagnostics![AnyDiagnostic<'db> -> UnresolvedMethodCall<'db>, UnresolvedModule, UnresolvedIdent, - UnresolvedRecordExpr, + UnresolvedVariant, UnusedMut, UnusedVariable, GenericArgsProhibited, @@ -364,8 +364,8 @@ pub struct UnresolvedIdent { } #[derive(Debug)] -pub struct UnresolvedRecordExpr { - pub expr: InFile, +pub struct UnresolvedVariant { + pub node: InFile, } #[derive(Debug)] @@ -907,9 +907,9 @@ impl<'db> AnyDiagnostic<'db> { }; UnresolvedIdent { node }.into() } - &InferenceDiagnostic::UnresolvedRecordExpr { expr } => { - let expr = expr_syntax(expr)?; - UnresolvedRecordExpr { expr }.into() + &InferenceDiagnostic::UnresolvedVariant { id } => { + let node = expr_or_pat_syntax(id)?; + UnresolvedVariant { node }.into() } &InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break, bad_value_break } => { let expr = expr_syntax(expr)?; diff --git a/crates/ide-diagnostics/src/handlers/unresolved_record_expr.rs b/crates/ide-diagnostics/src/handlers/unresolved_record_expr.rs deleted file mode 100644 index 5e11dbc0d704..000000000000 --- a/crates/ide-diagnostics/src/handlers/unresolved_record_expr.rs +++ /dev/null @@ -1,45 +0,0 @@ -use either::Either; -use syntax::{AstNode, ast::Expr}; - -use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; - -// Diagnostic: unresolved-record-expr -// -// This diagnostic is triggered if the struct, variant, or union type referred to by a record expression does not exist in the current scope. -pub(crate) fn unresolved_record_expr( - ctx: &DiagnosticsContext<'_, '_>, - d: &hir::UnresolvedRecordExpr, -) -> Diagnostic { - Diagnostic::new( - DiagnosticCode::RustcHardError("E0422"), - "cannot find struct, variant or union type in this scope".to_owned(), - crate::adjusted_display_range(ctx, d.expr, &|expr| match expr { - Either::Left(Expr::RecordExpr(it)) => it.path().map(|p| p.syntax().text_range()), - _ => None, - }), - ) - .stable() -} - -#[cfg(test)] -mod tests { - use crate::tests::check_diagnostics; - - #[test] - fn unresolved_record_expr() { - check_diagnostics( - r#" -struct Exist { - x: i32, - y: i32, -} - -fn main() { - let _ = Exist { x: 1, y: 2 }; - let _ = DoesNotExist { x: 1, y: 2 }; - // ^^^^^^^^^^^^ error: cannot find struct, variant or union type in this scope -} -"#, - ); - } -} diff --git a/crates/ide-diagnostics/src/handlers/unresolved_variant.rs b/crates/ide-diagnostics/src/handlers/unresolved_variant.rs new file mode 100644 index 000000000000..b4b14558fcc5 --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/unresolved_variant.rs @@ -0,0 +1,79 @@ +use either::Either; +use syntax::{AstNode, ast}; + +use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, adjusted_display_range}; + +// Diagnostic: unresolved-record-expr +// +// This diagnostic is triggered if the struct, variant, or union type referred to by a record expression does not exist in the current scope. +pub(crate) fn unresolved_variant( + ctx: &DiagnosticsContext<'_, '_>, + d: &hir::UnresolvedVariant, +) -> Diagnostic { + Diagnostic::new( + DiagnosticCode::RustcHardError("E0422"), + "cannot find struct, variant or union type in this scope".to_owned(), + adjusted_display_range(ctx, d.node, &|node| match node { + Either::Left(ast::Expr::RecordExpr(it)) => it.path().map(|p| p.syntax().text_range()), + Either::Right(ast::Pat::RecordPat(it)) => it.path().map(|p| p.syntax().text_range()), + Either::Right(ast::Pat::TupleStructPat(it)) => { + it.path().map(|p| p.syntax().text_range()) + } + _ => None, + }), + ) + .stable() +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn unresolved_record_expr() { + check_diagnostics( + r#" +fn main() { + let _ = DoesNotExist { x: 1, y: 2 }; + // ^^^^^^^^^^^^ error: cannot find struct, variant or union type in this scope +} +"#, + ); + } + + #[test] + fn unresolved_record_pat() { + check_diagnostics( + r#" +fn main() { + struct Exist { x: i32, y: i32 } + let a = Exist { x: 1, y: 2 }; + match a { + Exist { .. } => {} + DoesNotExist { .. } => {} + // ^^^^^^^^^^^^ error: cannot find struct, variant or union type in this scope + _ => {} + } +} +"#, + ); + } + + #[test] + fn unresolved_tuple_struct_pat() { + check_diagnostics( + r#" +fn main() { + struct Tuple(i32, i32); + let t = Tuple(1, 2); + match t { + Tuple( .. ) => {} + DoesNotExist( .. ) => {} + // ^^^^^^^^^^^^ error: cannot find struct, variant or union type in this scope + _ => {} + } +} +"#, + ); + } +} diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index ba1d04436550..ee6721918b15 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -92,7 +92,7 @@ mod handlers { pub(crate) mod unresolved_macro_call; pub(crate) mod unresolved_method; pub(crate) mod unresolved_module; - pub(crate) mod unresolved_record_expr; + pub(crate) mod unresolved_variant; pub(crate) mod unused_must_use; pub(crate) mod unused_variables; @@ -502,7 +502,7 @@ pub fn semantic_diagnostics( AnyDiagnostic::UnresolvedMacroCall(d) => handlers::unresolved_macro_call::unresolved_macro_call(&ctx, &d), AnyDiagnostic::UnresolvedMethodCall(d) => handlers::unresolved_method::unresolved_method(&ctx, &d), AnyDiagnostic::UnresolvedModule(d) => handlers::unresolved_module::unresolved_module(&ctx, &d), - AnyDiagnostic::UnresolvedRecordExpr(d) => handlers::unresolved_record_expr::unresolved_record_expr(&ctx, &d), + AnyDiagnostic::UnresolvedVariant(d) => handlers::unresolved_variant::unresolved_variant(&ctx, &d), AnyDiagnostic::UnusedMustUse(d) => handlers::unused_must_use::unused_must_use(&ctx, &d), AnyDiagnostic::UnusedMut(d) => match handlers::mutability_errors::unused_mut(&ctx, &d) { Some(it) => it, From d637244e05110d72422d4ec27fd7bfe089cd6cf5 Mon Sep 17 00:00:00 2001 From: Kao-Wei Yeh Date: Wed, 20 May 2026 23:01:08 +0800 Subject: [PATCH 3/4] Fix test `remove_trailing_return_in_match` --- crates/ide-diagnostics/src/handlers/remove_trailing_return.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ide-diagnostics/src/handlers/remove_trailing_return.rs b/crates/ide-diagnostics/src/handlers/remove_trailing_return.rs index b5a47e508e14..68f619f229cc 100644 --- a/crates/ide-diagnostics/src/handlers/remove_trailing_return.rs +++ b/crates/ide-diagnostics/src/handlers/remove_trailing_return.rs @@ -158,6 +158,7 @@ fn foo(x: usize) -> u8 { fn remove_trailing_return_in_match() { check_diagnostics( r#" +//- minicore: result fn foo(x: Result) -> u8 { match x { Ok(_) => return 1, From 783426a1580591a6a519274f587b0a241bb92a1a Mon Sep 17 00:00:00 2001 From: Kao-Wei Yeh Date: Wed, 20 May 2026 23:06:50 +0800 Subject: [PATCH 4/4] Fix `unresolved_variant` document --- crates/ide-diagnostics/src/handlers/unresolved_variant.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ide-diagnostics/src/handlers/unresolved_variant.rs b/crates/ide-diagnostics/src/handlers/unresolved_variant.rs index b4b14558fcc5..e89194122ecb 100644 --- a/crates/ide-diagnostics/src/handlers/unresolved_variant.rs +++ b/crates/ide-diagnostics/src/handlers/unresolved_variant.rs @@ -3,7 +3,7 @@ use syntax::{AstNode, ast}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, adjusted_display_range}; -// Diagnostic: unresolved-record-expr +// Diagnostic: unresolved-variant // // This diagnostic is triggered if the struct, variant, or union type referred to by a record expression does not exist in the current scope. pub(crate) fn unresolved_variant(