diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index b2b1f997ac545..591abdaa26839 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -3626,6 +3626,7 @@ impl Item { pub fn opt_generics(&self) -> Option<&Generics> { match &self.kind { ItemKind::ExternCrate(..) + | ItemKind::ConstBlock(_) | ItemKind::Use(_) | ItemKind::Mod(..) | ItemKind::ForeignMod(_) @@ -3895,6 +3896,17 @@ impl ConstItemRhs { } } +#[derive(Clone, Encodable, Decodable, Debug, Walkable)] +pub struct ConstBlockItem { + pub id: NodeId, + pub span: Span, + pub block: Box, +} + +impl ConstBlockItem { + pub const IDENT: Ident = Ident { name: kw::Underscore, span: DUMMY_SP }; +} + // Adding a new variant? Please update `test_item` in `tests/ui/macros/stringify.rs`. #[derive(Clone, Encodable, Decodable, Debug)] pub enum ItemKind { @@ -3914,6 +3926,11 @@ pub enum ItemKind { /// /// E.g., `const FOO: i32 = 42;`. Const(Box), + /// A module-level const block. + /// Equivalent to `const _: () = const { ... };`. + /// + /// E.g., `const { assert!(true) }`. + ConstBlock(ConstBlockItem), /// A function declaration (`fn`). /// /// E.g., `fn foo(bar: usize) -> usize { .. }`. @@ -3990,6 +4007,8 @@ impl ItemKind { | ItemKind::MacroDef(ident, _) | ItemKind::Delegation(box Delegation { ident, .. }) => Some(ident), + ItemKind::ConstBlock(_) => Some(ConstBlockItem::IDENT), + ItemKind::Use(_) | ItemKind::ForeignMod(_) | ItemKind::GlobalAsm(_) @@ -4003,9 +4022,9 @@ impl ItemKind { pub fn article(&self) -> &'static str { use ItemKind::*; match self { - Use(..) | Static(..) | Const(..) | Fn(..) | Mod(..) | GlobalAsm(..) | TyAlias(..) - | Struct(..) | Union(..) | Trait(..) | TraitAlias(..) | MacroDef(..) - | Delegation(..) | DelegationMac(..) => "a", + Use(..) | Static(..) | Const(..) | ConstBlock(..) | Fn(..) | Mod(..) + | GlobalAsm(..) | TyAlias(..) | Struct(..) | Union(..) | Trait(..) | TraitAlias(..) + | MacroDef(..) | Delegation(..) | DelegationMac(..) => "a", ExternCrate(..) | ForeignMod(..) | MacCall(..) | Enum(..) | Impl { .. } => "an", } } @@ -4016,6 +4035,7 @@ impl ItemKind { ItemKind::Use(..) => "`use` import", ItemKind::Static(..) => "static item", ItemKind::Const(..) => "constant item", + ItemKind::ConstBlock(..) => "const block", ItemKind::Fn(..) => "function", ItemKind::Mod(..) => "module", ItemKind::ForeignMod(..) => "extern block", @@ -4045,7 +4065,18 @@ impl ItemKind { | Self::Trait(box Trait { generics, .. }) | Self::TraitAlias(box TraitAlias { generics, .. }) | Self::Impl(Impl { generics, .. }) => Some(generics), - _ => None, + + Self::ExternCrate(..) + | Self::Use(..) + | Self::Static(..) + | Self::ConstBlock(..) + | Self::Mod(..) + | Self::ForeignMod(..) + | Self::GlobalAsm(..) + | Self::MacCall(..) + | Self::MacroDef(..) + | Self::Delegation(..) + | Self::DelegationMac(..) => None, } } } diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index f7b8b9da32417..51614460d3c4e 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -425,6 +425,7 @@ macro_rules! common_visitor_and_walkers { ByRef, Closure, Const, + ConstBlockItem, ConstItem, ConstItemRhs, Defaultness, @@ -825,6 +826,8 @@ macro_rules! common_visitor_and_walkers { visit_visitable!($($mut)? vis, use_tree), ItemKind::Static(item) => visit_visitable!($($mut)? vis, item), + ItemKind::ConstBlock(item) => + visit_visitable!($($mut)? vis, item), ItemKind::Const(item) => visit_visitable!($($mut)? vis, item), ItemKind::Mod(safety, ident, mod_kind) => diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index c8ebfb96b5583..b497c6beeb984 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -205,6 +205,7 @@ impl<'hir> LoweringContext<'_, 'hir> { | ItemKind::Use(..) | ItemKind::Static(..) | ItemKind::Const(..) + | ItemKind::ConstBlock(..) | ItemKind::Mod(..) | ItemKind::ForeignMod(..) | ItemKind::GlobalAsm(..) @@ -282,8 +283,13 @@ impl<'hir> LoweringContext<'_, 'hir> { self.lower_define_opaque(hir_id, define_opaque); hir::ItemKind::Static(*m, ident, ty, body_id) } - ItemKind::Const(box ast::ConstItem { - ident, generics, ty, rhs, define_opaque, .. + ItemKind::Const(box ConstItem { + defaultness: _, + ident, + generics, + ty, + rhs, + define_opaque, }) => { let ident = self.lower_ident(*ident); let (generics, (ty, rhs)) = self.lower_generics( @@ -302,6 +308,19 @@ impl<'hir> LoweringContext<'_, 'hir> { self.lower_define_opaque(hir_id, &define_opaque); hir::ItemKind::Const(ident, generics, ty, rhs) } + ItemKind::ConstBlock(ConstBlockItem { span, id, block }) => hir::ItemKind::Const( + self.lower_ident(ConstBlockItem::IDENT), + hir::Generics::empty(), + self.arena.alloc(self.ty_tup(DUMMY_SP, &[])), + hir::ConstItemRhs::Body({ + let body = hir::Expr { + hir_id: self.lower_node_id(*id), + kind: hir::ExprKind::Block(self.lower_block(block, false), None), + span: self.lower_span(*span), + }; + self.record_body(&[], body) + }), + ), ItemKind::Fn(box Fn { sig: FnSig { decl, header, span: fn_sig_span }, ident, diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index dd0e27948bec6..6376cab364db1 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -537,6 +537,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) { gate_all!(super_let, "`super let` is experimental"); gate_all!(frontmatter, "frontmatters are experimental"); gate_all!(coroutines, "coroutine syntax is experimental"); + gate_all!(const_block_items, "const block items are experimental"); if !visitor.features.never_patterns() { if let Some(spans) = spans.get(&sym::never_patterns) { diff --git a/compiler/rustc_ast_pretty/src/pprust/state/item.rs b/compiler/rustc_ast_pretty/src/pprust/state/item.rs index 3233d8c2c2510..68054b06e39ff 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/item.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/item.rs @@ -205,6 +205,17 @@ impl<'a> State<'a> { define_opaque.as_deref(), ); } + ast::ItemKind::ConstBlock(ast::ConstBlockItem { id: _, span: _, block }) => { + let ib = self.ibox(INDENT_UNIT); + self.word("const"); + self.nbsp(); + { + let cb = self.cbox(0); + let ib = self.ibox(0); + self.print_block_with_attrs(block, &[], cb, ib); + } + self.end(ib); + } ast::ItemKind::Const(box ast::ConstItem { defaultness, ident, diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index a9b2a4c96c334..3a540d80998d0 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -360,7 +360,8 @@ fn parse_cfg_attr_internal<'a>( ) -> PResult<'a, (CfgEntry, Vec<(ast::AttrItem, Span)>)> { // Parse cfg predicate let pred_start = parser.token.span; - let meta = MetaItemOrLitParser::parse_single(parser, ShouldEmit::ErrorsAndLints)?; + let meta = + MetaItemOrLitParser::parse_single(parser, ShouldEmit::ErrorsAndLints { recover: true })?; let pred_span = pred_start.with_hi(parser.token.span.hi()); let cfg_predicate = AttributeParser::parse_single_args( @@ -375,7 +376,7 @@ fn parse_cfg_attr_internal<'a>( CRATE_NODE_ID, Target::Crate, features, - ShouldEmit::ErrorsAndLints, + ShouldEmit::ErrorsAndLints { recover: true }, &meta, parse_cfg_entry, &CFG_ATTR_TEMPLATE, diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs index e80084021a849..ca844758daaa6 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs @@ -78,8 +78,9 @@ pub fn parse_cfg_select( } } } else { - let meta = MetaItemOrLitParser::parse_single(p, ShouldEmit::ErrorsAndLints) - .map_err(|diag| diag.emit())?; + let meta = + MetaItemOrLitParser::parse_single(p, ShouldEmit::ErrorsAndLints { recover: true }) + .map_err(|diag| diag.emit())?; let cfg_span = meta.span(); let cfg = AttributeParser::parse_single_args( sess, @@ -94,7 +95,7 @@ pub fn parse_cfg_select( // Doesn't matter what the target actually is here. Target::Crate, features, - ShouldEmit::ErrorsAndLints, + ShouldEmit::ErrorsAndLints { recover: true }, &meta, parse_cfg_entry, &AttributeTemplate::default(), diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index e500be68e2410..20bdfa45a013d 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -381,7 +381,7 @@ impl Stage for Late { } fn should_emit(&self) -> ShouldEmit { - ShouldEmit::ErrorsAndLints + ShouldEmit::ErrorsAndLints { recover: true } } } @@ -438,7 +438,7 @@ impl<'f, 'sess: 'f, S: Stage> SharedContext<'f, 'sess, S> { pub(crate) fn emit_lint(&mut self, lint: &'static Lint, kind: AttributeLintKind, span: Span) { if !matches!( self.stage.should_emit(), - ShouldEmit::ErrorsAndLints | ShouldEmit::EarlyFatal { also_emit_lints: true } + ShouldEmit::ErrorsAndLints { .. } | ShouldEmit::EarlyFatal { also_emit_lints: true } ) { return; } @@ -765,9 +765,18 @@ pub enum ShouldEmit { EarlyFatal { also_emit_lints: bool }, /// The operation will emit errors and lints. /// This is usually what you need. - ErrorsAndLints, - /// The operation will emit *not* errors and lints. - /// Use this if you are *sure* that this operation will be called at a different time with `ShouldEmit::ErrorsAndLints`. + ErrorsAndLints { + /// Whether [`ArgParser`] will attempt to recover from errors. + /// + /// If true, it will attempt to recover from bad input (like an invalid literal). Setting + /// this to false will instead return early, and not raise errors except at the top level + /// (in [`ArgParser::from_attr_args`]). + recover: bool, + }, + /// The operation will *not* emit errors and lints. + /// + /// The parser can still call `delay_bug`, so you *must* ensure that this operation will also be + /// called with `ShouldEmit::ErrorsAndLints`. Nothing, } @@ -776,7 +785,7 @@ impl ShouldEmit { match self { ShouldEmit::EarlyFatal { .. } if diag.level() == Level::DelayedBug => diag.emit(), ShouldEmit::EarlyFatal { .. } => diag.upgrade_to_fatal().emit(), - ShouldEmit::ErrorsAndLints => diag.emit(), + ShouldEmit::ErrorsAndLints { .. } => diag.emit(), ShouldEmit::Nothing => diag.delay_as_bug(), } } diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs index 99e36a78d6aeb..7f3c6d28005ff 100644 --- a/compiler/rustc_attr_parsing/src/parser.rs +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -16,7 +16,7 @@ use rustc_errors::{Diag, PResult}; use rustc_hir::{self as hir, AttrPath}; use rustc_parse::exp; use rustc_parse::parser::{ForceCollect, Parser, PathStyle, token_descr}; -use rustc_session::errors::{create_lit_error, report_lit_error}; +use rustc_session::errors::create_lit_error; use rustc_session::parse::ParseSess; use rustc_span::{Ident, Span, Symbol, sym}; use thin_vec::ThinVec; @@ -113,16 +113,29 @@ impl ArgParser { Some(match value { AttrArgs::Empty => Self::NoArgs, AttrArgs::Delimited(args) => { - // The arguments of rustc_dummy and diagnostic::do_not_recommend are not validated - // if the arguments are delimited. - // See https://doc.rust-lang.org/reference/attributes/diagnostics.html#r-attributes.diagnostic.namespace.unknown-invalid-syntax - if parts == &[sym::rustc_dummy] - || parts == &[sym::diagnostic, sym::do_not_recommend] - { - return Some(ArgParser::List(MetaItemListParser { - sub_parsers: ThinVec::new(), - span: args.dspan.entire(), - })); + // Diagnostic attributes can't error if they encounter non meta item syntax. + // However, the current syntax for diagnostic attributes is meta item syntax. + // Therefore we can substitute with a dummy value on invalid syntax. + if matches!(parts, [sym::rustc_dummy] | [sym::diagnostic, ..]) { + match MetaItemListParser::new( + &args.tokens, + args.dspan.entire(), + psess, + ShouldEmit::ErrorsAndLints { recover: false }, + ) { + Ok(p) => return Some(ArgParser::List(p)), + Err(e) => { + // We can just dispose of the diagnostic and not bother with a lint, + // because this will look like `#[diagnostic::attr()]` was used. This + // is invalid for all diagnostic attrs, so a lint explaining the proper + // form will be issued later. + e.cancel(); + return Some(ArgParser::List(MetaItemListParser { + sub_parsers: ThinVec::new(), + span: args.dspan.entire(), + })); + } + } } if args.delim != Delimiter::Parenthesis { @@ -141,7 +154,9 @@ impl ArgParser { } AttrArgs::Eq { eq_span, expr } => Self::NameValue(NameValueParser { eq_span: *eq_span, - value: expr_to_lit(psess, &expr, expr.span, should_emit)?, + value: expr_to_lit(psess, &expr, expr.span, should_emit) + .map_err(|e| should_emit.emit_err(e)) + .ok()??, value_span: expr.span, }), }) @@ -336,58 +351,53 @@ impl NameValueParser { } } -fn expr_to_lit( - psess: &ParseSess, +fn expr_to_lit<'sess>( + psess: &'sess ParseSess, expr: &Expr, span: Span, should_emit: ShouldEmit, -) -> Option { +) -> PResult<'sess, Option> { if let ExprKind::Lit(token_lit) = expr.kind { let res = MetaItemLit::from_token_lit(token_lit, expr.span); match res { Ok(lit) => { if token_lit.suffix.is_some() { - should_emit.emit_err( - psess.dcx().create_err(SuffixedLiteralInAttribute { span: lit.span }), - ); - None + Err(psess.dcx().create_err(SuffixedLiteralInAttribute { span: lit.span })) } else { - if !lit.kind.is_unsuffixed() { - // Emit error and continue, we can still parse the attribute as if the suffix isn't there - should_emit.emit_err( - psess.dcx().create_err(SuffixedLiteralInAttribute { span: lit.span }), - ); + if lit.kind.is_unsuffixed() { + Ok(Some(lit)) + } else { + Err(psess.dcx().create_err(SuffixedLiteralInAttribute { span: lit.span })) } - - Some(lit) } } Err(err) => { - let guar = report_lit_error(psess, err, token_lit, expr.span); - let lit = MetaItemLit { - symbol: token_lit.symbol, - suffix: token_lit.suffix, - kind: LitKind::Err(guar), - span: expr.span, - }; - Some(lit) + let err = create_lit_error(psess, err, token_lit, expr.span); + if matches!(should_emit, ShouldEmit::ErrorsAndLints { recover: false }) { + Err(err) + } else { + let lit = MetaItemLit { + symbol: token_lit.symbol, + suffix: token_lit.suffix, + kind: LitKind::Err(err.emit()), + span: expr.span, + }; + Ok(Some(lit)) + } } } } else { if matches!(should_emit, ShouldEmit::Nothing) { - return None; + return Ok(None); } // Example cases: // - `#[foo = 1+1]`: results in `ast::ExprKind::BinOp`. // - `#[foo = include_str!("nonexistent-file.rs")]`: - // results in `ast::ExprKind::Err`. In that case we delay - // the error because an earlier error will have already - // been reported. + // results in `ast::ExprKind::Err`. let msg = "attribute value must be a literal"; let err = psess.dcx().struct_span_err(span, msg); - should_emit.emit_err(err); - None + Err(err) } } @@ -420,9 +430,12 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> { if !lit.kind.is_unsuffixed() { // Emit error and continue, we can still parse the attribute as if the suffix isn't there - self.should_emit.emit_err( - self.parser.dcx().create_err(SuffixedLiteralInAttribute { span: lit.span }), - ); + let err = self.parser.dcx().create_err(SuffixedLiteralInAttribute { span: lit.span }); + if matches!(self.should_emit, ShouldEmit::ErrorsAndLints { recover: false }) { + return Err(err); + } else { + self.should_emit.emit_err(err) + }; } Ok(lit) diff --git a/compiler/rustc_builtin_macros/src/cfg.rs b/compiler/rustc_builtin_macros/src/cfg.rs index be1ce5a06d5ea..8e925cfe09a2e 100644 --- a/compiler/rustc_builtin_macros/src/cfg.rs +++ b/compiler/rustc_builtin_macros/src/cfg.rs @@ -40,8 +40,11 @@ fn parse_cfg(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream) -> Result, span: Span, tts: TokenStream) -> Result { let res: PResult<'_, Annotatable> = try { match annotatable { Annotatable::Item(_) => { - let item = parser.parse_item(ForceCollect::Yes)?.unwrap(); + let item = + parser.parse_item(ForceCollect::Yes, AllowConstBlockItems::Yes)?.unwrap(); Annotatable::Item(self.flat_map_item(item).pop().unwrap()) } Annotatable::AssocItem(_, ctxt) => { diff --git a/compiler/rustc_builtin_macros/src/source_util.rs b/compiler/rustc_builtin_macros/src/source_util.rs index 0f31de60a8a49..f535746014419 100644 --- a/compiler/rustc_builtin_macros/src/source_util.rs +++ b/compiler/rustc_builtin_macros/src/source_util.rs @@ -13,7 +13,7 @@ use rustc_expand::base::{ }; use rustc_expand::module::DirOwnership; use rustc_parse::lexer::StripTokens; -use rustc_parse::parser::ForceCollect; +use rustc_parse::parser::{AllowConstBlockItems, ForceCollect}; use rustc_parse::{new_parser_from_file, unwrap_or_emit_fatal, utf8_error}; use rustc_session::lint::builtin::INCOMPLETE_INCLUDE; use rustc_session::parse::ParseSess; @@ -168,7 +168,7 @@ pub(crate) fn expand_include<'cx>( )); let mut ret = SmallVec::new(); loop { - match p.parse_item(ForceCollect::No) { + match p.parse_item(ForceCollect::No, AllowConstBlockItems::Yes) { Err(err) => { err.emit(); break; diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index e2d62bdc5453b..6a3c9697a9392 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -22,7 +22,7 @@ use rustc_hir::limit::Limit; use rustc_hir::{Stability, find_attr}; use rustc_lint_defs::RegisteredTools; use rustc_parse::MACRO_ARGUMENTS; -use rustc_parse::parser::{ForceCollect, Parser}; +use rustc_parse::parser::{AllowConstBlockItems, ForceCollect, Parser}; use rustc_session::Session; use rustc_session::parse::ParseSess; use rustc_span::def_id::{CrateNum, DefId, LocalDefId}; @@ -1472,7 +1472,7 @@ pub(crate) fn stream_pretty_printing_compatibility_hack( let mut parser = Parser::new(psess, stream.clone(), None); // No need to collect tokens for this simple check. parser - .parse_item(ForceCollect::No) + .parse_item(ForceCollect::No, AllowConstBlockItems::No) .expect("failed to reparse item") .expect("an actual item") } diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs index 53392f100e148..4887bfa445889 100644 --- a/compiler/rustc_expand/src/config.rs +++ b/compiler/rustc_expand/src/config.rs @@ -393,9 +393,10 @@ impl<'a> StripUnconfigured<'a> { /// Determines if a node with the given attributes should be included in this configuration. fn in_cfg(&self, attrs: &[Attribute]) -> bool { - attrs - .iter() - .all(|attr| !is_cfg(attr) || self.cfg_true(attr, ShouldEmit::ErrorsAndLints).as_bool()) + attrs.iter().all(|attr| { + !is_cfg(attr) + || self.cfg_true(attr, ShouldEmit::ErrorsAndLints { recover: true }).as_bool() + }) } pub(crate) fn cfg_true(&self, attr: &Attribute, emit_errors: ShouldEmit) -> EvalConfigResult { diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 20cdffac2d670..cfa7725c74008 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -25,8 +25,8 @@ use rustc_hir::Target; use rustc_hir::def::MacroKinds; use rustc_hir::limit::Limit; use rustc_parse::parser::{ - AttemptLocalParseRecovery, CommaRecoveryMode, ForceCollect, Parser, RecoverColon, RecoverComma, - token_descr, + AllowConstBlockItems, AttemptLocalParseRecovery, CommaRecoveryMode, ForceCollect, Parser, + RecoverColon, RecoverComma, token_descr, }; use rustc_session::Session; use rustc_session::lint::builtin::{UNUSED_ATTRIBUTES, UNUSED_DOC_COMMENTS}; @@ -1099,7 +1099,7 @@ pub fn parse_ast_fragment<'a>( Ok(match kind { AstFragmentKind::Items => { let mut items = SmallVec::new(); - while let Some(item) = this.parse_item(ForceCollect::No)? { + while let Some(item) = this.parse_item(ForceCollect::No, AllowConstBlockItems::Yes)? { items.push(item); } AstFragment::Items(items) @@ -2170,7 +2170,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { call.span(), self.cx.current_expansion.lint_node_id, Some(self.cx.ecfg.features), - ShouldEmit::ErrorsAndLints, + ShouldEmit::ErrorsAndLints { recover: true }, ); let current_span = if let Some(sp) = span { sp.to(attr.span) } else { attr.span }; @@ -2220,7 +2220,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { // Target doesn't matter for `cfg` parsing. Target::Crate, self.cfg().features, - ShouldEmit::ErrorsAndLints, + ShouldEmit::ErrorsAndLints { recover: true }, parse_cfg, &CFG_TEMPLATE, ) else { diff --git a/compiler/rustc_expand/src/proc_macro.rs b/compiler/rustc_expand/src/proc_macro.rs index 5e7a272bcba4f..ea63ff7bfc46a 100644 --- a/compiler/rustc_expand/src/proc_macro.rs +++ b/compiler/rustc_expand/src/proc_macro.rs @@ -1,7 +1,7 @@ use rustc_ast::tokenstream::TokenStream; use rustc_errors::ErrorGuaranteed; use rustc_middle::ty::{self, TyCtxt}; -use rustc_parse::parser::{ForceCollect, Parser}; +use rustc_parse::parser::{AllowConstBlockItems, ForceCollect, Parser}; use rustc_session::Session; use rustc_session::config::ProcMacroExecutionStrategy; use rustc_span::profiling::SpannedEventArgRecorder; @@ -160,7 +160,10 @@ impl MultiItemModifier for DeriveProcMacro { let mut items = vec![]; loop { - match parser.parse_item(ForceCollect::No) { + match parser.parse_item( + ForceCollect::No, + if is_stmt { AllowConstBlockItems::No } else { AllowConstBlockItems::Yes }, + ) { Ok(None) => break, Ok(Some(item)) => { if is_stmt { diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 105eb573967df..22db43b7ee644 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -414,6 +414,8 @@ declare_features! ( (unstable, cmse_nonsecure_entry, "1.48.0", Some(75835)), /// Allows `async {}` expressions in const contexts. (unstable, const_async_blocks, "1.53.0", Some(85368)), + /// Allows `const { ... }` as a shorthand for `const _: () = const { ... };` for module items. + (unstable, const_block_items, "CURRENT_RUSTC_VERSION", Some(149226)), /// Allows `const || {}` closures in const contexts. (incomplete, const_closures, "1.68.0", Some(106003)), /// Allows using `[const] Destruct` bounds and calling drop impls in const contexts. diff --git a/compiler/rustc_hir/src/target.rs b/compiler/rustc_hir/src/target.rs index 87953321af3fc..b76c48fbe8ac2 100644 --- a/compiler/rustc_hir/src/target.rs +++ b/compiler/rustc_hir/src/target.rs @@ -171,6 +171,7 @@ impl Target { ast::ItemKind::Use(..) => Target::Use, ast::ItemKind::Static { .. } => Target::Static, ast::ItemKind::Const(..) => Target::Const, + ast::ItemKind::ConstBlock(..) => Target::Const, ast::ItemKind::Fn { .. } => Target::Fn, ast::ItemKind::Mod(..) => Target::Mod, ast::ItemKind::ForeignMod { .. } => Target::ForeignMod, diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index dcbed92d350b7..5e80314035652 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -807,12 +807,22 @@ pub enum PatKind<'tcx> { subpatterns: Vec>, }, - /// `box P`, `&P`, `&mut P`, etc. + /// Explicit or implicit `&P` or `&mut P`, for some subpattern `P`. + /// + /// Implicit `&`/`&mut` patterns can be inserted by match-ergonomics. + /// + /// With `feature(pin_ergonomics)`, this can also be `&pin const P` or + /// `&pin mut P`, as indicated by the `pin` field. Deref { + #[type_visitable(ignore)] + pin: hir::Pinnedness, subpattern: Box>, }, - /// Deref pattern, written `box P` for now. + /// Explicit or implicit `deref!(..)` pattern, under `feature(deref_patterns)`. + /// Represents a call to `Deref` or `DerefMut`, or a deref-move of `Box`. + /// + /// `box P` patterns also lower to this, under `feature(box_patterns)`. DerefPattern { subpattern: Box>, /// Whether the pattern scrutinee needs to be borrowed in order to call `Deref::deref` or diff --git a/compiler/rustc_middle/src/thir/visit.rs b/compiler/rustc_middle/src/thir/visit.rs index c06d5c5f0ebb0..8e0e3aa294b85 100644 --- a/compiler/rustc_middle/src/thir/visit.rs +++ b/compiler/rustc_middle/src/thir/visit.rs @@ -268,7 +268,7 @@ pub(crate) fn for_each_immediate_subpat<'a, 'tcx>( | PatKind::Error(_) => {} PatKind::Binding { subpattern: Some(subpattern), .. } - | PatKind::Deref { subpattern } + | PatKind::Deref { subpattern, .. } | PatKind::DerefPattern { subpattern, .. } => callback(subpattern), PatKind::Variant { subpatterns, .. } | PatKind::Leaf { subpatterns } => { diff --git a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs index e80e29415e6f0..a3662b2768cd3 100644 --- a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs +++ b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use rustc_abi::FieldIdx; use rustc_middle::mir::*; +use rustc_middle::span_bug; use rustc_middle::thir::*; use rustc_middle::ty::{self, Ty, TypeVisitableExt}; @@ -314,23 +315,24 @@ impl<'tcx> MatchPairTree<'tcx> { None } - // FIXME: Pin-patterns should probably have their own pattern kind, - // instead of overloading `PatKind::Deref` via the pattern type. - PatKind::Deref { ref subpattern } - if let Some(ref_ty) = pattern.ty.pinned_ty() - && ref_ty.is_ref() => - { + PatKind::Deref { pin: Pinnedness::Pinned, ref subpattern } => { + let pinned_ref_ty = match pattern.ty.pinned_ty() { + Some(p_ty) if p_ty.is_ref() => p_ty, + _ => span_bug!(pattern.span, "bad type for pinned deref: {:?}", pattern.ty), + }; MatchPairTree::for_pattern( - place_builder.field(FieldIdx::ZERO, ref_ty).deref(), + // Project into the `Pin(_)` struct, then deref the inner `&` or `&mut`. + place_builder.field(FieldIdx::ZERO, pinned_ref_ty).deref(), subpattern, cx, &mut subpairs, extra_data, ); + None } - PatKind::Deref { ref subpattern } + PatKind::Deref { pin: Pinnedness::Not, ref subpattern } | PatKind::DerefPattern { ref subpattern, borrow: DerefPatBorrowMode::Box } => { MatchPairTree::for_pattern( place_builder.deref(), diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index 2f9486c2d552e..d04322c320b4c 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -10,7 +10,7 @@ use std::mem; use std::sync::Arc; use itertools::{Itertools, Position}; -use rustc_abi::{FIRST_VARIANT, VariantIdx}; +use rustc_abi::{FIRST_VARIANT, FieldIdx, VariantIdx}; use rustc_data_structures::debug_assert_matches; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::stack::ensure_sufficient_stack; @@ -909,7 +909,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | PatKind::Never | PatKind::Error(_) => {} - PatKind::Deref { ref subpattern } => { + PatKind::Deref { pin: Pinnedness::Pinned, ref subpattern } => { + // Project into the `Pin(_)` struct, then deref the inner `&` or `&mut`. + visit_subpat(self, subpattern, &user_tys.leaf(FieldIdx::ZERO).deref(), f); + } + PatKind::Deref { pin: Pinnedness::Not, ref subpattern } => { visit_subpat(self, subpattern, &user_tys.deref(), f); } diff --git a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs index 7fbf8cd11466a..0a0e0d06061eb 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs @@ -294,8 +294,12 @@ impl<'tcx> ConstToPat<'tcx> { || pointee_ty.is_slice() || pointee_ty.is_sized(tcx, self.typing_env) { - // References have the same valtree representation as their pointee. PatKind::Deref { + // This node has type `ty::Ref`, so it's not a pin-deref. + pin: hir::Pinnedness::Not, + // Lower the valtree to a pattern as the pointee type. + // This works because references have the same valtree + // representation as their pointee. subpattern: self.valtree_to_pat(ty::Value { ty: *pointee_ty, valtree }), } } else { diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs index d0abb6396145d..3641561567bce 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs @@ -132,12 +132,16 @@ impl<'tcx> PatCtxt<'tcx> { debug!("{:?}: wrapping pattern with adjustment {:?}", thir_pat, adjust); let span = thir_pat.span; let kind = match adjust.kind { - PatAdjust::BuiltinDeref => PatKind::Deref { subpattern: thir_pat }, + PatAdjust::BuiltinDeref => { + PatKind::Deref { pin: hir::Pinnedness::Not, subpattern: thir_pat } + } PatAdjust::OverloadedDeref => { let borrow = self.typeck_results.deref_pat_borrow_mode(adjust.source, pat); PatKind::DerefPattern { subpattern: thir_pat, borrow } } - PatAdjust::PinDeref => PatKind::Deref { subpattern: thir_pat }, + PatAdjust::PinDeref => { + PatKind::Deref { pin: hir::Pinnedness::Pinned, subpattern: thir_pat } + } }; Box::new(Pat { span, ty: adjust.source, kind, extra: None }) }); @@ -334,7 +338,7 @@ impl<'tcx> PatCtxt<'tcx> { let borrow = self.typeck_results.deref_pat_borrow_mode(ty, subpattern); PatKind::DerefPattern { subpattern: self.lower_pattern(subpattern), borrow } } - hir::PatKind::Ref(subpattern, _, _) => { + hir::PatKind::Ref(subpattern, pin, _) => { // Track the default binding mode for the Rust 2024 migration suggestion. let opt_old_mode_span = self.rust_2024_migration.as_mut().and_then(|s| s.visit_explicit_deref()); @@ -342,7 +346,7 @@ impl<'tcx> PatCtxt<'tcx> { if let Some(s) = &mut self.rust_2024_migration { s.leave_ref(opt_old_mode_span); } - PatKind::Deref { subpattern } + PatKind::Deref { pin, subpattern } } hir::PatKind::Box(subpattern) => PatKind::DerefPattern { subpattern: self.lower_pattern(subpattern), diff --git a/compiler/rustc_mir_build/src/thir/print.rs b/compiler/rustc_mir_build/src/thir/print.rs index a87257fa79bf5..db8b2518981d3 100644 --- a/compiler/rustc_mir_build/src/thir/print.rs +++ b/compiler/rustc_mir_build/src/thir/print.rs @@ -774,8 +774,9 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { print_indented!(self, "]", depth_lvl + 2); print_indented!(self, "}", depth_lvl + 1); } - PatKind::Deref { subpattern } => { + PatKind::Deref { pin, subpattern } => { print_indented!(self, "Deref { ", depth_lvl + 1); + print_indented!(self, format_args!("pin: {pin:?}"), depth_lvl + 2); print_indented!(self, "subpattern:", depth_lvl + 2); self.print_pat(subpattern, depth_lvl + 2); print_indented!(self, "}", depth_lvl + 1); diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs index 1cb9a1b65d650..fe8c1deedff7c 100644 --- a/compiler/rustc_parse/src/parser/attr.rs +++ b/compiler/rustc_parse/src/parser/attr.rs @@ -9,7 +9,8 @@ use thin_vec::ThinVec; use tracing::debug; use super::{ - AttrWrapper, Capturing, FnParseMode, ForceCollect, Parser, PathStyle, Trailing, UsePreAttrPos, + AllowConstBlockItems, AttrWrapper, Capturing, FnParseMode, ForceCollect, Parser, PathStyle, + Trailing, UsePreAttrPos, }; use crate::parser::FnContext; use crate::{errors, exp, fluent_generated as fluent}; @@ -203,6 +204,7 @@ impl<'a> Parser<'a> { false, FnParseMode { req_name: |_, _| true, context: FnContext::Free, req_body: true }, ForceCollect::No, + AllowConstBlockItems::Yes, ) { Ok(Some(item)) => { // FIXME(#100717) diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index c7d09b8345407..6b61504f23273 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -22,8 +22,8 @@ use tracing::debug; use super::diagnostics::{ConsumeClosingDelim, dummy_arg}; use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign}; use super::{ - AttrWrapper, ExpKeywordPair, ExpTokenPair, FollowedByType, ForceCollect, Parser, PathStyle, - Recovered, Trailing, UsePreAttrPos, + AllowConstBlockItems, AttrWrapper, ExpKeywordPair, ExpTokenPair, FollowedByType, ForceCollect, + Parser, PathStyle, Recovered, Trailing, UsePreAttrPos, }; use crate::errors::{self, FnPointerCannotBeAsync, FnPointerCannotBeConst, MacroExpandsToAdtField}; use crate::{exp, fluent_generated as fluent}; @@ -69,7 +69,7 @@ impl<'a> Parser<'a> { // `parse_item` consumes the appropriate semicolons so any leftover is an error. loop { while self.maybe_consume_incorrect_semicolon(items.last().map(|x| &**x)) {} // Eat all bad semicolons - let Some(item) = self.parse_item(ForceCollect::No)? else { + let Some(item) = self.parse_item(ForceCollect::No, AllowConstBlockItems::Yes)? else { break; }; items.push(item); @@ -123,21 +123,34 @@ enum ReuseKind { } impl<'a> Parser<'a> { - pub fn parse_item(&mut self, force_collect: ForceCollect) -> PResult<'a, Option>> { + pub fn parse_item( + &mut self, + force_collect: ForceCollect, + allow_const_block_items: AllowConstBlockItems, + ) -> PResult<'a, Option>> { let fn_parse_mode = FnParseMode { req_name: |_, _| true, context: FnContext::Free, req_body: true }; - self.parse_item_(fn_parse_mode, force_collect).map(|i| i.map(Box::new)) + self.parse_item_(fn_parse_mode, force_collect, allow_const_block_items) + .map(|i| i.map(Box::new)) } fn parse_item_( &mut self, fn_parse_mode: FnParseMode, force_collect: ForceCollect, + const_block_items_allowed: AllowConstBlockItems, ) -> PResult<'a, Option> { self.recover_vcs_conflict_marker(); let attrs = self.parse_outer_attributes()?; self.recover_vcs_conflict_marker(); - self.parse_item_common(attrs, true, false, fn_parse_mode, force_collect) + self.parse_item_common( + attrs, + true, + false, + fn_parse_mode, + force_collect, + const_block_items_allowed, + ) } pub(super) fn parse_item_common( @@ -147,10 +160,11 @@ impl<'a> Parser<'a> { attrs_allowed: bool, fn_parse_mode: FnParseMode, force_collect: ForceCollect, + allow_const_block_items: AllowConstBlockItems, ) -> PResult<'a, Option> { - if let Some(item) = - self.eat_metavar_seq(MetaVarKind::Item, |this| this.parse_item(ForceCollect::Yes)) - { + if let Some(item) = self.eat_metavar_seq(MetaVarKind::Item, |this| { + this.parse_item(ForceCollect::Yes, allow_const_block_items) + }) { let mut item = item.expect("an actual item"); attrs.prepend_to_nt_inner(&mut item.attrs); return Ok(Some(*item)); @@ -163,6 +177,7 @@ impl<'a> Parser<'a> { let kind = this.parse_item_kind( &mut attrs, mac_allowed, + allow_const_block_items, lo, &vis, &mut def, @@ -209,6 +224,7 @@ impl<'a> Parser<'a> { &mut self, attrs: &mut AttrVec, macros_allowed: bool, + allow_const_block_items: AllowConstBlockItems, lo: Span, vis: &Visibility, def: &mut Defaultness, @@ -257,6 +273,15 @@ impl<'a> Parser<'a> { } else if self.check_impl_frontmatter(0) { // IMPL ITEM self.parse_item_impl(attrs, def_(), false)? + } else if let AllowConstBlockItems::Yes | AllowConstBlockItems::DoesNotMatter = + allow_const_block_items + && self.check_inline_const(0) + { + // CONST BLOCK ITEM + if let AllowConstBlockItems::DoesNotMatter = allow_const_block_items { + debug!("Parsing a const block item that does not matter: {:?}", self.token.span); + }; + ItemKind::ConstBlock(self.parse_const_block_item()?) } else if let Const::Yes(const_span) = self.parse_constness(case) { // CONST ITEM self.recover_const_mut(const_span); @@ -316,6 +341,7 @@ impl<'a> Parser<'a> { return self.parse_item_kind( attrs, macros_allowed, + allow_const_block_items, lo, vis, def, @@ -1068,8 +1094,13 @@ impl<'a> Parser<'a> { fn_parse_mode: FnParseMode, force_collect: ForceCollect, ) -> PResult<'a, Option>>> { - Ok(self.parse_item_(fn_parse_mode, force_collect)?.map( - |Item { attrs, id, span, vis, kind, tokens }| { + Ok(self + .parse_item_( + fn_parse_mode, + force_collect, + AllowConstBlockItems::DoesNotMatter, // due to `AssocItemKind::try_from` below + )? + .map(|Item { attrs, id, span, vis, kind, tokens }| { let kind = match AssocItemKind::try_from(kind) { Ok(kind) => kind, Err(kind) => match kind { @@ -1096,8 +1127,7 @@ impl<'a> Parser<'a> { }, }; Some(Box::new(Item { attrs, id, span, vis, kind, tokens })) - }, - )) + })) } /// Parses a `type` alias with the following grammar: @@ -1320,8 +1350,13 @@ impl<'a> Parser<'a> { context: FnContext::Free, req_body: false, }; - Ok(self.parse_item_(fn_parse_mode, force_collect)?.map( - |Item { attrs, id, span, vis, kind, tokens }| { + Ok(self + .parse_item_( + fn_parse_mode, + force_collect, + AllowConstBlockItems::DoesNotMatter, // due to `ForeignItemKind::try_from` below + )? + .map(|Item { attrs, id, span, vis, kind, tokens }| { let kind = match ForeignItemKind::try_from(kind) { Ok(kind) => kind, Err(kind) => match kind { @@ -1348,8 +1383,7 @@ impl<'a> Parser<'a> { }, }; Some(Box::new(Item { attrs, id, span, vis, kind, tokens })) - }, - )) + })) } fn error_bad_item_kind(&self, span: Span, kind: &ItemKind, ctx: &'static str) -> Option { @@ -1437,6 +1471,14 @@ impl<'a> Parser<'a> { } } + fn parse_const_block_item(&mut self) -> PResult<'a, ConstBlockItem> { + self.expect_keyword(exp!(Const))?; + let const_span = self.prev_token.span; + self.psess.gated_spans.gate(sym::const_block_items, const_span); + let block = self.parse_block()?; + Ok(ConstBlockItem { id: DUMMY_NODE_ID, span: const_span.to(block.span), block }) + } + /// Parse a static item with the prefix `"static" "mut"?` already parsed and stored in /// `mutability`. /// @@ -2394,7 +2436,10 @@ impl<'a> Parser<'a> { { let kw_token = self.token; let kw_str = pprust::token_to_string(&kw_token); - let item = self.parse_item(ForceCollect::No)?; + let item = self.parse_item( + ForceCollect::No, + AllowConstBlockItems::DoesNotMatter, // self.token != kw::Const + )?; let mut item = item.unwrap().span; if self.token == token::Comma { item = item.to(self.token.span); diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 2b711b2f43ef9..3ef73a55d47d7 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -145,6 +145,14 @@ pub enum ForceCollect { No, } +/// Whether to accept `const { ... }` as a shorthand for `const _: () = const { ... }`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum AllowConstBlockItems { + Yes, + No, + DoesNotMatter, +} + /// If the next tokens are ill-formed `$ty::` recover them as `<$ty>::`. #[macro_export] macro_rules! maybe_recover_from_interpolated_ty_qpath { diff --git a/compiler/rustc_parse/src/parser/nonterminal.rs b/compiler/rustc_parse/src/parser/nonterminal.rs index ea2b896f5fb9c..706b454835fab 100644 --- a/compiler/rustc_parse/src/parser/nonterminal.rs +++ b/compiler/rustc_parse/src/parser/nonterminal.rs @@ -6,7 +6,9 @@ use rustc_span::{Ident, kw}; use crate::errors::UnexpectedNonterminal; use crate::parser::pat::{CommaRecoveryMode, RecoverColon, RecoverComma}; -use crate::parser::{FollowedByType, ForceCollect, ParseNtResult, Parser, PathStyle}; +use crate::parser::{ + AllowConstBlockItems, FollowedByType, ForceCollect, ParseNtResult, Parser, PathStyle, +}; impl<'a> Parser<'a> { /// Checks whether a non-terminal may begin with a particular token. @@ -118,7 +120,9 @@ impl<'a> Parser<'a> { match kind { // Note that TT is treated differently to all the others. NonterminalKind::TT => Ok(ParseNtResult::Tt(self.parse_token_tree())), - NonterminalKind::Item => match self.parse_item(ForceCollect::Yes)? { + NonterminalKind::Item => match self + .parse_item(ForceCollect::Yes, AllowConstBlockItems::Yes)? + { Some(item) => Ok(ParseNtResult::Item(item)), None => Err(self.dcx().create_err(UnexpectedNonterminal::Item(self.token.span))), }, diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 26393bf61a32e..3c6629572ae50 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -20,8 +20,8 @@ use super::diagnostics::AttemptLocalParseRecovery; use super::pat::{PatternLocation, RecoverComma}; use super::path::PathStyle; use super::{ - AttrWrapper, BlockMode, FnContext, FnParseMode, ForceCollect, Parser, Restrictions, - SemiColonMode, Trailing, UsePreAttrPos, + AllowConstBlockItems, AttrWrapper, BlockMode, FnContext, FnParseMode, ForceCollect, Parser, + Restrictions, SemiColonMode, Trailing, UsePreAttrPos, }; use crate::errors::{self, MalformedLoopLabel}; use crate::exp; @@ -156,6 +156,7 @@ impl<'a> Parser<'a> { true, FnParseMode { req_name: |_, _| true, context: FnContext::Free, req_body: true }, force_collect, + AllowConstBlockItems::No, )? { self.mk_stmt(lo.to(item.span), StmtKind::Item(Box::new(item))) } else if self.eat(exp!(Semi)) { diff --git a/compiler/rustc_parse/src/parser/tests.rs b/compiler/rustc_parse/src/parser/tests.rs index 3cc0aa67a5bc4..61b498431b045 100644 --- a/compiler/rustc_parse/src/parser/tests.rs +++ b/compiler/rustc_parse/src/parser/tests.rs @@ -22,7 +22,7 @@ use rustc_span::{ }; use crate::lexer::StripTokens; -use crate::parser::{ForceCollect, Parser}; +use crate::parser::{AllowConstBlockItems, ForceCollect, Parser}; use crate::{new_parser_from_source_str, source_str_to_stream, unwrap_or_emit_fatal}; fn psess() -> ParseSess { @@ -2233,7 +2233,7 @@ fn parse_item_from_source_str( psess: &ParseSess, ) -> PResult<'_, Option>> { unwrap_or_emit_fatal(new_parser_from_source_str(psess, name, source, StripTokens::Nothing)) - .parse_item(ForceCollect::No) + .parse_item(ForceCollect::No, AllowConstBlockItems::Yes) } // Produces a `rustc_span::span`. @@ -2248,7 +2248,9 @@ fn string_to_expr(source_str: String) -> Box { /// Parses a string, returns an item. fn string_to_item(source_str: String) -> Option> { - with_error_checking_parse(source_str, &psess(), |p| p.parse_item(ForceCollect::No)) + with_error_checking_parse(source_str, &psess(), |p| { + p.parse_item(ForceCollect::No, AllowConstBlockItems::Yes) + }) } #[test] diff --git a/compiler/rustc_passes/src/input_stats.rs b/compiler/rustc_passes/src/input_stats.rs index 35101624bd68e..23fbb9ab3a2b7 100644 --- a/compiler/rustc_passes/src/input_stats.rs +++ b/compiler/rustc_passes/src/input_stats.rs @@ -572,6 +572,7 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> { Use, Static, Const, + ConstBlock, Fn, Mod, ForeignMod, diff --git a/compiler/rustc_passes/src/lang_items.rs b/compiler/rustc_passes/src/lang_items.rs index 141a60a8ec3f9..b9417af13b113 100644 --- a/compiler/rustc_passes/src/lang_items.rs +++ b/compiler/rustc_passes/src/lang_items.rs @@ -276,7 +276,7 @@ impl<'ast, 'tcx> visit::Visitor<'ast> for LanguageItemCollector<'ast, 'tcx> { ast::ItemKind::ExternCrate(..) => Target::ExternCrate, ast::ItemKind::Use(_) => Target::Use, ast::ItemKind::Static(_) => Target::Static, - ast::ItemKind::Const(_) => Target::Const, + ast::ItemKind::Const(_) | ast::ItemKind::ConstBlock(_) => Target::Const, ast::ItemKind::Fn(_) | ast::ItemKind::Delegation(..) => Target::Fn, ast::ItemKind::Mod(..) => Target::Mod, ast::ItemKind::ForeignMod(_) => Target::ForeignFn, diff --git a/compiler/rustc_pattern_analysis/src/lib.rs b/compiler/rustc_pattern_analysis/src/lib.rs index 364ffc7bace0d..2652840863776 100644 --- a/compiler/rustc_pattern_analysis/src/lib.rs +++ b/compiler/rustc_pattern_analysis/src/lib.rs @@ -4,6 +4,7 @@ // tidy-alphabetical-start #![allow(unused_crate_dependencies)] +#![cfg_attr(feature = "rustc", feature(if_let_guard))] // tidy-alphabetical-end pub(crate) mod checks; diff --git a/compiler/rustc_pattern_analysis/src/rustc.rs b/compiler/rustc_pattern_analysis/src/rustc.rs index 5e75192ff3092..3e97842b7f395 100644 --- a/compiler/rustc_pattern_analysis/src/rustc.rs +++ b/compiler/rustc_pattern_analysis/src/rustc.rs @@ -4,8 +4,8 @@ use std::iter::once; use rustc_abi::{FIRST_VARIANT, FieldIdx, Integer, VariantIdx}; use rustc_arena::DroplessArena; -use rustc_hir::HirId; use rustc_hir::def_id::DefId; +use rustc_hir::{self as hir, HirId}; use rustc_index::{Idx, IndexVec}; use rustc_middle::middle::stability::EvalResult; use rustc_middle::thir::{self, Pat, PatKind, PatRange, PatRangeBoundary}; @@ -468,12 +468,12 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> { fields = vec![]; arity = 0; } - PatKind::Deref { subpattern } => { + PatKind::Deref { pin, subpattern } => { fields = vec![self.lower_pat(subpattern).at_index(0)]; arity = 1; - ctor = match ty.pinned_ref() { - None if ty.is_ref() => Ref, - Some((inner_ty, _)) => { + ctor = match pin { + hir::Pinnedness::Not if ty.is_ref() => Ref, + hir::Pinnedness::Pinned if let Some((inner_ty, _)) = ty.pinned_ref() => { self.internal_state.has_lowered_deref_pat.set(true); DerefPattern(RevealedTy(inner_ty)) } diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 736648c125b69..aa458024b89eb 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -959,7 +959,10 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { } // These items do not add names to modules. - ItemKind::Impl { .. } | ItemKind::ForeignMod(..) | ItemKind::GlobalAsm(..) => {} + ItemKind::Impl { .. } + | ItemKind::ForeignMod(..) + | ItemKind::GlobalAsm(..) + | ItemKind::ConstBlock(..) => {} ItemKind::MacroDef(..) | ItemKind::MacCall(_) | ItemKind::DelegationMac(..) => { unreachable!() diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index 8f1a43c090b11..e2fea9146c798 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -131,7 +131,7 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> { mutability: s.mutability, nested: false, }, - ItemKind::Const(..) => DefKind::Const, + ItemKind::Const(..) | ItemKind::ConstBlock(..) => DefKind::Const, ItemKind::Fn(..) | ItemKind::Delegation(..) => DefKind::Fn, ItemKind::MacroDef(ident, def) => { let edition = i.span.edition(); diff --git a/compiler/rustc_resolve/src/effective_visibilities.rs b/compiler/rustc_resolve/src/effective_visibilities.rs index ebdb0060e1b94..295635a605477 100644 --- a/compiler/rustc_resolve/src/effective_visibilities.rs +++ b/compiler/rustc_resolve/src/effective_visibilities.rs @@ -275,6 +275,7 @@ impl<'a, 'ra, 'tcx> Visitor<'a> for EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> | ast::ItemKind::Use(..) | ast::ItemKind::Static(..) | ast::ItemKind::Const(..) + | ast::ItemKind::ConstBlock(..) | ast::ItemKind::GlobalAsm(..) | ast::ItemKind::TyAlias(..) | ast::ItemKind::TraitAlias(..) diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 963bee369f6b8..7a7261cf7b95b 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -2727,8 +2727,8 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { debug!("(resolving item) resolving {:?} ({:?})", item.kind.ident(), item.kind); let def_kind = self.r.local_def_kind(item.id); - match item.kind { - ItemKind::TyAlias(box TyAlias { ref generics, .. }) => { + match &item.kind { + ItemKind::TyAlias(box TyAlias { generics, .. }) => { self.with_generic_param_rib( &generics.params, RibKind::Item(HasGenericParams::Yes(generics.span), def_kind), @@ -2739,7 +2739,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { ); } - ItemKind::Fn(box Fn { ref generics, ref define_opaque, .. }) => { + ItemKind::Fn(box Fn { generics, define_opaque, .. }) => { self.with_generic_param_rib( &generics.params, RibKind::Item(HasGenericParams::Yes(generics.span), def_kind), @@ -2751,19 +2751,13 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { self.resolve_define_opaques(define_opaque); } - ItemKind::Enum(_, ref generics, _) - | ItemKind::Struct(_, ref generics, _) - | ItemKind::Union(_, ref generics, _) => { + ItemKind::Enum(_, generics, _) + | ItemKind::Struct(_, generics, _) + | ItemKind::Union(_, generics, _) => { self.resolve_adt(item, generics); } - ItemKind::Impl(Impl { - ref generics, - ref of_trait, - ref self_ty, - items: ref impl_items, - .. - }) => { + ItemKind::Impl(Impl { generics, of_trait, self_ty, items: impl_items, .. }) => { self.diag_metadata.current_impl_items = Some(impl_items); self.resolve_implementation( &item.attrs, @@ -2776,7 +2770,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { self.diag_metadata.current_impl_items = None; } - ItemKind::Trait(box Trait { ref generics, ref bounds, ref items, .. }) => { + ItemKind::Trait(box Trait { generics, bounds, items, .. }) => { // Create a new rib for the trait-wide type parameters. self.with_generic_param_rib( &generics.params, @@ -2795,7 +2789,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { ); } - ItemKind::TraitAlias(box TraitAlias { ref generics, ref bounds, .. }) => { + ItemKind::TraitAlias(box TraitAlias { generics, bounds, .. }) => { // Create a new rib for the trait-wide type parameters. self.with_generic_param_rib( &generics.params, @@ -2835,13 +2829,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { self.parent_scope.module = orig_module; } - ItemKind::Static(box ast::StaticItem { - ident, - ref ty, - ref expr, - ref define_opaque, - .. - }) => { + ItemKind::Static(box ast::StaticItem { ident, ty, expr, define_opaque, .. }) => { self.with_static_rib(def_kind, |this| { this.with_lifetime_rib(LifetimeRibKind::Elided(LifetimeRes::Static), |this| { this.visit_ty(ty); @@ -2849,7 +2837,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { if let Some(expr) = expr { // We already forbid generic params because of the above item rib, // so it doesn't matter whether this is a trivial constant. - this.resolve_static_body(expr, Some((ident, ConstantItemKind::Static))); + this.resolve_static_body(expr, Some((*ident, ConstantItemKind::Static))); } }); self.resolve_define_opaques(define_opaque); @@ -2857,11 +2845,11 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { ItemKind::Const(box ast::ConstItem { ident, - ref generics, - ref ty, - ref rhs, - ref define_opaque, - .. + generics, + ty, + rhs, + define_opaque, + defaultness: _, }) => { let is_type_const = attr::contains_name(&item.attrs, sym::type_const); self.with_generic_param_rib( @@ -2903,15 +2891,36 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { if let Some(rhs) = rhs { this.resolve_const_item_rhs( rhs, - Some((ident, ConstantItemKind::Const)), + Some((*ident, ConstantItemKind::Const)), ); } }, ); self.resolve_define_opaques(define_opaque); } + ItemKind::ConstBlock(ConstBlockItem { id: _, span: _, block }) => self + .with_generic_param_rib( + &[], + RibKind::Item(HasGenericParams::No, def_kind), + item.id, + LifetimeBinderKind::ConstItem, + DUMMY_SP, + |this| { + this.with_lifetime_rib( + LifetimeRibKind::Elided(LifetimeRes::Infer), + |this| { + this.with_constant_rib( + IsRepeatExpr::No, + ConstantHasGenerics::Yes, + Some((ConstBlockItem::IDENT, ConstantItemKind::Const)), + |this| this.resolve_labeled_block(None, block.id, block), + ) + }, + ); + }, + ), - ItemKind::Use(ref use_tree) => { + ItemKind::Use(use_tree) => { let maybe_exported = match use_tree.kind { UseTreeKind::Simple(_) | UseTreeKind::Glob => MaybeExported::Ok(item.id), UseTreeKind::Nested { .. } => MaybeExported::NestedUse(&item.vis), @@ -2921,7 +2930,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { self.future_proof_import(use_tree); } - ItemKind::MacroDef(_, ref macro_def) => { + ItemKind::MacroDef(_, macro_def) => { // Maintain macro_rules scopes in the same way as during early resolution // for diagnostics and doc links. if macro_def.macro_rules { @@ -2945,7 +2954,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { visit::walk_item(self, item); } - ItemKind::Delegation(ref delegation) => { + ItemKind::Delegation(delegation) => { let span = delegation.path.segments.last().unwrap().ident.span; self.with_generic_param_rib( &[], @@ -5477,6 +5486,7 @@ impl<'ast> Visitor<'ast> for ItemInfoCollector<'_, '_, '_> { ItemKind::Mod(..) | ItemKind::Static(..) + | ItemKind::ConstBlock(..) | ItemKind::Use(..) | ItemKind::ExternCrate(..) | ItemKind::MacroDef(..) diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 5767444025ec2..90a22c6bb67c0 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -731,6 +731,7 @@ symbols! { console, const_allocate, const_async_blocks, + const_block_items, const_closures, const_compare_raw_pointers, const_constructor, diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs index 53ba868ebeac0..d30d869cd4c19 100644 --- a/src/librustdoc/doctest/make.rs +++ b/src/librustdoc/doctest/make.rs @@ -539,7 +539,10 @@ fn parse_source( let mut prev_span_hi = 0; let not_crate_attrs = &[sym::forbid, sym::allow, sym::warn, sym::deny, sym::expect]; - let parsed = parser.parse_item(rustc_parse::parser::ForceCollect::No); + let parsed = parser.parse_item( + rustc_parse::parser::ForceCollect::No, + rustc_parse::parser::AllowConstBlockItems::No, + ); let result = match parsed { Ok(Some(ref item)) diff --git a/src/tools/miri/.gitignore b/src/tools/miri/.gitignore index 4a238dc031342..23f2513addb74 100644 --- a/src/tools/miri/.gitignore +++ b/src/tools/miri/.gitignore @@ -5,6 +5,7 @@ tex/*/out *.rs.bk .vscode .helix +.zed *.mm_profdata perf.data perf.data.old diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index a6ccd9bab3930..90ba52120ee87 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -b6fdaf2a15736cbccf248b532f48e33179614d40 +d10ac47c20152feb5e99b1c35a2e6830f77c66dc diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 48f8e146a190c..b28d70f058827 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -3,7 +3,7 @@ clippy::manual_range_contains, clippy::useless_format, clippy::field_reassign_with_default, - clippy::needless_lifetimes, + clippy::needless_lifetimes )] // The rustc crates we need diff --git a/src/tools/miri/src/shims/sig.rs b/src/tools/miri/src/shims/sig.rs index 4e4b1bc2b9fec..43b913edbebf5 100644 --- a/src/tools/miri/src/shims/sig.rs +++ b/src/tools/miri/src/shims/sig.rs @@ -28,45 +28,118 @@ pub struct ShimSig<'tcx, const ARGS: usize> { /// - `winapi::$ty` for a type from `std::sys::pal::windows::c` #[macro_export] macro_rules! shim_sig { - (extern $abi:literal fn($($arg:ty),* $(,)?) -> $ret:ty) => { + (extern $abi:literal fn($($args:tt)*) -> $($ret:tt)*) => { |this| $crate::shims::sig::ShimSig { abi: std::str::FromStr::from_str($abi).expect("incorrect abi specified"), - args: [$(shim_sig_arg!(this, $arg)),*], - ret: shim_sig_arg!(this, $ret), + args: shim_sig_args_sep!(this, [$($args)*]), + ret: shim_sig_arg!(this, $($ret)*), } }; } /// Helper for `shim_sig!`. +/// +/// Groups tokens into comma-separated chunks and calls the provided macro on them. +/// +/// # Examples +/// +/// ```ignore +/// shim_sig_args_sep!(this, [*const _, i32, libc::off64_t]); +/// // expands to: +/// [shim_sig_arg!(*const _), shim_sig_arg!(i32), shim_sig_arg!(libc::off64_t)]; +/// ``` +#[macro_export] +macro_rules! shim_sig_args_sep { + ($this:ident, [$($tt:tt)*]) => { + shim_sig_args_sep!(@ $this [] [] $($tt)*) + }; + + // All below matchers form a fairly simple iterator over the input. + // - Non-comma token - append to collector + // - Comma token - call the provided macro on the collector and reset the collector + // - End of input - empty collector one last time. emit output as an array + + // Handles `,` token - take collected type and call shim_sig_arg on it. + // Append the result to the final output. + (@ $this:ident [$($final:tt)*] [$($collected:tt)*] , $($tt:tt)*) => { + shim_sig_args_sep!(@ $this [$($final)* shim_sig_arg!($this, $($collected)*), ] [] $($tt)*) + }; + // Handle non-comma token - append to collected type. + (@ $this:ident [$($final:tt)*] [$($collected:tt)*] $first:tt $($tt:tt)*) => { + shim_sig_args_sep!(@ $this [$($final)*] [$($collected)* $first] $($tt)*) + }; + // No more tokens - emit final output, including final non-comma type. + (@ $this:ident [$($final:tt)*] [$($collected:tt)+] ) => { + [$($final)* shim_sig_arg!($this, $($collected)*)] + }; + // No more tokens - emit final output. + (@ $this:ident [$($final:tt)*] [] ) => { + [$($final)*] + }; +} + +/// Helper for `shim_sig!`. +/// +/// Converts a type #[macro_export] macro_rules! shim_sig_arg { - // Unfortuantely we cannot take apart a `ty`-typed token at compile time, - // so we have to stringify it and match at runtime. - ($this:ident, $x:ty) => {{ - match stringify!($x) { - "i8" => $this.tcx.types.i8, - "i16" => $this.tcx.types.i16, - "i32" => $this.tcx.types.i32, - "i64" => $this.tcx.types.i64, - "i128" => $this.tcx.types.i128, - "isize" => $this.tcx.types.isize, - "u8" => $this.tcx.types.u8, - "u16" => $this.tcx.types.u16, - "u32" => $this.tcx.types.u32, - "u64" => $this.tcx.types.u64, - "u128" => $this.tcx.types.u128, - "usize" => $this.tcx.types.usize, - "()" => $this.tcx.types.unit, - "bool" => $this.tcx.types.bool, - "*const _" => $this.machine.layouts.const_raw_ptr.ty, - "*mut _" => $this.machine.layouts.mut_raw_ptr.ty, - ty if let Some(win_ty) = ty.strip_prefix("winapi::") => - $this.windows_ty_layout(win_ty).ty, - ty if ty.contains("::") => - helpers::path_ty_layout($this, &ty.split("::").collect::>()).ty, - ty => panic!("unsupported signature type {ty:?}"), - } - }}; + ($this:ident, i8) => { + $this.tcx.types.i8 + }; + ($this:ident, i16) => { + $this.tcx.types.i16 + }; + ($this:ident, i32) => { + $this.tcx.types.i32 + }; + ($this:ident, i64) => { + $this.tcx.types.i64 + }; + ($this:ident, i128) => { + $this.tcx.types.i128 + }; + ($this:ident, isize) => { + $this.tcx.types.isize + }; + ($this:ident, u8) => { + $this.tcx.types.u8 + }; + ($this:ident, u16) => { + $this.tcx.types.u16 + }; + ($this:ident, u32) => { + $this.tcx.types.u32 + }; + ($this:ident, u64) => { + $this.tcx.types.u64 + }; + ($this:ident, u128) => { + $this.tcx.types.u128 + }; + ($this:ident, usize) => { + $this.tcx.types.usize + }; + ($this:ident, ()) => { + $this.tcx.types.unit + }; + ($this:ident, bool) => { + $this.tcx.types.bool + }; + ($this:ident, *const _) => { + $this.machine.layouts.const_raw_ptr.ty + }; + ($this:ident, *mut _) => { + $this.machine.layouts.mut_raw_ptr.ty + }; + ($this:ident, winapi::$ty:ident) => { + $this.windows_ty_layout(stringify!($ty)).ty + }; + ($this:ident, $krate:ident :: $($path:ident)::+) => { + helpers::path_ty_layout($this, &[stringify!($krate), $(stringify!($path)),*]).ty + }; + ($this:ident, $($other:tt)*) => { + compile_error!(concat!("unsupported signature type: ", stringify!($($other)*))) + } } /// Helper function to compare two ABIs. diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 5352b08d8d25e..c49aa9f20f6c0 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -347,7 +347,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { assert_eq!(unblock, UnblockKind::TimedOut); interp_ok(()) } - ) + ), ); interp_ok(Scalar::from_i32(0)) // KERN_SUCCESS diff --git a/src/tools/miri/src/shims/unix/android/foreign_items.rs b/src/tools/miri/src/shims/unix/android/foreign_items.rs index f00bfb0a20787..5259ca2294884 100644 --- a/src/tools/miri/src/shims/unix/android/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/android/foreign_items.rs @@ -39,12 +39,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.lstat(path, buf)?; this.write_scalar(result, dest)?; } - "readdir" => { - // FIXME: This does not have a direct test (#3179). - let [dirp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.readdir64("dirent", dirp)?; - this.write_scalar(result, dest)?; - } "pread64" => { // FIXME: This does not have a direct test (#3179). let [fd, buf, count, offset] = this.check_shim_sig( @@ -85,7 +79,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let fd = this.read_scalar(fd)?.to_i32()?; let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?; let whence = this.read_scalar(whence)?.to_i32()?; - this.lseek64(fd, offset, whence, dest)?; + this.lseek(fd, offset, whence, dest)?; } "ftruncate64" => { let [fd, length] = this.check_shim_sig( diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 04ec260eccd00..87a307c989484 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -363,7 +363,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } "opendir" => { - // FIXME: This does not have a direct test (#3179). let [name] = this.check_shim_sig( shim_sig!(extern "C" fn(*const _) -> *mut _), link_name, @@ -374,7 +373,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } "closedir" => { - // FIXME: This does not have a direct test (#3179). let [dirp] = this.check_shim_sig( shim_sig!(extern "C" fn(*mut _) -> i32), link_name, @@ -384,6 +382,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.closedir(dirp)?; this.write_scalar(result, dest)?; } + "readdir" => { + let [dirp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + this.readdir(dirp, dest)?; + } "lseek" => { // FIXME: This does not have a direct test (#3179). let [fd, offset, whence] = this.check_shim_sig( @@ -395,7 +397,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let fd = this.read_scalar(fd)?.to_i32()?; let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?; let whence = this.read_scalar(whence)?.to_i32()?; - this.lseek64(fd, offset, whence, dest)?; + this.lseek(fd, offset, whence, dest)?; } "ftruncate" => { let [fd, length] = this.check_shim_sig( diff --git a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs index b94ee27c46a00..f64ee3b162207 100644 --- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs @@ -155,11 +155,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.fstat(fd, buf)?; this.write_scalar(result, dest)?; } - "readdir" | "readdir@FBSD_1.0" => { - // FIXME: This does not have a direct test (#3179). + "readdir@FBSD_1.0" => { let [dirp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.readdir64("dirent", dirp)?; - this.write_scalar(result, dest)?; + this.readdir(dirp, dest)?; } // Miscellaneous "__error" => { diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index f43fd3fe2d188..a01755ef95ae7 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -1,15 +1,17 @@ //! File and file system access use std::borrow::Cow; +use std::ffi::OsString; use std::fs::{ - DirBuilder, File, FileType, OpenOptions, ReadDir, TryLockError, read_dir, remove_dir, - remove_file, rename, + self, DirBuilder, File, FileType, OpenOptions, TryLockError, read_dir, remove_dir, remove_file, + rename, }; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; use std::path::{Path, PathBuf}; use std::time::SystemTime; use rustc_abi::Size; +use rustc_data_structures::either::Either; use rustc_data_structures::fx::FxHashMap; use rustc_target::spec::Os; @@ -20,6 +22,40 @@ use crate::shims::sig::check_min_vararg_count; use crate::shims::unix::fd::{FlockOp, UnixFileDescription}; use crate::*; +/// An open directory, tracked by DirHandler. +#[derive(Debug)] +struct OpenDir { + /// The "special" entries that must still be yielded by the iterator. + /// Used for `.` and `..`. + special_entries: Vec<&'static str>, + /// The directory reader on the host. + read_dir: fs::ReadDir, + /// The most recent entry returned by readdir(). + /// Will be freed by the next call. + entry: Option, +} + +impl OpenDir { + fn new(read_dir: fs::ReadDir) -> Self { + Self { special_entries: vec!["..", "."], read_dir, entry: None } + } + + fn next_host_entry(&mut self) -> Option>> { + if let Some(special) = self.special_entries.pop() { + return Some(Ok(Either::Right(special))); + } + let entry = self.read_dir.next()?; + Some(entry.map(Either::Left)) + } +} + +#[derive(Debug)] +struct DirEntry { + name: OsString, + ino: u64, + d_type: i32, +} + impl UnixFileDescription for FileHandle { fn pread<'tcx>( &self, @@ -116,6 +152,71 @@ impl UnixFileDescription for FileHandle { } } +/// The table of open directories. +/// Curiously, Unix/POSIX does not unify this into the "file descriptor" concept... everything +/// is a file, except a directory is not? +#[derive(Debug)] +pub struct DirTable { + /// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir, + /// and closedir. + /// + /// When opendir is called, a directory iterator is created on the host for the target + /// directory, and an entry is stored in this hash map, indexed by an ID which represents + /// the directory stream. When readdir is called, the directory stream ID is used to look up + /// the corresponding ReadDir iterator from this map, and information from the next + /// directory entry is returned. When closedir is called, the ReadDir iterator is removed from + /// the map. + streams: FxHashMap, + /// ID number to be used by the next call to opendir + next_id: u64, +} + +impl DirTable { + #[expect(clippy::arithmetic_side_effects)] + fn insert_new(&mut self, read_dir: fs::ReadDir) -> u64 { + let id = self.next_id; + self.next_id += 1; + self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap(); + id + } +} + +impl Default for DirTable { + fn default() -> DirTable { + DirTable { + streams: FxHashMap::default(), + // Skip 0 as an ID, because it looks like a null pointer to libc + next_id: 1, + } + } +} + +impl VisitProvenance for DirTable { + fn visit_provenance(&self, visit: &mut VisitWith<'_>) { + let DirTable { streams, next_id: _ } = self; + + for dir in streams.values() { + dir.entry.visit_provenance(visit); + } + } +} + +fn maybe_sync_file( + file: &File, + writable: bool, + operation: fn(&File) -> std::io::Result<()>, +) -> std::io::Result { + if !writable && cfg!(windows) { + // sync_all() and sync_data() will return an error on Windows hosts if the file is not opened + // for writing. (FlushFileBuffers requires that the file handle have the + // GENERIC_WRITE right) + Ok(0i32) + } else { + let result = operation(file); + result.map(|_| 0i32) + } +} + impl<'tcx> EvalContextExtPrivate<'tcx> for crate::MiriInterpCx<'tcx> {} trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { fn write_stat_buf( @@ -178,14 +279,11 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(0) } - fn file_type_to_d_type( - &mut self, - file_type: std::io::Result, - ) -> InterpResult<'tcx, i32> { + fn file_type_to_d_type(&self, file_type: std::io::Result) -> InterpResult<'tcx, i32> { #[cfg(unix)] use std::os::unix::fs::FileTypeExt; - let this = self.eval_context_mut(); + let this = self.eval_context_ref(); match file_type { Ok(file_type) => { match () { @@ -216,86 +314,32 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { } } } -} -/// An open directory, tracked by DirHandler. -#[derive(Debug)] -struct OpenDir { - /// The directory reader on the host. - read_dir: ReadDir, - /// The most recent entry returned by readdir(). - /// Will be freed by the next call. - entry: Option, -} - -impl OpenDir { - fn new(read_dir: ReadDir) -> Self { - Self { read_dir, entry: None } - } -} - -/// The table of open directories. -/// Curiously, Unix/POSIX does not unify this into the "file descriptor" concept... everything -/// is a file, except a directory is not? -#[derive(Debug)] -pub struct DirTable { - /// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir, - /// and closedir. - /// - /// When opendir is called, a directory iterator is created on the host for the target - /// directory, and an entry is stored in this hash map, indexed by an ID which represents - /// the directory stream. When readdir is called, the directory stream ID is used to look up - /// the corresponding ReadDir iterator from this map, and information from the next - /// directory entry is returned. When closedir is called, the ReadDir iterator is removed from - /// the map. - streams: FxHashMap, - /// ID number to be used by the next call to opendir - next_id: u64, -} - -impl DirTable { - #[expect(clippy::arithmetic_side_effects)] - fn insert_new(&mut self, read_dir: ReadDir) -> u64 { - let id = self.next_id; - self.next_id += 1; - self.streams.try_insert(id, OpenDir::new(read_dir)).unwrap(); - id - } -} - -impl Default for DirTable { - fn default() -> DirTable { - DirTable { - streams: FxHashMap::default(), - // Skip 0 as an ID, because it looks like a null pointer to libc - next_id: 1, - } - } -} - -impl VisitProvenance for DirTable { - fn visit_provenance(&self, visit: &mut VisitWith<'_>) { - let DirTable { streams, next_id: _ } = self; - - for dir in streams.values() { - dir.entry.visit_provenance(visit); - } - } -} - -fn maybe_sync_file( - file: &File, - writable: bool, - operation: fn(&File) -> std::io::Result<()>, -) -> std::io::Result { - if !writable && cfg!(windows) { - // sync_all() and sync_data() will return an error on Windows hosts if the file is not opened - // for writing. (FlushFileBuffers requires that the file handle have the - // GENERIC_WRITE right) - Ok(0i32) - } else { - let result = operation(file); - result.map(|_| 0i32) + fn dir_entry_fields( + &self, + entry: Either, + ) -> InterpResult<'tcx, DirEntry> { + let this = self.eval_context_ref(); + interp_ok(match entry { + Either::Left(dir_entry) => { + DirEntry { + name: dir_entry.file_name(), + d_type: this.file_type_to_d_type(dir_entry.file_type())?, + // If the host is a Unix system, fill in the inode number with its real value. + // If not, use 0 as a fallback value. + #[cfg(unix)] + ino: std::os::unix::fs::DirEntryExt::ino(&dir_entry), + #[cfg(not(unix))] + ino: 0u64, + } + } + Either::Right(special) => + DirEntry { + name: special.into(), + d_type: this.eval_libc("DT_DIR").to_u8()?.into(), + ino: 0, + }, + }) } } @@ -443,7 +487,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(Scalar::from_i32(this.try_unwrap_io_result(fd)?)) } - fn lseek64( + fn lseek( &mut self, fd_num: i32, offset: i128, @@ -899,14 +943,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } } - fn readdir64(&mut self, dirent_type: &str, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { + fn readdir(&mut self, dirp_op: &OpTy<'tcx>, dest: &MPlaceTy<'tcx>) -> InterpResult<'tcx> { let this = self.eval_context_mut(); if !matches!( &this.tcx.sess.target.os, Os::Linux | Os::Android | Os::Solaris | Os::Illumos | Os::FreeBsd ) { - panic!("`readdir64` should not be called on {}", this.tcx.sess.target.os); + panic!("`readdir` should not be called on {}", this.tcx.sess.target.os); } let dirp = this.read_target_usize(dirp_op)?; @@ -915,21 +959,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { this.reject_in_isolation("`readdir`", reject_with)?; this.set_last_error(LibcError("EBADF"))?; - return interp_ok(Scalar::null_ptr(this)); + this.write_null(dest)?; + return interp_ok(()); } let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| { - err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir") + err_ub_format!("the DIR pointer passed to `readdir` did not come from opendir") })?; - let entry = match open_dir.read_dir.next() { + let entry = match open_dir.next_host_entry() { Some(Ok(dir_entry)) => { - // If the host is a Unix system, fill in the inode number with its real value. - // If not, use 0 as a fallback value. - #[cfg(unix)] - let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry); - #[cfg(not(unix))] - let ino = 0u64; + let dir_entry = this.dir_entry_fields(dir_entry)?; // Write the directory entry into a newly allocated buffer. // The name is written with write_bytes, while the rest of the @@ -954,23 +994,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // } // // On FreeBSD: - // pub struct dirent{ + // pub struct dirent { // pub d_fileno: uint32_t, // pub d_reclen: uint16_t, // pub d_type: uint8_t, // pub d_namlen: uint8_t, - // pub d_name: [c_char; 256] + // pub d_name: [c_char; 256], // } - let mut name = dir_entry.file_name(); // not a Path as there are no separators! + // We just use the pointee type here since determining the right pointee type + // independently is highly non-trivial: it depends on which exact alias of the + // function was invoked (e.g. `fstat` vs `fstat64`), and then on FreeBSD it also + // depends on the ABI level which can be different between the libc used by std and + // the libc used by everyone else. + let dirent_ty = dest.layout.ty.builtin_deref(true).unwrap(); + let dirent_layout = this.layout_of(dirent_ty)?; + let fields = &dirent_layout.fields; + let d_name_offset = fields.offset(fields.count().strict_sub(1)).bytes(); + + // Determine the size of the buffer we have to allocate. + let mut name = dir_entry.name; // not a Path as there are no separators! name.push("\0"); // Add a NUL terminator let name_bytes = name.as_encoded_bytes(); let name_len = u64::try_from(name_bytes.len()).unwrap(); - - let dirent_layout = this.libc_ty_layout(dirent_type); - let fields = &dirent_layout.fields; - let last_field = fields.count().strict_sub(1); - let d_name_offset = fields.offset(last_field).bytes(); let size = d_name_offset.strict_add(name_len); let entry = this.allocate_ptr( @@ -981,11 +1027,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; let entry = this.ptr_to_mplace(entry.into(), dirent_layout); - // Write common fields + // Write the name. + // The name is not a normal field, we already computed the offset above. + let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this); + this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?; + + // Write common fields. let ino_name = if this.tcx.sess.target.os == Os::FreeBsd { "d_fileno" } else { "d_ino" }; this.write_int_fields_named( - &[(ino_name, ino.into()), ("d_reclen", size.into())], + &[(ino_name, dir_entry.ino.into()), ("d_reclen", size.into())], &entry, )?; @@ -993,20 +1044,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if let Some(d_off) = this.try_project_field_named(&entry, "d_off")? { this.write_null(&d_off)?; } - if let Some(d_namlen) = this.try_project_field_named(&entry, "d_namlen")? { this.write_int(name_len.strict_sub(1), &d_namlen)?; } - - let file_type = this.file_type_to_d_type(dir_entry.file_type())?; if let Some(d_type) = this.try_project_field_named(&entry, "d_type")? { - this.write_int(file_type, &d_type)?; + this.write_int(dir_entry.d_type, &d_type)?; } - // The name is not a normal field, we already computed the offset above. - let name_ptr = entry.ptr().wrapping_offset(Size::from_bytes(d_name_offset), this); - this.write_bytes_ptr(name_ptr, name_bytes.iter().copied())?; - Some(entry.ptr()) } None => { @@ -1025,7 +1069,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.deallocate_ptr(old_entry, None, MiriMemoryKind::Runtime.into())?; } - interp_ok(Scalar::from_maybe_pointer(entry.unwrap_or_else(Pointer::null), this)) + this.write_pointer(entry.unwrap_or_else(Pointer::null), dest)?; + interp_ok(()) } fn macos_readdir_r( @@ -1051,8 +1096,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| { err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir") })?; - interp_ok(match open_dir.read_dir.next() { + interp_ok(match open_dir.next_host_entry() { Some(Ok(dir_entry)) => { + let dir_entry = this.dir_entry_fields(dir_entry)?; // Write into entry, write pointer to result, return 0 on success. // The name is written with write_os_str_to_c_str, while the rest of the // dirent struct is written using write_int_fields. @@ -1068,36 +1114,27 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // } let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?; - let name_place = this.project_field_named(&entry_place, "d_name")?; - let file_name = dir_entry.file_name(); // not a Path as there are no separators! + // Write the name. + let name_place = this.project_field_named(&entry_place, "d_name")?; let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str( - &file_name, + &dir_entry.name, name_place.ptr(), name_place.layout.size.bytes(), )?; - let file_name_len = file_name_buf_len.strict_sub(1); if !name_fits { throw_unsup_format!( "a directory entry had a name too large to fit in libc::dirent" ); } - // If the host is a Unix system, fill in the inode number with its real value. - // If not, use 0 as a fallback value. - #[cfg(unix)] - let ino = std::os::unix::fs::DirEntryExt::ino(&dir_entry); - #[cfg(not(unix))] - let ino = 0u64; - - let file_type = this.file_type_to_d_type(dir_entry.file_type())?; - + // Write the other fields. this.write_int_fields_named( &[ - ("d_reclen", 0), - ("d_namlen", file_name_len.into()), - ("d_type", file_type.into()), - ("d_ino", ino.into()), + ("d_reclen", entry_place.layout.size.bytes().into()), + ("d_namlen", file_name_buf_len.strict_sub(1).into()), + ("d_type", dir_entry.d_type.into()), + ("d_ino", dir_entry.ino.into()), ("d_seekoff", 0), ], &entry_place, diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs index 426bc28ce8878..12cee0d162a0c 100644 --- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs @@ -84,7 +84,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let fd = this.read_scalar(fd)?.to_i32()?; let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?; let whence = this.read_scalar(whence)?.to_i32()?; - this.lseek64(fd, offset, whence, dest)?; + this.lseek(fd, offset, whence, dest)?; } "ftruncate64" => { let [fd, length] = this.check_shim_sig( @@ -114,10 +114,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } "readdir64" => { - // FIXME: This does not have a direct test (#3179). let [dirp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.readdir64("dirent64", dirp)?; - this.write_scalar(result, dest)?; + this.readdir(dirp, dest)?; } "sync_file_range" => { let [fd, offset, nbytes, flags] = diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs index be4f2e66bab10..c9e9c30ac2c7d 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -64,13 +64,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } "opendir$INODE64" => { - // FIXME: This does not have a direct test (#3179). let [name] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; let result = this.opendir(name)?; this.write_scalar(result, dest)?; } "readdir_r" | "readdir_r$INODE64" => { - // FIXME: This does not have a direct test (#3179). let [dirp, entry, result] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; let result = this.macos_readdir_r(dirp, entry, result)?; @@ -122,8 +120,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(result, dest)?; } - // FIXME: add a test that directly calls this function. "mach_wait_until" => { + // FIXME: This does not have a direct test (#3179). let [deadline] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; let result = this.mach_wait_until(deadline)?; this.write_scalar(result, dest)?; diff --git a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs index f3918fdccf128..723e7bae4cdf8 100644 --- a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs @@ -102,12 +102,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.lstat(path, buf)?; this.write_scalar(result, dest)?; } - "readdir" => { - // FIXME: This does not have a direct test (#3179). - let [dirp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.readdir64("dirent", dirp)?; - this.write_scalar(result, dest)?; - } // Sockets and pipes "__xnet_socketpair" => { diff --git a/src/tools/miri/src/shims/windows/handle.rs b/src/tools/miri/src/shims/windows/handle.rs index 92d6321bed1fd..b0d29bdd6f271 100644 --- a/src/tools/miri/src/shims/windows/handle.rs +++ b/src/tools/miri/src/shims/windows/handle.rs @@ -314,7 +314,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "`DuplicateHandle` called on a thread handle, which is unsupported" ); } - Handle::Pseudo(pseudo) => Handle::Pseudo(pseudo), + Handle::Pseudo(_) => { + throw_unsup_format!( + "`DuplicateHandle` called on a pseudo handle, which is unsupported" + ); + } Handle::Null | Handle::Invalid => this.invalid_handle("DuplicateHandle")?, }; diff --git a/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.rs b/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.rs index a71778b1d0d49..8c085384a688d 100644 --- a/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.rs +++ b/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.rs @@ -12,7 +12,7 @@ use windows_sys::Win32::System::Threading::{INFINITE, WaitForSingleObject}; // XXX HACK: This is how miri represents the handle for thread 0. // This value can be "legitimately" obtained by using `GetCurrentThread` with `DuplicateHandle` -// but miri does not implement `DuplicateHandle` yet. (FIXME: it does now.) +// but miri does not implement `DuplicateHandle` for pseudo handles yet. const MAIN_THREAD: HANDLE = (2i32 << 29) as HANDLE; fn main() { diff --git a/src/tools/miri/tests/fail-dep/win/duplicate-pseudo-handle.rs b/src/tools/miri/tests/fail-dep/win/duplicate-pseudo-handle.rs new file mode 100644 index 0000000000000..c69ea419f7d2a --- /dev/null +++ b/src/tools/miri/tests/fail-dep/win/duplicate-pseudo-handle.rs @@ -0,0 +1,22 @@ +//@only-target: windows # Uses win32 api functions +use windows_sys::Win32::Foundation::{CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle}; +use windows_sys::Win32::System::Threading::{GetCurrentProcess, GetCurrentThread}; + +fn main() { + unsafe { + let cur_proc = GetCurrentProcess(); + + let pseudo = GetCurrentThread(); + let mut out = std::mem::zeroed(); + let res = + DuplicateHandle(cur_proc, pseudo, cur_proc, &mut out, 0, 0, DUPLICATE_SAME_ACCESS); + //~^ERROR: pseudo handle + assert!(res != 0); + assert!(out.addr() != 0); + // Since the original handle was a pseudo handle, we must return something different. + assert!(out != pseudo); + // And closing it should work (which it does not for a pseudo handle). + let res = CloseHandle(out); + assert!(res != 0); + } +} diff --git a/src/tools/miri/tests/fail-dep/win/duplicate-pseudo-handle.stderr b/src/tools/miri/tests/fail-dep/win/duplicate-pseudo-handle.stderr new file mode 100644 index 0000000000000..bbab2af677810 --- /dev/null +++ b/src/tools/miri/tests/fail-dep/win/duplicate-pseudo-handle.stderr @@ -0,0 +1,12 @@ +error: unsupported operation: `DuplicateHandle` called on a pseudo handle, which is unsupported + --> tests/fail-dep/win/duplicate-pseudo-handle.rs:LL:CC + | +LL | DuplicateHandle(cur_proc, pseudo, cur_proc, &mut out, 0, 0, DUPLICATE_SAME_ACCESS); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsupported operation occurred here + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index 00d5f7d97e281..f5e9a56d7d039 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -48,6 +48,8 @@ fn main() { test_nofollow_not_symlink(); #[cfg(target_os = "macos")] test_ioctl(); + test_opendir_closedir(); + test_readdir(); } fn test_file_open_unix_allow_two_args() { @@ -579,3 +581,83 @@ fn test_ioctl() { assert_eq!(libc::ioctl(fd, libc::FIOCLEX), 0); } } + +fn test_opendir_closedir() { + // dir should exist + use std::fs::{create_dir, remove_dir}; + let path = utils::prepare_dir("miri_test_libc_opendir_closedir"); + create_dir(&path).expect("create_dir failed"); + let cpath = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed"); + let dir: *mut libc::DIR = unsafe { libc::opendir(cpath.as_ptr()) }; + assert!(!dir.is_null()); + assert_eq!(unsafe { libc::closedir(dir) }, 0); + + // dir should not exist + remove_dir(&path).unwrap(); + let dir: *mut libc::DIR = unsafe { libc::opendir(cpath.as_ptr()) }; + assert!(dir.is_null()); + let e = std::io::Error::last_os_error(); + assert_eq!(e.raw_os_error(), Some(libc::ENOENT)); + assert_eq!(e.kind(), ErrorKind::NotFound); + + // open normal file as dir should fail + let file_path = utils::prepare_with_content("test_not_a_dir.txt", b"hello"); + let cfile = CString::new(file_path.as_os_str().as_bytes()).expect("CString::new failed"); + let dir: *mut libc::DIR = unsafe { libc::opendir(cfile.as_ptr()) }; + assert!(dir.is_null()); + let e = std::io::Error::last_os_error(); + assert_eq!(e.raw_os_error(), Some(libc::ENOTDIR)); + assert_eq!(e.kind(), ErrorKind::NotADirectory); + remove_file(&file_path).unwrap(); +} + +fn test_readdir() { + use std::fs::{create_dir, remove_dir, write}; + + let dir_path = utils::prepare_dir("miri_test_libc_readdir"); + create_dir(&dir_path).ok(); + + // Create test files + let file1 = dir_path.join("file1.txt"); + let file2 = dir_path.join("file2.txt"); + write(&file1, b"content1").unwrap(); + write(&file2, b"content2").unwrap(); + + let c_path = CString::new(dir_path.as_os_str().as_bytes()).unwrap(); + + unsafe { + let dirp = libc::opendir(c_path.as_ptr()); + assert!(!dirp.is_null()); + let mut entries = Vec::new(); + loop { + cfg_if::cfg_if! { + if #[cfg(target_os = "macos")] { + // On macos we only support readdir_r as that's what std uses there. + use std::mem::MaybeUninit; + use libc::dirent; + let mut entry: MaybeUninit = MaybeUninit::uninit(); + let mut result: *mut dirent = std::ptr::null_mut(); + let ret = libc::readdir_r(dirp, entry.as_mut_ptr(), &mut result); + assert_eq!(ret, 0); + let entry_ptr = result; + } else { + let entry_ptr = libc::readdir(dirp); + } + } + if entry_ptr.is_null() { + break; + } + let name_ptr = std::ptr::addr_of!((*entry_ptr).d_name) as *const libc::c_char; + let name = CStr::from_ptr(name_ptr); + let name_str = name.to_string_lossy(); + entries.push(name_str.into_owned()); + } + assert_eq!(libc::closedir(dirp), 0); + entries.sort(); + assert_eq!(&entries, &[".", "..", "file1.txt", "file2.txt"]); + } + + remove_file(&file1).unwrap(); + remove_file(&file2).unwrap(); + remove_dir(&dir_path).unwrap(); +} diff --git a/src/tools/rustfmt/src/parse/macros/cfg_if.rs b/src/tools/rustfmt/src/parse/macros/cfg_if.rs index 26bf6c5326f5f..495f12c8f5d5d 100644 --- a/src/tools/rustfmt/src/parse/macros/cfg_if.rs +++ b/src/tools/rustfmt/src/parse/macros/cfg_if.rs @@ -3,7 +3,7 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; use rustc_ast::ast; use rustc_ast::token::TokenKind; use rustc_parse::exp; -use rustc_parse::parser::ForceCollect; +use rustc_parse::parser::{AllowConstBlockItems, ForceCollect}; use rustc_span::symbol::kw; use crate::parse::macros::build_stream_parser; @@ -61,7 +61,7 @@ fn parse_cfg_if_inner<'a>( } while parser.token != TokenKind::CloseBrace && parser.token.kind != TokenKind::Eof { - let item = match parser.parse_item(ForceCollect::No) { + let item = match parser.parse_item(ForceCollect::No, AllowConstBlockItems::Yes) { Ok(Some(item_ptr)) => *item_ptr, Ok(None) => continue, Err(err) => { diff --git a/src/tools/rustfmt/src/parse/macros/mod.rs b/src/tools/rustfmt/src/parse/macros/mod.rs index c063990dfa0b1..552300b1b8284 100644 --- a/src/tools/rustfmt/src/parse/macros/mod.rs +++ b/src/tools/rustfmt/src/parse/macros/mod.rs @@ -2,7 +2,7 @@ use rustc_ast::ast; use rustc_ast::token::{Delimiter, NonterminalKind, NtExprKind::*, NtPatKind::*, TokenKind}; use rustc_ast::tokenstream::TokenStream; use rustc_parse::MACRO_ARGUMENTS; -use rustc_parse::parser::{ForceCollect, Parser, Recovery}; +use rustc_parse::parser::{AllowConstBlockItems, ForceCollect, Parser, Recovery}; use rustc_session::parse::ParseSess; use rustc_span::symbol; @@ -67,7 +67,7 @@ fn parse_macro_arg<'a, 'b: 'a>(parser: &'a mut Parser<'b>) -> Option { parse_macro_arg!( Item, NonterminalKind::Item, - |parser: &mut Parser<'b>| parser.parse_item(ForceCollect::No), + |parser: &mut Parser<'b>| parser.parse_item(ForceCollect::No, AllowConstBlockItems::Yes), |x: Option>| x ); diff --git a/src/tools/rustfmt/src/visitor.rs b/src/tools/rustfmt/src/visitor.rs index c521b497a2d99..8abcba121e58b 100644 --- a/src/tools/rustfmt/src/visitor.rs +++ b/src/tools/rustfmt/src/visitor.rs @@ -7,7 +7,9 @@ use rustc_span::{BytePos, Ident, Pos, Span, symbol}; use tracing::debug; use crate::attr::*; -use crate::comment::{CodeCharKind, CommentCodeSlices, contains_comment, rewrite_comment}; +use crate::comment::{ + CodeCharKind, CommentCodeSlices, contains_comment, recover_comment_removed, rewrite_comment, +}; use crate::config::{BraceStyle, Config, MacroSelector, StyleEdition}; use crate::coverage::transform_missing_snippet; use crate::items::{ @@ -533,6 +535,28 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { ast::ItemKind::Static(..) | ast::ItemKind::Const(..) => { self.visit_static(&StaticParts::from_item(item)); } + ast::ItemKind::ConstBlock(ast::ConstBlockItem { + id: _, + span, + ref block, + }) => { + let context = &self.get_context(); + let offset = self.block_indent; + self.push_rewrite( + item.span, + block + .rewrite( + context, + Shape::legacy( + context.budget(offset.block_indent), + offset.block_only(), + ), + ) + .map(|rhs| { + recover_comment_removed(format!("const {rhs}"), span, context) + }), + ); + } ast::ItemKind::Fn(ref fn_kind) => { let ast::Fn { defaultness, diff --git a/src/tools/rustfmt/tests/source/const-block-items.rs b/src/tools/rustfmt/tests/source/const-block-items.rs new file mode 100644 index 0000000000000..f30f0fe256a53 --- /dev/null +++ b/src/tools/rustfmt/tests/source/const-block-items.rs @@ -0,0 +1,24 @@ +#![feature(const_block_items)] + + const { + + + assert!(true) + } + + #[cfg(false)] const { assert!(false) } + + + #[cfg(false)] +// foo + const + + { + // bar + assert!(false) + // baz + } // 123 + + + #[expect(unused)] +pub const { let a = 1; assert!(true); } diff --git a/src/tools/rustfmt/tests/target/const-block-items.rs b/src/tools/rustfmt/tests/target/const-block-items.rs new file mode 100644 index 0000000000000..bbf6dbf481c7e --- /dev/null +++ b/src/tools/rustfmt/tests/target/const-block-items.rs @@ -0,0 +1,20 @@ +#![feature(const_block_items)] + +const { assert!(true) } + +#[cfg(false)] +const { assert!(false) } + +#[cfg(false)] +// foo +const { + // bar + assert!(false) + // baz +} // 123 + +#[expect(unused)] +const { + let a = 1; + assert!(true); +} diff --git a/tests/ui/consts/const-block-items/assert-fail.rs b/tests/ui/consts/const-block-items/assert-fail.rs new file mode 100644 index 0000000000000..a7e3478666ef8 --- /dev/null +++ b/tests/ui/consts/const-block-items/assert-fail.rs @@ -0,0 +1,10 @@ +//@ check-fail + +#![feature(const_block_items)] + +const { assert!(false) } +//~^ ERROR: evaluation panicked: assertion failed: false [E0080] +const { assert!(2 + 2 == 5) } +//~^ ERROR: evaluation panicked: assertion failed: 2 + 2 == 5 [E0080] + +fn main() {} diff --git a/tests/ui/consts/const-block-items/assert-fail.stderr b/tests/ui/consts/const-block-items/assert-fail.stderr new file mode 100644 index 0000000000000..b1444dc5e0d01 --- /dev/null +++ b/tests/ui/consts/const-block-items/assert-fail.stderr @@ -0,0 +1,15 @@ +error[E0080]: evaluation panicked: assertion failed: false + --> $DIR/assert-fail.rs:5:9 + | +LL | const { assert!(false) } + | ^^^^^^^^^^^^^^ evaluation of `_` failed here + +error[E0080]: evaluation panicked: assertion failed: 2 + 2 == 5 + --> $DIR/assert-fail.rs:7:9 + | +LL | const { assert!(2 + 2 == 5) } + | ^^^^^^^^^^^^^^^^^^^ evaluation of `_` failed here + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/consts/const-block-items/assert-pass.rs b/tests/ui/consts/const-block-items/assert-pass.rs new file mode 100644 index 0000000000000..c409cc5b91f7d --- /dev/null +++ b/tests/ui/consts/const-block-items/assert-pass.rs @@ -0,0 +1,7 @@ +//@ check-pass +#![feature(const_block_items)] + +const { assert!(true) } +const { assert!(2 + 2 == 4) } + +fn main() {} diff --git a/tests/ui/consts/const-block-items/hir.rs b/tests/ui/consts/const-block-items/hir.rs new file mode 100644 index 0000000000000..1d9c5e91b57ef --- /dev/null +++ b/tests/ui/consts/const-block-items/hir.rs @@ -0,0 +1,10 @@ +//@ build-pass +//@ compile-flags: -Zunpretty=hir + +#![feature(const_block_items)] + +const { + // foo +} + +fn main() { } diff --git a/tests/ui/consts/const-block-items/hir.stdout b/tests/ui/consts/const-block-items/hir.stdout new file mode 100644 index 0000000000000..e2df04e98d4ea --- /dev/null +++ b/tests/ui/consts/const-block-items/hir.stdout @@ -0,0 +1,14 @@ +//@ build-pass +//@ compile-flags: -Zunpretty=hir + +#![feature(const_block_items)] +extern crate std; +#[prelude_import] +use ::std::prelude::rust_2015::*; + +const _: () = + { + // foo + }; + +fn main() { } diff --git a/tests/ui/consts/const-block-items/typecheck.rs b/tests/ui/consts/const-block-items/typecheck.rs new file mode 100644 index 0000000000000..2e521cbfda05d --- /dev/null +++ b/tests/ui/consts/const-block-items/typecheck.rs @@ -0,0 +1,20 @@ +//@ check-fail + +#![feature(const_block_items)] + +const { + assert!(true); + 2 + 2 //~ ERROR: mismatched types [E0308] +} + + +const fn id(t: T) -> T { + t +} + +const { id(2) } +//~^ ERROR: mismatched types [E0308] +const { id(()) } + + +fn main() {} diff --git a/tests/ui/consts/const-block-items/typecheck.stderr b/tests/ui/consts/const-block-items/typecheck.stderr new file mode 100644 index 0000000000000..93d1eb1bc9e42 --- /dev/null +++ b/tests/ui/consts/const-block-items/typecheck.stderr @@ -0,0 +1,30 @@ +error[E0308]: mismatched types + --> $DIR/typecheck.rs:7:5 + | +LL | 2 + 2 + | ^^^^^ expected `()`, found integer + +error[E0308]: mismatched types + --> $DIR/typecheck.rs:15:12 + | +LL | const { id(2) } + | -- ^ expected `()`, found integer + | | + | arguments to this function are incorrect + | +help: the return type of this call is `{integer}` due to the type of the argument passed + --> $DIR/typecheck.rs:15:9 + | +LL | const { id(2) } + | ^^^-^ + | | + | this argument influences the return type of `id` +note: function defined here + --> $DIR/typecheck.rs:11:10 + | +LL | const fn id(t: T) -> T { + | ^^ ---- + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/consts/const-block-item-macro-codegen.rs b/tests/ui/consts/const-item-with-block-body/macro-codegen.rs similarity index 100% rename from tests/ui/consts/const-block-item-macro-codegen.rs rename to tests/ui/consts/const-item-with-block-body/macro-codegen.rs diff --git a/tests/ui/consts/const-block-item.rs b/tests/ui/consts/const-item-with-block-body/static.rs similarity index 100% rename from tests/ui/consts/const-block-item.rs rename to tests/ui/consts/const-item-with-block-body/static.rs diff --git a/tests/ui/consts/const-block-item.stderr b/tests/ui/consts/const-item-with-block-body/static.stderr similarity index 84% rename from tests/ui/consts/const-block-item.stderr rename to tests/ui/consts/const-item-with-block-body/static.stderr index b325976a60b69..feaabb92803fc 100644 --- a/tests/ui/consts/const-block-item.stderr +++ b/tests/ui/consts/const-item-with-block-body/static.stderr @@ -1,5 +1,5 @@ warning: trait `Value` is never used - --> $DIR/const-block-item.rs:5:15 + --> $DIR/static.rs:5:15 | LL | pub trait Value { | ^^^^^ diff --git a/tests/ui/feature-gates/feature-gate-const-block-items.rs b/tests/ui/feature-gates/feature-gate-const-block-items.rs new file mode 100644 index 0000000000000..5523633197bba --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-const-block-items.rs @@ -0,0 +1,7 @@ +//@ check-fail + +const { //~ ERROR: const block items are experimental + assert!(true) +} + +fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-const-block-items.stderr b/tests/ui/feature-gates/feature-gate-const-block-items.stderr new file mode 100644 index 0000000000000..60a724f907120 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-const-block-items.stderr @@ -0,0 +1,13 @@ +error[E0658]: const block items are experimental + --> $DIR/feature-gate-const-block-items.rs:3:1 + | +LL | const { + | ^^^^^ + | + = note: see issue #149226 for more information + = help: add `#![feature(const_block_items)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/macros/stringify.rs b/tests/ui/macros/stringify.rs index fa06da5cbfbc6..0367b8c2d023c 100644 --- a/tests/ui/macros/stringify.rs +++ b/tests/ui/macros/stringify.rs @@ -5,6 +5,7 @@ #![allow(incomplete_features)] #![feature(auto_traits)] #![feature(box_patterns)] +#![feature(const_block_items)] #![feature(const_trait_impl)] #![feature(coroutines)] #![feature(decl_macro)] @@ -369,6 +370,9 @@ fn test_item() { c1!(item, [ pub const S: () = {}; ], "pub const S: () = {};"); c1!(item, [ const S: (); ], "const S: ();"); + // ItemKind::ConstBlock + c1!(item, [ const {} ], "const {}"); + // ItemKind::Fn c1!(item, [ pub default const async unsafe extern "C" fn f() {} ], diff --git a/tests/ui/parser/const-block-items/attrs.rs b/tests/ui/parser/const-block-items/attrs.rs new file mode 100644 index 0000000000000..eb7a0554ea9c6 --- /dev/null +++ b/tests/ui/parser/const-block-items/attrs.rs @@ -0,0 +1,14 @@ +//@ check-pass + +#![feature(const_block_items)] + +#[cfg(false)] +const { assert!(false) } + +#[expect(unused)] +const { + let a = 1; + assert!(true); +} + +fn main() {} diff --git a/tests/ui/parser/const-block-items/inner-attr.rs b/tests/ui/parser/const-block-items/inner-attr.rs new file mode 100644 index 0000000000000..5da3191760d26 --- /dev/null +++ b/tests/ui/parser/const-block-items/inner-attr.rs @@ -0,0 +1,12 @@ +#![feature(const_block_items)] + +// ATTENTION: if we ever start accepting inner attributes here, make sure `rustfmt` can handle them. +// see: https://github.com/rust-lang/rustfmt/issues/6158 + +const { + #![expect(unused)] //~ ERROR: an inner attribute is not permitted in this context + let a = 1; + assert!(true); +} + +fn main() {} diff --git a/tests/ui/parser/const-block-items/inner-attr.stderr b/tests/ui/parser/const-block-items/inner-attr.stderr new file mode 100644 index 0000000000000..e0a70566332cf --- /dev/null +++ b/tests/ui/parser/const-block-items/inner-attr.stderr @@ -0,0 +1,13 @@ +error: an inner attribute is not permitted in this context + --> $DIR/inner-attr.rs:7:5 + | +LL | #![expect(unused)] + | ^^^^^^^^^^^^^^^^^^ +... +LL | fn main() {} + | ------------ the inner attribute doesn't annotate this function + | + = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files + +error: aborting due to 1 previous error + diff --git a/tests/ui/parser/const-block-items/macro-item.rs b/tests/ui/parser/const-block-items/macro-item.rs new file mode 100644 index 0000000000000..0c07bfc203d25 --- /dev/null +++ b/tests/ui/parser/const-block-items/macro-item.rs @@ -0,0 +1,12 @@ +//@ check-pass +#![feature(const_block_items)] + +macro_rules! foo { + ($item:item) => { + $item + }; +} + +foo!(const {}); + +fn main() {} diff --git a/tests/ui/parser/const-block-items/macro-stmt.rs b/tests/ui/parser/const-block-items/macro-stmt.rs new file mode 100644 index 0000000000000..38632d2eec337 --- /dev/null +++ b/tests/ui/parser/const-block-items/macro-stmt.rs @@ -0,0 +1,14 @@ +//@ check-fail + +#![feature(const_block_items)] + +macro_rules! foo { + ($item:item) => { + $item + //~^ ERROR: expected expression, found `` + }; +} + +fn main() { + foo!(const {}); +} diff --git a/tests/ui/parser/const-block-items/macro-stmt.stderr b/tests/ui/parser/const-block-items/macro-stmt.stderr new file mode 100644 index 0000000000000..dce90e5daa4ca --- /dev/null +++ b/tests/ui/parser/const-block-items/macro-stmt.stderr @@ -0,0 +1,13 @@ +error: expected expression, found `` + --> $DIR/macro-stmt.rs:7:9 + | +LL | $item + | ^^^^^ expected expression +... +LL | foo!(const {}); + | -------------- in this macro invocation + | + = note: this error originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 1 previous error + diff --git a/tests/ui/parser/const-block-items/mod-in-fn.rs b/tests/ui/parser/const-block-items/mod-in-fn.rs new file mode 100644 index 0000000000000..ec98f95a84485 --- /dev/null +++ b/tests/ui/parser/const-block-items/mod-in-fn.rs @@ -0,0 +1,9 @@ +//@ check-pass + +#![feature(const_block_items)] + +fn main() { + mod foo { + const { assert!(true) } + } +} diff --git a/tests/ui/parser/const-block-items/pub.rs b/tests/ui/parser/const-block-items/pub.rs new file mode 100644 index 0000000000000..b76d30d9bda18 --- /dev/null +++ b/tests/ui/parser/const-block-items/pub.rs @@ -0,0 +1,10 @@ +#![feature(const_block_items)] + +//@ check-pass + +// FIXME(const_block_items): `pub`` is useless here +pub const { + assert!(true); +} + +fn main() { } diff --git a/tests/ui/parser/const-block-items/unsafe.rs b/tests/ui/parser/const-block-items/unsafe.rs new file mode 100644 index 0000000000000..f53dfab7a0a21 --- /dev/null +++ b/tests/ui/parser/const-block-items/unsafe.rs @@ -0,0 +1,10 @@ +#![feature(const_block_items)] + +const unsafe fn foo() -> bool { + true +} + +const unsafe { assert!(foo()) } +//~^ ERROR: expected one of `extern` or `fn`, found `{` + +fn main() { } diff --git a/tests/ui/parser/const-block-items/unsafe.stderr b/tests/ui/parser/const-block-items/unsafe.stderr new file mode 100644 index 0000000000000..0d38af838b3ea --- /dev/null +++ b/tests/ui/parser/const-block-items/unsafe.stderr @@ -0,0 +1,8 @@ +error: expected one of `extern` or `fn`, found `{` + --> $DIR/unsafe.rs:7:14 + | +LL | const unsafe { assert!(foo()) } + | ^ expected one of `extern` or `fn` + +error: aborting due to 1 previous error + diff --git a/tests/ui/pin-ergonomics/user-type-projection.rs b/tests/ui/pin-ergonomics/user-type-projection.rs new file mode 100644 index 0000000000000..f482586b6ebcc --- /dev/null +++ b/tests/ui/pin-ergonomics/user-type-projection.rs @@ -0,0 +1,19 @@ +#![crate_type = "rlib"] +#![feature(pin_ergonomics)] +#![expect(incomplete_features)] +//@ edition: 2024 +//@ check-pass + +// Test that we don't ICE when projecting user-type-annotations through a `&pin` pattern. +// +// Historically, this could occur when the code handling those projections did not know +// about `&pin` patterns, and incorrectly treated them as plain `&`/`&mut` patterns instead. + +struct Data { + x: u32 +} + +pub fn project_user_type_through_pin() -> u32 { + let &pin const Data { x }: &pin const Data = &pin const Data { x: 30 }; + x +} diff --git a/tests/ui/thir-print/str-patterns.stdout b/tests/ui/thir-print/str-patterns.stdout index 6941ab15130f8..09212e7d68aee 100644 --- a/tests/ui/thir-print/str-patterns.stdout +++ b/tests/ui/thir-print/str-patterns.stdout @@ -10,6 +10,7 @@ Thir { span: $DIR/str-patterns.rs:11:9: 11:16 (#0), extra: None, kind: Deref { + pin: Not, subpattern: Pat { ty: str, span: $DIR/str-patterns.rs:11:9: 11:16 (#0), @@ -50,6 +51,7 @@ Thir { }, ), kind: Deref { + pin: Not, subpattern: Pat { ty: str, span: $DIR/str-patterns.rs:12:9: 12:17 (#0),