diff --git a/crates/hir-ty/src/diagnostics/unsafe_check.rs b/crates/hir-ty/src/diagnostics/unsafe_check.rs index 20cf3c781151..20d148917b86 100644 --- a/crates/hir-ty/src/diagnostics/unsafe_check.rs +++ b/crates/hir-ty/src/diagnostics/unsafe_check.rs @@ -296,6 +296,13 @@ impl<'db> UnsafeVisitor<'db> { return; } + Expr::Field { .. } => { + if self.contains_union_field_access(*expr) { + // Walk the entire field access chain without triggering union field errors + self.walk_field_chain_for_raw_ptr(*expr); + return; + } + } _ => (), } } @@ -401,4 +408,43 @@ impl<'db> UnsafeVisitor<'db> { } } } + + fn contains_union_field_access(&mut self, expr: ExprId) -> bool { + match &self.body.exprs[expr] { + Expr::Field { expr: base_expr, .. } => { + // Check if this field access is from a union + if matches!( + self.infer.field_resolution(expr), + Some(Either::Left(FieldId { parent: VariantId::UnionId(_), .. })) + ) { + true + } else { + // Recursively check the base expression + self.contains_union_field_access(*base_expr) + } + } + _ => false, + } + } + + /// Walks a field access chain for raw pointer creation, avoiding union field access errors + fn walk_field_chain_for_raw_ptr(&mut self, expr: ExprId) { + match &self.body.exprs[expr] { + Expr::Field { expr: base_expr, .. } => { + // First, recursively handle the base expression + self.walk_field_chain_for_raw_ptr(*base_expr); + + // Then handle any non-field child expressions of this field access + self.body.walk_child_exprs_without_pats(expr, |child| { + if child != *base_expr { + self.walk_expr(child); + } + }); + } + _ => { + // We've reached the base expression (not a field access), walk it normally + self.walk_expr(expr); + } + } + } } diff --git a/crates/ide-diagnostics/src/handlers/missing_unsafe.rs b/crates/ide-diagnostics/src/handlers/missing_unsafe.rs index d8f6e813d800..f0369e5d20a9 100644 --- a/crates/ide-diagnostics/src/handlers/missing_unsafe.rs +++ b/crates/ide-diagnostics/src/handlers/missing_unsafe.rs @@ -845,6 +845,76 @@ fn bar(mut v: Union2) { ) } + #[test] + fn raw_deref_on_union_field() { + check_diagnostics( + r#" +fn main() { + union U1 { + a: u8 + } + let x = U1 { a: 3 }; + + let a = x.a; + // ^^^ 💡 error: access to union field is unsafe and requires an unsafe function or block + + + let b = &raw const x.a; + + let tmp = Vec::from([1, 2, 3]); + + let c = &raw const tmp[x.a]; + // ^^^ 💡 error: access to union field is unsafe and requires an unsafe function or block + + union URef { + p: &'static mut i32, + } + + fn deref_union_field(u: URef) { + // Not an assignment but an access to the union field! + *(u.p) = 13; + // ^^^ 💡 error: access to union field is unsafe and requires an unsafe function or block + } +} +"#, + ) + } + + #[test] + fn union_fields_chain_is_allowed() { + check_diagnostics( + r#" +union Inner { + a: u8, +} + +union MoreInner { + moreinner: ManuallyDrop, +} + +union LessOuter { + lessouter: ManuallyDrop, +} + +union Outer { + outer: ManuallyDrop, +} + +fn main() { + let super_outer = Outer { + outer: ManuallyDrop::new(LessOuter { + lessouter: ManuallyDrop::new(MoreInner { + moreinner: ManuallyDrop::new(Inner { a: 42 }), + }), + }), + }; + + let ptr = &raw const super_outer.outer.lessouter.moreinner.a; +} +"#, + ); + } + #[test] fn raw_ref_reborrow_is_safe() { check_diagnostics( diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html index 828b8f762c58..8339daf32462 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html @@ -96,7 +96,7 @@ u.field; &u.field; - &raw const u.field; + &raw const u.field; // this should be safe! let Union { field: _ }; // but not these