Skip to content

Commit ce25ffa

Browse files
committed
Auto merge of rust-lang#132915 - veluca93:unsafe-fields, r=<try>
Draft implementation of unsafe-fields. RFC: rust-lang/rfcs#3458 Tracking: - rust-lang#132922 r? jswrenn
2 parents 583b25d + db32d80 commit ce25ffa

File tree

32 files changed

+521
-79
lines changed

32 files changed

+521
-79
lines changed

Diff for: compiler/rustc_ast/src/ast.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3063,6 +3063,7 @@ pub struct FieldDef {
30633063
pub id: NodeId,
30643064
pub span: Span,
30653065
pub vis: Visibility,
3066+
pub safety: Safety,
30663067
pub ident: Option<Ident>,
30673068

30683069
pub ty: P<Ty>,

Diff for: compiler/rustc_ast/src/mut_visit.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1044,10 +1044,11 @@ pub fn walk_flat_map_field_def<T: MutVisitor>(
10441044
visitor: &mut T,
10451045
mut fd: FieldDef,
10461046
) -> SmallVec<[FieldDef; 1]> {
1047-
let FieldDef { span, ident, vis, id, ty, attrs, is_placeholder: _ } = &mut fd;
1047+
let FieldDef { span, ident, vis, id, ty, attrs, is_placeholder: _, safety } = &mut fd;
10481048
visitor.visit_id(id);
10491049
visit_attrs(visitor, attrs);
10501050
visitor.visit_vis(vis);
1051+
visit_safety(visitor, safety);
10511052
visit_opt(ident, |ident| visitor.visit_ident(ident));
10521053
visitor.visit_ty(ty);
10531054
visitor.visit_span(span);

Diff for: compiler/rustc_ast/src/visit.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -929,7 +929,7 @@ pub fn walk_struct_def<'a, V: Visitor<'a>>(
929929
}
930930

931931
pub fn walk_field_def<'a, V: Visitor<'a>>(visitor: &mut V, field: &'a FieldDef) -> V::Result {
932-
let FieldDef { attrs, id: _, span: _, vis, ident, ty, is_placeholder: _ } = field;
932+
let FieldDef { attrs, id: _, span: _, vis, ident, ty, is_placeholder: _, safety: _ } = field;
933933
walk_list!(visitor, visit_attribute, attrs);
934934
try_visit!(visitor.visit_vis(vis));
935935
visit_opt!(visitor, visit_ident, ident);

Diff for: compiler/rustc_ast_lowering/src/item.rs

+1
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
724724
},
725725
vis_span: self.lower_span(f.vis.span),
726726
ty,
727+
safety: self.lower_safety(f.safety, hir::Safety::Safe),
727728
}
728729
}
729730

Diff for: compiler/rustc_ast_passes/src/feature_gate.rs

+1
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
557557
gate_all!(global_registration, "global registration is experimental");
558558
gate_all!(return_type_notation, "return type notation is experimental");
559559
gate_all!(pin_ergonomics, "pinned reference syntax is experimental");
560+
gate_all!(unsafe_fields, "`unsafe` fields are experimental");
560561

