Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion crates/hir-ty/src/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,10 @@ pub enum InferenceDiagnostic {
#[type_visitable(ignore)]
id: ExprOrPatId,
},
UnresolvedVariant {
#[type_visitable(ignore)]
id: ExprOrPatId,
},
// FIXME: This should be emitted in body lowering
BreakOutsideOfLoop {
#[type_visitable(ignore)]
Expand Down Expand Up @@ -2274,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 });
Copy link
Copy Markdown
Contributor

@ChayimFriedman2 ChayimFriedman2 May 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry about this, but IMO this is still not the right place. We should emit an error in resolve_path_in_X(), and I don't know if we should do it yet.

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also there are more cases in this method after resolution.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry about this, but IMO this is still not the right place. We should emit an error in resolve_path_in_X(), and I don't know if we should do it yet.

View changes since the review

I found that if I emit diagnostics in resolve_path_in_value_ns and resolve_path_in_type_ns, different call sites need different error code (for example, another resolve_path_in_type_ns call site need E0425 rather than E0422). I feel like there is no proper place for now.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First, the error codes are not set in stone. The code organization is more important. Second, we can pass this information from the outside.

But as I said, I am not sure we should do this yet.

return (self.types.types.error, None);
};
match res {
Expand Down Expand Up @@ -2309,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 {
Expand Down
1 change: 0 additions & 1 deletion crates/hir-ty/src/infer/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
// FIXME: Emit an error.
for field in fields {
self.infer_expr_no_expect(field.expr, ExprIsRead::Yes);
}
Expand Down
10 changes: 10 additions & 0 deletions crates/hir/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ diagnostics![AnyDiagnostic<'db> ->
UnresolvedMethodCall<'db>,
UnresolvedModule,
UnresolvedIdent,
UnresolvedVariant,
UnusedMut,
UnusedVariable,
GenericArgsProhibited,
Expand Down Expand Up @@ -362,6 +363,11 @@ pub struct UnresolvedIdent {
pub node: InFile<(ExprOrPatPtr, Option<TextRange>)>,
}

#[derive(Debug)]
pub struct UnresolvedVariant {
pub node: InFile<ExprOrPatPtr>,
}

#[derive(Debug)]
pub struct PrivateField {
pub expr: InFile<ExprOrPatPtr>,
Expand Down Expand Up @@ -901,6 +907,10 @@ impl<'db> AnyDiagnostic<'db> {
};
UnresolvedIdent { node }.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)?;
BreakOutsideOfLoop { expr, is_break, bad_value_break }.into()
Expand Down
3 changes: 2 additions & 1 deletion crates/ide-diagnostics/src/handlers/inactive_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ fn foo(x: usize) -> u8 {
fn remove_trailing_return_in_match() {
check_diagnostics(
r#"
//- minicore: result
fn foo<T, E>(x: Result<T, E>) -> u8 {
match x {
Ok(_) => return 1,
Expand Down
79 changes: 79 additions & 0 deletions crates/ide-diagnostics/src/handlers/unresolved_variant.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use either::Either;
use syntax::{AstNode, ast};

use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, adjusted_display_range};

// 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(
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
_ => {}
}
}
"#,
);
}
}
2 changes: 2 additions & 0 deletions crates/ide-diagnostics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_variant;
pub(crate) mod unused_must_use;
pub(crate) mod unused_variables;

Expand Down Expand Up @@ -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::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,
Expand Down
Loading