diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index ab0409efb3b29..1e4d3ba47f472 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1271,6 +1271,7 @@ impl Expr { ExprKind::Continue(..) => ExprPrecedence::Continue, ExprKind::Ret(..) => ExprPrecedence::Ret, ExprKind::InlineAsm(..) => ExprPrecedence::InlineAsm, + ExprKind::OffsetOf(..) => ExprPrecedence::OffsetOf, ExprKind::MacCall(..) => ExprPrecedence::Mac, ExprKind::Struct(..) => ExprPrecedence::Struct, ExprKind::Repeat(..) => ExprPrecedence::Repeat, @@ -1469,6 +1470,9 @@ pub enum ExprKind { /// Output of the `asm!()` macro. InlineAsm(P), + /// Output of the `offset_of!()` macro. + OffsetOf(P, P<[Ident]>), + /// A macro invocation; pre-expansion. MacCall(P), diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs index 694d31d8f1fd4..99f1f4bd9685f 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -1456,6 +1456,12 @@ pub fn noop_visit_expr( } ExprKind::InlineAsm(asm) => vis.visit_inline_asm(asm), ExprKind::FormatArgs(fmt) => vis.visit_format_args(fmt), + ExprKind::OffsetOf(container, fields) => { + vis.visit_ty(container); + for field in fields.iter_mut() { + vis.visit_ident(field); + } + } ExprKind::MacCall(mac) => vis.visit_mac_call(mac), ExprKind::Struct(se) => { let StructExpr { qself, path, fields, rest } = se.deref_mut(); diff --git a/compiler/rustc_ast/src/util/parser.rs b/compiler/rustc_ast/src/util/parser.rs index 3893875e9a405..24b4bd8623f04 100644 --- a/compiler/rustc_ast/src/util/parser.rs +++ b/compiler/rustc_ast/src/util/parser.rs @@ -269,6 +269,7 @@ pub enum ExprPrecedence { Index, Try, InlineAsm, + OffsetOf, Mac, FormatArgs, @@ -335,7 +336,8 @@ impl ExprPrecedence { | ExprPrecedence::Try | ExprPrecedence::InlineAsm | ExprPrecedence::Mac - | ExprPrecedence::FormatArgs => PREC_POSTFIX, + | ExprPrecedence::FormatArgs + | ExprPrecedence::OffsetOf => PREC_POSTFIX, // Never need parens ExprPrecedence::Array diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index ac9b321b71c6f..8a6b5d5c9052d 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -909,6 +909,12 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) { ExprKind::Paren(subexpression) => visitor.visit_expr(subexpression), ExprKind::InlineAsm(asm) => visitor.visit_inline_asm(asm), ExprKind::FormatArgs(f) => visitor.visit_format_args(f), + ExprKind::OffsetOf(container, fields) => { + visitor.visit_ty(container); + for &field in fields { + visitor.visit_ident(field); + } + } ExprKind::Yield(optional_expression) => { walk_list!(visitor, visit_expr, optional_expression); } diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 0d212b3e130a2..6863100d9bac0 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -289,6 +289,13 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::ExprKind::InlineAsm(self.lower_inline_asm(e.span, asm)) } ExprKind::FormatArgs(fmt) => self.lower_format_args(e.span, fmt), + ExprKind::OffsetOf(container, fields) => hir::ExprKind::OffsetOf( + self.lower_ty( + container, + &mut ImplTraitContext::Disallowed(ImplTraitPosition::OffsetOf), + ), + self.arena.alloc_from_iter(fields.iter().map(|&ident| self.lower_ident(ident))), + ), ExprKind::Struct(se) => { let rest = match &se.rest { StructRest::Base(e) => Some(self.lower_expr(e)), diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index bfd9956b0043e..c969d70960810 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -283,6 +283,7 @@ enum ImplTraitPosition { FieldTy, Cast, ImplSelf, + OffsetOf, } impl std::fmt::Display for ImplTraitPosition { @@ -313,6 +314,7 @@ impl std::fmt::Display for ImplTraitPosition { ImplTraitPosition::FieldTy => "field types", ImplTraitPosition::Cast => "cast types", ImplTraitPosition::ImplSelf => "impl headers", + ImplTraitPosition::OffsetOf => "`offset_of!` params", }; write!(f, "{name}") diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index 776bf54244ed1..aeb0c76202041 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -549,6 +549,26 @@ impl<'a> State<'a> { self.end(); self.pclose(); } + ast::ExprKind::OffsetOf(container, fields) => { + // FIXME: This should have its own syntax, distinct from a macro invocation. + self.word("offset_of!"); + self.popen(); + self.rbox(0, Inconsistent); + self.print_type(container); + self.word(","); + self.space(); + + if let Some((&first, rest)) = fields.split_first() { + self.print_ident(first); + + for &field in rest { + self.word("."); + self.print_ident(field); + } + } + + self.end(); + } ast::ExprKind::MacCall(m) => self.print_mac(m), ast::ExprKind::Paren(e) => { self.popen(); diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 06b80bd3f29ad..7cb0cec82c76a 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -2306,7 +2306,8 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { Rvalue::AddressOf(..) | Rvalue::ThreadLocalRef(..) | Rvalue::Len(..) - | Rvalue::Discriminant(..) => {} + | Rvalue::Discriminant(..) + | Rvalue::NullaryOp(NullOp::OffsetOf(..), _) => {} } } diff --git a/compiler/rustc_builtin_macros/messages.ftl b/compiler/rustc_builtin_macros/messages.ftl index 83dc1ac50e55d..fca6012a408c1 100644 --- a/compiler/rustc_builtin_macros/messages.ftl +++ b/compiler/rustc_builtin_macros/messages.ftl @@ -149,3 +149,6 @@ builtin_macros_format_pos_mismatch = {$n} positional {$n -> [one] argument *[more] arguments } in format string, but {$desc} +builtin_macros_offset_of_expected_field = expected field + +builtin_macros_offset_of_expected_two_args = expected 2 arguments diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index c9e3cd486f855..090e00616fb48 100644 --- a/compiler/rustc_builtin_macros/src/assert/context.rs +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -301,6 +301,7 @@ impl<'cx, 'a> Context<'cx, 'a> { | ExprKind::If(_, _, _) | ExprKind::IncludedBytes(..) | ExprKind::InlineAsm(_) + | ExprKind::OffsetOf(_, _) | ExprKind::Let(_, _, _) | ExprKind::Lit(_) | ExprKind::Loop(_, _, _) diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index 134d64ce9cc89..8f86ef44aa3ab 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -45,6 +45,7 @@ mod format; mod format_foreign; mod global_allocator; mod log_syntax; +mod offset_of; mod source_util; mod test; mod trace_macros; @@ -92,6 +93,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) { line: source_util::expand_line, log_syntax: log_syntax::expand_log_syntax, module_path: source_util::expand_mod, + offset_of: offset_of::expand_offset_of, option_env: env::expand_option_env, core_panic: edition_panic::expand_panic, std_panic: edition_panic::expand_panic, diff --git a/compiler/rustc_builtin_macros/src/offset_of.rs b/compiler/rustc_builtin_macros/src/offset_of.rs new file mode 100644 index 0000000000000..0ef3e000e414c --- /dev/null +++ b/compiler/rustc_builtin_macros/src/offset_of.rs @@ -0,0 +1,99 @@ +use rustc_ast as ast; +use rustc_ast::ptr::P; +use rustc_ast::token; +use rustc_ast::tokenstream::TokenStream; +use rustc_errors::PResult; +use rustc_expand::base::{self, *}; +use rustc_macros::Diagnostic; +use rustc_parse::parser::Parser; +use rustc_span::{symbol::Ident, Span}; + +#[derive(Diagnostic)] +#[diag(builtin_macros_offset_of_expected_field)] +struct ExpectedField { + #[primary_span] + span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_offset_of_expected_two_args)] +struct ExpectedTwoArgs { + #[primary_span] + span: Span, +} + +fn parse_field<'a>(cx: &ExtCtxt<'a>, p: &mut Parser<'a>) -> PResult<'a, Ident> { + let token = p.token.uninterpolate(); + let field = match token.kind { + token::Ident(name, _) => Ident::new(name, token.span), + token::Literal(token::Lit { kind: token::Integer, symbol, suffix: None }) => { + Ident::new(symbol, token.span) + } + _ => return Err(cx.create_err(ExpectedField { span: p.token.span })), + }; + + p.bump(); + + Ok(field) +} + +fn parse_args<'a>( + cx: &mut ExtCtxt<'a>, + sp: Span, + tts: TokenStream, +) -> PResult<'a, (P, P<[Ident]>)> { + let mut p = cx.new_parser_from_tts(tts); + + let container = p.parse_ty()?; + + p.expect(&token::Comma)?; + + if p.eat(&token::Eof) { + return Err(cx.create_err(ExpectedTwoArgs { span: sp })); + } + + let mut fields = Vec::new(); + + loop { + let field = parse_field(cx, &mut p)?; + fields.push(field); + + if p.eat(&token::Dot) { + continue; + } + + p.eat(&token::Comma); + + if !p.eat(&token::Eof) { + return Err(cx.create_err(ExpectedTwoArgs { span: sp })); + } + + break; + } + + Ok((container, fields.into())) +} + +pub fn expand_offset_of<'cx>( + cx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box { + match parse_args(cx, sp, tts) { + Ok((container, fields)) => { + let expr = P(ast::Expr { + id: ast::DUMMY_NODE_ID, + kind: ast::ExprKind::OffsetOf(container, fields), + span: sp, + attrs: ast::AttrVec::new(), + tokens: None, + }); + + MacEager::expr(expr) + } + Err(mut err) => { + err.emit(); + DummyResult::any(sp) + } + } +} diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index f5301f9f7f10b..f481290583e06 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -781,12 +781,15 @@ fn codegen_stmt<'tcx>( let operand = operand.load_scalar(fx); lval.write_cvalue(fx, CValue::by_val(operand, box_layout)); } - Rvalue::NullaryOp(null_op, ty) => { + Rvalue::NullaryOp(ref null_op, ty) => { assert!(lval.layout().ty.is_sized(fx.tcx, ParamEnv::reveal_all())); let layout = fx.layout_of(fx.monomorphize(ty)); let val = match null_op { NullOp::SizeOf => layout.size.bytes(), NullOp::AlignOf => layout.align.abi.bytes(), + NullOp::OffsetOf(fields) => { + layout.offset_of_subfield(fx, fields.iter().map(|f| f.index())).bytes() + } }; let val = CValue::const_val(fx, fx.layout_of(fx.tcx.types.usize), val.into()); lval.write_cvalue(fx, val); diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index bd11d47500a55..94de19a9c2935 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -666,13 +666,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } } - mir::Rvalue::NullaryOp(null_op, ty) => { + mir::Rvalue::NullaryOp(ref null_op, ty) => { let ty = self.monomorphize(ty); assert!(bx.cx().type_is_sized(ty)); let layout = bx.cx().layout_of(ty); let val = match null_op { mir::NullOp::SizeOf => layout.size.bytes(), mir::NullOp::AlignOf => layout.align.abi.bytes(), + mir::NullOp::OffsetOf(fields) => { + layout.offset_of_subfield(bx.cx(), fields.iter().map(|f| f.index())).bytes() + } }; let val = bx.cx().const_usize(val); let tcx = self.cx.tcx(); diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index 9a366364e769e..4d3a11935085d 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -280,20 +280,23 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { self.write_immediate(*val, &dest)?; } - NullaryOp(null_op, ty) => { + NullaryOp(ref null_op, ty) => { let ty = self.subst_from_current_frame_and_normalize_erasing_regions(ty)?; let layout = self.layout_of(ty)?; - if layout.is_unsized() { + if let mir::NullOp::SizeOf | mir::NullOp::AlignOf = null_op && layout.is_unsized() { // FIXME: This should be a span_bug (#80742) self.tcx.sess.delay_span_bug( self.frame().current_span(), - &format!("Nullary MIR operator called for unsized type {}", ty), + &format!("{null_op:?} MIR operator called for unsized type {ty}"), ); throw_inval!(SizeOfUnsizedType(ty)); } let val = match null_op { mir::NullOp::SizeOf => layout.size.bytes(), mir::NullOp::AlignOf => layout.align.abi.bytes(), + mir::NullOp::OffsetOf(fields) => { + layout.offset_of_subfield(self, fields.iter().map(|f| f.index())).bytes() + } }; self.write_scalar(Scalar::from_target_usize(val, self), &dest)?; } diff --git a/compiler/rustc_const_eval/src/transform/check_consts/check.rs b/compiler/rustc_const_eval/src/transform/check_consts/check.rs index 76522ed6e490a..696c451770090 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/check.rs @@ -558,7 +558,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { Rvalue::Cast(_, _, _) => {} - Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf, _) => {} + Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_), _) => {} Rvalue::ShallowInitBox(_, _) => {} Rvalue::UnaryOp(_, operand) => { diff --git a/compiler/rustc_const_eval/src/transform/promote_consts.rs b/compiler/rustc_const_eval/src/transform/promote_consts.rs index 1e3d61a78befb..e978e3442832a 100644 --- a/compiler/rustc_const_eval/src/transform/promote_consts.rs +++ b/compiler/rustc_const_eval/src/transform/promote_consts.rs @@ -514,6 +514,7 @@ impl<'tcx> Validator<'_, 'tcx> { Rvalue::NullaryOp(op, _) => match op { NullOp::SizeOf => {} NullOp::AlignOf => {} + NullOp::OffsetOf(_) => {} }, Rvalue::ShallowInitBox(_, _) => return Err(Unpromotable), diff --git a/compiler/rustc_const_eval/src/transform/validate.rs b/compiler/rustc_const_eval/src/transform/validate.rs index 8aee019e99494..9ab2b13d9d161 100644 --- a/compiler/rustc_const_eval/src/transform/validate.rs +++ b/compiler/rustc_const_eval/src/transform/validate.rs @@ -8,9 +8,10 @@ use rustc_middle::mir::interpret::Scalar; use rustc_middle::mir::visit::{NonUseContext, PlaceContext, Visitor}; use rustc_middle::mir::{ traversal, BasicBlock, BinOp, Body, BorrowKind, CastKind, CopyNonOverlapping, Local, Location, - MirPass, MirPhase, NonDivergingIntrinsic, Operand, Place, PlaceElem, PlaceRef, ProjectionElem, - RetagKind, RuntimePhase, Rvalue, SourceScope, Statement, StatementKind, Terminator, - TerminatorKind, UnOp, UnwindAction, VarDebugInfo, VarDebugInfoContents, START_BLOCK, + MirPass, MirPhase, NonDivergingIntrinsic, NullOp, Operand, Place, PlaceElem, PlaceRef, + ProjectionElem, RetagKind, RuntimePhase, Rvalue, SourceScope, Statement, StatementKind, + Terminator, TerminatorKind, UnOp, UnwindAction, VarDebugInfo, VarDebugInfoContents, + START_BLOCK, }; use rustc_middle::ty::{self, InstanceDef, ParamEnv, Ty, TyCtxt, TypeVisitableExt}; use rustc_mir_dataflow::impls::MaybeStorageLive; @@ -711,10 +712,54 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } } } + Rvalue::NullaryOp(NullOp::OffsetOf(fields), container) => { + let fail_out_of_bounds = |this: &Self, location, field, ty| { + this.fail(location, format!("Out of bounds field {field:?} for {ty:?}")); + }; + + let mut current_ty = *container; + + for field in fields.iter() { + match current_ty.kind() { + ty::Tuple(fields) => { + let Some(&f_ty) = fields.get(field.as_usize()) else { + fail_out_of_bounds(self, location, field, current_ty); + return; + }; + + current_ty = self.tcx.normalize_erasing_regions(self.param_env, f_ty); + } + ty::Adt(adt_def, substs) => { + if adt_def.is_enum() { + self.fail( + location, + format!("Cannot get field offset from enum {current_ty:?}"), + ); + return; + } + + let Some(field) = adt_def.non_enum_variant().fields.get(field) else { + fail_out_of_bounds(self, location, field, current_ty); + return; + }; + + let f_ty = field.ty(self.tcx, substs); + current_ty = self.tcx.normalize_erasing_regions(self.param_env, f_ty); + } + _ => { + self.fail( + location, + format!("Cannot get field offset from non-adt type {current_ty:?}"), + ); + return; + } + } + } + } Rvalue::Repeat(_, _) | Rvalue::ThreadLocalRef(_) | Rvalue::AddressOf(_, _) - | Rvalue::NullaryOp(_, _) + | Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf, _) | Rvalue::Discriminant(_) => {} } self.super_rvalue(rvalue, location); diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 54b328e78f8ee..52ed9660256cb 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1715,6 +1715,7 @@ impl Expr<'_> { ExprKind::Continue(..) => ExprPrecedence::Continue, ExprKind::Ret(..) => ExprPrecedence::Ret, ExprKind::InlineAsm(..) => ExprPrecedence::InlineAsm, + ExprKind::OffsetOf(..) => ExprPrecedence::OffsetOf, ExprKind::Struct(..) => ExprPrecedence::Struct, ExprKind::Repeat(..) => ExprPrecedence::Repeat, ExprKind::Yield(..) => ExprPrecedence::Yield, @@ -1774,6 +1775,7 @@ impl Expr<'_> { | ExprKind::Loop(..) | ExprKind::Assign(..) | ExprKind::InlineAsm(..) + | ExprKind::OffsetOf(..) | ExprKind::AssignOp(..) | ExprKind::Lit(_) | ExprKind::ConstBlock(..) @@ -1818,7 +1820,7 @@ impl Expr<'_> { pub fn can_have_side_effects(&self) -> bool { match self.peel_drop_temps().kind { - ExprKind::Path(_) | ExprKind::Lit(_) => false, + ExprKind::Path(_) | ExprKind::Lit(_) | ExprKind::OffsetOf(..) => false, ExprKind::Type(base, _) | ExprKind::Unary(_, base) | ExprKind::Field(base, _) @@ -2022,6 +2024,9 @@ pub enum ExprKind<'hir> { /// Inline assembly (from `asm!`), with its outputs and inputs. InlineAsm(&'hir InlineAsm<'hir>), + /// Field offset (`offset_of!`) + OffsetOf(&'hir Ty<'hir>, &'hir [Ident]), + /// A struct or struct-like variant literal expression. /// /// E.g., `Foo {x: 1, y: 2}`, or `Foo {x: 1, .. base}`, diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index 234256ab553c5..df0047d82e12e 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -786,6 +786,10 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>) ExprKind::InlineAsm(ref asm) => { visitor.visit_inline_asm(asm, expression.hir_id); } + ExprKind::OffsetOf(ref container, ref fields) => { + visitor.visit_ty(container); + walk_list!(visitor, visit_ident, fields.iter().copied()); + } ExprKind::Yield(ref subexpression, _) => { visitor.visit_expr(subexpression); } diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index 74f5b359021d2..2db4f1e50d48e 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -1551,6 +1551,23 @@ impl<'a> State<'a> { self.word("asm!"); self.print_inline_asm(asm); } + hir::ExprKind::OffsetOf(container, ref fields) => { + self.word("offset_of!("); + self.print_type(container); + self.word(","); + self.space(); + + if let Some((&first, rest)) = fields.split_first() { + self.print_ident(first); + + for &field in rest { + self.word("."); + self.print_ident(field); + } + } + + self.word(")"); + } hir::ExprKind::Yield(expr, _) => { self.word_space("yield"); self.print_expr_maybe_paren(expr, parser::PREC_JUMP); diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 0c0a7515d9c3b..3ffc583d43f61 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -309,6 +309,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.deferred_asm_checks.borrow_mut().push((asm, expr.hir_id)); self.check_expr_asm(asm) } + ExprKind::OffsetOf(container, ref fields) => { + self.check_offset_of(container, fields, expr) + } ExprKind::Break(destination, ref expr_opt) => { self.check_expr_break(destination, expr_opt.as_deref(), expr) } @@ -2450,15 +2453,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { base_did: DefId, return_ty: Option>, ) -> ErrorGuaranteed { - let struct_path = self.tcx().def_path_str(base_did); - let kind_name = self.tcx().def_descr(base_did); - let mut err = struct_span_err!( - self.tcx().sess, - field.span, - E0616, - "field `{field}` of {kind_name} `{struct_path}` is private", - ); - err.span_label(field.span, "private field"); + let mut err = self.private_field_err(field, base_did); + // Also check if an accessible method exists, which is often what is meant. if self.method_exists(field, expr_t, expr.hir_id, false, return_ty) && !self.expr_in_place(expr.hir_id) @@ -2698,6 +2694,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err } + fn private_field_err( + &self, + field: Ident, + base_did: DefId, + ) -> DiagnosticBuilder<'_, ErrorGuaranteed> { + let struct_path = self.tcx().def_path_str(base_did); + let kind_name = self.tcx().def_descr(base_did); + let mut err = struct_span_err!( + self.tcx().sess, + field.span, + E0616, + "field `{field}` of {kind_name} `{struct_path}` is private", + ); + err.span_label(field.span, "private field"); + + err + } + pub(crate) fn get_field_candidates_considering_privacy( &self, span: Span, @@ -3042,4 +3056,78 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.tcx.mk_unit() } } + + fn check_offset_of( + &self, + container: &'tcx hir::Ty<'tcx>, + fields: &[Ident], + expr: &'tcx hir::Expr<'tcx>, + ) -> Ty<'tcx> { + let container = self.to_ty(container).normalized; + + let mut field_indices = Vec::with_capacity(fields.len()); + let mut current_container = container; + + for &field in fields { + let container = self.structurally_resolved_type(expr.span, current_container); + + match container.kind() { + ty::Adt(container_def, substs) if !container_def.is_enum() => { + let block = self.tcx.hir().local_def_id_to_hir_id(self.body_id); + let (ident, def_scope) = + self.tcx.adjust_ident_and_get_scope(field, container_def.did(), block); + + let fields = &container_def.non_enum_variant().fields; + if let Some((index, field)) = fields + .iter_enumerated() + .find(|(_, f)| f.ident(self.tcx).normalize_to_macros_2_0() == ident) + { + let field_ty = self.field_ty(expr.span, field, substs); + + // FIXME: DSTs with static alignment should be allowed + self.require_type_is_sized(field_ty, expr.span, traits::MiscObligation); + + if field.vis.is_accessible_from(def_scope, self.tcx) { + self.tcx.check_stability(field.did, Some(expr.hir_id), expr.span, None); + } else { + self.private_field_err(ident, container_def.did()).emit(); + } + + // Save the index of all fields regardless of their visibility in case + // of error recovery. + field_indices.push(index); + current_container = field_ty; + + continue; + } + } + ty::Tuple(tys) => { + let fstr = field.as_str(); + + if let Ok(index) = fstr.parse::() { + if fstr == index.to_string() { + if let Some(&field_ty) = tys.get(index) { + field_indices.push(index.into()); + current_container = field_ty; + + continue; + } + } + } + } + _ => (), + }; + + self.no_such_field_err(field, container, expr.hir_id).emit(); + + break; + } + + self.typeck_results + .borrow_mut() + .offset_of_data_mut() + .insert(expr.hir_id, (container, field_indices)); + + self.tcx.types.usize + } } diff --git a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs index ee1c6fbfd650f..94b6a0f8f47d7 100644 --- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs +++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs @@ -300,6 +300,7 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> { hir::ExprKind::Continue(..) | hir::ExprKind::Lit(..) | hir::ExprKind::ConstBlock(..) + | hir::ExprKind::OffsetOf(..) | hir::ExprKind::Err(_) => {} hir::ExprKind::Loop(blk, ..) => { diff --git a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs index 3e9a9ce1b3114..28c44aa5703e8 100644 --- a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs @@ -215,6 +215,7 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { | ExprKind::Continue(..) | ExprKind::Ret(..) | ExprKind::InlineAsm(..) + | ExprKind::OffsetOf(..) | ExprKind::Struct(..) | ExprKind::Repeat(..) | ExprKind::Yield(..) @@ -485,6 +486,7 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { | ExprKind::Field(..) | ExprKind::Index(..) | ExprKind::InlineAsm(..) + | ExprKind::OffsetOf(..) | ExprKind::Let(..) | ExprKind::Lit(..) | ExprKind::Path(..) diff --git a/compiler/rustc_hir_typeck/src/mem_categorization.rs b/compiler/rustc_hir_typeck/src/mem_categorization.rs index 6c861b5930a1e..f5fca14eca88f 100644 --- a/compiler/rustc_hir_typeck/src/mem_categorization.rs +++ b/compiler/rustc_hir_typeck/src/mem_categorization.rs @@ -381,6 +381,7 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { | hir::ExprKind::Struct(..) | hir::ExprKind::Repeat(..) | hir::ExprKind::InlineAsm(..) + | hir::ExprKind::OffsetOf(..) | hir::ExprKind::Err(_) => Ok(self.cat_rvalue(expr.hir_id, expr.span, expr_ty)), } } diff --git a/compiler/rustc_hir_typeck/src/writeback.rs b/compiler/rustc_hir_typeck/src/writeback.rs index e876fa27593d4..9432a5840b276 100644 --- a/compiler/rustc_hir_typeck/src/writeback.rs +++ b/compiler/rustc_hir_typeck/src/writeback.rs @@ -70,6 +70,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { wbcx.visit_user_provided_tys(); wbcx.visit_user_provided_sigs(); wbcx.visit_generator_interior_types(); + wbcx.visit_offset_of_container_types(); wbcx.typeck_results.rvalue_scopes = mem::take(&mut self.typeck_results.borrow_mut().rvalue_scopes); @@ -295,7 +296,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for WritebackCx<'cx, 'tcx> { self.visit_field_id(field.hir_id); } } - hir::ExprKind::Field(..) => { + hir::ExprKind::Field(..) | hir::ExprKind::OffsetOf(..) => { self.visit_field_id(e.hir_id); } hir::ExprKind::ConstBlock(anon_const) => { @@ -682,6 +683,28 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { } } + fn visit_offset_of_container_types(&mut self) { + let fcx_typeck_results = self.fcx.typeck_results.borrow(); + assert_eq!(fcx_typeck_results.hir_owner, self.typeck_results.hir_owner); + let common_hir_owner = fcx_typeck_results.hir_owner; + + for (local_id, &(container, ref indices)) in + fcx_typeck_results.offset_of_data().items_in_stable_order() + { + let hir_id = hir::HirId { owner: common_hir_owner, local_id }; + + if cfg!(debug_assertions) && container.needs_infer() { + span_bug!( + hir_id.to_span(self.fcx.tcx), + "writeback: `{:?}` has inference variables", + container + ); + }; + + self.typeck_results.offset_of_data_mut().insert(hir_id, (container, indices.clone())); + } + } + fn resolve(&mut self, x: T, span: &dyn Locatable) -> T where T: TypeFoldable>, diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index 9becc04e5d1cf..6b6a2e561f5af 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -2041,7 +2041,11 @@ impl<'tcx> Debug for Rvalue<'tcx> { } UnaryOp(ref op, ref a) => write!(fmt, "{:?}({:?})", op, a), Discriminant(ref place) => write!(fmt, "discriminant({:?})", place), - NullaryOp(ref op, ref t) => write!(fmt, "{:?}({:?})", op, t), + NullaryOp(ref op, ref t) => match op { + NullOp::SizeOf => write!(fmt, "SizeOf({:?})", t), + NullOp::AlignOf => write!(fmt, "AlignOf({:?})", t), + NullOp::OffsetOf(fields) => write!(fmt, "OffsetOf({:?}, {:?})", t, fields), + }, ThreadLocalRef(did) => ty::tls::with(|tcx| { let muta = tcx.static_mutability(did).unwrap().prefix_str(); write!(fmt, "&/*tls*/ {}{}", muta, tcx.def_path_str(did)) diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index c38a347809f4f..d7d0366e101ab 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -1115,7 +1115,7 @@ pub enum Rvalue<'tcx> { CheckedBinaryOp(BinOp, Box<(Operand<'tcx>, Operand<'tcx>)>), /// Computes a value as described by the operation. - NullaryOp(NullOp, Ty<'tcx>), + NullaryOp(NullOp<'tcx>, Ty<'tcx>), /// Exactly like `BinaryOp`, but less operands. /// @@ -1211,12 +1211,14 @@ pub enum AggregateKind<'tcx> { Generator(DefId, SubstsRef<'tcx>, hir::Movability), } -#[derive(Copy, Clone, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)] -pub enum NullOp { +#[derive(Clone, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)] +pub enum NullOp<'tcx> { /// Returns the size of a value of that type SizeOf, /// Returns the minimum alignment of a type AlignOf, + /// Returns the offset of a field + OffsetOf(&'tcx List), } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] diff --git a/compiler/rustc_middle/src/mir/tcx.rs b/compiler/rustc_middle/src/mir/tcx.rs index 4f00abf7fabc1..5ca8241344832 100644 --- a/compiler/rustc_middle/src/mir/tcx.rs +++ b/compiler/rustc_middle/src/mir/tcx.rs @@ -188,7 +188,9 @@ impl<'tcx> Rvalue<'tcx> { } Rvalue::UnaryOp(UnOp::Not | UnOp::Neg, ref operand) => operand.ty(local_decls, tcx), Rvalue::Discriminant(ref place) => place.ty(local_decls, tcx).ty.discriminant_ty(tcx), - Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf, _) => tcx.types.usize, + Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => { + tcx.types.usize + } Rvalue::Aggregate(ref ak, ref ops) => match **ak { AggregateKind::Array(ty) => tcx.mk_array(ty, ops.len() as u64), AggregateKind::Tuple => { diff --git a/compiler/rustc_middle/src/mir/type_foldable.rs b/compiler/rustc_middle/src/mir/type_foldable.rs index ace856b9f95e0..06874741bb065 100644 --- a/compiler/rustc_middle/src/mir/type_foldable.rs +++ b/compiler/rustc_middle/src/mir/type_foldable.rs @@ -16,7 +16,6 @@ TrivialTypeTraversalAndLiftImpls! { UserTypeAnnotationIndex, BorrowKind, CastKind, - NullOp, hir::Movability, BasicBlock, SwitchTargets, @@ -26,6 +25,7 @@ TrivialTypeTraversalAndLiftImpls! { TrivialTypeTraversalImpls! { ConstValue<'tcx>, + NullOp<'tcx>, } impl<'tcx> TypeFoldable> for &'tcx [InlineAsmTemplatePiece] { diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index b49125f4e5f3d..8700a98b365e5 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -20,7 +20,7 @@ use rustc_middle::mir::interpret::AllocId; use rustc_middle::mir::{self, BinOp, BorrowKind, FakeReadCause, Mutability, UnOp}; use rustc_middle::ty::adjustment::PointerCast; use rustc_middle::ty::subst::SubstsRef; -use rustc_middle::ty::{self, AdtDef, FnSig, Ty, UpvarSubsts}; +use rustc_middle::ty::{self, AdtDef, FnSig, List, Ty, UpvarSubsts}; use rustc_middle::ty::{CanonicalUserType, CanonicalUserTypeAnnotation}; use rustc_span::def_id::LocalDefId; use rustc_span::{sym, Span, Symbol, DUMMY_SP}; @@ -481,6 +481,11 @@ pub enum ExprKind<'tcx> { }, /// Inline assembly, i.e. `asm!()`. InlineAsm(Box>), + /// Field offset (`offset_of!`) + OffsetOf { + container: Ty<'tcx>, + fields: &'tcx List, + }, /// An expression taking a reference to a thread local. ThreadLocalRef(DefId), /// A `yield` expression. diff --git a/compiler/rustc_middle/src/thir/visit.rs b/compiler/rustc_middle/src/thir/visit.rs index 5614528c4cb25..5c7ec31cf93d3 100644 --- a/compiler/rustc_middle/src/thir/visit.rs +++ b/compiler/rustc_middle/src/thir/visit.rs @@ -160,6 +160,7 @@ pub fn walk_expr<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, expr: &Exp } } } + OffsetOf { container: _, fields: _ } => {} ThreadLocalRef(_) => {} Yield { value } => visitor.visit_expr(&visitor.thir()[value]), } diff --git a/compiler/rustc_middle/src/ty/codec.rs b/compiler/rustc_middle/src/ty/codec.rs index 8ef4a46a733aa..5454d406dd128 100644 --- a/compiler/rustc_middle/src/ty/codec.rs +++ b/compiler/rustc_middle/src/ty/codec.rs @@ -19,6 +19,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_middle::ty::TyCtxt; use rustc_serialize::{Decodable, Encodable}; use rustc_span::Span; +use rustc_target::abi::FieldIdx; pub use rustc_type_ir::{TyDecoder, TyEncoder}; use std::hash::Hash; use std::intrinsics; @@ -401,6 +402,15 @@ impl<'tcx, D: TyDecoder>> RefDecodable<'tcx, D> for ty::List>> RefDecodable<'tcx, D> for ty::List { + fn decode(decoder: &mut D) -> &'tcx Self { + let len = decoder.read_usize(); + decoder + .interner() + .mk_fields_from_iter((0..len).map::(|_| Decodable::decode(decoder))) + } +} + impl_decodable_via_ref! { &'tcx ty::TypeckResults<'tcx>, &'tcx ty::List>, @@ -412,6 +422,7 @@ impl_decodable_via_ref! { &'tcx mir::coverage::CodeRegion, &'tcx ty::List, &'tcx ty::List>, + &'tcx ty::List, } #[macro_export] diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index af3a54148e114..6f122d97a39f0 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -155,6 +155,7 @@ pub struct CtxtInterners<'tcx> { layout: InternedSet<'tcx, LayoutS>, adt_def: InternedSet<'tcx, AdtDefData>, external_constraints: InternedSet<'tcx, ExternalConstraintsData<'tcx>>, + fields: InternedSet<'tcx, List>, } impl<'tcx> CtxtInterners<'tcx> { @@ -178,6 +179,7 @@ impl<'tcx> CtxtInterners<'tcx> { layout: Default::default(), adt_def: Default::default(), external_constraints: Default::default(), + fields: Default::default(), } } @@ -1571,6 +1573,7 @@ slice_interners!( projs: pub mk_projs(ProjectionKind), place_elems: pub mk_place_elems(PlaceElem<'tcx>), bound_variable_kinds: pub mk_bound_variable_kinds(ty::BoundVariableKind), + fields: pub mk_fields(FieldIdx), ); impl<'tcx> TyCtxt<'tcx> { @@ -2239,6 +2242,14 @@ impl<'tcx> TyCtxt<'tcx> { T::collect_and_apply(iter, |xs| self.mk_place_elems(xs)) } + pub fn mk_fields_from_iter(self, iter: I) -> T::Output + where + I: Iterator, + T: CollectAndApply>, + { + T::collect_and_apply(iter, |xs| self.mk_fields(xs)) + } + pub fn mk_substs_trait( self, self_ty: Ty<'tcx>, diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs index 47943b94c3b18..ef8955b1d3a31 100644 --- a/compiler/rustc_middle/src/ty/typeck_results.rs +++ b/compiler/rustc_middle/src/ty/typeck_results.rs @@ -208,6 +208,9 @@ pub struct TypeckResults<'tcx> { /// Contains the data for evaluating the effect of feature `capture_disjoint_fields` /// on closure size. pub closure_size_eval: FxHashMap>, + + /// Container types and field indices of `offset_of!` expressions + offset_of_data: ItemLocalMap<(Ty<'tcx>, Vec)>, } /// Whenever a value may be live across a generator yield, the type of that value winds up in the @@ -280,6 +283,7 @@ impl<'tcx> TypeckResults<'tcx> { generator_interior_predicates: Default::default(), treat_byte_string_as_slice: Default::default(), closure_size_eval: Default::default(), + offset_of_data: Default::default(), } } @@ -530,6 +534,14 @@ impl<'tcx> TypeckResults<'tcx> { pub fn coercion_casts(&self) -> &ItemLocalSet { &self.coercion_casts } + + pub fn offset_of_data(&self) -> LocalTableInContext<'_, (Ty<'tcx>, Vec)> { + LocalTableInContext { hir_owner: self.hir_owner, data: &self.offset_of_data } + } + + pub fn offset_of_data_mut(&mut self) -> LocalTableInContextMut<'_, (Ty<'tcx>, Vec)> { + LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.offset_of_data } + } } /// Validate that the given HirId (respectively its `local_id` part) can be diff --git a/compiler/rustc_mir_build/src/build/expr/as_place.rs b/compiler/rustc_mir_build/src/build/expr/as_place.rs index fb775766c6541..7ec57add66b59 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_place.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_place.rs @@ -557,6 +557,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | ExprKind::ConstBlock { .. } | ExprKind::StaticRef { .. } | ExprKind::InlineAsm { .. } + | ExprKind::OffsetOf { .. } | ExprKind::Yield { .. } | ExprKind::ThreadLocalRef(_) | ExprKind::Call { .. } => { diff --git a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs index 8631749a524be..fbde0b28f54ea 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs @@ -481,6 +481,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { })))) } + ExprKind::OffsetOf { container, fields } => { + block.and(Rvalue::NullaryOp(NullOp::OffsetOf(fields), container)) + } + ExprKind::Literal { .. } | ExprKind::NamedConst { .. } | ExprKind::NonHirLiteral { .. } diff --git a/compiler/rustc_mir_build/src/build/expr/category.rs b/compiler/rustc_mir_build/src/build/expr/category.rs index d33401f07645e..d9aa461c19d40 100644 --- a/compiler/rustc_mir_build/src/build/expr/category.rs +++ b/compiler/rustc_mir_build/src/build/expr/category.rs @@ -67,7 +67,8 @@ impl Category { | ExprKind::Repeat { .. } | ExprKind::Assign { .. } | ExprKind::AssignOp { .. } - | ExprKind::ThreadLocalRef(_) => Some(Category::Rvalue(RvalueFunc::AsRvalue)), + | ExprKind::ThreadLocalRef(_) + | ExprKind::OffsetOf { .. } => Some(Category::Rvalue(RvalueFunc::AsRvalue)), ExprKind::ConstBlock { .. } | ExprKind::Literal { .. } diff --git a/compiler/rustc_mir_build/src/build/expr/into.rs b/compiler/rustc_mir_build/src/build/expr/into.rs index 9b38ac1cc4ce7..29ff916d2cc9c 100644 --- a/compiler/rustc_mir_build/src/build/expr/into.rs +++ b/compiler/rustc_mir_build/src/build/expr/into.rs @@ -561,7 +561,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | ExprKind::ZstLiteral { .. } | ExprKind::ConstParam { .. } | ExprKind::ThreadLocalRef(_) - | ExprKind::StaticRef { .. } => { + | ExprKind::StaticRef { .. } + | ExprKind::OffsetOf { .. } => { debug_assert!(match Category::of(&expr.kind).unwrap() { // should be handled above Category::Rvalue(RvalueFunc::Into) => false, diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs index bc1a0fac1297e..0506f2bf2385c 100644 --- a/compiler/rustc_mir_build/src/check_unsafety.rs +++ b/compiler/rustc_mir_build/src/check_unsafety.rs @@ -323,6 +323,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { | ExprKind::Box { .. } | ExprKind::If { .. } | ExprKind::InlineAsm { .. } + | ExprKind::OffsetOf { .. } | ExprKind::LogicalOp { .. } | ExprKind::Use { .. } => { // We don't need to save the old value and restore it diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 8e2e92e6f6a94..ce13d522aae0d 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -664,6 +664,14 @@ impl<'tcx> Cx<'tcx> { line_spans: asm.line_spans, })), + hir::ExprKind::OffsetOf(_, _) => { + let data = self.typeck_results.offset_of_data(); + let &(container, ref indices) = data.get(expr.hir_id).unwrap(); + let fields = tcx.mk_fields_from_iter(indices.iter().copied()); + + ExprKind::OffsetOf { container, fields } + } + hir::ExprKind::ConstBlock(ref anon_const) => { let ty = self.typeck_results().node_type(anon_const.hir_id); let did = anon_const.def_id.to_def_id(); diff --git a/compiler/rustc_mir_build/src/thir/print.rs b/compiler/rustc_mir_build/src/thir/print.rs index 0e4eca6844f2f..b2f2a64e29c8c 100644 --- a/compiler/rustc_mir_build/src/thir/print.rs +++ b/compiler/rustc_mir_build/src/thir/print.rs @@ -519,6 +519,19 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { self.print_inline_asm_expr(&**expr, depth_lvl + 2); print_indented!(self, "}", depth_lvl); } + OffsetOf { container, fields } => { + print_indented!(self, "OffsetOf {", depth_lvl); + print_indented!(self, format!("container: {:?}", container), depth_lvl + 1); + print_indented!(self, "fields: [", depth_lvl + 1); + + for field in fields.iter() { + print_indented!(self, format!("{:?}", field), depth_lvl + 2); + print_indented!(self, ",", depth_lvl + 1); + } + + print_indented!(self, "]", depth_lvl + 1); + print_indented!(self, "}", depth_lvl); + } ThreadLocalRef(def_id) => { print_indented!(self, "ThreadLocalRef {", depth_lvl); print_indented!(self, format!("def_id: {:?}", def_id), depth_lvl + 1); diff --git a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs index 64ed7a29f6f3d..736ca62cacccc 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs @@ -360,7 +360,7 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> { | Rvalue::AddressOf(..) | Rvalue::Discriminant(..) | Rvalue::Len(..) - | Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf, _) => {} + | Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {} } } diff --git a/compiler/rustc_mir_transform/src/separate_const_switch.rs b/compiler/rustc_mir_transform/src/separate_const_switch.rs index ef367faf6a707..2479856b727da 100644 --- a/compiler/rustc_mir_transform/src/separate_const_switch.rs +++ b/compiler/rustc_mir_transform/src/separate_const_switch.rs @@ -303,8 +303,7 @@ fn find_determining_place<'tcx>( | Rvalue::NullaryOp(_, _) | Rvalue::ShallowInitBox(_, _) | Rvalue::UnaryOp(_, Operand::Constant(_)) - | Rvalue::Cast(_, Operand::Constant(_), _) - => return None, + | Rvalue::Cast(_, Operand::Constant(_), _) => return None, } } diff --git a/compiler/rustc_passes/src/dead.rs b/compiler/rustc_passes/src/dead.rs index 5cfe691df17a1..3ae5b45d33024 100644 --- a/compiler/rustc_passes/src/dead.rs +++ b/compiler/rustc_passes/src/dead.rs @@ -237,6 +237,37 @@ impl<'tcx> MarkSymbolVisitor<'tcx> { } } + fn handle_offset_of(&mut self, expr: &'tcx hir::Expr<'tcx>) { + let data = self.typeck_results().offset_of_data(); + let &(container, ref indices) = + data.get(expr.hir_id).expect("no offset_of_data for offset_of"); + + let body_did = self.typeck_results().hir_owner.to_def_id(); + let param_env = self.tcx.param_env(body_did); + + let mut current_ty = container; + + for &index in indices { + match current_ty.kind() { + ty::Adt(def, subst) => { + let field = &def.non_enum_variant().fields[index]; + + self.insert_def_id(field.did); + let field_ty = field.ty(self.tcx, subst); + + current_ty = self.tcx.normalize_erasing_regions(param_env, field_ty); + } + // we don't need to mark tuple fields as live, + // but we may need to mark subfields + ty::Tuple(tys) => { + current_ty = + self.tcx.normalize_erasing_regions(param_env, tys[index.as_usize()]); + } + _ => span_bug!(expr.span, "named field access on non-ADT"), + } + } + } + fn mark_live_symbols(&mut self) { let mut scanned = LocalDefIdSet::default(); while let Some(id) = self.worklist.pop() { @@ -405,6 +436,9 @@ impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> { hir::ExprKind::Closure(cls) => { self.insert_def_id(cls.def_id.to_def_id()); } + hir::ExprKind::OffsetOf(..) => { + self.handle_offset_of(expr); + } _ => (), } diff --git a/compiler/rustc_passes/src/hir_stats.rs b/compiler/rustc_passes/src/hir_stats.rs index 47e032758f23d..dc5e454074ded 100644 --- a/compiler/rustc_passes/src/hir_stats.rs +++ b/compiler/rustc_passes/src/hir_stats.rs @@ -302,7 +302,8 @@ impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> { [ ConstBlock, Array, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, DropTemps, Let, If, Loop, Match, Closure, Block, Assign, AssignOp, Field, Index, - Path, AddrOf, Break, Continue, Ret, InlineAsm, Struct, Repeat, Yield, Err + Path, AddrOf, Break, Continue, Ret, InlineAsm, OffsetOf, Struct, Repeat, Yield, + Err ] ); hir_visit::walk_expr(self, e) @@ -568,7 +569,7 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> { Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let, If, While, ForLoop, Loop, Match, Closure, Block, Async, Await, TryBlock, Assign, AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret, - InlineAsm, FormatArgs, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, IncludedBytes, Err + InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, IncludedBytes, Err ] ); ast_visit::walk_expr(self, e) diff --git a/compiler/rustc_passes/src/liveness.rs b/compiler/rustc_passes/src/liveness.rs index a8471ce3b6fb2..b39a8c5598f91 100644 --- a/compiler/rustc_passes/src/liveness.rs +++ b/compiler/rustc_passes/src/liveness.rs @@ -473,6 +473,7 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> { | hir::ExprKind::Struct(..) | hir::ExprKind::Repeat(..) | hir::ExprKind::InlineAsm(..) + | hir::ExprKind::OffsetOf(..) | hir::ExprKind::Type(..) | hir::ExprKind::Err(_) | hir::ExprKind::Path(hir::QPath::TypeRelative(..)) @@ -1129,7 +1130,8 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { | hir::ExprKind::ConstBlock(..) | hir::ExprKind::Err(_) | hir::ExprKind::Path(hir::QPath::TypeRelative(..)) - | hir::ExprKind::Path(hir::QPath::LangItem(..)) => succ, + | hir::ExprKind::Path(hir::QPath::LangItem(..)) + | hir::ExprKind::OffsetOf(..) => succ, // Note that labels have been resolved, so we don't need to look // at the label ident @@ -1418,6 +1420,7 @@ fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr<'tcx>) { | hir::ExprKind::ConstBlock(..) | hir::ExprKind::Block(..) | hir::ExprKind::AddrOf(..) + | hir::ExprKind::OffsetOf(..) | hir::ExprKind::Struct(..) | hir::ExprKind::Repeat(..) | hir::ExprKind::Closure { .. } diff --git a/compiler/rustc_passes/src/naked_functions.rs b/compiler/rustc_passes/src/naked_functions.rs index c398467f03ef8..cf8d9300a1167 100644 --- a/compiler/rustc_passes/src/naked_functions.rs +++ b/compiler/rustc_passes/src/naked_functions.rs @@ -203,6 +203,7 @@ impl<'tcx> CheckInlineAssembly<'tcx> { | ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) + | ExprKind::OffsetOf(..) | ExprKind::Struct(..) | ExprKind::Repeat(..) | ExprKind::Yield(..) => { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index d6ee7ac34aaca..9891915d076dd 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1037,6 +1037,7 @@ symbols! { object_safe_for_dispatch, of, offset, + offset_of, omit_gdb_pretty_printer_section, on, on_unimplemented, diff --git a/compiler/rustc_target/src/abi/mod.rs b/compiler/rustc_target/src/abi/mod.rs index 8d2e92cc76c6f..589cd3cf96b3e 100644 --- a/compiler/rustc_target/src/abi/mod.rs +++ b/compiler/rustc_target/src/abi/mod.rs @@ -124,6 +124,21 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { { Ty::is_unit(self) } + + pub fn offset_of_subfield(self, cx: &C, indices: impl Iterator) -> Size + where + Ty: TyAbiInterface<'a, C>, + { + let mut layout = self; + let mut offset = Size::ZERO; + + for index in indices { + offset += layout.fields.offset(index); + layout = layout.field(cx, index); + } + + offset + } } impl<'a, Ty> TyAndLayout<'a, Ty> { diff --git a/compiler/rustc_ty_utils/src/consts.rs b/compiler/rustc_ty_utils/src/consts.rs index e650792f4d5d4..b08a92570ed71 100644 --- a/compiler/rustc_ty_utils/src/consts.rs +++ b/compiler/rustc_ty_utils/src/consts.rs @@ -256,6 +256,7 @@ fn recurse_build<'tcx>( ExprKind::VarRef { .. } | ExprKind::UpvarRef { .. } | ExprKind::StaticRef { .. } + | ExprKind::OffsetOf { .. } | ExprKind::ThreadLocalRef(_) => { error(GenericConstantTooComplexSub::OperationNotSupported(node.span))? } @@ -347,6 +348,7 @@ impl<'a, 'tcx> IsThirPolymorphic<'a, 'tcx> { | thir::ExprKind::ZstLiteral { .. } | thir::ExprKind::StaticRef { .. } | thir::ExprKind::InlineAsm(_) + | thir::ExprKind::OffsetOf { .. } | thir::ExprKind::ThreadLocalRef(_) | thir::ExprKind::Yield { .. } => false, } diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs index 30ec73cabf849..7d2f297152365 100644 --- a/library/core/src/mem/mod.rs +++ b/library/core/src/mem/mod.rs @@ -1279,3 +1279,45 @@ pub trait SizedTypeProperties: Sized { #[doc(hidden)] #[unstable(feature = "sized_type_properties", issue = "none")] impl SizedTypeProperties for T {} + +/// Expands to the offset in bytes of a field from the beginning of the given type. +/// +/// Only structs, unions and tuples are supported. +/// +/// Nested field accesses may be used, but not array indexes like in `C`'s `offsetof`. +/// +/// Note that the output of this macro is not stable, except for `#[repr(C)]` types. +/// +/// # Examples +/// +/// ``` +/// #![feature(offset_of)] +/// +/// use std::mem; +/// #[repr(C)] +/// struct FieldStruct { +/// first: u8, +/// second: u16, +/// third: u8 +/// } +/// +/// assert_eq!(mem::offset_of!(FieldStruct, first), 0); +/// assert_eq!(mem::offset_of!(FieldStruct, second), 2); +/// assert_eq!(mem::offset_of!(FieldStruct, third), 4); +/// +/// #[repr(C)] +/// struct NestedA { +/// b: NestedB +/// } +/// +/// #[repr(C)] +/// struct NestedB(u8); +/// +/// assert_eq!(mem::offset_of!(NestedA, b.0), 0); +/// ``` +#[unstable(feature = "offset_of", issue = "106655")] +#[rustc_builtin_macro] +#[cfg(not(bootstrap))] +pub macro offset_of($Container:ty, $($fields:tt).+ $(,)?) { + /* compiler built-in */ +} diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index f460da35dd3e6..84859a54c2604 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -109,6 +109,7 @@ #![feature(utf8_chunks)] #![feature(is_ascii_octdigit)] #![feature(get_many_mut)] +#![cfg_attr(not(bootstrap), feature(offset_of))] #![deny(unsafe_op_in_unsafe_fn)] #![deny(fuzzy_provenance_casts)] diff --git a/library/core/tests/mem.rs b/library/core/tests/mem.rs index f7740a114e738..7f03381690141 100644 --- a/library/core/tests/mem.rs +++ b/library/core/tests/mem.rs @@ -364,3 +364,193 @@ fn const_maybe_uninit() { assert_eq!(FIELD_BY_FIELD, Foo { x: 1, y: 2 }); } + +#[test] +#[cfg(not(bootstrap))] +fn offset_of() { + #[repr(C)] + struct Foo { + x: u8, + y: u16, + z: Bar, + } + + #[repr(C)] + struct Bar(u8, u8); + + assert_eq!(offset_of!(Foo, x), 0); + assert_eq!(offset_of!(Foo, y), 2); + assert_eq!(offset_of!(Foo, z.0), 4); + assert_eq!(offset_of!(Foo, z.1), 5); + + // Layout of tuples is unstable + assert!(offset_of!((u8, u16), 0) <= size_of::<(u8, u16)>() - 1); + assert!(offset_of!((u8, u16), 1) <= size_of::<(u8, u16)>() - 2); +} + +#[test] +#[cfg(not(bootstrap))] +fn offset_of_union() { + #[repr(C)] + union Foo { + x: u8, + y: u16, + z: Bar, + } + + #[repr(C)] + #[derive(Copy, Clone)] + struct Bar(u8, u8); + + assert_eq!(offset_of!(Foo, x), 0); + assert_eq!(offset_of!(Foo, y), 0); + assert_eq!(offset_of!(Foo, z.0), 0); + assert_eq!(offset_of!(Foo, z.1), 1); +} + +#[test] +#[cfg(not(bootstrap))] +fn offset_of_dst() { + #[repr(C)] + struct Alpha { + x: u8, + y: u16, + z: [u8], + } + + trait Trait {} + + #[repr(C)] + struct Beta { + x: u8, + y: u16, + z: dyn Trait, + } + + extern "C" { + type Extern; + } + + #[repr(C)] + struct Gamma { + x: u8, + y: u16, + z: Extern, + } + + assert_eq!(offset_of!(Alpha, x), 0); + assert_eq!(offset_of!(Alpha, y), 2); + + assert_eq!(offset_of!(Beta, x), 0); + assert_eq!(offset_of!(Beta, y), 2); + + assert_eq!(offset_of!(Gamma, x), 0); + assert_eq!(offset_of!(Gamma, y), 2); +} + +#[test] +#[cfg(not(bootstrap))] +fn offset_of_packed() { + #[repr(C, packed)] + struct Foo { + x: u8, + y: u16, + } + + assert_eq!(offset_of!(Foo, x), 0); + assert_eq!(offset_of!(Foo, y), 1); +} + +#[test] +#[cfg(not(bootstrap))] +fn offset_of_projection() { + #[repr(C)] + struct Foo { + x: u8, + y: u16, + } + + trait Projector { + type Type; + } + + impl Projector for () { + type Type = Foo; + } + + assert_eq!(offset_of!(<() as Projector>::Type, x), 0); + assert_eq!(offset_of!(<() as Projector>::Type, y), 2); +} + +#[test] +#[cfg(not(bootstrap))] +fn offset_of_alias() { + #[repr(C)] + struct Foo { + x: u8, + y: u16, + } + + type Bar = Foo; + + assert_eq!(offset_of!(Bar, x), 0); + assert_eq!(offset_of!(Bar, y), 2); +} + +#[test] +#[cfg(not(bootstrap))] +fn const_offset_of() { + #[repr(C)] + struct Foo { + x: u8, + y: u16, + } + + const X_OFFSET: usize = offset_of!(Foo, x); + const Y_OFFSET: usize = offset_of!(Foo, y); + + assert_eq!(X_OFFSET, 0); + assert_eq!(Y_OFFSET, 2); +} + +#[test] +#[cfg(not(bootstrap))] +fn offset_of_without_const_promotion() { + #[repr(C)] + struct Foo { + x: u8, + y: u16, + _scp: SuppressConstPromotion, + } + + // Normally, offset_of is always const promoted. + // The generic parameter prevents this from happening. + // This is needed to test the codegen impl of offset_of + fn inner() { + assert_eq!(offset_of!(Foo, x), 0); + assert_eq!(offset_of!(Foo, y), 2); + } + + inner::<()>(); +} + +#[test] +#[cfg(not(bootstrap))] +fn offset_of_addr() { + #[repr(C)] + struct Foo { + x: u8, + y: u16, + z: Bar, + } + + #[repr(C)] + struct Bar(u8, u8); + + let base = Foo { x: 0, y: 0, z: Bar(0, 0) }; + + assert_eq!(ptr::addr_of!(base).addr() + offset_of!(Foo, x), ptr::addr_of!(base.x).addr()); + assert_eq!(ptr::addr_of!(base).addr() + offset_of!(Foo, y), ptr::addr_of!(base.y).addr()); + assert_eq!(ptr::addr_of!(base).addr() + offset_of!(Foo, z.0), ptr::addr_of!(base.z.0).addr()); + assert_eq!(ptr::addr_of!(base).addr() + offset_of!(Foo, z.1), ptr::addr_of!(base.z.1).addr()); +} diff --git a/src/tools/clippy/clippy_lints/src/loops/never_loop.rs b/src/tools/clippy/clippy_lints/src/loops/never_loop.rs index f0a1b1dfe5628..5f1fdf00be8c3 100644 --- a/src/tools/clippy/clippy_lints/src/loops/never_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/never_loop.rs @@ -226,7 +226,8 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec, main_loop_id: H | InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise, }) .fold(NeverLoopResult::Otherwise, combine_seq), - ExprKind::Yield(_, _) + ExprKind::OffsetOf(_, _) + | ExprKind::Yield(_, _) | ExprKind::Closure { .. } | ExprKind::Path(_) | ExprKind::ConstBlock(_) diff --git a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 04225beeb704b..7945275393c04 100644 --- a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -342,6 +342,7 @@ impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> { ExprKind::DropTemps(_) | ExprKind::Err(_) | ExprKind::InlineAsm(_) | + ExprKind::OffsetOf(_, _) | ExprKind::Let(_) | ExprKind::Lit(_) | ExprKind::Loop(_, _, _, _) | diff --git a/src/tools/clippy/clippy_lints/src/utils/author.rs b/src/tools/clippy/clippy_lints/src/utils/author.rs index 2dac807c420e3..01927b6b5f10d 100644 --- a/src/tools/clippy/clippy_lints/src/utils/author.rs +++ b/src/tools/clippy/clippy_lints/src/utils/author.rs @@ -558,6 +558,10 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { kind!("InlineAsm(_)"); out!("// unimplemented: `ExprKind::InlineAsm` is not further destructured at the moment"); }, + ExprKind::OffsetOf(container, ref fields) => { + bind!(self, container, fields); + kind!("OffsetOf({container}, {fields})"); + } ExprKind::Struct(qpath, fields, base) => { bind!(self, qpath, fields); opt_bind!(self, base); diff --git a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs index 28c8571706135..3df40942e7b5a 100644 --- a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs +++ b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs @@ -218,7 +218,8 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS | ExprKind::AddrOf(..) | ExprKind::Struct(..) | ExprKind::Repeat(..) - | ExprKind::Block(Block { stmts: [], .. }, _) => (), + | ExprKind::Block(Block { stmts: [], .. }, _) + | ExprKind::OffsetOf(..) => (), // Assignment might be to a local defined earlier, so don't eagerly evaluate. // Blocks with multiple statements might be expensive, so don't eagerly evaluate. diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs index 3ee7147828bd5..d972ed82c258b 100644 --- a/src/tools/clippy/clippy_utils/src/hir_utils.rs +++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs @@ -301,6 +301,9 @@ impl HirEqInterExpr<'_, '_, '_> { (&ExprKind::Unary(l_op, le), &ExprKind::Unary(r_op, re)) => l_op == r_op && self.eq_expr(le, re), (&ExprKind::Array(l), &ExprKind::Array(r)) => self.eq_exprs(l, r), (&ExprKind::DropTemps(le), &ExprKind::DropTemps(re)) => self.eq_expr(le, re), + (&ExprKind::OffsetOf(l_container, ref l_fields), &ExprKind::OffsetOf(r_container, ref r_fields)) => { + self.eq_ty(l_container, r_container) && over(l_fields, r_fields, |l, r| l.name == r.name) + }, _ => false, }; (is_eq && (!self.should_ignore(left) || !self.should_ignore(right))) @@ -701,6 +704,12 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { } } }, + ExprKind::OffsetOf(container, fields) => { + self.hash_ty(container); + for field in fields { + self.hash_name(field.name); + } + }, ExprKind::Let(Let { pat, init, ty, .. }) => { self.hash_expr(init); if let Some(ty) = ty { diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs index 354b6d71aa466..ecd712f32dc1f 100644 --- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs @@ -194,7 +194,7 @@ fn check_rvalue<'tcx>( )) } }, - Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf, _) | Rvalue::ShallowInitBox(_, _) => Ok(()), + Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_), _) | Rvalue::ShallowInitBox(_, _) => Ok(()), Rvalue::UnaryOp(_, operand) => { let ty = operand.ty(body, tcx); if ty.is_integral() || ty.is_bool() { diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs index a5a4a921d94ec..e81eadceec0aa 100644 --- a/src/tools/clippy/clippy_utils/src/sugg.rs +++ b/src/tools/clippy/clippy_utils/src/sugg.rs @@ -139,6 +139,7 @@ impl<'a> Sugg<'a> { | hir::ExprKind::Field(..) | hir::ExprKind::Index(..) | hir::ExprKind::InlineAsm(..) + | hir::ExprKind::OffsetOf(..) | hir::ExprKind::ConstBlock(..) | hir::ExprKind::Lit(..) | hir::ExprKind::Loop(..) @@ -197,6 +198,7 @@ impl<'a> Sugg<'a> { | ast::ExprKind::ForLoop(..) | ast::ExprKind::Index(..) | ast::ExprKind::InlineAsm(..) + | ast::ExprKind::OffsetOf(..) | ast::ExprKind::ConstBlock(..) | ast::ExprKind::Lit(..) | ast::ExprKind::IncludedBytes(..) diff --git a/src/tools/clippy/clippy_utils/src/visitors.rs b/src/tools/clippy/clippy_utils/src/visitors.rs index 1dc19bac98444..5dcd71cef127e 100644 --- a/src/tools/clippy/clippy_utils/src/visitors.rs +++ b/src/tools/clippy/clippy_utils/src/visitors.rs @@ -662,6 +662,7 @@ pub fn for_each_unconsumed_temporary<'tcx, B>( | ExprKind::Path(_) | ExprKind::Continue(_) | ExprKind::InlineAsm(_) + | ExprKind::OffsetOf(..) | ExprKind::Err(_) => (), } ControlFlow::Continue(()) diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs index ac96bedf2fe86..824e42191364f 100644 --- a/src/tools/rustfmt/src/expr.rs +++ b/src/tools/rustfmt/src/expr.rs @@ -399,7 +399,9 @@ pub(crate) fn format_expr( } } ast::ExprKind::Underscore => Some("_".to_owned()), - ast::ExprKind::FormatArgs(..) | ast::ExprKind::IncludedBytes(..) => { + ast::ExprKind::FormatArgs(..) + | ast::ExprKind::IncludedBytes(..) + | ast::ExprKind::OffsetOf(..) => { // These do not occur in the AST because macros aren't expanded. unreachable!() } diff --git a/src/tools/rustfmt/src/utils.rs b/src/tools/rustfmt/src/utils.rs index a26375ee64384..ca1716574071b 100644 --- a/src/tools/rustfmt/src/utils.rs +++ b/src/tools/rustfmt/src/utils.rs @@ -499,6 +499,7 @@ pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr | ast::ExprKind::Field(..) | ast::ExprKind::IncludedBytes(..) | ast::ExprKind::InlineAsm(..) + | ast::ExprKind::OffsetOf(..) | ast::ExprKind::Let(..) | ast::ExprKind::Path(..) | ast::ExprKind::Range(..) diff --git a/tests/mir-opt/const_prop/offset_of.concrete.ConstProp.diff b/tests/mir-opt/const_prop/offset_of.concrete.ConstProp.diff new file mode 100644 index 0000000000000..4e2f1b39d2b16 --- /dev/null +++ b/tests/mir-opt/const_prop/offset_of.concrete.ConstProp.diff @@ -0,0 +1,43 @@ +- // MIR for `concrete` before ConstProp ++ // MIR for `concrete` after ConstProp + + fn concrete() -> () { + let mut _0: (); // return place in scope 0 at $DIR/offset_of.rs:+0:15: +0:15 + let _1: usize; // in scope 0 at $DIR/offset_of.rs:+1:9: +1:10 + scope 1 { + debug x => _1; // in scope 1 at $DIR/offset_of.rs:+1:9: +1:10 + let _2: usize; // in scope 1 at $DIR/offset_of.rs:+2:9: +2:10 + scope 2 { + debug y => _2; // in scope 2 at $DIR/offset_of.rs:+2:9: +2:10 + let _3: usize; // in scope 2 at $DIR/offset_of.rs:+3:9: +3:11 + scope 3 { + debug z0 => _3; // in scope 3 at $DIR/offset_of.rs:+3:9: +3:11 + let _4: usize; // in scope 3 at $DIR/offset_of.rs:+4:9: +4:11 + scope 4 { + debug z1 => _4; // in scope 4 at $DIR/offset_of.rs:+4:9: +4:11 + } + } + } + } + + bb0: { + StorageLive(_1); // scope 0 at $DIR/offset_of.rs:+1:9: +1:10 +- _1 = OffsetOf(Alpha, [0]); // scope 0 at $DIR/offset_of.rs:+1:13: +1:33 ++ _1 = const 4_usize; // scope 0 at $DIR/offset_of.rs:+1:13: +1:33 + StorageLive(_2); // scope 1 at $DIR/offset_of.rs:+2:9: +2:10 +- _2 = OffsetOf(Alpha, [1]); // scope 1 at $DIR/offset_of.rs:+2:13: +2:33 ++ _2 = const 0_usize; // scope 1 at $DIR/offset_of.rs:+2:13: +2:33 + StorageLive(_3); // scope 2 at $DIR/offset_of.rs:+3:9: +3:11 +- _3 = OffsetOf(Alpha, [2, 0]); // scope 2 at $DIR/offset_of.rs:+3:14: +3:36 ++ _3 = const 2_usize; // scope 2 at $DIR/offset_of.rs:+3:14: +3:36 + StorageLive(_4); // scope 3 at $DIR/offset_of.rs:+4:9: +4:11 +- _4 = OffsetOf(Alpha, [2, 1]); // scope 3 at $DIR/offset_of.rs:+4:14: +4:36 ++ _4 = const 3_usize; // scope 3 at $DIR/offset_of.rs:+4:14: +4:36 + StorageDead(_4); // scope 3 at $DIR/offset_of.rs:+5:1: +5:2 + StorageDead(_3); // scope 2 at $DIR/offset_of.rs:+5:1: +5:2 + StorageDead(_2); // scope 1 at $DIR/offset_of.rs:+5:1: +5:2 + StorageDead(_1); // scope 0 at $DIR/offset_of.rs:+5:1: +5:2 + return; // scope 0 at $DIR/offset_of.rs:+5:2: +5:2 + } + } + diff --git a/tests/mir-opt/const_prop/offset_of.generic.ConstProp.diff b/tests/mir-opt/const_prop/offset_of.generic.ConstProp.diff new file mode 100644 index 0000000000000..5c6cb47089e82 --- /dev/null +++ b/tests/mir-opt/const_prop/offset_of.generic.ConstProp.diff @@ -0,0 +1,39 @@ +- // MIR for `generic` before ConstProp ++ // MIR for `generic` after ConstProp + + fn generic() -> () { + let mut _0: (); // return place in scope 0 at $DIR/offset_of.rs:+0:17: +0:17 + let _1: usize; // in scope 0 at $DIR/offset_of.rs:+1:9: +1:11 + scope 1 { + debug gx => _1; // in scope 1 at $DIR/offset_of.rs:+1:9: +1:11 + let _2: usize; // in scope 1 at $DIR/offset_of.rs:+2:9: +2:11 + scope 2 { + debug gy => _2; // in scope 2 at $DIR/offset_of.rs:+2:9: +2:11 + let _3: usize; // in scope 2 at $DIR/offset_of.rs:+3:9: +3:11 + scope 3 { + debug dx => _3; // in scope 3 at $DIR/offset_of.rs:+3:9: +3:11 + let _4: usize; // in scope 3 at $DIR/offset_of.rs:+4:9: +4:11 + scope 4 { + debug dy => _4; // in scope 4 at $DIR/offset_of.rs:+4:9: +4:11 + } + } + } + } + + bb0: { + StorageLive(_1); // scope 0 at $DIR/offset_of.rs:+1:9: +1:11 + _1 = OffsetOf(Gamma, [0]); // scope 0 at $DIR/offset_of.rs:+1:14: +1:37 + StorageLive(_2); // scope 1 at $DIR/offset_of.rs:+2:9: +2:11 + _2 = OffsetOf(Gamma, [1]); // scope 1 at $DIR/offset_of.rs:+2:14: +2:37 + StorageLive(_3); // scope 2 at $DIR/offset_of.rs:+3:9: +3:11 + _3 = OffsetOf(Delta, [1]); // scope 2 at $DIR/offset_of.rs:+3:14: +3:37 + StorageLive(_4); // scope 3 at $DIR/offset_of.rs:+4:9: +4:11 + _4 = OffsetOf(Delta, [2]); // scope 3 at $DIR/offset_of.rs:+4:14: +4:37 + StorageDead(_4); // scope 3 at $DIR/offset_of.rs:+5:1: +5:2 + StorageDead(_3); // scope 2 at $DIR/offset_of.rs:+5:1: +5:2 + StorageDead(_2); // scope 1 at $DIR/offset_of.rs:+5:1: +5:2 + StorageDead(_1); // scope 0 at $DIR/offset_of.rs:+5:1: +5:2 + return; // scope 0 at $DIR/offset_of.rs:+5:2: +5:2 + } + } + diff --git a/tests/mir-opt/const_prop/offset_of.rs b/tests/mir-opt/const_prop/offset_of.rs new file mode 100644 index 0000000000000..eabdf84807986 --- /dev/null +++ b/tests/mir-opt/const_prop/offset_of.rs @@ -0,0 +1,49 @@ +// unit-test +// compile-flags: -O + +#![feature(offset_of)] + +use std::marker::PhantomData; +use std::mem::offset_of; + +struct Alpha { + x: u8, + y: u16, + z: Beta, +} + +struct Beta(u8, u8); + +struct Gamma { + x: u8, + y: u16, + _t: T, +} + +#[repr(C)] +struct Delta { + _phantom: PhantomData, + x: u8, + y: u16, +} + +// EMIT_MIR offset_of.concrete.ConstProp.diff +fn concrete() { + let x = offset_of!(Alpha, x); + let y = offset_of!(Alpha, y); + let z0 = offset_of!(Alpha, z.0); + let z1 = offset_of!(Alpha, z.1); +} + +// EMIT_MIR offset_of.generic.ConstProp.diff +fn generic() { + let gx = offset_of!(Gamma, x); + let gy = offset_of!(Gamma, y); + let dx = offset_of!(Delta, x); + let dy = offset_of!(Delta, y); +} + +fn main() { + concrete(); + generic::<()>(); +} diff --git a/tests/ui/lint/dead-code/offset-of-correct-param-env.rs b/tests/ui/lint/dead-code/offset-of-correct-param-env.rs new file mode 100644 index 0000000000000..2c6fcef250049 --- /dev/null +++ b/tests/ui/lint/dead-code/offset-of-correct-param-env.rs @@ -0,0 +1,42 @@ +// check-pass + +#![feature(offset_of)] +#![deny(dead_code)] + +// This struct contains a projection that can only be normalized after getting the field type. +struct A { + a: ::EquateParamTo, +} + +// This is the inner struct that we want to get. +struct MyFieldIsNotDead { + not_dead: u8, +} + +// These are some helpers. +// Inside the param env of `test`, we want to make it so that it considers T=MyFieldIsNotDead. +struct GenericIsEqual(T); +trait Project { + type EquateParamTo; +} +impl Project for GenericIsEqual { + type EquateParamTo = T; +} + +fn test() -> usize +where + GenericIsEqual: Project, +{ + // The first field of the A that we construct here is + // `> as Project>::EquateParamTo`. + // Typeck normalizes this and figures that the not_dead field is totally fine and accessible. + // But importantly, the normalization ends up with T, which, as we've declared in our param + // env is MyFieldDead. When we're in the param env of the `a` field, the where bound above + // is not in scope, so we don't know what T is - it's generic. + // If we use the wrong param env, the lint will ICE. + std::mem::offset_of!(A>, a.not_dead) +} + +fn main() { + test::(); +} diff --git a/tests/ui/lint/dead-code/offset-of.rs b/tests/ui/lint/dead-code/offset-of.rs new file mode 100644 index 0000000000000..da91de3862fc8 --- /dev/null +++ b/tests/ui/lint/dead-code/offset-of.rs @@ -0,0 +1,44 @@ +#![feature(offset_of)] +#![deny(dead_code)] + +use std::mem::offset_of; + +struct Alpha { + a: (), + b: (), //~ ERROR field `b` is never read + c: Beta, +} + +struct Beta { + a: (), //~ ERROR field `a` is never read + b: (), +} + +struct Gamma { + a: (), //~ ERROR field `a` is never read + b: (), +} + +struct Delta { + a: (), + b: (), //~ ERROR field `b` is never read +} + +trait Trait { + type Assoc; +} +impl Trait for () { + type Assoc = Delta; +} + +struct Project { + a: u8, //~ ERROR field `a` is never read + b: ::Assoc, +} + +fn main() { + offset_of!(Alpha, a); + offset_of!(Alpha, c.b); + offset_of!((Gamma,), 0.b); + offset_of!(Project::<()>, b.a); +} diff --git a/tests/ui/lint/dead-code/offset-of.stderr b/tests/ui/lint/dead-code/offset-of.stderr new file mode 100644 index 0000000000000..ed2916461cde8 --- /dev/null +++ b/tests/ui/lint/dead-code/offset-of.stderr @@ -0,0 +1,50 @@ +error: field `b` is never read + --> $DIR/offset-of.rs:8:5 + | +LL | struct Alpha { + | ----- field in this struct +LL | a: (), +LL | b: (), + | ^ + | +note: the lint level is defined here + --> $DIR/offset-of.rs:2:9 + | +LL | #![deny(dead_code)] + | ^^^^^^^^^ + +error: field `a` is never read + --> $DIR/offset-of.rs:13:5 + | +LL | struct Beta { + | ---- field in this struct +LL | a: (), + | ^ + +error: field `a` is never read + --> $DIR/offset-of.rs:18:5 + | +LL | struct Gamma { + | ----- field in this struct +LL | a: (), + | ^ + +error: field `b` is never read + --> $DIR/offset-of.rs:24:5 + | +LL | struct Delta { + | ----- field in this struct +LL | a: (), +LL | b: (), + | ^ + +error: field `a` is never read + --> $DIR/offset-of.rs:35:5 + | +LL | struct Project { + | ------- field in this struct +LL | a: u8, + | ^ + +error: aborting due to 5 previous errors + diff --git a/tests/ui/user-defined-macro-rules.rs b/tests/ui/macros/user-defined-macro-rules.rs similarity index 100% rename from tests/ui/user-defined-macro-rules.rs rename to tests/ui/macros/user-defined-macro-rules.rs diff --git a/tests/ui/offset-of/auxiliary/offset-of-staged-api.rs b/tests/ui/offset-of/auxiliary/offset-of-staged-api.rs new file mode 100644 index 0000000000000..088086cc58052 --- /dev/null +++ b/tests/ui/offset-of/auxiliary/offset-of-staged-api.rs @@ -0,0 +1,33 @@ +#![crate_type = "lib"] +#![feature(staged_api)] +#![stable(feature = "stable_test_feature", since = "1.0")] + +#[unstable(feature = "unstable_test_feature", issue = "none")] +pub struct Unstable { + #[unstable(feature = "unstable_test_feature", issue = "none")] + pub unstable: u8, +} + +#[stable(feature = "stable_test_feature", since = "1.0")] +pub struct Stable { + #[stable(feature = "stable_test_feature", since = "1.0")] + pub stable: u8, +} + +#[stable(feature = "stable_test_feature", since = "1.0")] +pub struct StableWithUnstableField { + #[unstable(feature = "unstable_test_feature", issue = "none")] + pub unstable: u8, +} + +#[stable(feature = "stable_test_feature", since = "1.0")] +pub struct StableWithUnstableFieldType { + #[stable(feature = "stable_test_feature", since = "1.0")] + pub stable: Unstable, +} + +#[unstable(feature = "unstable_test_feature", issue = "none")] +pub struct UnstableWithStableFieldType { + #[unstable(feature = "unstable_test_feature", issue = "none")] + pub unstable: Stable, +} diff --git a/tests/ui/offset-of/offset-of-arg-count.rs b/tests/ui/offset-of/offset-of-arg-count.rs new file mode 100644 index 0000000000000..163b07454ecdf --- /dev/null +++ b/tests/ui/offset-of/offset-of-arg-count.rs @@ -0,0 +1,9 @@ +#![feature(offset_of)] + +use std::mem::offset_of; + +fn main() { + offset_of!(NotEnoughArguments); //~ ERROR expected one of + offset_of!(NotEnoughArgumentsWithAComma, ); //~ ERROR expected 2 arguments + offset_of!(Container, field, too many arguments); //~ ERROR expected 2 arguments +} diff --git a/tests/ui/offset-of/offset-of-arg-count.stderr b/tests/ui/offset-of/offset-of-arg-count.stderr new file mode 100644 index 0000000000000..ebecc982c5174 --- /dev/null +++ b/tests/ui/offset-of/offset-of-arg-count.stderr @@ -0,0 +1,20 @@ +error: expected one of `!`, `(`, `+`, `,`, `::`, or `<`, found `` + --> $DIR/offset-of-arg-count.rs:6:16 + | +LL | offset_of!(NotEnoughArguments); + | ^^^^^^^^^^^^^^^^^^ expected one of `!`, `(`, `+`, `,`, `::`, or `<` + +error: expected 2 arguments + --> $DIR/offset-of-arg-count.rs:7:5 + | +LL | offset_of!(NotEnoughArgumentsWithAComma, ); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: expected 2 arguments + --> $DIR/offset-of-arg-count.rs:8:5 + | +LL | offset_of!(Container, field, too many arguments); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/tests/ui/offset-of/offset-of-dst-field.rs b/tests/ui/offset-of/offset-of-dst-field.rs new file mode 100644 index 0000000000000..a0269ca2d1251 --- /dev/null +++ b/tests/ui/offset-of/offset-of-dst-field.rs @@ -0,0 +1,33 @@ +#![feature(offset_of, extern_types)] + +use std::mem::offset_of; + +struct Alpha { + x: u8, + y: u16, + z: [u8], +} + +trait Trait {} + +struct Beta { + x: u8, + y: u16, + z: dyn Trait, +} + +extern { + type Extern; +} + +struct Gamma { + x: u8, + y: u16, + z: Extern, +} + +fn main() { + offset_of!(Alpha, z); //~ ERROR the size for values of type + offset_of!(Beta, z); //~ ERROR the size for values of type + offset_of!(Gamma, z); //~ ERROR the size for values of type +} diff --git a/tests/ui/offset-of/offset-of-dst-field.stderr b/tests/ui/offset-of/offset-of-dst-field.stderr new file mode 100644 index 0000000000000..8e88015b07a74 --- /dev/null +++ b/tests/ui/offset-of/offset-of-dst-field.stderr @@ -0,0 +1,27 @@ +error[E0277]: the size for values of type `[u8]` cannot be known at compilation time + --> $DIR/offset-of-dst-field.rs:30:5 + | +LL | offset_of!(Alpha, z); + | ^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `[u8]` + +error[E0277]: the size for values of type `(dyn Trait + 'static)` cannot be known at compilation time + --> $DIR/offset-of-dst-field.rs:31:5 + | +LL | offset_of!(Beta, z); + | ^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `(dyn Trait + 'static)` + +error[E0277]: the size for values of type `Extern` cannot be known at compilation time + --> $DIR/offset-of-dst-field.rs:32:5 + | +LL | offset_of!(Gamma, z); + | ^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `Extern` + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/offset-of/offset-of-enum.rs b/tests/ui/offset-of/offset-of-enum.rs new file mode 100644 index 0000000000000..d73505821ff53 --- /dev/null +++ b/tests/ui/offset-of/offset-of-enum.rs @@ -0,0 +1,13 @@ +#![feature(offset_of)] + +use std::mem::offset_of; + +enum Alpha { + One(u8), + Two(u8), +} + +fn main() { + offset_of!(Alpha::One, 0); //~ ERROR expected type, found variant `Alpha::One` + offset_of!(Alpha, Two.0); //~ ERROR no field `Two` on type `Alpha` +} diff --git a/tests/ui/offset-of/offset-of-enum.stderr b/tests/ui/offset-of/offset-of-enum.stderr new file mode 100644 index 0000000000000..6958d199fbdb4 --- /dev/null +++ b/tests/ui/offset-of/offset-of-enum.stderr @@ -0,0 +1,19 @@ +error[E0573]: expected type, found variant `Alpha::One` + --> $DIR/offset-of-enum.rs:11:16 + | +LL | offset_of!(Alpha::One, 0); + | ^^^^^^^^^^ + | | + | not a type + | help: try using the variant's enum: `Alpha` + +error[E0609]: no field `Two` on type `Alpha` + --> $DIR/offset-of-enum.rs:12:23 + | +LL | offset_of!(Alpha, Two.0); + | ^^^ + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0573, E0609. +For more information about an error, try `rustc --explain E0573`. diff --git a/tests/ui/offset-of/offset-of-private.rs b/tests/ui/offset-of/offset-of-private.rs new file mode 100644 index 0000000000000..0291b7825cabc --- /dev/null +++ b/tests/ui/offset-of/offset-of-private.rs @@ -0,0 +1,16 @@ +#![feature(offset_of)] + +use std::mem::offset_of; + +mod m { + #[repr(C)] + pub struct Foo { + pub public: u8, + private: u8, + } +} + +fn main() { + offset_of!(m::Foo, public); + offset_of!(m::Foo, private); //~ ERROR field `private` of struct `Foo` is private +} diff --git a/tests/ui/offset-of/offset-of-private.stderr b/tests/ui/offset-of/offset-of-private.stderr new file mode 100644 index 0000000000000..8a186dd5a02e7 --- /dev/null +++ b/tests/ui/offset-of/offset-of-private.stderr @@ -0,0 +1,9 @@ +error[E0616]: field `private` of struct `Foo` is private + --> $DIR/offset-of-private.rs:15:24 + | +LL | offset_of!(m::Foo, private); + | ^^^^^^^ private field + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0616`. diff --git a/tests/ui/offset-of/offset-of-unstable-with-feature.rs b/tests/ui/offset-of/offset-of-unstable-with-feature.rs new file mode 100644 index 0000000000000..7d2eb46c056eb --- /dev/null +++ b/tests/ui/offset-of/offset-of-unstable-with-feature.rs @@ -0,0 +1,20 @@ +// check-pass +// aux-build:offset-of-staged-api.rs + +#![feature(offset_of, unstable_test_feature)] + +use std::mem::offset_of; + +extern crate offset_of_staged_api; + +use offset_of_staged_api::*; + +fn main() { + offset_of!(Unstable, unstable); + offset_of!(Stable, stable); + offset_of!(StableWithUnstableField, unstable); + offset_of!(StableWithUnstableFieldType, stable); + offset_of!(StableWithUnstableFieldType, stable.unstable); + offset_of!(UnstableWithStableFieldType, unstable); + offset_of!(UnstableWithStableFieldType, unstable.stable); +} diff --git a/tests/ui/offset-of/offset-of-unstable.rs b/tests/ui/offset-of/offset-of-unstable.rs new file mode 100644 index 0000000000000..1e19f2091f29f --- /dev/null +++ b/tests/ui/offset-of/offset-of-unstable.rs @@ -0,0 +1,31 @@ +// aux-build:offset-of-staged-api.rs + +#![feature(offset_of)] + +use std::mem::offset_of; + +extern crate offset_of_staged_api; + +use offset_of_staged_api::*; + +fn main() { + offset_of!( + //~^ ERROR use of unstable library feature + Unstable, //~ ERROR use of unstable library feature + unstable + ); + offset_of!(Stable, stable); + offset_of!(StableWithUnstableField, unstable); //~ ERROR use of unstable library feature + offset_of!(StableWithUnstableFieldType, stable); + offset_of!(StableWithUnstableFieldType, stable.unstable); //~ ERROR use of unstable library feature + offset_of!( + //~^ ERROR use of unstable library feature + UnstableWithStableFieldType, //~ ERROR use of unstable library feature + unstable + ); + offset_of!( + //~^ ERROR use of unstable library feature + UnstableWithStableFieldType, //~ ERROR use of unstable library feature + unstable.stable + ); +} diff --git a/tests/ui/offset-of/offset-of-unstable.stderr b/tests/ui/offset-of/offset-of-unstable.stderr new file mode 100644 index 0000000000000..25811a061d7f0 --- /dev/null +++ b/tests/ui/offset-of/offset-of-unstable.stderr @@ -0,0 +1,79 @@ +error[E0658]: use of unstable library feature 'unstable_test_feature' + --> $DIR/offset-of-unstable.rs:14:9 + | +LL | Unstable, + | ^^^^^^^^ + | + = help: add `#![feature(unstable_test_feature)]` to the crate attributes to enable + +error[E0658]: use of unstable library feature 'unstable_test_feature' + --> $DIR/offset-of-unstable.rs:23:9 + | +LL | UnstableWithStableFieldType, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(unstable_test_feature)]` to the crate attributes to enable + +error[E0658]: use of unstable library feature 'unstable_test_feature' + --> $DIR/offset-of-unstable.rs:28:9 + | +LL | UnstableWithStableFieldType, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(unstable_test_feature)]` to the crate attributes to enable + +error[E0658]: use of unstable library feature 'unstable_test_feature' + --> $DIR/offset-of-unstable.rs:12:5 + | +LL | / offset_of!( +LL | | +LL | | Unstable, +LL | | unstable +LL | | ); + | |_____^ + | + = help: add `#![feature(unstable_test_feature)]` to the crate attributes to enable + +error[E0658]: use of unstable library feature 'unstable_test_feature' + --> $DIR/offset-of-unstable.rs:18:5 + | +LL | offset_of!(StableWithUnstableField, unstable); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(unstable_test_feature)]` to the crate attributes to enable + +error[E0658]: use of unstable library feature 'unstable_test_feature' + --> $DIR/offset-of-unstable.rs:20:5 + | +LL | offset_of!(StableWithUnstableFieldType, stable.unstable); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(unstable_test_feature)]` to the crate attributes to enable + +error[E0658]: use of unstable library feature 'unstable_test_feature' + --> $DIR/offset-of-unstable.rs:21:5 + | +LL | / offset_of!( +LL | | +LL | | UnstableWithStableFieldType, +LL | | unstable +LL | | ); + | |_____^ + | + = help: add `#![feature(unstable_test_feature)]` to the crate attributes to enable + +error[E0658]: use of unstable library feature 'unstable_test_feature' + --> $DIR/offset-of-unstable.rs:26:5 + | +LL | / offset_of!( +LL | | +LL | | UnstableWithStableFieldType, +LL | | unstable.stable +LL | | ); + | |_____^ + | + = help: add `#![feature(unstable_test_feature)]` to the crate attributes to enable + +error: aborting due to 8 previous errors + +For more information about this error, try `rustc --explain E0658`.