561562
if !visitor.features.never_patterns() {
562563
if let Some(spans) = spans.get(&sym::never_patterns) {

Diff for: compiler/rustc_expand/src/placeholders.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use rustc_ast::mut_visit::*;
22
use rustc_ast::ptr::P;
33
use rustc_ast::token::Delimiter;
44
use rustc_ast::visit::AssocCtxt;
5-
use rustc_ast::{self as ast};
5+
use rustc_ast::{self as ast, Safety};
66
use rustc_data_structures::fx::FxHashMap;
77
use rustc_span::DUMMY_SP;
88
use rustc_span::symbol::Ident;
@@ -173,6 +173,7 @@ pub(crate) fn placeholder(
173173
ty: ty(),
174174
vis,
175175
is_placeholder: true,
176+
safety: Safety::Default,
176177
}]),
177178
AstFragmentKind::Variants => AstFragment::Variants(smallvec![ast::Variant {
178179
attrs: Default::default(),

Diff for: compiler/rustc_feature/src/unstable.rs

+2
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,8 @@ declare_features! (
623623
/// Allows creation of instances of a struct by moving fields that have
624624
/// not changed from prior instances of the same struct (RFC #2528)
625625
(unstable, type_changing_struct_update, "1.58.0", Some(86555)),
626+
/// Allows declaring fields `unsafe`.
627+
(incomplete, unsafe_fields, "CURRENT_RUSTC_VERSION", Some(132922)),
626628
/// Allows const generic parameters to be defined with types that
627629
/// are not `Sized`, e.g. `fn foo<const N: [u8]>() {`.
628630
(incomplete, unsized_const_params, "1.82.0", Some(95174)),

Diff for: compiler/rustc_hir/src/hir.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3177,6 +3177,7 @@ pub struct FieldDef<'hir> {
31773177
pub hir_id: HirId,
31783178
pub def_id: LocalDefId,
31793179
pub ty: &'hir Ty<'hir>,
3180+
pub safety: Safety,
31803181
}
31813182

31823183
impl FieldDef<'_> {

Diff for: compiler/rustc_hir_analysis/messages.ftl

+7
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,13 @@ hir_analysis_invalid_union_field =
253253
hir_analysis_invalid_union_field_sugg =
254254
wrap the field type in `ManuallyDrop<...>`
255255
256+
hir_analysis_invalid_unsafe_field =
257+
field must implement `Copy` or be wrapped in `ManuallyDrop<...>` to be unsafe
258+
.note = unsafe fields must not have drop side-effects, which is currently enforced via either `Copy` or `ManuallyDrop<...>`
259+
260+
hir_analysis_invalid_unsafe_field_sugg =
261+
wrap the field type in `ManuallyDrop<...>`
262+
256263
hir_analysis_late_bound_const_in_apit = `impl Trait` can only mention const parameters from an fn or impl
257264
.label = const parameter declared here
258265

Diff for: compiler/rustc_hir_analysis/src/check/check.rs

+77-28
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rustc_data_structures::unord::{UnordMap, UnordSet};
66
use rustc_errors::MultiSpan;
77
use rustc_errors::codes::*;
88
use rustc_hir::def::{CtorKind, DefKind};
9-
use rustc_hir::{Node, intravisit};
9+
use rustc_hir::{Node, Safety, intravisit};
1010
use rustc_infer::infer::{RegionVariableOrigin, TyCtxtInferExt};
1111
use rustc_infer::traits::{Obligation, ObligationCauseCode};
1212
use rustc_lint_defs::builtin::{
@@ -71,6 +71,7 @@ fn check_struct(tcx: TyCtxt<'_>, def_id: LocalDefId) {
7171

7272
check_transparent(tcx, def);
7373
check_packed(tcx, span, def);
74+
check_unsafe_fields(tcx, span, def_id);
7475
}
7576

7677
fn check_union(tcx: TyCtxt<'_>, def_id: LocalDefId) {
@@ -82,38 +83,38 @@ fn check_union(tcx: TyCtxt<'_>, def_id: LocalDefId) {
8283
check_packed(tcx, span, def);
8384
}
8485

86+
fn allowed_union_or_unsafe_field<'tcx>(
87+
ty: Ty<'tcx>,
88+
tcx: TyCtxt<'tcx>,
89+
param_env: ty::ParamEnv<'tcx>,
90+
) -> bool {
91+
// We don't just accept all !needs_drop fields, due to semver concerns.
92+
match ty.kind() {
93+
ty::Ref(..) => true, // references never drop (even mutable refs, which are non-Copy and hence fail the later check)
94+
ty::Tuple(tys) => {
95+
// allow tuples of allowed types
96+
tys.iter().all(|ty| allowed_union_or_unsafe_field(ty, tcx, param_env))
97+
}
98+
ty::Array(elem, _len) => {
99+
// Like `Copy`, we do *not* special-case length 0.
100+
allowed_union_or_unsafe_field(*elem, tcx, param_env)
101+
}
102+
_ => {
103+
// Fallback case: allow `ManuallyDrop` and things that are `Copy`,
104+
// also no need to report an error if the type is unresolved.
105+
ty.ty_adt_def().is_some_and(|adt_def| adt_def.is_manually_drop())
106+
|| ty.is_copy_modulo_regions(tcx, param_env)
107+
|| ty.references_error()
108+
}
109+
}
110+
}
111+
85112
/// Check that the fields of the `union` do not need dropping.
86113
fn check_union_fields(tcx: TyCtxt<'_>, span: Span, item_def_id: LocalDefId) -> bool {
87114
let item_type = tcx.type_of(item_def_id).instantiate_identity();
88115
if let ty::Adt(def, args) = item_type.kind() {
89116
assert!(def.is_union());
90117

91-
fn allowed_union_field<'tcx>(
92-
ty: Ty<'tcx>,
93-
tcx: TyCtxt<'tcx>,
94-
param_env: ty::ParamEnv<'tcx>,
95-
) -> bool {
96-
// We don't just accept all !needs_drop fields, due to semver concerns.
97-
match ty.kind() {
98-
ty::Ref(..) => true, // references never drop (even mutable refs, which are non-Copy and hence fail the later check)
99-
ty::Tuple(tys) => {
100-
// allow tuples of allowed types
101-
tys.iter().all(|ty| allowed_union_field(ty, tcx, param_env))
102-
}
103-
ty::Array(elem, _len) => {
104-
// Like `Copy`, we do *not* special-case length 0.
105-
allowed_union_field(*elem, tcx, param_env)
106-
}
107-
_ => {
108-
// Fallback case: allow `ManuallyDrop` and things that are `Copy`,
109-
// also no need to report an error if the type is unresolved.
110-
ty.ty_adt_def().is_some_and(|adt_def| adt_def.is_manually_drop())
111-
|| ty.is_copy_modulo_regions(tcx, param_env)
112-
|| ty.references_error()
113-
}
114-
}
115-
}
116-
117118
let param_env = tcx.param_env(item_def_id);
118119
for field in &def.non_enum_variant().fields {
119120
let Ok(field_ty) = tcx.try_normalize_erasing_regions(param_env, field.ty(tcx, args))
@@ -122,7 +123,7 @@ fn check_union_fields(tcx: TyCtxt<'_>, span: Span, item_def_id: LocalDefId) -> b
122123
continue;
123124
};
124125

125-
if !allowed_union_field(field_ty, tcx, param_env) {
126+
if !allowed_union_or_unsafe_field(field_ty, tcx, param_env) {
126127
let (field_span, ty_span) = match tcx.hir().get_if_local(field.did) {
127128
// We are currently checking the type this field came from, so it must be local.
128129
Some(Node::Field(field)) => (field.span, field.ty.span),
@@ -149,6 +150,53 @@ fn check_union_fields(tcx: TyCtxt<'_>, span: Span, item_def_id: LocalDefId) -> b
149150
true
150151
}
151152

153+
/// Check that the unsafe fields do not need dropping.
154+
fn check_unsafe_fields(tcx: TyCtxt<'_>, span: Span, item_def_id: LocalDefId) -> bool {
155+
let item_type = tcx.type_of(item_def_id).instantiate_identity();
156+
if let ty::Adt(def, args) = item_type.kind() {
157+
let param_env = tcx.param_env(item_def_id);
158+
for variant in def.variants() {
159+
for field in &variant.fields {
160+
if field.safety != Safety::Unsafe {
161+
continue;
162+
}
163+
let Ok(field_ty) =
164+
tcx.try_normalize_erasing_regions(param_env, field.ty(tcx, args))
165+
else {
166+
tcx.dcx().span_delayed_bug(span, "could not normalize field type");
167+
continue;
168+
};
169+
170+
if !allowed_union_or_unsafe_field(field_ty, tcx, param_env) {
171+
let (field_span, ty_span) = match tcx.hir().get_if_local(field.did) {
172+
// We are currently checking the type this field came from, so it must be local.
173+
Some(Node::Field(field)) => (field.span, field.ty.span),
174+
_ => unreachable!("mir field has to correspond to hir field"),
175+
};
176+
tcx.dcx().emit_err(errors::InvalidUnsafeField {
177+
field_span,
178+
sugg: errors::InvalidUnsafeFieldSuggestion {
179+
lo: ty_span.shrink_to_lo(),
180+
hi: ty_span.shrink_to_hi(),
181+
},
182+
note: (),
183+
});
184+
return false;
185+
} else if field_ty.needs_drop(tcx, param_env) {
186+
// This should never happen. But we can get here e.g. in case of name resolution errors.
187+
tcx.dcx().span_delayed_bug(
188+
span,
189+
"we should never accept maybe-dropping union fields",
190+
);
191+
}
192+
}
193+
}
194+
} else {
195+
span_bug!(span, "structs/enums must be ty::Adt, but got {:?}", item_type.kind());
196+
}
197+
true
198+
}
199+
152200
/// Check that a `static` is inhabited.
153201
fn check_static_inhabited(tcx: TyCtxt<'_>, def_id: LocalDefId) {
154202
// Make sure statics are inhabited.
@@ -1460,6 +1508,7 @@ fn check_enum(tcx: TyCtxt<'_>, def_id: LocalDefId) {
14601508

14611509
detect_discriminant_duplicate(tcx, def);
14621510
check_transparent(tcx, def);
1511+
check_unsafe_fields(tcx, tcx.def_span(def_id), def_id);
14631512
}
14641513

14651514
/// Part of enum check. Given the discriminants of an enum, errors if two or more discriminants are equal

Diff for: compiler/rustc_hir_analysis/src/collect.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,7 @@ fn lower_variant(
10311031
did: f.def_id.to_def_id(),
10321032
name: f.ident.name,
10331033
vis: tcx.visibility(f.def_id),
1034+
safety: f.safety,
10341035
})
10351036
.collect();
10361037
let recovered = match def {

Diff for: compiler/rustc_hir_analysis/src/errors.rs

+23
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,17 @@ pub(crate) struct InvalidUnionField {
734734
pub note: (),
735735
}
736736

737+
#[derive(Diagnostic)]
738+
#[diag(hir_analysis_invalid_unsafe_field, code = E0740)]
739+
pub(crate) struct InvalidUnsafeField {
740+
#[primary_span]
741+
pub field_span: Span,
742+
#[subdiagnostic]
743+
pub sugg: InvalidUnsafeFieldSuggestion,
744+
#[note]
745+
pub note: (),
746+
}
747+
737748
#[derive(Diagnostic)]
738749
#[diag(hir_analysis_return_type_notation_on_non_rpitit)]
739750
pub(crate) struct ReturnTypeNotationOnNonRpitit<'tcx> {
@@ -755,6 +766,18 @@ pub(crate) struct InvalidUnionFieldSuggestion {
755766
pub hi: Span,
756767
}
757768

769+
#[derive(Subdiagnostic)]
770+
#[multipart_suggestion(
771+
hir_analysis_invalid_unsafe_field_sugg,
772+
applicability = "machine-applicable"
773+
)]
774+
pub(crate) struct InvalidUnsafeFieldSuggestion {
775+
#[suggestion_part(code = "std::mem::ManuallyDrop<")]
776+
pub lo: Span,
777+
#[suggestion_part(code = ">")]
778+
pub hi: Span,
779+
}
780+
758781
#[derive(Diagnostic)]
759782
#[diag(hir_analysis_return_type_notation_equality_bound)]
760783
pub(crate) struct ReturnTypeNotationEqualityBound {

Diff for: compiler/rustc_metadata/src/rmeta/decoder.rs

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use rustc_data_structures::sync::{Lock, Lrc, OnceLock};
1515
use rustc_data_structures::unhash::UnhashMap;
1616
use rustc_expand::base::{SyntaxExtension, SyntaxExtensionKind};
1717
use rustc_expand::proc_macro::{AttrProcMacro, BangProcMacro, DeriveProcMacro};
18+
use rustc_hir::Safety;
1819
use rustc_hir::def::Res;
1920
use rustc_hir::def_id::{CRATE_DEF_INDEX, LOCAL_CRATE};
2021
use rustc_hir::definitions::{DefPath, DefPathData};
@@ -1101,6 +1102,7 @@ impl<'a> CrateMetadataRef<'a> {
11011102
did,
11021103
name: self.item_name(did.index),
11031104
vis: self.get_visibility(did.index),
1105+
safety: self.get_safety(did.index),
11041106
})
11051107
.collect(),
11061108
adt_kind,
@@ -1162,6 +1164,10 @@ impl<'a> CrateMetadataRef<'a> {
11621164
.map_id(|index| self.local_def_id(index))
11631165
}
11641166

1167+
fn get_safety(self, id: DefIndex) -> Safety {
1168+
self.root.tables.safety.get(self, id).unwrap_or_else(|| self.missing("safety", id))
1169+
}
1170+
11651171
fn get_trait_item_def_id(self, id: DefIndex) -> Option<DefId> {
11661172
self.root.tables.trait_item_def_id.get(self, id).map(|d| d.decode_from_cdata(self))
11671173
}

Diff for: compiler/rustc_metadata/src/rmeta/encoder.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1595,6 +1595,11 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
15951595
f.did.index
15961596
}));
15971597

1598+
for field in &variant.fields {
1599+
// FIXME: this probably blows up rmeta size.
1600+
self.tables.safety.set_some(field.did.index, field.safety);
1601+
}
1602+
15981603
if let Some((CtorKind::Fn, ctor_def_id)) = variant.ctor {
15991604
let fn_sig = tcx.fn_sig(ctor_def_id);
16001605
// FIXME only encode signature for ctor_def_id

Diff for: compiler/rustc_metadata/src/rmeta/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ define_tables! {
411411
associated_item_or_field_def_ids: Table<DefIndex, LazyArray<DefIndex>>,
412412
def_kind: Table<DefIndex, DefKind>,
413413
visibility: Table<DefIndex, LazyValue<ty::Visibility<DefIndex>>>,
414+
safety: Table<DefIndex, hir::Safety>,
414415
def_span: Table<DefIndex, LazyValue<Span>>,
415416
def_ident_span: Table<DefIndex, LazyValue<Span>>,
416417
lookup_stability: Table<DefIndex, LazyValue<attr::Stability>>,

Diff for: compiler/rustc_metadata/src/rmeta/table.rs

+7
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,13 @@ fixed_size_enum! {
198198
}
199199
}
200200

201+
fixed_size_enum! {
202+
hir::Safety {
203+
( Unsafe )
204+
( Safe )
205+
}
206+
}
207+
201208
fixed_size_enum! {
202209
ty::Asyncness {
203210
( Yes )

0 commit comments

Comments
 (0)