From 71b093fd2c79a4de1354f8b00adba64d0bce25b6 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Fri, 5 Dec 2025 15:06:07 +0100 Subject: [PATCH 01/24] Emit `check-cfg` lints during attribute parsing rather than evaluation#149215 --- .../rustc_attr_parsing/src/attributes/cfg.rs | 82 +++++++------------ .../src/attributes/cfg_old.rs | 9 +- compiler/rustc_builtin_macros/src/cfg.rs | 8 +- .../rustc_builtin_macros/src/cfg_select.rs | 16 +--- compiler/rustc_codegen_ssa/src/back/link.rs | 7 +- compiler/rustc_expand/src/config.rs | 32 ++------ compiler/rustc_expand/src/expand.rs | 2 +- .../rustc_hir/src/attrs/data_structures.rs | 2 +- compiler/rustc_lint/src/early/diagnostics.rs | 16 ++-- compiler/rustc_lint_defs/src/lib.rs | 4 +- compiler/rustc_metadata/src/native_libs.rs | 7 +- compiler/rustc_resolve/src/diagnostics.rs | 2 +- 12 files changed, 64 insertions(+), 123 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index bd228315b2c95..490e28ed64c5d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -2,16 +2,16 @@ use std::convert::identity; use rustc_ast::token::Delimiter; use rustc_ast::tokenstream::DelimSpan; -use rustc_ast::{AttrItem, Attribute, CRATE_NODE_ID, LitKind, NodeId, ast, token}; +use rustc_ast::{AttrItem, Attribute, CRATE_NODE_ID, LitKind, ast, token}; use rustc_errors::{Applicability, PResult}; use rustc_feature::{AttrSuggestionStyle, AttributeTemplate, Features, template}; use rustc_hir::attrs::CfgEntry; +use rustc_hir::lints::AttributeLintKind; use rustc_hir::{AttrPath, RustcVersion}; use rustc_parse::parser::{ForceCollect, Parser}; use rustc_parse::{exp, parse_in}; use rustc_session::Session; use rustc_session::config::ExpectedValues; -use rustc_session::lint::BuiltinLintDiag; use rustc_session::lint::builtin::UNEXPECTED_CFGS; use rustc_session::parse::{ParseSess, feature_err}; use rustc_span::{ErrorGuaranteed, Span, Symbol, sym}; @@ -23,10 +23,7 @@ use crate::session_diagnostics::{ AttributeParseError, AttributeParseErrorReason, CfgAttrBadDelim, MetaBadDelimSugg, ParsedDescription, }; -use crate::{ - AttributeParser, CfgMatchesLintEmitter, fluent_generated, parse_version, session_diagnostics, - try_gate_cfg, -}; +use crate::{AttributeParser, fluent_generated, parse_version, session_diagnostics, try_gate_cfg}; pub const CFG_TEMPLATE: AttributeTemplate = template!( List: &["predicate"], @@ -195,43 +192,46 @@ fn parse_name_value( } }; - Ok(CfgEntry::NameValue { name, name_span, value, span }) + match cx.sess.psess.check_config.expecteds.get(&name) { + Some(ExpectedValues::Some(values)) if !values.contains(&value.map(|(v, _)| v)) => cx + .emit_lint( + UNEXPECTED_CFGS, + AttributeLintKind::UnexpectedCfgValue((name, name_span), value), + span, + ), + None if cx.sess.psess.check_config.exhaustive_names => cx.emit_lint( + UNEXPECTED_CFGS, + AttributeLintKind::UnexpectedCfgName((name, name_span), value), + span, + ), + _ => { /* not unexpected */ } + } + + Ok(CfgEntry::NameValue { name, value: value.map(|(v, _)| v), span }) } -pub fn eval_config_entry( - sess: &Session, - cfg_entry: &CfgEntry, - id: NodeId, - emit_lints: ShouldEmit, -) -> EvalConfigResult { +pub fn eval_config_entry(sess: &Session, cfg_entry: &CfgEntry) -> EvalConfigResult { match cfg_entry { CfgEntry::All(subs, ..) => { - let mut all = None; for sub in subs { - let res = eval_config_entry(sess, sub, id, emit_lints); - // We cannot short-circuit because `eval_config_entry` emits some lints + let res = eval_config_entry(sess, sub); if !res.as_bool() { - all.get_or_insert(res); + return res; } } - all.unwrap_or_else(|| EvalConfigResult::True) + EvalConfigResult::True } CfgEntry::Any(subs, span) => { - let mut any = None; for sub in subs { - let res = eval_config_entry(sess, sub, id, emit_lints); - // We cannot short-circuit because `eval_config_entry` emits some lints + let res = eval_config_entry(sess, sub); if res.as_bool() { - any.get_or_insert(res); + return res; } } - any.unwrap_or_else(|| EvalConfigResult::False { - reason: cfg_entry.clone(), - reason_span: *span, - }) + EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span } } CfgEntry::Not(sub, span) => { - if eval_config_entry(sess, sub, id, emit_lints).as_bool() { + if eval_config_entry(sess, sub).as_bool() { EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span } } else { EvalConfigResult::True @@ -244,32 +244,8 @@ pub fn eval_config_entry( EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span } } } - CfgEntry::NameValue { name, name_span, value, span } => { - if let ShouldEmit::ErrorsAndLints = emit_lints { - match sess.psess.check_config.expecteds.get(name) { - Some(ExpectedValues::Some(values)) - if !values.contains(&value.map(|(v, _)| v)) => - { - id.emit_span_lint( - sess, - UNEXPECTED_CFGS, - *span, - BuiltinLintDiag::UnexpectedCfgValue((*name, *name_span), *value), - ); - } - None if sess.psess.check_config.exhaustive_names => { - id.emit_span_lint( - sess, - UNEXPECTED_CFGS, - *span, - BuiltinLintDiag::UnexpectedCfgName((*name, *name_span), *value), - ); - } - _ => { /* not unexpected */ } - } - } - - if sess.psess.config.contains(&(*name, value.map(|(v, _)| v))) { + CfgEntry::NameValue { name, value, span } => { + if sess.psess.config.contains(&(*name, *value)) { EvalConfigResult::True } else { EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span } diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs b/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs index 70228d1e15101..adae3fa635f43 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg_old.rs @@ -2,6 +2,7 @@ use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit, Nod use rustc_ast_pretty::pprust; use rustc_feature::{Features, GatedCfg, find_gated_cfg}; use rustc_hir::RustcVersion; +use rustc_hir::lints::AttributeLintKind; use rustc_session::Session; use rustc_session::config::ExpectedValues; use rustc_session::lint::builtin::UNEXPECTED_CFGS; @@ -51,10 +52,10 @@ pub fn cfg_matches( sess, UNEXPECTED_CFGS, cfg.span, - BuiltinLintDiag::UnexpectedCfgValue( + BuiltinLintDiag::AttributeLint(AttributeLintKind::UnexpectedCfgValue( (cfg.name, cfg.name_span), cfg.value.map(|v| (v, cfg.value_span.unwrap())), - ), + )), ); } None if sess.psess.check_config.exhaustive_names => { @@ -62,10 +63,10 @@ pub fn cfg_matches( sess, UNEXPECTED_CFGS, cfg.span, - BuiltinLintDiag::UnexpectedCfgName( + BuiltinLintDiag::AttributeLint(AttributeLintKind::UnexpectedCfgName( (cfg.name, cfg.name_span), cfg.value.map(|v| (v, cfg.value_span.unwrap())), - ), + )), ); } _ => { /* not unexpected */ } diff --git a/compiler/rustc_builtin_macros/src/cfg.rs b/compiler/rustc_builtin_macros/src/cfg.rs index b24e3065622d2..7bc9080ba0229 100644 --- a/compiler/rustc_builtin_macros/src/cfg.rs +++ b/compiler/rustc_builtin_macros/src/cfg.rs @@ -26,13 +26,7 @@ pub(crate) fn expand_cfg( ExpandResult::Ready(match parse_cfg(cx, sp, tts) { Ok(cfg) => { - let matches_cfg = attr::eval_config_entry( - cx.sess, - &cfg, - cx.current_expansion.lint_node_id, - ShouldEmit::ErrorsAndLints, - ) - .as_bool(); + let matches_cfg = attr::eval_config_entry(cx.sess, &cfg).as_bool(); MacEager::expr(cx.expr_bool(sp, matches_cfg)) } diff --git a/compiler/rustc_builtin_macros/src/cfg_select.rs b/compiler/rustc_builtin_macros/src/cfg_select.rs index b77a121ca0b95..dc8077b2a1ffb 100644 --- a/compiler/rustc_builtin_macros/src/cfg_select.rs +++ b/compiler/rustc_builtin_macros/src/cfg_select.rs @@ -1,7 +1,7 @@ use rustc_ast::tokenstream::TokenStream; use rustc_attr_parsing as attr; use rustc_attr_parsing::{ - CfgSelectBranches, CfgSelectPredicate, EvalConfigResult, ShouldEmit, parse_cfg_select, + CfgSelectBranches, CfgSelectPredicate, EvalConfigResult, parse_cfg_select, }; use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult}; use rustc_span::{Ident, Span, sym}; @@ -10,21 +10,13 @@ use crate::errors::{CfgSelectNoMatches, CfgSelectUnreachable}; /// Selects the first arm whose predicate evaluates to true. fn select_arm(ecx: &ExtCtxt<'_>, branches: CfgSelectBranches) -> Option<(TokenStream, Span)> { - let mut result = None; for (cfg, tt, arm_span) in branches.reachable { - if let EvalConfigResult::True = attr::eval_config_entry( - &ecx.sess, - &cfg, - ecx.current_expansion.lint_node_id, - ShouldEmit::ErrorsAndLints, - ) { - // FIXME(#149215) Ideally we should short-circuit here, but `eval_config_entry` currently emits lints so we cannot do this yet. - result.get_or_insert((tt, arm_span)); + if let EvalConfigResult::True = attr::eval_config_entry(&ecx.sess, &cfg) { + return Some((tt, arm_span)); } } - let wildcard = branches.wildcard.map(|(_, tt, span)| (tt, span)); - result.or(wildcard) + branches.wildcard.map(|(_, tt, span)| (tt, span)) } pub(super) fn expand_cfg_select<'cx>( diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index d35c3b6bb189e..70db6794742d0 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -13,8 +13,7 @@ use find_msvc_tools; use itertools::Itertools; use regex::Regex; use rustc_arena::TypedArena; -use rustc_ast::CRATE_NODE_ID; -use rustc_attr_parsing::{ShouldEmit, eval_config_entry}; +use rustc_attr_parsing::eval_config_entry; use rustc_data_structures::fx::FxIndexSet; use rustc_data_structures::memmap::Mmap; use rustc_data_structures::temp_dir::MaybeTempDir; @@ -3029,9 +3028,7 @@ fn add_dynamic_crate(cmd: &mut dyn Linker, sess: &Session, cratepath: &Path) { fn relevant_lib(sess: &Session, lib: &NativeLib) -> bool { match lib.cfg { - Some(ref cfg) => { - eval_config_entry(sess, cfg, CRATE_NODE_ID, ShouldEmit::ErrorsAndLints).as_bool() - } + Some(ref cfg) => eval_config_entry(sess, cfg).as_bool(), None => true, } } diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs index 0b3ac472e0e83..492c845df1710 100644 --- a/compiler/rustc_expand/src/config.rs +++ b/compiler/rustc_expand/src/config.rs @@ -163,10 +163,7 @@ pub fn pre_configure_attrs(sess: &Session, attrs: &[Attribute]) -> ast::AttrVec .iter() .flat_map(|attr| strip_unconfigured.process_cfg_attr(attr)) .take_while(|attr| { - !is_cfg(attr) - || strip_unconfigured - .cfg_true(attr, strip_unconfigured.lint_node_id, ShouldEmit::Nothing) - .as_bool() + !is_cfg(attr) || strip_unconfigured.cfg_true(attr, ShouldEmit::Nothing).as_bool() }) .collect() } @@ -309,14 +306,7 @@ impl<'a> StripUnconfigured<'a> { ); } - if !attr::eval_config_entry( - self.sess, - &cfg_predicate, - ast::CRATE_NODE_ID, - ShouldEmit::ErrorsAndLints, - ) - .as_bool() - { + if !attr::eval_config_entry(self.sess, &cfg_predicate).as_bool() { return vec![trace_attr]; } @@ -400,23 +390,17 @@ 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, self.lint_node_id, ShouldEmit::ErrorsAndLints).as_bool() - }) + attrs + .iter() + .all(|attr| !is_cfg(attr) || self.cfg_true(attr, ShouldEmit::ErrorsAndLints).as_bool()) } - pub(crate) fn cfg_true( - &self, - attr: &Attribute, - node: NodeId, - emit_errors: ShouldEmit, - ) -> EvalConfigResult { + pub(crate) fn cfg_true(&self, attr: &Attribute, emit_errors: ShouldEmit) -> EvalConfigResult { let Some(cfg) = AttributeParser::parse_single( self.sess, attr, attr.span, - node, + self.lint_node_id, self.features, emit_errors, parse_cfg, @@ -426,7 +410,7 @@ impl<'a> StripUnconfigured<'a> { return EvalConfigResult::True; }; - eval_config_entry(self.sess, &cfg, self.lint_node_id, emit_errors) + eval_config_entry(self.sess, &cfg) } /// If attributes are not allowed on expressions, emit an error for `attr` diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 20fb321307ac5..90563b21d2e80 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -2213,7 +2213,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { attr: ast::Attribute, pos: usize, ) -> EvalConfigResult { - let res = self.cfg().cfg_true(&attr, node.node_id(), ShouldEmit::ErrorsAndLints); + let res = self.cfg().cfg_true(&attr, ShouldEmit::ErrorsAndLints); if res.as_bool() { // A trace attribute left in AST in place of the original `cfg` attribute. // It can later be used by lints or other diagnostics. diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index d3cc1dd6cf751..aff79d0558387 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -190,7 +190,7 @@ pub enum CfgEntry { Any(ThinVec, Span), Not(Box, Span), Bool(bool, Span), - NameValue { name: Symbol, name_span: Span, value: Option<(Symbol, Span)>, span: Span }, + NameValue { name: Symbol, value: Option, span: Span }, Version(Option, Span), } diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index b654bc848ecfc..5345c9a4a9851 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -169,12 +169,6 @@ pub fn decorate_builtin_lint( } .decorate_lint(diag); } - BuiltinLintDiag::UnexpectedCfgName(name, value) => { - check_cfg::unexpected_cfg_name(sess, tcx, name, value).decorate_lint(diag); - } - BuiltinLintDiag::UnexpectedCfgValue(name, value) => { - check_cfg::unexpected_cfg_value(sess, tcx, name, value).decorate_lint(diag); - } BuiltinLintDiag::DeprecatedWhereclauseLocation(left_sp, sugg) => { let suggestion = match sugg { Some((right_sp, sugg)) => lints::DeprecatedWhereClauseLocationSugg::MoveToEnd { @@ -307,8 +301,8 @@ pub fn decorate_builtin_lint( } pub fn decorate_attribute_lint( - _sess: &Session, - _tcx: Option>, + sess: &Session, + tcx: Option>, kind: &AttributeLintKind, diag: &mut Diag<'_, ()>, ) { @@ -364,5 +358,11 @@ pub fn decorate_attribute_lint( suggestion: lints::UnsafeAttrOutsideUnsafeSuggestion { left, right }, } .decorate_lint(diag), + &AttributeLintKind::UnexpectedCfgName(name, value) => { + check_cfg::unexpected_cfg_name(sess, tcx, name, value).decorate_lint(diag) + } + &AttributeLintKind::UnexpectedCfgValue(name, value) => { + check_cfg::unexpected_cfg_value(sess, tcx, name, value).decorate_lint(diag) + } } } diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 3c6e7d04a29dd..cc4062775ad30 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -636,8 +636,6 @@ pub enum BuiltinLintDiag { }, BreakWithLabelAndLoop(Span), UnicodeTextFlow(Span, String), - UnexpectedCfgName((Symbol, Span), Option<(Symbol, Span)>), - UnexpectedCfgValue((Symbol, Span), Option<(Symbol, Span)>), DeprecatedWhereclauseLocation(Span, Option<(Span, String)>), SingleUseLifetime { /// Span of the parameter which declares this lifetime. @@ -732,6 +730,8 @@ pub enum AttributeLintKind { attribute_name_span: Span, sugg_spans: (Span, Span), }, + UnexpectedCfgName((Symbol, Span), Option<(Symbol, Span)>), + UnexpectedCfgValue((Symbol, Span), Option<(Symbol, Span)>), } pub type RegisteredTools = FxIndexSet; diff --git a/compiler/rustc_metadata/src/native_libs.rs b/compiler/rustc_metadata/src/native_libs.rs index 250657bc6806b..e08460c3d4c9a 100644 --- a/compiler/rustc_metadata/src/native_libs.rs +++ b/compiler/rustc_metadata/src/native_libs.rs @@ -2,8 +2,7 @@ use std::ops::ControlFlow; use std::path::{Path, PathBuf}; use rustc_abi::ExternAbi; -use rustc_ast::CRATE_NODE_ID; -use rustc_attr_parsing::{ShouldEmit, eval_config_entry}; +use rustc_attr_parsing::eval_config_entry; use rustc_data_structures::fx::FxHashSet; use rustc_hir::attrs::{AttributeKind, NativeLibKind, PeImportNameType}; use rustc_hir::find_attr; @@ -188,9 +187,7 @@ pub(crate) fn collect(tcx: TyCtxt<'_>, LocalCrate: LocalCrate) -> Vec pub(crate) fn relevant_lib(sess: &Session, lib: &NativeLib) -> bool { match lib.cfg { - Some(ref cfg) => { - eval_config_entry(sess, cfg, CRATE_NODE_ID, ShouldEmit::ErrorsAndLints).as_bool() - } + Some(ref cfg) => eval_config_entry(sess, cfg).as_bool(), None => true, } } diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index 33c111708e366..a675c62ddf705 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -3070,7 +3070,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { continue; } - let item_was = if let CfgEntry::NameValue { value: Some((feature, _)), .. } = cfg.0 { + let item_was = if let CfgEntry::NameValue { value: Some(feature), .. } = cfg.0 { errors::ItemWas::BehindFeature { feature, span: cfg.1 } } else { errors::ItemWas::CfgOut { span: cfg.1 } From e7df90412c208f3e18af8ed60781a343dd87645a Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Fri, 5 Dec 2025 15:08:04 +0100 Subject: [PATCH 02/24] Changes to uitests --- .../ui/feature-gates/feature-gate-link_cfg.rs | 2 +- .../feature-gate-link_cfg.stderr | 4 ++-- tests/ui/link-native-libs/link-cfg-works.rs | 2 +- tests/ui/macros/cfg.rs | 3 ++- tests/ui/macros/cfg.stderr | 17 ++++++++++--- tests/ui/macros/cfg_select.rs | 2 ++ tests/ui/macros/cfg_select.stderr | 24 +++++++++++++++++-- 7 files changed, 44 insertions(+), 10 deletions(-) diff --git a/tests/ui/feature-gates/feature-gate-link_cfg.rs b/tests/ui/feature-gates/feature-gate-link_cfg.rs index d30ee3bcfdb14..286c3d0a4977d 100644 --- a/tests/ui/feature-gates/feature-gate-link_cfg.rs +++ b/tests/ui/feature-gates/feature-gate-link_cfg.rs @@ -1,4 +1,4 @@ -#[link(name = "foo", cfg(foo))] +#[link(name = "foo", cfg(false))] //~^ ERROR: is unstable extern "C" {} diff --git a/tests/ui/feature-gates/feature-gate-link_cfg.stderr b/tests/ui/feature-gates/feature-gate-link_cfg.stderr index bfe7f74a92137..0e9707c6c9620 100644 --- a/tests/ui/feature-gates/feature-gate-link_cfg.stderr +++ b/tests/ui/feature-gates/feature-gate-link_cfg.stderr @@ -1,8 +1,8 @@ error[E0658]: link cfg is unstable --> $DIR/feature-gate-link_cfg.rs:1:22 | -LL | #[link(name = "foo", cfg(foo))] - | ^^^^^^^^ +LL | #[link(name = "foo", cfg(false))] + | ^^^^^^^^^^ | = help: add `#![feature(link_cfg)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date diff --git a/tests/ui/link-native-libs/link-cfg-works.rs b/tests/ui/link-native-libs/link-cfg-works.rs index 7b936bc43b1a0..0e02933ce01c2 100644 --- a/tests/ui/link-native-libs/link-cfg-works.rs +++ b/tests/ui/link-native-libs/link-cfg-works.rs @@ -7,7 +7,7 @@ extern crate link_cfg_works_transitive_dylib; extern crate link_cfg_works_transitive_rlib; -#[link(name = "foo", cfg(foo))] +#[link(name = "foo", cfg(false))] extern "C" {} fn main() {} diff --git a/tests/ui/macros/cfg.rs b/tests/ui/macros/cfg.rs index d992ec82e2fd2..f0c51b2942f16 100644 --- a/tests/ui/macros/cfg.rs +++ b/tests/ui/macros/cfg.rs @@ -2,5 +2,6 @@ fn main() { cfg!(); //~ ERROR macro requires a cfg-pattern cfg!(123); //~ ERROR malformed `cfg` macro input cfg!(foo = 123); //~ ERROR malformed `cfg` macro input - cfg!(foo, bar); //~ ERROR expected 1 cfg-pattern + cfg!(false, false); //~ ERROR expected 1 cfg-pattern + cfg!(foo); //~ WARN unexpected `cfg` condition name: `foo` } diff --git a/tests/ui/macros/cfg.stderr b/tests/ui/macros/cfg.stderr index 9a4c187f37b27..06529a5b7a61f 100644 --- a/tests/ui/macros/cfg.stderr +++ b/tests/ui/macros/cfg.stderr @@ -29,9 +29,20 @@ LL | cfg!(foo = 123); error: expected 1 cfg-pattern --> $DIR/cfg.rs:5:5 | -LL | cfg!(foo, bar); - | ^^^^^^^^^^^^^^ +LL | cfg!(false, false); + | ^^^^^^^^^^^^^^^^^^ -error: aborting due to 4 previous errors +warning: unexpected `cfg` condition name: `foo` + --> $DIR/cfg.rs:6:10 + | +LL | cfg!(foo); + | ^^^ + | + = help: expected names are: `FALSE` and `test` and 31 more + = help: to expect this configuration use `--check-cfg=cfg(foo)` + = note: see for more information about checking conditional configuration + = note: `#[warn(unexpected_cfgs)]` on by default + +error: aborting due to 4 previous errors; 1 warning emitted For more information about this error, try `rustc --explain E0539`. diff --git a/tests/ui/macros/cfg_select.rs b/tests/ui/macros/cfg_select.rs index 2a627cc05b93b..a54c385747fa6 100644 --- a/tests/ui/macros/cfg_select.rs +++ b/tests/ui/macros/cfg_select.rs @@ -89,9 +89,11 @@ cfg_select! { cfg_select! { a + 1 => {} //~^ ERROR expected one of `(`, `::`, `=>`, or `=`, found `+` + //~| WARN unexpected `cfg` condition name } cfg_select! { cfg!() => {} //~^ ERROR expected one of `(`, `::`, `=>`, or `=`, found `!` + //~| WARN unexpected `cfg` condition name } diff --git a/tests/ui/macros/cfg_select.stderr b/tests/ui/macros/cfg_select.stderr index 3a5d2b0a1e1ee..d79e1b9b5c10a 100644 --- a/tests/ui/macros/cfg_select.stderr +++ b/tests/ui/macros/cfg_select.stderr @@ -60,12 +60,32 @@ LL | a + 1 => {} | ^ expected one of `(`, `::`, `=>`, or `=` error: expected one of `(`, `::`, `=>`, or `=`, found `!` - --> $DIR/cfg_select.rs:95:8 + --> $DIR/cfg_select.rs:96:8 | LL | cfg!() => {} | ^ expected one of `(`, `::`, `=>`, or `=` -error: aborting due to 9 previous errors; 1 warning emitted +warning: unexpected `cfg` condition name: `a` + --> $DIR/cfg_select.rs:90:5 + | +LL | a + 1 => {} + | ^ help: found config with similar value: `target_feature = "a"` + | + = help: expected names are: `FALSE` and `test` and 31 more + = help: to expect this configuration use `--check-cfg=cfg(a)` + = note: see for more information about checking conditional configuration + = note: `#[warn(unexpected_cfgs)]` on by default + +warning: unexpected `cfg` condition name: `cfg` + --> $DIR/cfg_select.rs:96:5 + | +LL | cfg!() => {} + | ^^^ + | + = help: to expect this configuration use `--check-cfg=cfg(cfg)` + = note: see for more information about checking conditional configuration + +error: aborting due to 9 previous errors; 3 warnings emitted Some errors have detailed explanations: E0537, E0539. For more information about an error, try `rustc --explain E0537`. From 01576a6de101ca85a8b43f2c3db2028c876a355c Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sat, 6 Dec 2025 10:38:15 +0100 Subject: [PATCH 03/24] Add regression test for nested cfgs --- tests/ui/check-cfg/nested-cfg.rs | 8 ++++++++ tests/ui/check-cfg/nested-cfg.stderr | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 tests/ui/check-cfg/nested-cfg.rs create mode 100644 tests/ui/check-cfg/nested-cfg.stderr diff --git a/tests/ui/check-cfg/nested-cfg.rs b/tests/ui/check-cfg/nested-cfg.rs new file mode 100644 index 0000000000000..2b3a21f88dc59 --- /dev/null +++ b/tests/ui/check-cfg/nested-cfg.rs @@ -0,0 +1,8 @@ +//@ check-pass + +#[cfg(unknown)] //~ WARN unexpected `cfg` condition name +#[cfg(false)] +#[cfg(unknown)] // Should not warn +fn foo() {} + +fn main() {} diff --git a/tests/ui/check-cfg/nested-cfg.stderr b/tests/ui/check-cfg/nested-cfg.stderr new file mode 100644 index 0000000000000..6fdae732bbe58 --- /dev/null +++ b/tests/ui/check-cfg/nested-cfg.stderr @@ -0,0 +1,23 @@ +warning: unexpected `cfg` condition name: `unknown` + --> $DIR/nested-cfg.rs:3:7 + | +LL | #[cfg(unknown)] + | ^^^^^^^ + | + = help: expected names are: `FALSE` and `test` and 31 more + = help: to expect this configuration use `--check-cfg=cfg(unknown)` + = note: see for more information about checking conditional configuration + = note: `#[warn(unexpected_cfgs)]` on by default +help: found config with similar value + | +LL - #[cfg(unknown)] +LL + #[cfg(target_os = "unknown")] + | +help: found config with similar value + | +LL - #[cfg(unknown)] +LL + #[cfg(target_vendor = "unknown")] + | + +warning: 1 warning emitted + From a3a041da11202dd4c5fbbac4dbc1461e4c783f90 Mon Sep 17 00:00:00 2001 From: Justin Tracey Date: Sat, 6 Dec 2025 11:16:14 -0500 Subject: [PATCH 04/24] rustdoc book: mention inner doc attribute --- src/doc/rustdoc/src/write-documentation/the-doc-attribute.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md b/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md index 4d7f1a4aafc93..3af53fa58e18c 100644 --- a/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md +++ b/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md @@ -4,8 +4,8 @@ The `#[doc]` attribute lets you control various aspects of how `rustdoc` does its job. The most basic function of `#[doc]` is to handle the actual documentation -text. That is, `///` is syntax sugar for `#[doc]`. This means that these two -are the same: +text. That is, `///` is syntax sugar for `#[doc]` (as is `//!` for `#![doc]`). +This means that these two are the same: ```rust,no_run /// This is a doc comment. From 260b1ffc2cc6e2b83c68e6357417022db262f511 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Mon, 8 Dec 2025 09:37:11 +0800 Subject: [PATCH 05/24] compiletest: require `host`/`target` flags specified Instead of allowing them to be missing and using some placeholder "(none)" value instead. --- src/tools/compiletest/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs index f3bd467db3e41..625d839dece1a 100644 --- a/src/tools/compiletest/src/lib.rs +++ b/src/tools/compiletest/src/lib.rs @@ -255,7 +255,9 @@ fn parse_config(args: Vec) -> Config { } } - let target = opt_str2(matches.opt_str("target")); + let host = matches.opt_str("host").expect("`--host` must be unconditionally specified"); + let target = matches.opt_str("target").expect("`--target` must be unconditionally specified"); + let android_cross_path = matches.opt_str("android-cross-path").map(Utf8PathBuf::from); // FIXME: `cdb_version` is *derived* from cdb, but it's *not* technically a config! let cdb = debuggers::discover_cdb(matches.opt_str("cdb"), &target); @@ -433,7 +435,7 @@ fn parse_config(args: Vec) -> Config { optimize_tests: matches.opt_present("optimize-tests"), rust_randomized_layout: matches.opt_present("rust-randomized-layout"), target, - host: opt_str2(matches.opt_str("host")), + host, cdb, cdb_version, gdb, From a7ad2142e35a1aa7e51b052b9ffad83e653e6340 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Mon, 8 Dec 2025 09:38:31 +0800 Subject: [PATCH 06/24] compiletest: make presence/absence of adb-related options clear Instead of possibly falling back to "(none)" when they are not specified. --- src/tools/compiletest/src/lib.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs index 625d839dece1a..cd875d78f2313 100644 --- a/src/tools/compiletest/src/lib.rs +++ b/src/tools/compiletest/src/lib.rs @@ -259,6 +259,13 @@ fn parse_config(args: Vec) -> Config { let target = matches.opt_str("target").expect("`--target` must be unconditionally specified"); let android_cross_path = matches.opt_str("android-cross-path").map(Utf8PathBuf::from); + + // FIXME: `adb_path` should be an `Option`... + let adb_path = matches.opt_str("adb-path").map(Utf8PathBuf::from).unwrap_or_default(); + // FIXME: `adb_test_dir` should be an `Option`... + let adb_test_dir = matches.opt_str("adb-test-dir").map(Utf8PathBuf::from).unwrap_or_default(); + let adb_device_status = target.contains("android") && !adb_test_dir.as_str().is_empty(); + // FIXME: `cdb_version` is *derived* from cdb, but it's *not* technically a config! let cdb = debuggers::discover_cdb(matches.opt_str("cdb"), &target); let cdb_version = cdb.as_deref().and_then(debuggers::query_cdb_version); @@ -445,11 +452,9 @@ fn parse_config(args: Vec) -> Config { llvm_version, system_llvm: matches.opt_present("system-llvm"), android_cross_path, - adb_path: Utf8PathBuf::from(opt_str2(matches.opt_str("adb-path"))), - adb_test_dir: Utf8PathBuf::from(opt_str2(matches.opt_str("adb-test-dir"))), - adb_device_status: opt_str2(matches.opt_str("target")).contains("android") - && "(none)" != opt_str2(matches.opt_str("adb-test-dir")) - && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(), + adb_path, + adb_test_dir, + adb_device_status, verbose: matches.opt_present("verbose"), only_modified: matches.opt_present("only-modified"), color, From 72541e9a511f4844000ca42a60516eddb2bfa892 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Mon, 8 Dec 2025 09:41:46 +0800 Subject: [PATCH 07/24] compiletest: retire `opt_str2` We either have the value of a flag specified, or we don't. Use `Option<...>` to represent that -- don't invent a new "(none)" sentinel value... --- src/tools/compiletest/src/lib.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs index cd875d78f2313..ff4cd81d33ff6 100644 --- a/src/tools/compiletest/src/lib.rs +++ b/src/tools/compiletest/src/lib.rs @@ -500,13 +500,6 @@ fn parse_config(args: Vec) -> Config { } } -fn opt_str2(maybestr: Option) -> String { - match maybestr { - None => "(none)".to_owned(), - Some(s) => s, - } -} - /// Called by `main` after the config has been parsed. fn run_tests(config: Arc) { debug!(?config, "run_tests"); From 8c582e1f530b967dcbf2a549f70fce08819da3a1 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sun, 7 Dec 2025 15:27:50 +1100 Subject: [PATCH 08/24] Don't require `test::Coverage` to implement Ord This derive was an artifact of test-only method `Cache::all` wanting to automatically sort its output to hide HashMap iteration order. We can achieve an equivalent result by requiring the caller to provide a projection function that returns results that _are_ sortable. --- src/bootstrap/src/core/build_steps/test.rs | 2 +- src/bootstrap/src/core/builder/tests.rs | 3 +-- src/bootstrap/src/utils/cache.rs | 20 +++++++++++--------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 09c98bd7c88b4..67cd5b8d67c3a 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -1582,7 +1582,7 @@ test!(AssemblyLlvm { /// Runs the coverage test suite at `tests/coverage` in some or all of the /// coverage test modes. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Coverage { pub compiler: Compiler, pub target: TargetSelection, diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index b8ba1b4c2c340..965b75f24c203 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -345,8 +345,7 @@ fn test_test_coverage() { let config = configure_with_args(cmd, &[], &[TEST_TRIPLE_1]); let mut cache = run_build(&config.paths.clone(), config); - let modes = - cache.all::().iter().map(|(step, ())| step.mode).collect::>(); + let modes = cache.inspect_all_steps_of_type::(|step, ()| step.mode); assert_eq!(modes, expected); } } diff --git a/src/bootstrap/src/utils/cache.rs b/src/bootstrap/src/utils/cache.rs index 5098e2f03c439..8c5b3529979d7 100644 --- a/src/bootstrap/src/utils/cache.rs +++ b/src/bootstrap/src/utils/cache.rs @@ -270,16 +270,18 @@ impl Cache { #[cfg(test)] impl Cache { - pub fn all(&mut self) -> Vec<(S, S::Output)> { - let cache = self.cache.get_mut(); - let type_id = TypeId::of::(); - let mut v = cache - .remove(&type_id) - .map(|b| b.downcast::>().expect("correct type")) - .map(|m| m.into_iter().collect::>()) + pub(crate) fn inspect_all_steps_of_type( + &self, + map_fn: impl Fn(&S, &S::Output) -> T, + ) -> Vec { + let cache = self.cache.borrow(); + let mut values = cache + .get(&TypeId::of::()) + .map(|any| any.downcast_ref::>().expect("correct type")) + .map(|m| m.iter().map(|(step, output)| map_fn(step, output)).collect::>()) .unwrap_or_default(); - v.sort_by_key(|(s, _)| s.clone()); - v + values.sort(); + values } pub fn contains(&self) -> bool { From dc8cdb1c00da2649fb7ff56b56af11b4e9592802 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sun, 7 Dec 2025 15:14:26 +1100 Subject: [PATCH 09/24] Use a `CompiletestMode` enum in bootstrap --- src/bootstrap/src/core/build_steps/test.rs | 116 +++++++++++------- .../src/core/build_steps/test/compiletest.rs | 71 +++++++++++ src/bootstrap/src/core/builder/tests.rs | 3 +- 3 files changed, 142 insertions(+), 48 deletions(-) create mode 100644 src/bootstrap/src/core/build_steps/test/compiletest.rs diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 67cd5b8d67c3a..a699cd23fb607 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -3,6 +3,9 @@ //! `./x.py test` (aka [`Kind::Test`]) is currently allowed to reach build steps in other modules. //! However, this contains ~all test parts we expect people to be able to build and run locally. +// (This file should be split up, but having tidy block all changes is not helpful.) +// ignore-tidy-filelength + use std::collections::HashSet; use std::env::split_paths; use std::ffi::{OsStr, OsString}; @@ -17,6 +20,7 @@ use crate::core::build_steps::gcc::{Gcc, add_cg_gcc_cargo_flags}; use crate::core::build_steps::llvm::get_llvm_version; use crate::core::build_steps::run::{get_completion_paths, get_help_path}; use crate::core::build_steps::synthetic_targets::MirOptPanicAbortSyntheticTarget; +use crate::core::build_steps::test::compiletest::CompiletestMode; use crate::core::build_steps::tool::{ self, RustcPrivateCompilers, SourceType, TEST_FLOAT_PARSE_ALLOW_FEATURES, Tool, ToolTargetBuildMode, get_tool_target_compiler, @@ -39,6 +43,8 @@ use crate::utils::helpers::{ use crate::utils::render_tests::{add_flags_and_try_run_tests, try_run_tests}; use crate::{CLang, CodegenBackendKind, DocTests, GitRepo, Mode, PathSet, envify}; +mod compiletest; + /// Runs `cargo test` on various internal tools used by bootstrap. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct CrateBootstrap { @@ -1085,7 +1091,7 @@ impl Step for RustdocJSNotStd { builder.ensure(Compiletest { test_compiler: self.compiler, target: self.target, - mode: "rustdoc-js", + mode: CompiletestMode::RustdocJs, suite: "rustdoc-js", path: "tests/rustdoc-js", compare_mode: None, @@ -1478,7 +1484,7 @@ macro_rules! test { builder.ensure(Compiletest { test_compiler: self.test_compiler, target: self.target, - mode: $mode, + mode: const { $mode }, suite: $suite, path: $path, compare_mode: (const { @@ -1493,34 +1499,39 @@ macro_rules! test { }; } -test!(Ui { path: "tests/ui", mode: "ui", suite: "ui", default: true }); +test!(Ui { path: "tests/ui", mode: CompiletestMode::Ui, suite: "ui", default: true }); -test!(Crashes { path: "tests/crashes", mode: "crashes", suite: "crashes", default: true }); +test!(Crashes { + path: "tests/crashes", + mode: CompiletestMode::Crashes, + suite: "crashes", + default: true, +}); test!(CodegenLlvm { path: "tests/codegen-llvm", - mode: "codegen", + mode: CompiletestMode::Codegen, suite: "codegen-llvm", default: true }); test!(CodegenUnits { path: "tests/codegen-units", - mode: "codegen-units", + mode: CompiletestMode::CodegenUnits, suite: "codegen-units", default: true, }); test!(Incremental { path: "tests/incremental", - mode: "incremental", + mode: CompiletestMode::Incremental, suite: "incremental", default: true, }); test!(Debuginfo { path: "tests/debuginfo", - mode: "debuginfo", + mode: CompiletestMode::Debuginfo, suite: "debuginfo", default: true, compare_mode: Some("split-dwarf"), @@ -1528,7 +1539,7 @@ test!(Debuginfo { test!(UiFullDeps { path: "tests/ui-fulldeps", - mode: "ui", + mode: CompiletestMode::Ui, suite: "ui-fulldeps", default: true, IS_HOST: true, @@ -1536,14 +1547,14 @@ test!(UiFullDeps { test!(Rustdoc { path: "tests/rustdoc", - mode: "rustdoc", + mode: CompiletestMode::Rustdoc, suite: "rustdoc", default: true, IS_HOST: true, }); test!(RustdocUi { path: "tests/rustdoc-ui", - mode: "ui", + mode: CompiletestMode::Ui, suite: "rustdoc-ui", default: true, IS_HOST: true, @@ -1551,7 +1562,7 @@ test!(RustdocUi { test!(RustdocJson { path: "tests/rustdoc-json", - mode: "rustdoc-json", + mode: CompiletestMode::RustdocJson, suite: "rustdoc-json", default: true, IS_HOST: true, @@ -1559,23 +1570,28 @@ test!(RustdocJson { test!(Pretty { path: "tests/pretty", - mode: "pretty", + mode: CompiletestMode::Pretty, suite: "pretty", default: true, IS_HOST: true, }); -test!(RunMake { path: "tests/run-make", mode: "run-make", suite: "run-make", default: true }); +test!(RunMake { + path: "tests/run-make", + mode: CompiletestMode::RunMake, + suite: "run-make", + default: true, +}); test!(RunMakeCargo { path: "tests/run-make-cargo", - mode: "run-make", + mode: CompiletestMode::RunMake, suite: "run-make-cargo", default: true }); test!(AssemblyLlvm { path: "tests/assembly-llvm", - mode: "assembly", + mode: CompiletestMode::Assembly, suite: "assembly-llvm", default: true }); @@ -1586,13 +1602,14 @@ test!(AssemblyLlvm { pub struct Coverage { pub compiler: Compiler, pub target: TargetSelection, - pub mode: &'static str, + pub(crate) mode: CompiletestMode, } impl Coverage { const PATH: &'static str = "tests/coverage"; const SUITE: &'static str = "coverage"; - const ALL_MODES: &[&str] = &["coverage-map", "coverage-run"]; + const ALL_MODES: &[CompiletestMode] = + &[CompiletestMode::CoverageMap, CompiletestMode::CoverageRun]; } impl Step for Coverage { @@ -1608,7 +1625,7 @@ impl Step for Coverage { // - `./x test coverage-run -- tests/coverage/trivial.rs` run = run.suite_path(Self::PATH); for mode in Self::ALL_MODES { - run = run.alias(mode); + run = run.alias(mode.as_str()); } run } @@ -1631,15 +1648,15 @@ impl Step for Coverage { for path in &run.paths { match path { PathSet::Set(_) => { - for mode in Self::ALL_MODES { - if path.assert_single_path().path == Path::new(mode) { + for &mode in Self::ALL_MODES { + if path.assert_single_path().path == Path::new(mode.as_str()) { modes.push(mode); break; } } } PathSet::Suite(_) => { - modes.extend(Self::ALL_MODES); + modes.extend_from_slice(Self::ALL_MODES); break; } } @@ -1647,7 +1664,9 @@ impl Step for Coverage { // Skip any modes that were explicitly skipped/excluded on the command-line. // FIXME(Zalathar): Integrate this into central skip handling somehow? - modes.retain(|mode| !run.builder.config.skip.iter().any(|skip| skip == Path::new(mode))); + modes.retain(|mode| { + !run.builder.config.skip.iter().any(|skip| skip == Path::new(mode.as_str())) + }); // FIXME(Zalathar): Make these commands skip all coverage tests, as expected: // - `./x test --skip=tests` @@ -1678,7 +1697,7 @@ impl Step for Coverage { test!(CoverageRunRustdoc { path: "tests/coverage-run-rustdoc", - mode: "coverage-run", + mode: CompiletestMode::CoverageRun, suite: "coverage-run-rustdoc", default: true, IS_HOST: true, @@ -1712,7 +1731,7 @@ impl Step for MirOpt { builder.ensure(Compiletest { test_compiler: self.compiler, target, - mode: "mir-opt", + mode: CompiletestMode::MirOpt, suite: "mir-opt", path: "tests/mir-opt", compare_mode: None, @@ -1755,7 +1774,7 @@ struct Compiletest { /// The compiler that we're testing. test_compiler: Compiler, target: TargetSelection, - mode: &'static str, + mode: CompiletestMode, suite: &'static str, path: &'static str, compare_mode: Option<&'static str>, @@ -1791,7 +1810,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the let suite_path = self.path; // Skip codegen tests if they aren't enabled in configuration. - if !builder.config.codegen_tests && mode == "codegen" { + if !builder.config.codegen_tests && mode == CompiletestMode::Codegen { return; } @@ -1829,7 +1848,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the target, }); } - if mode == "run-make" { + if mode == CompiletestMode::RunMake { builder.tool_exe(Tool::RunMakeSupport); } @@ -1886,7 +1905,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the // suites, `run-make` and `run-make-cargo`. That way, contributors who do not need to run // the `run-make` tests that need in-tree cargo do not need to spend time building in-tree // cargo. - if mode == "run-make" { + if mode == CompiletestMode::RunMake { // We need to pass the compiler that was used to compile run-make-support, // because we have to use the same compiler to compile rmake.rs recipes. let stage0_rustc_path = builder.compiler(0, test_compiler.host); @@ -1910,17 +1929,18 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the } // Avoid depending on rustdoc when we don't need it. - if mode == "rustdoc" - || mode == "run-make" - || (mode == "ui" && is_rustdoc) - || mode == "rustdoc-js" - || mode == "rustdoc-json" - || suite == "coverage-run-rustdoc" + if matches!( + mode, + CompiletestMode::RunMake + | CompiletestMode::Rustdoc + | CompiletestMode::RustdocJs + | CompiletestMode::RustdocJson + ) || matches!(suite, "rustdoc-ui" | "coverage-run-rustdoc") { cmd.arg("--rustdoc-path").arg(builder.rustdoc_for_compiler(test_compiler)); } - if mode == "rustdoc-json" { + if mode == CompiletestMode::RustdocJson { // Use the stage0 compiler for jsondocck let json_compiler = builder.compiler(0, builder.host_target); cmd.arg("--jsondocck-path") @@ -1930,7 +1950,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the ); } - if matches!(mode, "coverage-map" | "coverage-run") { + if matches!(mode, CompiletestMode::CoverageMap | CompiletestMode::CoverageRun) { let coverage_dump = builder.tool_exe(Tool::CoverageDump); cmd.arg("--coverage-dump-path").arg(coverage_dump); } @@ -1957,7 +1977,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the cmd.arg("--sysroot-base").arg(sysroot); cmd.arg("--suite").arg(suite); - cmd.arg("--mode").arg(mode); + cmd.arg("--mode").arg(mode.as_str()); cmd.arg("--target").arg(target.rustc_target_arg()); cmd.arg("--host").arg(&*test_compiler.host.triple); cmd.arg("--llvm-filecheck").arg(builder.llvm_filecheck(builder.config.host_target)); @@ -2036,7 +2056,7 @@ Please disable assertions with `rust.debug-assertions = false`. if let Some(ref nodejs) = builder.config.nodejs { cmd.arg("--nodejs").arg(nodejs); - } else if mode == "rustdoc-js" { + } else if mode == CompiletestMode::RustdocJs { panic!("need nodejs to run rustdoc-js suite"); } if builder.config.rust_optimize_tests { @@ -2055,7 +2075,7 @@ Please disable assertions with `rust.debug-assertions = false`. let mut flags = if is_rustdoc { Vec::new() } else { vec!["-Crpath".to_string()] }; flags.push(format!( "-Cdebuginfo={}", - if mode == "codegen" { + if mode == CompiletestMode::Codegen { // codegen tests typically check LLVM IR and are sensitive to additional debuginfo. // So do not apply `rust.debuginfo-level-tests` for codegen tests. if builder.config.rust_debuginfo_level_tests @@ -2122,7 +2142,7 @@ Please disable assertions with `rust.debug-assertions = false`. cmd.arg("--android-cross-path").arg(android_cross_path); } - if mode == "debuginfo" { + if mode == CompiletestMode::Debuginfo { if let Some(debuggers::Gdb { gdb }) = debuggers::discover_gdb(builder, android.as_ref()) { cmd.arg("--gdb").arg(gdb.as_ref()); @@ -2155,7 +2175,7 @@ Please disable assertions with `rust.debug-assertions = false`. // in rustdoc-js mode, allow filters to be rs files or js files. // use a late-initialized Vec to avoid cloning for other modes. let mut paths_v; - if mode == "rustdoc-js" { + if mode == CompiletestMode::RustdocJs { paths_v = paths.to_vec(); for p in &mut paths_v { if let Some(ext) = p.extension() @@ -2237,7 +2257,9 @@ Please disable assertions with `rust.debug-assertions = false`. cmd.arg("--host-rustcflags").arg(link_llvm); } - if !builder.config.dry_run() && matches!(mode, "run-make" | "coverage-run") { + if !builder.config.dry_run() + && matches!(mode, CompiletestMode::RunMake | CompiletestMode::CoverageRun) + { // The llvm/bin directory contains many useful cross-platform // tools. Pass the path to run-make tests so they can use them. // (The coverage-run tests also need these tools to process @@ -2249,7 +2271,7 @@ Please disable assertions with `rust.debug-assertions = false`. cmd.arg("--llvm-bin-dir").arg(llvm_bin_path); } - if !builder.config.dry_run() && mode == "run-make" { + if !builder.config.dry_run() && mode == CompiletestMode::RunMake { // If LLD is available, add it to the PATH if builder.config.lld_enabled { let lld_install_root = @@ -2269,7 +2291,7 @@ Please disable assertions with `rust.debug-assertions = false`. // Only pass correct values for these flags for the `run-make` suite as it // requires that a C++ compiler was configured which isn't always the case. - if !builder.config.dry_run() && mode == "run-make" { + if !builder.config.dry_run() && mode == CompiletestMode::RunMake { let mut cflags = builder.cc_handled_clags(target, CLang::C); cflags.extend(builder.cc_unhandled_cflags(target, GitRepo::Rustc, CLang::C)); let mut cxxflags = builder.cc_handled_clags(target, CLang::Cxx); @@ -2385,7 +2407,7 @@ Please disable assertions with `rust.debug-assertions = false`. builder.metrics.begin_test_suite( build_helper::metrics::TestSuiteMetadata::Compiletest { suite: suite.into(), - mode: mode.into(), + mode: mode.to_string(), compare_mode: None, target: self.target.triple.to_string(), host: self.test_compiler.host.triple.to_string(), @@ -2408,7 +2430,7 @@ Please disable assertions with `rust.debug-assertions = false`. builder.metrics.begin_test_suite( build_helper::metrics::TestSuiteMetadata::Compiletest { suite: suite.into(), - mode: mode.into(), + mode: mode.to_string(), compare_mode: Some(compare_mode.into()), target: self.target.triple.to_string(), host: self.test_compiler.host.triple.to_string(), diff --git a/src/bootstrap/src/core/build_steps/test/compiletest.rs b/src/bootstrap/src/core/build_steps/test/compiletest.rs new file mode 100644 index 0000000000000..359f6bb1a6efb --- /dev/null +++ b/src/bootstrap/src/core/build_steps/test/compiletest.rs @@ -0,0 +1,71 @@ +use std::fmt; + +/// Enum of all the "test modes" understood by compiletest. +/// +/// Some of these mode names happen to overlap with the names of test suite +/// directories, but the relationship between modes and suites is not 1:1. +/// For example: +/// - Mode `ui` is used by suites `tests/ui` and `tests/rustdoc-ui` +/// - Suite `tests/coverage` uses modes `coverage-map` and `coverage-run` +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) enum CompiletestMode { + // tidy-alphabetical-start + Assembly, + Codegen, + CodegenUnits, + CoverageMap, + CoverageRun, + Crashes, + Debuginfo, + Incremental, + MirOpt, + Pretty, + RunMake, + Rustdoc, + RustdocJs, + RustdocJson, + Ui, + // tidy-alphabetical-end +} + +impl CompiletestMode { + /// Returns a string representing this mode, which can be passed to + /// compiletest via a command-line argument. + /// + /// These mode names must be kept in sync with the ones understood by + /// compiletest's `TestMode`, but they change so rarely that doing so + /// manually should not be burdensome. + pub(crate) const fn as_str(self) -> &'static str { + match self { + // tidy-alphabetical-start + Self::Assembly => "assembly", + Self::Codegen => "codegen", + Self::CodegenUnits => "codegen-units", + Self::CoverageMap => "coverage-map", + Self::CoverageRun => "coverage-run", + Self::Crashes => "crashes", + Self::Debuginfo => "debuginfo", + Self::Incremental => "incremental", + Self::MirOpt => "mir-opt", + Self::Pretty => "pretty", + Self::RunMake => "run-make", + Self::Rustdoc => "rustdoc", + Self::RustdocJs => "rustdoc-js", + Self::RustdocJson => "rustdoc-json", + Self::Ui => "ui", + // tidy-alphabetical-end + } + } +} + +impl fmt::Display for CompiletestMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl fmt::Debug for CompiletestMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + ::fmt(self, f) + } +} diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index 965b75f24c203..709d646bb359a 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -345,7 +345,8 @@ fn test_test_coverage() { let config = configure_with_args(cmd, &[], &[TEST_TRIPLE_1]); let mut cache = run_build(&config.paths.clone(), config); - let modes = cache.inspect_all_steps_of_type::(|step, ()| step.mode); + let modes = + cache.inspect_all_steps_of_type::(|step, ()| step.mode.as_str()); assert_eq!(modes, expected); } } From 0f4ec281558a40645b125fda43f36d7006180e54 Mon Sep 17 00:00:00 2001 From: SATVIKsynopsis Date: Sun, 7 Dec 2025 16:58:31 +0530 Subject: [PATCH 10/24] lint: treat unsafe binders in improper_ctypes instead of ICE Replaced _binder with _ --- compiler/rustc_lint/messages.ftl | 2 ++ compiler/rustc_lint/src/types/improper_ctypes.rs | 4 +++- .../lint/improper-ctypes/unsafe-binder-basic.rs | 10 ++++++++++ .../improper-ctypes/unsafe-binder-basic.stderr | 15 +++++++++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/ui/lint/improper-ctypes/unsafe-binder-basic.rs create mode 100644 tests/ui/lint/improper-ctypes/unsafe-binder-basic.stderr diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 1bcdda96e13a2..a90a9c91ef13c 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -416,6 +416,8 @@ lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` or `#[re lint_improper_ctypes_union_layout_reason = this union has unspecified layout lint_improper_ctypes_union_non_exhaustive = this union is non-exhaustive +lint_improper_ctypes_unsafe_binder = unsafe binders are incompatible with foreign function interfaces + lint_int_to_ptr_transmutes = transmuting an integer to a pointer creates a pointer without provenance .note = this is dangerous because dereferencing the resulting pointer is undefined behavior .note_exposed_provenance = exposed provenance semantics can be used to create a pointer based on some previously exposed provenance diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 9e38ea6b685bd..38094c67c34a0 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -669,7 +669,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { FfiSafe } - ty::UnsafeBinder(_) => todo!("FIXME(unsafe_binder)"), + ty::UnsafeBinder(_) => { + FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_unsafe_binder, help: None } + } ty::Param(..) | ty::Alias(ty::Projection | ty::Inherent | ty::Free, ..) diff --git a/tests/ui/lint/improper-ctypes/unsafe-binder-basic.rs b/tests/ui/lint/improper-ctypes/unsafe-binder-basic.rs new file mode 100644 index 0000000000000..5d4279fc834d1 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/unsafe-binder-basic.rs @@ -0,0 +1,10 @@ +#![feature(unsafe_binders)] +#![expect(incomplete_features)] +#![deny(improper_ctypes)] + +extern "C" { + fn exit_2(x: unsafe<'a> &'a ()); + //~^ ERROR `extern` block uses type `unsafe<'a> &'a ()`, which is not FFI-safe +} + +fn main() {} diff --git a/tests/ui/lint/improper-ctypes/unsafe-binder-basic.stderr b/tests/ui/lint/improper-ctypes/unsafe-binder-basic.stderr new file mode 100644 index 0000000000000..4b8d51690f1a0 --- /dev/null +++ b/tests/ui/lint/improper-ctypes/unsafe-binder-basic.stderr @@ -0,0 +1,15 @@ +error: `extern` block uses type `unsafe<'a> &'a ()`, which is not FFI-safe + --> $DIR/unsafe-binder-basic.rs:6:18 + | +LL | fn exit_2(x: unsafe<'a> &'a ()); + | ^^^^^^^^^^^^^^^^^ not FFI-safe + | + = note: unsafe binders are incompatible with foreign function interfaces +note: the lint level is defined here + --> $DIR/unsafe-binder-basic.rs:3:9 + | +LL | #![deny(improper_ctypes)] + | ^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + From 611c956c78fc9f95bd8af1f9b0567669959d1efe Mon Sep 17 00:00:00 2001 From: dianqk Date: Mon, 8 Dec 2025 21:57:05 +0800 Subject: [PATCH 11/24] test: Add test for 146133 Even if a crate is marked as #![no_builtins], we can still generate bitcode for rlib, but we cannot emit bitcode to the linker in rustc's LTO. --- .../no-builtins-linker-plugin-lto/main.rs | 3 ++ .../no_builtins.rs | 4 ++ .../no-builtins-linker-plugin-lto/rmake.rs | 53 +++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 tests/run-make/no-builtins-linker-plugin-lto/main.rs create mode 100644 tests/run-make/no-builtins-linker-plugin-lto/no_builtins.rs create mode 100644 tests/run-make/no-builtins-linker-plugin-lto/rmake.rs diff --git a/tests/run-make/no-builtins-linker-plugin-lto/main.rs b/tests/run-make/no-builtins-linker-plugin-lto/main.rs new file mode 100644 index 0000000000000..f96605be89499 --- /dev/null +++ b/tests/run-make/no-builtins-linker-plugin-lto/main.rs @@ -0,0 +1,3 @@ +fn main() { + no_builtins::foo(); +} diff --git a/tests/run-make/no-builtins-linker-plugin-lto/no_builtins.rs b/tests/run-make/no-builtins-linker-plugin-lto/no_builtins.rs new file mode 100644 index 0000000000000..a56c1d6b41334 --- /dev/null +++ b/tests/run-make/no-builtins-linker-plugin-lto/no_builtins.rs @@ -0,0 +1,4 @@ +#![no_builtins] + +#[inline(never)] +pub fn foo() {} diff --git a/tests/run-make/no-builtins-linker-plugin-lto/rmake.rs b/tests/run-make/no-builtins-linker-plugin-lto/rmake.rs new file mode 100644 index 0000000000000..7133085677377 --- /dev/null +++ b/tests/run-make/no-builtins-linker-plugin-lto/rmake.rs @@ -0,0 +1,53 @@ +//@ only-x86_64-unknown-linux-gnu + +use std::fs; +use std::path::Path; + +use run_make_support::{cwd, has_extension, llvm_ar, llvm_bcanalyzer, rust_lib_name, rustc}; + +// A regression test for #146133. + +fn main() { + // Compile a `#![no_builtins]` rlib crate with `-Clinker-plugin-lto`. + // It is acceptable to generate bitcode for rlib, so there is no need to check something. + rustc().input("no_builtins.rs").crate_type("rlib").linker_plugin_lto("on").run(); + + // Checks that rustc's LTO doesn't emit any bitcode to the linker. + let stdout = rustc() + .input("main.rs") + .extern_("no_builtins", rust_lib_name("no_builtins")) + .lto("thin") + .print("link-args") + .arg("-Csave-temps") + .arg("-Clinker-features=-lld") + .run() + .stdout_utf8(); + for object in stdout + .split_whitespace() + .map(|s| s.trim_matches('"')) + .filter(|path| has_extension(path, "rlib") || has_extension(path, "o")) + { + let object_path = if !fs::exists(object).unwrap() { + cwd().join(object) + } else { + Path::new(object).to_path_buf() + }; + if has_extension(object, "rlib") { + let ar_stdout = llvm_ar().arg("t").arg(&object_path).run().stdout_utf8(); + llvm_ar().extract().arg(&object_path).run(); + for object in ar_stdout.split_whitespace().filter(|o| has_extension(o, "o")) { + let object_path = cwd().join(object); + not_bitcode(&object_path); + } + } else { + not_bitcode(&object_path); + } + } +} + +fn not_bitcode(object: &Path) { + llvm_bcanalyzer() + .input(object) + .run_fail() + .assert_stderr_contains("llvm-bcanalyzer: Invalid record at top-level"); +} From ba462864f1175690d1925cb5b1c6bf0a0c3cb579 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 10 Oct 2025 12:59:41 -0700 Subject: [PATCH 12/24] std: Use more `unix.rs` code on WASI targets This commit is a large change to the implementation of filesystem and other system-related operations on WASI targets. Previously the standard library explicitly used the `wasi` crate at the 0.11.x version track which means that it used WASIp1 APIs directly. This meant that `std` was hard-coded to use WASIp1 syscalls and there was no separate implementation for the WASIp{2,3} targets, for example. The high-level goal of this commit is to decouple this interaction and avoid the use of the `wasi` crate on the WASIp2 target. Historically when WASIp1 was originally added to Rust the wasi-libc library was in a much different position than it is today. Nowadays Rust already depends on wasi-libc on WASI targets for things like memory allocation and environment variable management. As a libc library it also has all the functions necessary to implement all filesystem operations Rust wants. Recently wasi-libc additionally was updated to use WASIp2 APIs directly on the `wasm32-wasip2` target instead of using `wasm32-wasip1` APIs. This commit is leveraging this work by enabling Rust to completely sever the dependence on WASIp1 APIs when compiling for `wasm32-wasip2`. This is also intended to make it easier to migrate to `wasm32-wasip3` internally in the future where now only libc need be updated and Rust doesn't need to explicitly change as well. The overall premise of this commit is that there's no need for WASI-specific implementation modules throughout the standard library. Instead the libc-style bindings already implemented for Unix-like targets are sufficient. This means that Rust will now be using libc-style interfaces to interact with the filesystem, for example, and wasi-libc is the one responsible for translating these POSIX-ish functions into WASIp{1,2} calls. Concrete changes here are: * `std` for `wasm32-wasip2` no longer depends on `wasi 0.11.x` * The implementation of `std::os::wasi::fs`, which was previously unstable and still is, now has portions gated to only work on the WASIp1 target which use the `wasi` crate directly. Traits have been trimmed down in some cases, updated in others, or now present a different API on WASIp1 and WASIp2. It's expected this'll get further cleanup in the future. * The `std::sys::fd::wasi` module is deleted and `unix` is used instead. * The `std::sys::fs::wasi` module is deleted and `unix` is used instead. * The `std::sys::io::io_slice::wasi` module is deleted and `unix` is used instead. * The `std::sys::pal::{wasip1,wasip2}` modules are now merged together as their difference is much smaller than before. * The `std::sys::pal::wasi::time` is deleted and the `unix` variant is used directly instead. * The `std::sys::stdio::wasip{1,2}` modules are deleted and the `unix` variant is used instead. * The `std::sys::thread::wasip{1,2}` modules are deleted and the `unix` variant is used instead. Overall Rust's libstd is effectively more tightly bound to libc when compiled to WASI targets. This is intended to mirror how it's expected all other languages will also bind to WASI. This additionally has the nice goal of drastically reducing the WASI-specific maintenance burden in libstd (in theory) and the only real changes required here are extra definitions being added to `libc` (done in separate PRs). This might be required for more symbols in the future but for now everything should be mostly complete. --- library/Cargo.lock | 4 +- library/std/Cargo.toml | 4 +- library/std/src/os/wasi/fs.rs | 310 ++---- library/std/src/os/wasi/net/mod.rs | 5 +- library/std/src/sys/fd/mod.rs | 6 +- library/std/src/sys/fd/unix.rs | 10 +- library/std/src/sys/fd/wasi.rs | 331 ------- library/std/src/sys/fs/mod.rs | 12 +- library/std/src/sys/fs/unix.rs | 55 +- library/std/src/sys/fs/wasi.rs | 913 ------------------ library/std/src/sys/io/io_slice/iovec.rs | 2 +- library/std/src/sys/io/io_slice/wasi.rs | 76 -- library/std/src/sys/io/mod.rs | 6 +- .../std/src/sys/net/connection/socket/mod.rs | 6 +- .../std/src/sys/net/connection/socket/unix.rs | 6 +- .../src/sys/net/connection/socket/wasip2.rs | 408 -------- library/std/src/sys/net/connection/wasip1.rs | 20 +- library/std/src/sys/pal/mod.rs | 10 +- library/std/src/sys/pal/unix/time.rs | 7 + .../sys/pal/{wasip2 => wasi}/cabi_realloc.rs | 0 library/std/src/sys/pal/wasi/helpers.rs | 63 ++ library/std/src/sys/pal/wasi/mod.rs | 39 + .../std/src/sys/pal/{wasip1 => wasi}/os.rs | 30 +- .../std/src/sys/pal/wasi/stack_overflow.rs | 7 + library/std/src/sys/pal/wasip1/helpers.rs | 114 --- library/std/src/sys/pal/wasip1/mod.rs | 38 - library/std/src/sys/pal/wasip1/time.rs | 65 -- library/std/src/sys/pal/wasip2/mod.rs | 35 - library/std/src/sys/pal/wasip2/time.rs | 58 -- library/std/src/sys/stdio/mod.rs | 10 +- library/std/src/sys/stdio/unix.rs | 7 +- library/std/src/sys/stdio/wasip1.rs | 117 --- library/std/src/sys/stdio/wasip2.rs | 120 --- library/std/src/sys/thread/mod.rs | 28 +- library/std/src/sys/thread/unix.rs | 3 + library/std/src/sys/thread/wasip1.rs | 183 ---- library/std/src/sys/thread/wasip2.rs | 32 - tests/rustdoc-js-std/unbox-type-result.js | 1 - triagebot.toml | 3 +- 39 files changed, 342 insertions(+), 2802 deletions(-) delete mode 100644 library/std/src/sys/fd/wasi.rs delete mode 100644 library/std/src/sys/fs/wasi.rs delete mode 100644 library/std/src/sys/io/io_slice/wasi.rs delete mode 100644 library/std/src/sys/net/connection/socket/wasip2.rs rename library/std/src/sys/pal/{wasip2 => wasi}/cabi_realloc.rs (100%) create mode 100644 library/std/src/sys/pal/wasi/helpers.rs create mode 100644 library/std/src/sys/pal/wasi/mod.rs rename library/std/src/sys/pal/{wasip1 => wasi}/os.rs (88%) create mode 100644 library/std/src/sys/pal/wasi/stack_overflow.rs delete mode 100644 library/std/src/sys/pal/wasip1/helpers.rs delete mode 100644 library/std/src/sys/pal/wasip1/mod.rs delete mode 100644 library/std/src/sys/pal/wasip1/time.rs delete mode 100644 library/std/src/sys/pal/wasip2/mod.rs delete mode 100644 library/std/src/sys/pal/wasip2/time.rs delete mode 100644 library/std/src/sys/stdio/wasip1.rs delete mode 100644 library/std/src/sys/stdio/wasip2.rs delete mode 100644 library/std/src/sys/thread/wasip1.rs delete mode 100644 library/std/src/sys/thread/wasip2.rs diff --git a/library/Cargo.lock b/library/Cargo.lock index accbbe9d236ff..513d3f1366987 100644 --- a/library/Cargo.lock +++ b/library/Cargo.lock @@ -146,9 +146,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" dependencies = [ "rustc-std-workspace-core", ] diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 3a673cf23b835..83a0826ac425d 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -33,7 +33,7 @@ miniz_oxide = { version = "0.8.0", optional = true, default-features = false } addr2line = { version = "0.25.0", optional = true, default-features = false } [target.'cfg(not(all(windows, target_env = "msvc")))'.dependencies] -libc = { version = "0.2.177", default-features = false, features = [ +libc = { version = "0.2.178", default-features = false, features = [ 'rustc-dep-of-std', ], public = true } @@ -78,7 +78,7 @@ hermit-abi = { version = "0.5.0", features = [ 'rustc-dep-of-std', ], public = true } -[target.'cfg(target_os = "wasi")'.dependencies] +[target.'cfg(all(target_os = "wasi", target_env = "p1"))'.dependencies] wasi = { version = "0.11.0", features = [ 'rustc-dep-of-std', ], default-features = false } diff --git a/library/std/src/os/wasi/fs.rs b/library/std/src/os/wasi/fs.rs index 5ea91dd6521ad..9145283dae6e1 100644 --- a/library/std/src/os/wasi/fs.rs +++ b/library/std/src/os/wasi/fs.rs @@ -1,4 +1,4 @@ -//! WASI-specific extensions to primitives in the [`std::fs`] module. +//! WASIp1-specific extensions to primitives in the [`std::fs`] module. //! //! [`std::fs`]: crate::fs @@ -8,11 +8,16 @@ #[allow(unused_imports)] use io::{Read, Write}; +#[cfg(target_env = "p1")] use crate::ffi::OsStr; -use crate::fs::{self, File, Metadata, OpenOptions}; -use crate::io::{self, IoSlice, IoSliceMut}; -use crate::path::{Path, PathBuf}; -use crate::sys_common::{AsInner, AsInnerMut, FromInner}; +use crate::fs::{self, File, OpenOptions}; +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; +#[cfg(target_env = "p1")] +use crate::os::fd::AsRawFd; +use crate::path::Path; +#[cfg(target_env = "p1")] +use crate::sys::err2io; +use crate::sys_common::{AsInner, AsInnerMut}; /// WASI-specific extensions to [`File`]. pub trait FileExt { @@ -27,10 +32,7 @@ pub trait FileExt { /// /// Note that similar to [`File::read`], it is not an error to return with a /// short read. - fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result { - let bufs = &mut [IoSliceMut::new(buf)]; - self.read_vectored_at(bufs, offset) - } + fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result; /// Reads a number of bytes starting from a given offset. /// @@ -45,6 +47,13 @@ pub trait FileExt { /// return with a short read. fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result; + /// Reads some bytes starting from a given offset into the buffer. + /// + /// This equivalent to the [`read_at`](FileExt::read_at) method, except that it is passed a + /// [`BorrowedCursor`] rather than `&mut [u8]` to allow use with uninitialized buffers. The new + /// data will be appended to any existing contents of `buf`. + fn read_buf_at(&self, buf: BorrowedCursor<'_>, offset: u64) -> io::Result<()>; + /// Reads the exact number of byte required to fill `buf` from the given offset. /// /// The offset is relative to the start of the file and thus independent @@ -102,10 +111,7 @@ pub trait FileExt { /// /// Note that similar to [`File::write`], it is not an error to return a /// short write. - fn write_at(&self, buf: &[u8], offset: u64) -> io::Result { - let bufs = &[IoSlice::new(buf)]; - self.write_vectored_at(bufs, offset) - } + fn write_at(&self, buf: &[u8], offset: u64) -> io::Result; /// Writes a number of bytes starting from a given offset. /// @@ -164,54 +170,49 @@ pub trait FileExt { /// /// This corresponds to the `fd_fdstat_set_flags` syscall. #[doc(alias = "fd_fdstat_set_flags")] + #[cfg(target_env = "p1")] fn fdstat_set_flags(&self, flags: u16) -> io::Result<()>; /// Adjusts the rights associated with this file. /// /// This corresponds to the `fd_fdstat_set_rights` syscall. #[doc(alias = "fd_fdstat_set_rights")] + #[cfg(target_env = "p1")] fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()>; /// Provides file advisory information on a file descriptor. /// /// This corresponds to the `fd_advise` syscall. #[doc(alias = "fd_advise")] + #[cfg(target_env = "p1")] fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()>; /// Forces the allocation of space in a file. /// /// This corresponds to the `fd_allocate` syscall. #[doc(alias = "fd_allocate")] + #[cfg(target_env = "p1")] fn allocate(&self, offset: u64, len: u64) -> io::Result<()>; /// Creates a directory. /// /// This corresponds to the `path_create_directory` syscall. #[doc(alias = "path_create_directory")] + #[cfg(target_env = "p1")] fn create_directory>(&self, dir: P) -> io::Result<()>; - /// Reads the contents of a symbolic link. - /// - /// This corresponds to the `path_readlink` syscall. - #[doc(alias = "path_readlink")] - fn read_link>(&self, path: P) -> io::Result; - - /// Returns the attributes of a file or directory. - /// - /// This corresponds to the `path_filestat_get` syscall. - #[doc(alias = "path_filestat_get")] - fn metadata_at>(&self, lookup_flags: u32, path: P) -> io::Result; - /// Unlinks a file. /// /// This corresponds to the `path_unlink_file` syscall. #[doc(alias = "path_unlink_file")] + #[cfg(target_env = "p1")] fn remove_file>(&self, path: P) -> io::Result<()>; /// Removes a directory. /// /// This corresponds to the `path_remove_directory` syscall. #[doc(alias = "path_remove_directory")] + #[cfg(target_env = "p1")] fn remove_directory>(&self, path: P) -> io::Result<()>; } @@ -222,23 +223,41 @@ pub trait FileExt { // FIXME: bind poll_oneoff maybe? - probably should wait for I/O to settle // FIXME: bind random_get maybe? - on crates.io for unix -impl FileExt for fs::File { +impl FileExt for File { + fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result { + self.as_inner().read_at(buf, offset) + } + + fn read_buf_at(&self, buf: BorrowedCursor<'_>, offset: u64) -> io::Result<()> { + self.as_inner().read_buf_at(buf, offset) + } + fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result { - self.as_inner().as_inner().pread(bufs, offset) + self.as_inner().read_vectored_at(bufs, offset) + } + + fn write_at(&self, buf: &[u8], offset: u64) -> io::Result { + self.as_inner().write_at(buf, offset) } fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result { - self.as_inner().as_inner().pwrite(bufs, offset) + self.as_inner().write_vectored_at(bufs, offset) } + #[cfg(target_env = "p1")] fn fdstat_set_flags(&self, flags: u16) -> io::Result<()> { - self.as_inner().as_inner().set_flags(flags) + unsafe { wasi::fd_fdstat_set_flags(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) } } + #[cfg(target_env = "p1")] fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()> { - self.as_inner().as_inner().set_rights(rights, inheriting) + unsafe { + wasi::fd_fdstat_set_rights(self.as_raw_fd() as wasi::Fd, rights, inheriting) + .map_err(err2io) + } } + #[cfg(target_env = "p1")] fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()> { let advice = match advice { a if a == wasi::ADVICE_NORMAL.raw() => wasi::ADVICE_NORMAL, @@ -255,149 +274,46 @@ impl FileExt for fs::File { } }; - self.as_inner().as_inner().advise(offset, len, advice) + unsafe { + wasi::fd_advise(self.as_raw_fd() as wasi::Fd, offset, len, advice).map_err(err2io) + } } + #[cfg(target_env = "p1")] fn allocate(&self, offset: u64, len: u64) -> io::Result<()> { - self.as_inner().as_inner().allocate(offset, len) + unsafe { wasi::fd_allocate(self.as_raw_fd() as wasi::Fd, offset, len).map_err(err2io) } } + #[cfg(target_env = "p1")] fn create_directory>(&self, dir: P) -> io::Result<()> { - self.as_inner().as_inner().create_directory(osstr2str(dir.as_ref().as_ref())?) - } - - fn read_link>(&self, path: P) -> io::Result { - self.as_inner().read_link(path.as_ref()) - } - - fn metadata_at>(&self, lookup_flags: u32, path: P) -> io::Result { - let m = self.as_inner().metadata_at(lookup_flags, path.as_ref())?; - Ok(FromInner::from_inner(m)) + let path = osstr2str(dir.as_ref().as_ref())?; + unsafe { wasi::path_create_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } } + #[cfg(target_env = "p1")] fn remove_file>(&self, path: P) -> io::Result<()> { - self.as_inner().as_inner().unlink_file(osstr2str(path.as_ref().as_ref())?) + let path = osstr2str(path.as_ref().as_ref())?; + unsafe { wasi::path_unlink_file(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } } + #[cfg(target_env = "p1")] fn remove_directory>(&self, path: P) -> io::Result<()> { - self.as_inner().as_inner().remove_directory(osstr2str(path.as_ref().as_ref())?) + let path = osstr2str(path.as_ref().as_ref())?; + unsafe { wasi::path_remove_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } } } -/// WASI-specific extensions to [`fs::OpenOptions`]. +/// WASI-specific extensions to [`OpenOptions`]. pub trait OpenOptionsExt { - /// Pass custom `dirflags` argument to `path_open`. - /// - /// This option configures the `dirflags` argument to the - /// `path_open` syscall which `OpenOptions` will eventually call. The - /// `dirflags` argument configures how the file is looked up, currently - /// primarily affecting whether symlinks are followed or not. - /// - /// By default this value is `__WASI_LOOKUP_SYMLINK_FOLLOW`, or symlinks are - /// followed. You can call this method with 0 to disable following symlinks - fn lookup_flags(&mut self, flags: u32) -> &mut Self; - - /// Indicates whether `OpenOptions` must open a directory or not. - /// - /// This method will configure whether the `__WASI_O_DIRECTORY` flag is - /// passed when opening a file. When passed it will require that the opened - /// path is a directory. - /// - /// This option is by default `false` - fn directory(&mut self, dir: bool) -> &mut Self; - - /// Indicates whether `__WASI_FDFLAG_DSYNC` is passed in the `fs_flags` - /// field of `path_open`. - /// - /// This option is by default `false` - fn dsync(&mut self, dsync: bool) -> &mut Self; - - /// Indicates whether `__WASI_FDFLAG_NONBLOCK` is passed in the `fs_flags` - /// field of `path_open`. - /// - /// This option is by default `false` - fn nonblock(&mut self, nonblock: bool) -> &mut Self; - - /// Indicates whether `__WASI_FDFLAG_RSYNC` is passed in the `fs_flags` - /// field of `path_open`. - /// - /// This option is by default `false` - fn rsync(&mut self, rsync: bool) -> &mut Self; - - /// Indicates whether `__WASI_FDFLAG_SYNC` is passed in the `fs_flags` - /// field of `path_open`. - /// - /// This option is by default `false` - fn sync(&mut self, sync: bool) -> &mut Self; - - /// Indicates the value that should be passed in for the `fs_rights_base` - /// parameter of `path_open`. - /// - /// This option defaults based on the `read` and `write` configuration of - /// this `OpenOptions` builder. If this method is called, however, the - /// exact mask passed in will be used instead. - fn fs_rights_base(&mut self, rights: u64) -> &mut Self; - - /// Indicates the value that should be passed in for the - /// `fs_rights_inheriting` parameter of `path_open`. - /// - /// The default for this option is the same value as what will be passed - /// for the `fs_rights_base` parameter but if this method is called then - /// the specified value will be used instead. - fn fs_rights_inheriting(&mut self, rights: u64) -> &mut Self; - - /// Open a file or directory. - /// - /// This corresponds to the `path_open` syscall. - #[doc(alias = "path_open")] - fn open_at>(&self, file: &File, path: P) -> io::Result; + /// Pass custom flags to the `flags` argument of `open`. + fn custom_flags(&mut self, flags: i32) -> &mut Self; } impl OpenOptionsExt for OpenOptions { - fn lookup_flags(&mut self, flags: u32) -> &mut OpenOptions { - self.as_inner_mut().lookup_flags(flags); - self - } - - fn directory(&mut self, dir: bool) -> &mut OpenOptions { - self.as_inner_mut().directory(dir); - self - } - - fn dsync(&mut self, enabled: bool) -> &mut OpenOptions { - self.as_inner_mut().dsync(enabled); - self - } - - fn nonblock(&mut self, enabled: bool) -> &mut OpenOptions { - self.as_inner_mut().nonblock(enabled); - self - } - - fn rsync(&mut self, enabled: bool) -> &mut OpenOptions { - self.as_inner_mut().rsync(enabled); - self - } - - fn sync(&mut self, enabled: bool) -> &mut OpenOptions { - self.as_inner_mut().sync(enabled); + fn custom_flags(&mut self, flags: i32) -> &mut OpenOptions { + self.as_inner_mut().custom_flags(flags); self } - - fn fs_rights_base(&mut self, rights: u64) -> &mut OpenOptions { - self.as_inner_mut().fs_rights_base(rights); - self - } - - fn fs_rights_inheriting(&mut self, rights: u64) -> &mut OpenOptions { - self.as_inner_mut().fs_rights_inheriting(rights); - self - } - - fn open_at>(&self, file: &File, path: P) -> io::Result { - let inner = file.as_inner().open_at(path.as_ref(), self.as_inner())?; - Ok(File::from_inner(inner)) - } } /// WASI-specific extensions to [`fs::Metadata`]. @@ -408,37 +324,17 @@ pub trait MetadataExt { fn ino(&self) -> u64; /// Returns the `st_nlink` field of the internal `filestat_t` fn nlink(&self) -> u64; - /// Returns the `st_size` field of the internal `filestat_t` - fn size(&self) -> u64; - /// Returns the `st_atim` field of the internal `filestat_t` - fn atim(&self) -> u64; - /// Returns the `st_mtim` field of the internal `filestat_t` - fn mtim(&self) -> u64; - /// Returns the `st_ctim` field of the internal `filestat_t` - fn ctim(&self) -> u64; } impl MetadataExt for fs::Metadata { fn dev(&self) -> u64 { - self.as_inner().as_wasi().dev + self.as_inner().as_inner().st_dev } fn ino(&self) -> u64 { - self.as_inner().as_wasi().ino + self.as_inner().as_inner().st_ino } fn nlink(&self) -> u64 { - self.as_inner().as_wasi().nlink - } - fn size(&self) -> u64 { - self.as_inner().as_wasi().size - } - fn atim(&self) -> u64 { - self.as_inner().as_wasi().atim - } - fn mtim(&self) -> u64 { - self.as_inner().as_wasi().mtim - } - fn ctim(&self) -> u64 { - self.as_inner().as_wasi().ctim + self.as_inner().as_inner().st_nlink } } @@ -451,28 +347,19 @@ pub trait FileTypeExt { fn is_block_device(&self) -> bool; /// Returns `true` if this file type is a character device. fn is_char_device(&self) -> bool; - /// Returns `true` if this file type is a socket datagram. - fn is_socket_dgram(&self) -> bool; - /// Returns `true` if this file type is a socket stream. - fn is_socket_stream(&self) -> bool; /// Returns `true` if this file type is any type of socket. - fn is_socket(&self) -> bool { - self.is_socket_stream() || self.is_socket_dgram() - } + fn is_socket(&self) -> bool; } impl FileTypeExt for fs::FileType { fn is_block_device(&self) -> bool { - self.as_inner().bits() == wasi::FILETYPE_BLOCK_DEVICE + self.as_inner().is(libc::S_IFBLK) } fn is_char_device(&self) -> bool { - self.as_inner().bits() == wasi::FILETYPE_CHARACTER_DEVICE - } - fn is_socket_dgram(&self) -> bool { - self.as_inner().bits() == wasi::FILETYPE_SOCKET_DGRAM + self.as_inner().is(libc::S_IFCHR) } - fn is_socket_stream(&self) -> bool { - self.as_inner().bits() == wasi::FILETYPE_SOCKET_STREAM + fn is_socket(&self) -> bool { + self.as_inner().is(libc::S_IFSOCK) } } @@ -492,6 +379,7 @@ impl DirEntryExt for fs::DirEntry { /// /// This corresponds to the `path_link` syscall. #[doc(alias = "path_link")] +#[cfg(target_env = "p1")] pub fn link, U: AsRef>( old_fd: &File, old_flags: u32, @@ -499,43 +387,58 @@ pub fn link, U: AsRef>( new_fd: &File, new_path: U, ) -> io::Result<()> { - old_fd.as_inner().as_inner().link( - old_flags, - osstr2str(old_path.as_ref().as_ref())?, - new_fd.as_inner().as_inner(), - osstr2str(new_path.as_ref().as_ref())?, - ) + unsafe { + wasi::path_link( + old_fd.as_raw_fd() as wasi::Fd, + old_flags, + osstr2str(old_path.as_ref().as_ref())?, + new_fd.as_raw_fd() as wasi::Fd, + osstr2str(new_path.as_ref().as_ref())?, + ) + .map_err(err2io) + } } /// Renames a file or directory. /// /// This corresponds to the `path_rename` syscall. #[doc(alias = "path_rename")] +#[cfg(target_env = "p1")] pub fn rename, U: AsRef>( old_fd: &File, old_path: P, new_fd: &File, new_path: U, ) -> io::Result<()> { - old_fd.as_inner().as_inner().rename( - osstr2str(old_path.as_ref().as_ref())?, - new_fd.as_inner().as_inner(), - osstr2str(new_path.as_ref().as_ref())?, - ) + unsafe { + wasi::path_rename( + old_fd.as_raw_fd() as wasi::Fd, + osstr2str(old_path.as_ref().as_ref())?, + new_fd.as_raw_fd() as wasi::Fd, + osstr2str(new_path.as_ref().as_ref())?, + ) + .map_err(err2io) + } } /// Creates a symbolic link. /// /// This corresponds to the `path_symlink` syscall. #[doc(alias = "path_symlink")] +#[cfg(target_env = "p1")] pub fn symlink, U: AsRef>( old_path: P, fd: &File, new_path: U, ) -> io::Result<()> { - fd.as_inner() - .as_inner() - .symlink(osstr2str(old_path.as_ref().as_ref())?, osstr2str(new_path.as_ref().as_ref())?) + unsafe { + wasi::path_symlink( + osstr2str(old_path.as_ref().as_ref())?, + fd.as_raw_fd() as wasi::Fd, + osstr2str(new_path.as_ref().as_ref())?, + ) + .map_err(err2io) + } } /// Creates a symbolic link. @@ -546,6 +449,7 @@ pub fn symlink_path, U: AsRef>(old_path: P, new_path: U) -> crate::sys::fs::symlink(old_path.as_ref(), new_path.as_ref()) } +#[cfg(target_env = "p1")] fn osstr2str(f: &OsStr) -> io::Result<&str> { f.to_str().ok_or_else(|| io::const_error!(io::ErrorKind::Uncategorized, "input must be utf-8")) } diff --git a/library/std/src/os/wasi/net/mod.rs b/library/std/src/os/wasi/net/mod.rs index 4704dd574517a..9430cd3b05eee 100644 --- a/library/std/src/os/wasi/net/mod.rs +++ b/library/std/src/os/wasi/net/mod.rs @@ -2,7 +2,8 @@ #![unstable(feature = "wasi_ext", issue = "71213")] -use crate::sys_common::AsInner; +use crate::os::fd::AsRawFd; +use crate::sys::err2io; use crate::{io, net}; /// WASI-specific extensions to [`std::net::TcpListener`]. @@ -17,6 +18,6 @@ pub trait TcpListenerExt { impl TcpListenerExt for net::TcpListener { fn sock_accept(&self, flags: u16) -> io::Result { - self.as_inner().as_inner().as_inner().sock_accept(flags) + unsafe { wasi::sock_accept(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) } } } diff --git a/library/std/src/sys/fd/mod.rs b/library/std/src/sys/fd/mod.rs index 330499ecc18f6..02d61a62f4e6b 100644 --- a/library/std/src/sys/fd/mod.rs +++ b/library/std/src/sys/fd/mod.rs @@ -3,7 +3,7 @@ #![forbid(unsafe_op_in_unsafe_fn)] cfg_select! { - target_family = "unix" => { + any(target_family = "unix", target_os = "wasi") => { mod unix; pub use unix::*; } @@ -19,9 +19,5 @@ cfg_select! { mod sgx; pub use sgx::*; } - target_os = "wasi" => { - mod wasi; - pub use wasi::*; - } _ => {} } diff --git a/library/std/src/sys/fd/unix.rs b/library/std/src/sys/fd/unix.rs index a631e1d91393f..33fff36b35b01 100644 --- a/library/std/src/sys/fd/unix.rs +++ b/library/std/src/sys/fd/unix.rs @@ -35,7 +35,7 @@ cfg_select! { use crate::cmp; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read}; -use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; use crate::sys::cvt; #[cfg(all(target_os = "android", target_pointer_width = "64"))] use crate::sys::pal::weak::syscall; @@ -152,7 +152,8 @@ impl FileDesc { target_os = "espidf", target_os = "horizon", target_os = "vita", - target_os = "nuttx" + target_os = "nuttx", + target_os = "wasi", ))) } @@ -385,7 +386,8 @@ impl FileDesc { target_os = "espidf", target_os = "horizon", target_os = "vita", - target_os = "nuttx" + target_os = "nuttx", + target_os = "wasi", ))) } @@ -560,6 +562,7 @@ impl FileDesc { target_os = "redox", target_os = "vxworks", target_os = "nto", + target_os = "wasi", )))] pub fn set_cloexec(&self) -> io::Result<()> { unsafe { @@ -583,6 +586,7 @@ impl FileDesc { target_os = "redox", target_os = "vxworks", target_os = "nto", + target_os = "wasi", ))] pub fn set_cloexec(&self) -> io::Result<()> { unsafe { diff --git a/library/std/src/sys/fd/wasi.rs b/library/std/src/sys/fd/wasi.rs deleted file mode 100644 index a468b31168afa..0000000000000 --- a/library/std/src/sys/fd/wasi.rs +++ /dev/null @@ -1,331 +0,0 @@ -#![expect(dead_code)] - -use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; -use crate::mem; -use crate::net::Shutdown; -use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; -use crate::sys::pal::err2io; -use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; - -#[derive(Debug)] -pub struct WasiFd { - fd: OwnedFd, -} - -fn iovec<'a>(a: &'a mut [IoSliceMut<'_>]) -> &'a [wasi::Iovec] { - assert_eq!(size_of::>(), size_of::()); - assert_eq!(align_of::>(), align_of::()); - // SAFETY: `IoSliceMut` and `IoVec` have exactly the same memory layout. - // We decorate our `IoSliceMut` with `repr(transparent)` (see `io.rs`), and - // `crate::io::IoSliceMut` is a `repr(transparent)` wrapper around our type, so this is - // guaranteed. - unsafe { mem::transmute(a) } -} - -fn ciovec<'a>(a: &'a [IoSlice<'_>]) -> &'a [wasi::Ciovec] { - assert_eq!(size_of::>(), size_of::()); - assert_eq!(align_of::>(), align_of::()); - // SAFETY: `IoSlice` and `CIoVec` have exactly the same memory layout. - // We decorate our `IoSlice` with `repr(transparent)` (see `io.rs`), and - // `crate::io::IoSlice` is a `repr(transparent)` wrapper around our type, so this is - // guaranteed. - unsafe { mem::transmute(a) } -} - -impl WasiFd { - pub fn datasync(&self) -> io::Result<()> { - unsafe { wasi::fd_datasync(self.as_raw_fd() as wasi::Fd).map_err(err2io) } - } - - pub fn pread(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result { - unsafe { wasi::fd_pread(self.as_raw_fd() as wasi::Fd, iovec(bufs), offset).map_err(err2io) } - } - - pub fn pwrite(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result { - unsafe { - wasi::fd_pwrite(self.as_raw_fd() as wasi::Fd, ciovec(bufs), offset).map_err(err2io) - } - } - - pub fn read(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { - unsafe { wasi::fd_read(self.as_raw_fd() as wasi::Fd, iovec(bufs)).map_err(err2io) } - } - - pub fn read_buf(&self, mut buf: BorrowedCursor<'_>) -> io::Result<()> { - unsafe { - let bufs = [wasi::Iovec { - buf: buf.as_mut().as_mut_ptr() as *mut u8, - buf_len: buf.capacity(), - }]; - match wasi::fd_read(self.as_raw_fd() as wasi::Fd, &bufs) { - Ok(n) => { - buf.advance(n); - Ok(()) - } - Err(e) => Err(err2io(e)), - } - } - } - - pub fn write(&self, bufs: &[IoSlice<'_>]) -> io::Result { - unsafe { wasi::fd_write(self.as_raw_fd() as wasi::Fd, ciovec(bufs)).map_err(err2io) } - } - - pub fn seek(&self, pos: SeekFrom) -> io::Result { - let (whence, offset) = match pos { - SeekFrom::Start(pos) => (wasi::WHENCE_SET, pos as i64), - SeekFrom::End(pos) => (wasi::WHENCE_END, pos), - SeekFrom::Current(pos) => (wasi::WHENCE_CUR, pos), - }; - unsafe { wasi::fd_seek(self.as_raw_fd() as wasi::Fd, offset, whence).map_err(err2io) } - } - - pub fn tell(&self) -> io::Result { - unsafe { wasi::fd_tell(self.as_raw_fd() as wasi::Fd).map_err(err2io) } - } - - // FIXME: __wasi_fd_fdstat_get - - pub fn set_flags(&self, flags: wasi::Fdflags) -> io::Result<()> { - unsafe { wasi::fd_fdstat_set_flags(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) } - } - - pub fn set_rights(&self, base: wasi::Rights, inheriting: wasi::Rights) -> io::Result<()> { - unsafe { - wasi::fd_fdstat_set_rights(self.as_raw_fd() as wasi::Fd, base, inheriting) - .map_err(err2io) - } - } - - pub fn sync(&self) -> io::Result<()> { - unsafe { wasi::fd_sync(self.as_raw_fd() as wasi::Fd).map_err(err2io) } - } - - pub(crate) fn advise(&self, offset: u64, len: u64, advice: wasi::Advice) -> io::Result<()> { - unsafe { - wasi::fd_advise(self.as_raw_fd() as wasi::Fd, offset, len, advice).map_err(err2io) - } - } - - pub fn allocate(&self, offset: u64, len: u64) -> io::Result<()> { - unsafe { wasi::fd_allocate(self.as_raw_fd() as wasi::Fd, offset, len).map_err(err2io) } - } - - pub fn create_directory(&self, path: &str) -> io::Result<()> { - unsafe { wasi::path_create_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } - } - - pub fn link( - &self, - old_flags: wasi::Lookupflags, - old_path: &str, - new_fd: &WasiFd, - new_path: &str, - ) -> io::Result<()> { - unsafe { - wasi::path_link( - self.as_raw_fd() as wasi::Fd, - old_flags, - old_path, - new_fd.as_raw_fd() as wasi::Fd, - new_path, - ) - .map_err(err2io) - } - } - - pub fn open( - &self, - dirflags: wasi::Lookupflags, - path: &str, - oflags: wasi::Oflags, - fs_rights_base: wasi::Rights, - fs_rights_inheriting: wasi::Rights, - fs_flags: wasi::Fdflags, - ) -> io::Result { - unsafe { - wasi::path_open( - self.as_raw_fd() as wasi::Fd, - dirflags, - path, - oflags, - fs_rights_base, - fs_rights_inheriting, - fs_flags, - ) - .map(|fd| WasiFd::from_raw_fd(fd as RawFd)) - .map_err(err2io) - } - } - - pub fn readdir(&self, buf: &mut [u8], cookie: wasi::Dircookie) -> io::Result { - unsafe { - wasi::fd_readdir(self.as_raw_fd() as wasi::Fd, buf.as_mut_ptr(), buf.len(), cookie) - .map_err(err2io) - } - } - - pub fn readlink(&self, path: &str, buf: &mut [u8]) -> io::Result { - unsafe { - wasi::path_readlink(self.as_raw_fd() as wasi::Fd, path, buf.as_mut_ptr(), buf.len()) - .map_err(err2io) - } - } - - pub fn rename(&self, old_path: &str, new_fd: &WasiFd, new_path: &str) -> io::Result<()> { - unsafe { - wasi::path_rename( - self.as_raw_fd() as wasi::Fd, - old_path, - new_fd.as_raw_fd() as wasi::Fd, - new_path, - ) - .map_err(err2io) - } - } - - pub(crate) fn filestat_get(&self) -> io::Result { - unsafe { wasi::fd_filestat_get(self.as_raw_fd() as wasi::Fd).map_err(err2io) } - } - - pub fn filestat_set_times( - &self, - atim: wasi::Timestamp, - mtim: wasi::Timestamp, - fstflags: wasi::Fstflags, - ) -> io::Result<()> { - unsafe { - wasi::fd_filestat_set_times(self.as_raw_fd() as wasi::Fd, atim, mtim, fstflags) - .map_err(err2io) - } - } - - pub fn filestat_set_size(&self, size: u64) -> io::Result<()> { - unsafe { wasi::fd_filestat_set_size(self.as_raw_fd() as wasi::Fd, size).map_err(err2io) } - } - - pub(crate) fn path_filestat_get( - &self, - flags: wasi::Lookupflags, - path: &str, - ) -> io::Result { - unsafe { - wasi::path_filestat_get(self.as_raw_fd() as wasi::Fd, flags, path).map_err(err2io) - } - } - - pub fn path_filestat_set_times( - &self, - flags: wasi::Lookupflags, - path: &str, - atim: wasi::Timestamp, - mtim: wasi::Timestamp, - fstflags: wasi::Fstflags, - ) -> io::Result<()> { - unsafe { - wasi::path_filestat_set_times( - self.as_raw_fd() as wasi::Fd, - flags, - path, - atim, - mtim, - fstflags, - ) - .map_err(err2io) - } - } - - pub fn symlink(&self, old_path: &str, new_path: &str) -> io::Result<()> { - unsafe { - wasi::path_symlink(old_path, self.as_raw_fd() as wasi::Fd, new_path).map_err(err2io) - } - } - - pub fn unlink_file(&self, path: &str) -> io::Result<()> { - unsafe { wasi::path_unlink_file(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } - } - - pub fn remove_directory(&self, path: &str) -> io::Result<()> { - unsafe { wasi::path_remove_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } - } - - pub fn sock_accept(&self, flags: wasi::Fdflags) -> io::Result { - unsafe { wasi::sock_accept(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) } - } - - pub fn sock_recv( - &self, - ri_data: &mut [IoSliceMut<'_>], - ri_flags: wasi::Riflags, - ) -> io::Result<(usize, wasi::Roflags)> { - unsafe { - wasi::sock_recv(self.as_raw_fd() as wasi::Fd, iovec(ri_data), ri_flags).map_err(err2io) - } - } - - pub fn sock_send(&self, si_data: &[IoSlice<'_>], si_flags: wasi::Siflags) -> io::Result { - unsafe { - wasi::sock_send(self.as_raw_fd() as wasi::Fd, ciovec(si_data), si_flags).map_err(err2io) - } - } - - pub fn sock_shutdown(&self, how: Shutdown) -> io::Result<()> { - let how = match how { - Shutdown::Read => wasi::SDFLAGS_RD, - Shutdown::Write => wasi::SDFLAGS_WR, - Shutdown::Both => wasi::SDFLAGS_WR | wasi::SDFLAGS_RD, - }; - unsafe { wasi::sock_shutdown(self.as_raw_fd() as wasi::Fd, how).map_err(err2io) } - } -} - -impl AsInner for WasiFd { - #[inline] - fn as_inner(&self) -> &OwnedFd { - &self.fd - } -} - -impl AsInnerMut for WasiFd { - #[inline] - fn as_inner_mut(&mut self) -> &mut OwnedFd { - &mut self.fd - } -} - -impl IntoInner for WasiFd { - fn into_inner(self) -> OwnedFd { - self.fd - } -} - -impl FromInner for WasiFd { - fn from_inner(owned_fd: OwnedFd) -> Self { - Self { fd: owned_fd } - } -} - -impl AsFd for WasiFd { - fn as_fd(&self) -> BorrowedFd<'_> { - self.fd.as_fd() - } -} - -impl AsRawFd for WasiFd { - #[inline] - fn as_raw_fd(&self) -> RawFd { - self.fd.as_raw_fd() - } -} - -impl IntoRawFd for WasiFd { - fn into_raw_fd(self) -> RawFd { - self.fd.into_raw_fd() - } -} - -impl FromRawFd for WasiFd { - unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { - unsafe { Self { fd: FromRawFd::from_raw_fd(raw_fd) } } - } -} diff --git a/library/std/src/sys/fs/mod.rs b/library/std/src/sys/fs/mod.rs index eaea28871241a..4a66227ce5f45 100644 --- a/library/std/src/sys/fs/mod.rs +++ b/library/std/src/sys/fs/mod.rs @@ -6,12 +6,14 @@ use crate::path::{Path, PathBuf}; pub mod common; cfg_select! { - target_family = "unix" => { + any(target_family = "unix", target_os = "wasi") => { mod unix; use unix as imp; + #[cfg(not(target_os = "wasi"))] pub use unix::{chown, fchown, lchown, mkfifo}; - #[cfg(not(target_os = "fuchsia"))] + #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))] pub use unix::chroot; + #[cfg(not(target_os = "wasi"))] pub(crate) use unix::debug_assert_fd_is_open; #[cfg(any(target_os = "linux", target_os = "android"))] pub(super) use unix::CachedFileMetadata; @@ -43,10 +45,6 @@ cfg_select! { mod vexos; use vexos as imp; } - target_os = "wasi" => { - mod wasi; - use wasi as imp; - } _ => { mod unsupported; use unsupported as imp; @@ -54,7 +52,7 @@ cfg_select! { } // FIXME: Replace this with platform-specific path conversion functions. -#[cfg(not(any(target_family = "unix", target_os = "windows")))] +#[cfg(not(any(target_family = "unix", target_os = "windows", target_os = "wasi")))] #[inline] pub fn with_native_path(path: &Path, f: &dyn Fn(&Path) -> io::Result) -> io::Result { f(path) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 47d9ee226653e..507d093d2b2f6 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -31,6 +31,7 @@ use libc::fstatat64; target_os = "redox", target_os = "solaris", target_os = "vita", + target_os = "wasi", all(target_os = "linux", target_env = "musl"), ))] use libc::readdir as readdir64; @@ -47,6 +48,7 @@ use libc::readdir as readdir64; target_os = "redox", target_os = "solaris", target_os = "vita", + target_os = "wasi", )))] use libc::readdir_r as readdir64_r; #[cfg(any(all(target_os = "linux", not(target_env = "musl")), target_os = "hurd"))] @@ -80,8 +82,11 @@ use crate::ffi::{CStr, OsStr, OsString}; use crate::fmt::{self, Write as _}; use crate::fs::TryLockError; use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom}; -use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd}; +use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd}; +#[cfg(target_family = "unix")] use crate::os::unix::prelude::*; +#[cfg(target_os = "wasi")] +use crate::os::wasi::prelude::*; use crate::path::{Path, PathBuf}; use crate::sync::Arc; use crate::sys::common::small_c_string::run_path_with_cstr; @@ -285,6 +290,7 @@ unsafe impl Sync for Dir {} target_os = "redox", target_os = "solaris", target_os = "vita", + target_os = "wasi", ))] pub struct DirEntry { dir: Arc, @@ -310,6 +316,7 @@ pub struct DirEntry { target_os = "redox", target_os = "solaris", target_os = "vita", + target_os = "wasi", ))] struct dirent64_min { d_ino: u64, @@ -335,6 +342,7 @@ struct dirent64_min { target_os = "redox", target_os = "solaris", target_os = "vita", + target_os = "wasi", )))] pub struct DirEntry { dir: Arc, @@ -480,7 +488,7 @@ impl FileAttr { } } -#[cfg(not(any(target_os = "netbsd", target_os = "nto", target_os = "aix")))] +#[cfg(not(any(target_os = "netbsd", target_os = "nto", target_os = "aix", target_os = "wasi")))] impl FileAttr { #[cfg(not(any( target_os = "vxworks", @@ -595,18 +603,18 @@ impl FileAttr { } } -#[cfg(target_os = "nto")] +#[cfg(any(target_os = "nto", target_os = "wasi"))] impl FileAttr { pub fn modified(&self) -> io::Result { - SystemTime::new(self.stat.st_mtim.tv_sec, self.stat.st_mtim.tv_nsec) + SystemTime::new(self.stat.st_mtim.tv_sec, self.stat.st_mtim.tv_nsec.into()) } pub fn accessed(&self) -> io::Result { - SystemTime::new(self.stat.st_atim.tv_sec, self.stat.st_atim.tv_nsec) + SystemTime::new(self.stat.st_atim.tv_sec, self.stat.st_atim.tv_nsec.into()) } pub fn created(&self) -> io::Result { - SystemTime::new(self.stat.st_ctim.tv_sec, self.stat.st_ctim.tv_nsec) + SystemTime::new(self.stat.st_ctim.tv_sec, self.stat.st_ctim.tv_nsec.into()) } } @@ -632,6 +640,7 @@ impl FilePermissions { self.mode |= 0o222; } } + #[cfg(not(target_os = "wasi"))] pub fn mode(&self) -> u32 { self.mode as u32 } @@ -715,6 +724,7 @@ impl Iterator for ReadDir { target_os = "redox", target_os = "solaris", target_os = "vita", + target_os = "wasi", ))] fn next(&mut self) -> Option> { use crate::sys::os::{errno, set_errno}; @@ -812,6 +822,7 @@ impl Iterator for ReadDir { target_os = "redox", target_os = "solaris", target_os = "vita", + target_os = "wasi", )))] fn next(&mut self) -> Option> { if self.end_of_stream { @@ -1002,6 +1013,7 @@ impl DirEntry { target_os = "solaris", target_os = "vita", target_os = "vxworks", + target_os = "wasi", target_vendor = "apple", ))] pub fn ino(&self) -> u64 { @@ -1057,6 +1069,7 @@ impl DirEntry { target_os = "nto", target_os = "vita", target_os = "hurd", + target_os = "wasi", )))] fn name_cstr(&self) -> &CStr { unsafe { CStr::from_ptr(self.entry.d_name.as_ptr()) } @@ -1073,6 +1086,7 @@ impl DirEntry { target_os = "nto", target_os = "vita", target_os = "hurd", + target_os = "wasi", ))] fn name_cstr(&self) -> &CStr { &self.name @@ -1121,6 +1135,7 @@ impl OpenOptions { pub fn custom_flags(&mut self, flags: i32) { self.custom_flags = flags; } + #[cfg(not(target_os = "wasi"))] pub fn mode(&mut self, mode: u32) { self.mode = mode as mode_t; } @@ -1769,6 +1784,7 @@ impl DirBuilder { run_path_with_cstr(p, &|p| cvt(unsafe { libc::mkdir(p.as_ptr(), self.mode) }).map(|_| ())) } + #[cfg(not(target_os = "wasi"))] pub fn set_mode(&mut self, mode: u32) { self.mode = mode as mode_t; } @@ -2217,7 +2233,7 @@ pub fn set_times_nofollow(p: &CStr, times: FileTimes) -> io::Result<()> { set_times_impl(p, times, false) } -#[cfg(target_os = "espidf")] +#[cfg(any(target_os = "espidf", target_os = "wasi"))] fn open_to_and_set_permissions( to: &Path, _reader_metadata: &crate::fs::Metadata, @@ -2228,7 +2244,7 @@ fn open_to_and_set_permissions( Ok((writer, writer_metadata)) } -#[cfg(not(target_os = "espidf"))] +#[cfg(not(any(target_os = "espidf", target_os = "wasi")))] fn open_to_and_set_permissions( to: &Path, reader_metadata: &crate::fs::Metadata, @@ -2376,6 +2392,7 @@ pub fn copy(from: &Path, to: &Path) -> io::Result { Ok(bytes_copied as u64) } +#[cfg(not(target_os = "wasi"))] pub fn chown(path: &Path, uid: u32, gid: u32) -> io::Result<()> { run_path_with_cstr(path, &|path| { cvt(unsafe { libc::chown(path.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) }) @@ -2383,12 +2400,13 @@ pub fn chown(path: &Path, uid: u32, gid: u32) -> io::Result<()> { }) } +#[cfg(not(target_os = "wasi"))] pub fn fchown(fd: c_int, uid: u32, gid: u32) -> io::Result<()> { cvt(unsafe { libc::fchown(fd, uid as libc::uid_t, gid as libc::gid_t) })?; Ok(()) } -#[cfg(not(target_os = "vxworks"))] +#[cfg(not(any(target_os = "vxworks", target_os = "wasi")))] pub fn lchown(path: &Path, uid: u32, gid: u32) -> io::Result<()> { run_path_with_cstr(path, &|path| { cvt(unsafe { libc::lchown(path.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) }) @@ -2402,7 +2420,7 @@ pub fn lchown(path: &Path, uid: u32, gid: u32) -> io::Result<()> { Err(io::const_error!(io::ErrorKind::Unsupported, "lchown not supported by vxworks")) } -#[cfg(not(any(target_os = "fuchsia", target_os = "vxworks")))] +#[cfg(not(any(target_os = "fuchsia", target_os = "vxworks", target_os = "wasi")))] pub fn chroot(dir: &Path) -> io::Result<()> { run_path_with_cstr(dir, &|dir| cvt(unsafe { libc::chroot(dir.as_ptr()) }).map(|_| ())) } @@ -2413,6 +2431,7 @@ pub fn chroot(dir: &Path) -> io::Result<()> { Err(io::const_error!(io::ErrorKind::Unsupported, "chroot not supported by vxworks")) } +#[cfg(not(target_os = "wasi"))] pub fn mkfifo(path: &Path, mode: u32) -> io::Result<()> { run_path_with_cstr(path, &|path| { cvt(unsafe { libc::mkfifo(path.as_ptr(), mode.try_into().unwrap()) }).map(|_| ()) @@ -2451,11 +2470,11 @@ mod remove_dir_impl { #[cfg(all(target_os = "linux", target_env = "gnu"))] use libc::{fdopendir, openat64 as openat, unlinkat}; - use super::{Dir, DirEntry, InnerReadDir, ReadDir, lstat}; + use super::{ + AsRawFd, Dir, DirEntry, FromRawFd, InnerReadDir, IntoRawFd, OwnedFd, RawFd, ReadDir, lstat, + }; use crate::ffi::CStr; use crate::io; - use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}; - use crate::os::unix::prelude::{OwnedFd, RawFd}; use crate::path::{Path, PathBuf}; use crate::sys::common::small_c_string::run_path_with_cstr; use crate::sys::{cvt, cvt_r}; @@ -2543,6 +2562,16 @@ mod remove_dir_impl { // open the directory passing ownership of the fd let (dir, fd) = fdreaddir(fd)?; + + // For WASI all directory entries for this directory are read first + // before any removal is done. This works around the fact that the + // WASIp1 API for reading directories is not well-designed for handling + // mutations between invocations of reading a directory. By reading all + // the entries at once this ensures that, at least without concurrent + // modifications, it should be possible to delete everything. + #[cfg(target_os = "wasi")] + let dir = dir.collect::>(); + for child in dir { let child = child?; let child_name = child.name_cstr(); diff --git a/library/std/src/sys/fs/wasi.rs b/library/std/src/sys/fs/wasi.rs deleted file mode 100644 index 92eb35317415f..0000000000000 --- a/library/std/src/sys/fs/wasi.rs +++ /dev/null @@ -1,913 +0,0 @@ -use crate::ffi::{CStr, OsStr, OsString}; -use crate::fs::TryLockError; -use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; -use crate::mem::{self, ManuallyDrop}; -use crate::os::raw::c_int; -use crate::os::wasi::ffi::{OsStrExt, OsStringExt}; -use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; -use crate::path::{Path, PathBuf}; -use crate::sync::Arc; -use crate::sys::common::small_c_string::run_path_with_cstr; -use crate::sys::fd::WasiFd; -pub use crate::sys::fs::common::exists; -use crate::sys::time::SystemTime; -use crate::sys::{unsupported, unsupported_err}; -use crate::sys_common::{AsInner, FromInner, IntoInner, ignore_notfound}; -use crate::{fmt, iter, ptr}; - -pub struct File { - fd: WasiFd, -} - -#[derive(Clone)] -pub struct FileAttr { - meta: wasi::Filestat, -} - -pub struct ReadDir { - inner: Arc, - state: ReadDirState, -} - -enum ReadDirState { - /// Fill `buf` with `buf.len()` bytes starting from `next_read_offset`. - FillBuffer { - next_read_offset: wasi::Dircookie, - buf: Vec, - }, - ProcessEntry { - buf: Vec, - next_read_offset: Option, - offset: usize, - }, - /// There is no more data to get in [`Self::FillBuffer`]; keep returning - /// entries via ProcessEntry until `buf` is exhausted. - RunUntilExhaustion { - buf: Vec, - offset: usize, - }, - Done, -} - -struct ReadDirInner { - root: PathBuf, - dir: File, -} - -pub struct DirEntry { - meta: wasi::Dirent, - name: Vec, - inner: Arc, -} - -#[derive(Clone, Debug, Default)] -pub struct OpenOptions { - read: bool, - write: bool, - append: bool, - dirflags: wasi::Lookupflags, - fdflags: wasi::Fdflags, - oflags: wasi::Oflags, - rights_base: Option, - rights_inheriting: Option, -} - -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct FilePermissions { - readonly: bool, -} - -#[derive(Copy, Clone, Debug, Default)] -pub struct FileTimes { - accessed: Option, - modified: Option, -} - -#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] -pub struct FileType { - bits: wasi::Filetype, -} - -#[derive(Debug)] -pub struct DirBuilder {} - -impl FileAttr { - pub fn size(&self) -> u64 { - self.meta.size - } - - pub fn perm(&self) -> FilePermissions { - // not currently implemented in wasi yet - FilePermissions { readonly: false } - } - - pub fn file_type(&self) -> FileType { - FileType { bits: self.meta.filetype } - } - - pub fn modified(&self) -> io::Result { - Ok(SystemTime::from_wasi_timestamp(self.meta.mtim)) - } - - pub fn accessed(&self) -> io::Result { - Ok(SystemTime::from_wasi_timestamp(self.meta.atim)) - } - - pub fn created(&self) -> io::Result { - Ok(SystemTime::from_wasi_timestamp(self.meta.ctim)) - } - - pub(crate) fn as_wasi(&self) -> &wasi::Filestat { - &self.meta - } -} - -impl FilePermissions { - pub fn readonly(&self) -> bool { - self.readonly - } - - pub fn set_readonly(&mut self, readonly: bool) { - self.readonly = readonly; - } -} - -impl FileTimes { - pub fn set_accessed(&mut self, t: SystemTime) { - self.accessed = Some(t); - } - - pub fn set_modified(&mut self, t: SystemTime) { - self.modified = Some(t); - } -} - -impl FileType { - pub fn is_dir(&self) -> bool { - self.bits == wasi::FILETYPE_DIRECTORY - } - - pub fn is_file(&self) -> bool { - self.bits == wasi::FILETYPE_REGULAR_FILE - } - - pub fn is_symlink(&self) -> bool { - self.bits == wasi::FILETYPE_SYMBOLIC_LINK - } - - pub(crate) fn bits(&self) -> wasi::Filetype { - self.bits - } -} - -impl ReadDir { - fn new(dir: File, root: PathBuf) -> ReadDir { - ReadDir { - inner: Arc::new(ReadDirInner { dir, root }), - state: ReadDirState::FillBuffer { next_read_offset: 0, buf: vec![0; 128] }, - } - } -} - -impl fmt::Debug for ReadDir { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ReadDir").finish_non_exhaustive() - } -} - -impl core::iter::FusedIterator for ReadDir {} - -impl Iterator for ReadDir { - type Item = io::Result; - - fn next(&mut self) -> Option> { - match &mut self.state { - ReadDirState::FillBuffer { next_read_offset, buf } => { - let result = self.inner.dir.fd.readdir(buf, *next_read_offset); - match result { - Ok(read_bytes) => { - if read_bytes < buf.len() { - buf.truncate(read_bytes); - self.state = - ReadDirState::RunUntilExhaustion { buf: mem::take(buf), offset: 0 }; - } else { - debug_assert_eq!(read_bytes, buf.len()); - self.state = ReadDirState::ProcessEntry { - buf: mem::take(buf), - offset: 0, - next_read_offset: Some(*next_read_offset), - }; - } - self.next() - } - Err(e) => { - self.state = ReadDirState::Done; - return Some(Err(e)); - } - } - } - ReadDirState::ProcessEntry { buf, next_read_offset, offset } => { - let contents = &buf[*offset..]; - const DIRENT_SIZE: usize = size_of::(); - if contents.len() >= DIRENT_SIZE { - let (dirent, data) = contents.split_at(DIRENT_SIZE); - let dirent = - unsafe { ptr::read_unaligned(dirent.as_ptr() as *const wasi::Dirent) }; - // If the file name was truncated, then we need to reinvoke - // `readdir` so we truncate our buffer to start over and reread this - // descriptor. - if data.len() < dirent.d_namlen as usize { - if buf.len() < dirent.d_namlen as usize + DIRENT_SIZE { - buf.resize(dirent.d_namlen as usize + DIRENT_SIZE, 0); - } - if let Some(next_read_offset) = *next_read_offset { - self.state = - ReadDirState::FillBuffer { next_read_offset, buf: mem::take(buf) }; - } else { - self.state = ReadDirState::Done; - } - - return self.next(); - } - next_read_offset.as_mut().map(|cookie| { - *cookie = dirent.d_next; - }); - *offset = *offset + DIRENT_SIZE + dirent.d_namlen as usize; - - let name = &data[..(dirent.d_namlen as usize)]; - - // These names are skipped on all other platforms, so let's skip - // them here too - if name == b"." || name == b".." { - return self.next(); - } - - return Some(Ok(DirEntry { - meta: dirent, - name: name.to_vec(), - inner: self.inner.clone(), - })); - } else if let Some(next_read_offset) = *next_read_offset { - self.state = ReadDirState::FillBuffer { next_read_offset, buf: mem::take(buf) }; - } else { - self.state = ReadDirState::Done; - } - self.next() - } - ReadDirState::RunUntilExhaustion { buf, offset } => { - if *offset >= buf.len() { - self.state = ReadDirState::Done; - } else { - self.state = ReadDirState::ProcessEntry { - buf: mem::take(buf), - offset: *offset, - next_read_offset: None, - }; - } - - self.next() - } - ReadDirState::Done => None, - } - } -} - -impl DirEntry { - pub fn path(&self) -> PathBuf { - let name = OsStr::from_bytes(&self.name); - self.inner.root.join(name) - } - - pub fn file_name(&self) -> OsString { - OsString::from_vec(self.name.clone()) - } - - pub fn metadata(&self) -> io::Result { - metadata_at(&self.inner.dir.fd, 0, OsStr::from_bytes(&self.name).as_ref()) - } - - pub fn file_type(&self) -> io::Result { - Ok(FileType { bits: self.meta.d_type }) - } - - pub fn ino(&self) -> wasi::Inode { - self.meta.d_ino - } -} - -impl OpenOptions { - pub fn new() -> OpenOptions { - let mut base = OpenOptions::default(); - base.dirflags = wasi::LOOKUPFLAGS_SYMLINK_FOLLOW; - base - } - - pub fn read(&mut self, read: bool) { - self.read = read; - } - - pub fn write(&mut self, write: bool) { - self.write = write; - } - - pub fn truncate(&mut self, truncate: bool) { - self.oflag(wasi::OFLAGS_TRUNC, truncate); - } - - pub fn create(&mut self, create: bool) { - self.oflag(wasi::OFLAGS_CREAT, create); - } - - pub fn create_new(&mut self, create_new: bool) { - self.oflag(wasi::OFLAGS_EXCL, create_new); - self.oflag(wasi::OFLAGS_CREAT, create_new); - } - - pub fn directory(&mut self, directory: bool) { - self.oflag(wasi::OFLAGS_DIRECTORY, directory); - } - - fn oflag(&mut self, bit: wasi::Oflags, set: bool) { - if set { - self.oflags |= bit; - } else { - self.oflags &= !bit; - } - } - - pub fn append(&mut self, append: bool) { - self.append = append; - self.fdflag(wasi::FDFLAGS_APPEND, append); - } - - pub fn dsync(&mut self, set: bool) { - self.fdflag(wasi::FDFLAGS_DSYNC, set); - } - - pub fn nonblock(&mut self, set: bool) { - self.fdflag(wasi::FDFLAGS_NONBLOCK, set); - } - - pub fn rsync(&mut self, set: bool) { - self.fdflag(wasi::FDFLAGS_RSYNC, set); - } - - pub fn sync(&mut self, set: bool) { - self.fdflag(wasi::FDFLAGS_SYNC, set); - } - - fn fdflag(&mut self, bit: wasi::Fdflags, set: bool) { - if set { - self.fdflags |= bit; - } else { - self.fdflags &= !bit; - } - } - - pub fn fs_rights_base(&mut self, rights: wasi::Rights) { - self.rights_base = Some(rights); - } - - pub fn fs_rights_inheriting(&mut self, rights: wasi::Rights) { - self.rights_inheriting = Some(rights); - } - - fn rights_base(&self) -> wasi::Rights { - if let Some(rights) = self.rights_base { - return rights; - } - - // If rights haven't otherwise been specified try to pick a reasonable - // set. This can always be overridden by users via extension traits, and - // implementations may give us fewer rights silently than we ask for. So - // given that, just look at `read` and `write` and bucket permissions - // based on that. - let mut base = 0; - if self.read { - base |= wasi::RIGHTS_FD_READ; - base |= wasi::RIGHTS_FD_READDIR; - } - if self.write || self.append { - base |= wasi::RIGHTS_FD_WRITE; - base |= wasi::RIGHTS_FD_DATASYNC; - base |= wasi::RIGHTS_FD_ALLOCATE; - base |= wasi::RIGHTS_FD_FILESTAT_SET_SIZE; - } - - // FIXME: some of these should probably be read-only or write-only... - base |= wasi::RIGHTS_FD_ADVISE; - base |= wasi::RIGHTS_FD_FDSTAT_SET_FLAGS; - base |= wasi::RIGHTS_FD_FILESTAT_GET; - base |= wasi::RIGHTS_FD_FILESTAT_SET_TIMES; - base |= wasi::RIGHTS_FD_SEEK; - base |= wasi::RIGHTS_FD_SYNC; - base |= wasi::RIGHTS_FD_TELL; - base |= wasi::RIGHTS_PATH_CREATE_DIRECTORY; - base |= wasi::RIGHTS_PATH_CREATE_FILE; - base |= wasi::RIGHTS_PATH_FILESTAT_GET; - base |= wasi::RIGHTS_PATH_LINK_SOURCE; - base |= wasi::RIGHTS_PATH_LINK_TARGET; - base |= wasi::RIGHTS_PATH_OPEN; - base |= wasi::RIGHTS_PATH_READLINK; - base |= wasi::RIGHTS_PATH_REMOVE_DIRECTORY; - base |= wasi::RIGHTS_PATH_RENAME_SOURCE; - base |= wasi::RIGHTS_PATH_RENAME_TARGET; - base |= wasi::RIGHTS_PATH_SYMLINK; - base |= wasi::RIGHTS_PATH_UNLINK_FILE; - base |= wasi::RIGHTS_POLL_FD_READWRITE; - - base - } - - fn rights_inheriting(&self) -> wasi::Rights { - self.rights_inheriting.unwrap_or_else(|| self.rights_base()) - } - - pub fn lookup_flags(&mut self, flags: wasi::Lookupflags) { - self.dirflags = flags; - } -} - -impl File { - pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { - let (dir, file) = open_parent(path)?; - open_at(&dir, &file, opts) - } - - pub fn open_at(&self, path: &Path, opts: &OpenOptions) -> io::Result { - open_at(&self.fd, path, opts) - } - - pub fn file_attr(&self) -> io::Result { - self.fd.filestat_get().map(|meta| FileAttr { meta }) - } - - pub fn metadata_at(&self, flags: wasi::Lookupflags, path: &Path) -> io::Result { - metadata_at(&self.fd, flags, path) - } - - pub fn fsync(&self) -> io::Result<()> { - self.fd.sync() - } - - pub fn datasync(&self) -> io::Result<()> { - self.fd.datasync() - } - - pub fn lock(&self) -> io::Result<()> { - unsupported() - } - - pub fn lock_shared(&self) -> io::Result<()> { - unsupported() - } - - pub fn try_lock(&self) -> Result<(), TryLockError> { - Err(TryLockError::Error(unsupported_err())) - } - - pub fn try_lock_shared(&self) -> Result<(), TryLockError> { - Err(TryLockError::Error(unsupported_err())) - } - - pub fn unlock(&self) -> io::Result<()> { - unsupported() - } - - pub fn truncate(&self, size: u64) -> io::Result<()> { - self.fd.filestat_set_size(size) - } - - pub fn read(&self, buf: &mut [u8]) -> io::Result { - self.read_vectored(&mut [IoSliceMut::new(buf)]) - } - - pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { - self.fd.read(bufs) - } - - #[inline] - pub fn is_read_vectored(&self) -> bool { - true - } - - pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> { - self.fd.read_buf(cursor) - } - - pub fn write(&self, buf: &[u8]) -> io::Result { - self.write_vectored(&[IoSlice::new(buf)]) - } - - pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { - self.fd.write(bufs) - } - - #[inline] - pub fn is_write_vectored(&self) -> bool { - true - } - - pub fn flush(&self) -> io::Result<()> { - Ok(()) - } - - pub fn seek(&self, pos: SeekFrom) -> io::Result { - self.fd.seek(pos) - } - - pub fn size(&self) -> Option> { - None - } - - pub fn tell(&self) -> io::Result { - self.fd.tell() - } - - pub fn duplicate(&self) -> io::Result { - // https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-rationale.md#why-no-dup - unsupported() - } - - pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> { - // Permissions haven't been fully figured out in wasi yet, so this is - // likely temporary - unsupported() - } - - pub fn set_times(&self, times: FileTimes) -> io::Result<()> { - self.fd.filestat_set_times( - to_wasi_timestamp_or_now(times.accessed)?, - to_wasi_timestamp_or_now(times.modified)?, - times.accessed.map_or(0, |_| wasi::FSTFLAGS_ATIM) - | times.modified.map_or(0, |_| wasi::FSTFLAGS_MTIM), - ) - } - - pub fn read_link(&self, file: &Path) -> io::Result { - read_link(&self.fd, file) - } -} - -impl AsInner for File { - #[inline] - fn as_inner(&self) -> &WasiFd { - &self.fd - } -} - -impl IntoInner for File { - fn into_inner(self) -> WasiFd { - self.fd - } -} - -impl FromInner for File { - fn from_inner(fd: WasiFd) -> File { - File { fd } - } -} - -impl AsFd for File { - fn as_fd(&self) -> BorrowedFd<'_> { - self.fd.as_fd() - } -} - -impl AsRawFd for File { - #[inline] - fn as_raw_fd(&self) -> RawFd { - self.fd.as_raw_fd() - } -} - -impl IntoRawFd for File { - fn into_raw_fd(self) -> RawFd { - self.fd.into_raw_fd() - } -} - -impl FromRawFd for File { - unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { - unsafe { Self { fd: FromRawFd::from_raw_fd(raw_fd) } } - } -} - -impl DirBuilder { - pub fn new() -> DirBuilder { - DirBuilder {} - } - - pub fn mkdir(&self, p: &Path) -> io::Result<()> { - let (dir, file) = open_parent(p)?; - dir.create_directory(osstr2str(file.as_ref())?) - } -} - -impl fmt::Debug for File { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("File").field("fd", &self.as_raw_fd()).finish() - } -} - -pub fn readdir(p: &Path) -> io::Result { - let mut opts = OpenOptions::new(); - opts.directory(true); - opts.read(true); - let dir = File::open(p, &opts)?; - Ok(ReadDir::new(dir, p.to_path_buf())) -} - -pub fn unlink(p: &Path) -> io::Result<()> { - let (dir, file) = open_parent(p)?; - dir.unlink_file(osstr2str(file.as_ref())?) -} - -pub fn rename(old: &Path, new: &Path) -> io::Result<()> { - let (old, old_file) = open_parent(old)?; - let (new, new_file) = open_parent(new)?; - old.rename(osstr2str(old_file.as_ref())?, &new, osstr2str(new_file.as_ref())?) -} - -pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { - // Permissions haven't been fully figured out in wasi yet, so this is - // likely temporary - unsupported() -} - -#[inline(always)] -pub fn set_times(p: &Path, times: FileTimes) -> io::Result<()> { - let (dir, file) = open_parent(p)?; - set_times_impl(&dir, &file, times, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW) -} - -#[inline(always)] -pub fn set_times_nofollow(p: &Path, times: FileTimes) -> io::Result<()> { - let (dir, file) = open_parent(p)?; - set_times_impl(&dir, &file, times, 0) -} - -fn to_wasi_timestamp_or_now(time: Option) -> io::Result { - match time { - Some(time) if let Some(ts) = time.to_wasi_timestamp() => Ok(ts), - Some(_) => Err(io::const_error!( - io::ErrorKind::InvalidInput, - "timestamp is too large to set as a file time", - )), - None => Ok(0), - } -} - -fn set_times_impl( - fd: &WasiFd, - path: &Path, - times: FileTimes, - flags: wasi::Lookupflags, -) -> io::Result<()> { - fd.path_filestat_set_times( - flags, - osstr2str(path.as_ref())?, - to_wasi_timestamp_or_now(times.accessed)?, - to_wasi_timestamp_or_now(times.modified)?, - times.accessed.map_or(0, |_| wasi::FSTFLAGS_ATIM) - | times.modified.map_or(0, |_| wasi::FSTFLAGS_MTIM), - ) -} - -pub fn rmdir(p: &Path) -> io::Result<()> { - let (dir, file) = open_parent(p)?; - dir.remove_directory(osstr2str(file.as_ref())?) -} - -pub fn readlink(p: &Path) -> io::Result { - let (dir, file) = open_parent(p)?; - read_link(&dir, &file) -} - -fn read_link(fd: &WasiFd, file: &Path) -> io::Result { - // Try to get a best effort initial capacity for the vector we're going to - // fill. Note that if it's not a symlink we don't use a file to avoid - // allocating gigabytes if you read_link a huge movie file by accident. - // Additionally we add 1 to the initial size so if it doesn't change until - // when we call `readlink` the returned length will be less than the - // capacity, guaranteeing that we got all the data. - let meta = metadata_at(fd, 0, file)?; - let initial_size = if meta.file_type().is_symlink() { - (meta.size() as usize).saturating_add(1) - } else { - 1 // this'll fail in just a moment - }; - - // Now that we have an initial guess of how big to make our buffer, call - // `readlink` in a loop until it fails or reports it filled fewer bytes than - // we asked for, indicating we got everything. - let file = osstr2str(file.as_ref())?; - let mut destination = vec![0u8; initial_size]; - loop { - let len = fd.readlink(file, &mut destination)?; - if len < destination.len() { - destination.truncate(len); - destination.shrink_to_fit(); - return Ok(PathBuf::from(OsString::from_vec(destination))); - } - let amt_to_add = destination.len(); - destination.extend(iter::repeat(0).take(amt_to_add)); - } -} - -pub fn symlink(original: &Path, link: &Path) -> io::Result<()> { - let (link, link_file) = open_parent(link)?; - link.symlink(osstr2str(original.as_ref())?, osstr2str(link_file.as_ref())?) -} - -pub fn link(original: &Path, link: &Path) -> io::Result<()> { - let (original, original_file) = open_parent(original)?; - let (link, link_file) = open_parent(link)?; - // Pass 0 as the flags argument, meaning don't follow symlinks. - original.link(0, osstr2str(original_file.as_ref())?, &link, osstr2str(link_file.as_ref())?) -} - -pub fn stat(p: &Path) -> io::Result { - let (dir, file) = open_parent(p)?; - metadata_at(&dir, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW, &file) -} - -pub fn lstat(p: &Path) -> io::Result { - let (dir, file) = open_parent(p)?; - metadata_at(&dir, 0, &file) -} - -fn metadata_at(fd: &WasiFd, flags: wasi::Lookupflags, path: &Path) -> io::Result { - let meta = fd.path_filestat_get(flags, osstr2str(path.as_ref())?)?; - Ok(FileAttr { meta }) -} - -pub fn canonicalize(_p: &Path) -> io::Result { - // This seems to not be in wasi's API yet, and we may need to end up - // emulating it ourselves. For now just return an error. - unsupported() -} - -fn open_at(fd: &WasiFd, path: &Path, opts: &OpenOptions) -> io::Result { - let fd = fd.open( - opts.dirflags, - osstr2str(path.as_ref())?, - opts.oflags, - opts.rights_base(), - opts.rights_inheriting(), - opts.fdflags, - )?; - Ok(File { fd }) -} - -/// Attempts to open a bare path `p`. -/// -/// WASI has no fundamental capability to do this. All syscalls and operations -/// are relative to already-open file descriptors. The C library, however, -/// manages a map of pre-opened file descriptors to their path, and then the C -/// library provides an API to look at this. In other words, when you want to -/// open a path `p`, you have to find a previously opened file descriptor in a -/// global table and then see if `p` is relative to that file descriptor. -/// -/// This function, if successful, will return two items: -/// -/// * The first is a `ManuallyDrop`. This represents a pre-opened file -/// descriptor which we don't have ownership of, but we can use. You shouldn't -/// actually drop the `fd`. -/// -/// * The second is a path that should be a part of `p` and represents a -/// relative traversal from the file descriptor specified to the desired -/// location `p`. -/// -/// If successful you can use the returned file descriptor to perform -/// file-descriptor-relative operations on the path returned as well. The -/// `rights` argument indicates what operations are desired on the returned file -/// descriptor, and if successful the returned file descriptor should have the -/// appropriate rights for performing `rights` actions. -/// -/// Note that this can fail if `p` doesn't look like it can be opened relative -/// to any pre-opened file descriptor. -fn open_parent(p: &Path) -> io::Result<(ManuallyDrop, PathBuf)> { - run_path_with_cstr(p, &|p| { - let mut buf = Vec::::with_capacity(512); - loop { - unsafe { - let mut relative_path = buf.as_ptr().cast(); - let mut abs_prefix = ptr::null(); - let fd = __wasilibc_find_relpath( - p.as_ptr(), - &mut abs_prefix, - &mut relative_path, - buf.capacity(), - ); - if fd == -1 { - if io::Error::last_os_error().raw_os_error() == Some(libc::ENOMEM) { - // Trigger the internal buffer resizing logic of `Vec` by requiring - // more space than the current capacity. - let cap = buf.capacity(); - buf.set_len(cap); - buf.reserve(1); - continue; - } - let msg = format!( - "failed to find a pre-opened file descriptor \ - through which {p:?} could be opened", - ); - return Err(io::Error::new(io::ErrorKind::Uncategorized, msg)); - } - let relative = CStr::from_ptr(relative_path).to_bytes().to_vec(); - - return Ok(( - ManuallyDrop::new(WasiFd::from_raw_fd(fd as c_int)), - PathBuf::from(OsString::from_vec(relative)), - )); - } - } - - unsafe extern "C" { - pub fn __wasilibc_find_relpath( - path: *const libc::c_char, - abs_prefix: *mut *const libc::c_char, - relative_path: *mut *const libc::c_char, - relative_path_len: libc::size_t, - ) -> libc::c_int; - } - }) -} - -pub fn osstr2str(f: &OsStr) -> io::Result<&str> { - f.to_str().ok_or_else(|| io::const_error!(io::ErrorKind::Uncategorized, "input must be utf-8")) -} - -pub fn copy(from: &Path, to: &Path) -> io::Result { - use crate::fs::File; - - let mut reader = File::open(from)?; - let mut writer = File::create(to)?; - - io::copy(&mut reader, &mut writer) -} - -pub fn remove_dir_all(path: &Path) -> io::Result<()> { - let (parent, path) = open_parent(path)?; - remove_dir_all_recursive(&parent, &path) -} - -fn remove_dir_all_recursive(parent: &WasiFd, path: &Path) -> io::Result<()> { - // Open up a file descriptor for the directory itself. Note that we don't - // follow symlinks here and we specifically open directories. - // - // At the root invocation of this function this will correctly handle - // symlinks passed to the top-level `remove_dir_all`. At the recursive - // level this will double-check that after the `readdir` call deduced this - // was a directory it's still a directory by the time we open it up. - // - // If the opened file was actually a symlink then the symlink is deleted, - // not the directory recursively. - let mut opts = OpenOptions::new(); - opts.lookup_flags(0); - opts.directory(true); - opts.read(true); - let fd = open_at(parent, path, &opts)?; - if fd.file_attr()?.file_type().is_symlink() { - return parent.unlink_file(osstr2str(path.as_ref())?); - } - - // this "root" is only used by `DirEntry::path` which we don't use below so - // it's ok for this to be a bogus value - let dummy_root = PathBuf::new(); - - // Iterate over all the entries in this directory, and travel recursively if - // necessary - // - // Note that all directory entries for this directory are read first before - // any removal is done. This works around the fact that the WASIp1 API for - // reading directories is not well-designed for handling mutations between - // invocations of reading a directory. By reading all the entries at once - // this ensures that, at least without concurrent modifications, it should - // be possible to delete everything. - for entry in ReadDir::new(fd, dummy_root).collect::>() { - let entry = entry?; - let path = crate::str::from_utf8(&entry.name).map_err(|_| { - io::const_error!(io::ErrorKind::Uncategorized, "invalid utf-8 file name found") - })?; - - let result: io::Result<()> = try { - if entry.file_type()?.is_dir() { - remove_dir_all_recursive(&entry.inner.dir.fd, path.as_ref())?; - } else { - entry.inner.dir.fd.unlink_file(path)?; - } - }; - // ignore internal NotFound errors - if let Err(err) = &result - && err.kind() != io::ErrorKind::NotFound - { - return result; - } - } - - // Once all this directory's contents are deleted it should be safe to - // delete the directory tiself. - ignore_notfound(parent.remove_directory(osstr2str(path.as_ref())?)) -} diff --git a/library/std/src/sys/io/io_slice/iovec.rs b/library/std/src/sys/io/io_slice/iovec.rs index df56358969a39..d549aca250d5f 100644 --- a/library/std/src/sys/io/io_slice/iovec.rs +++ b/library/std/src/sys/io/io_slice/iovec.rs @@ -1,6 +1,6 @@ #[cfg(target_os = "hermit")] use hermit_abi::iovec; -#[cfg(any(target_family = "unix", target_os = "trusty"))] +#[cfg(any(target_family = "unix", target_os = "trusty", target_os = "wasi"))] use libc::iovec; use crate::ffi::c_void; diff --git a/library/std/src/sys/io/io_slice/wasi.rs b/library/std/src/sys/io/io_slice/wasi.rs deleted file mode 100644 index 87acbbd924e56..0000000000000 --- a/library/std/src/sys/io/io_slice/wasi.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::marker::PhantomData; -use crate::slice; - -#[derive(Copy, Clone)] -#[repr(transparent)] -pub struct IoSlice<'a> { - vec: wasi::Ciovec, - _p: PhantomData<&'a [u8]>, -} - -impl<'a> IoSlice<'a> { - #[inline] - pub fn new(buf: &'a [u8]) -> IoSlice<'a> { - IoSlice { vec: wasi::Ciovec { buf: buf.as_ptr(), buf_len: buf.len() }, _p: PhantomData } - } - - #[inline] - pub fn advance(&mut self, n: usize) { - if self.vec.buf_len < n { - panic!("advancing IoSlice beyond its length"); - } - - unsafe { - self.vec.buf_len -= n; - self.vec.buf = self.vec.buf.add(n); - } - } - - #[inline] - pub const fn as_slice(&self) -> &'a [u8] { - unsafe { slice::from_raw_parts(self.vec.buf as *const u8, self.vec.buf_len) } - } -} - -#[repr(transparent)] -pub struct IoSliceMut<'a> { - vec: wasi::Iovec, - _p: PhantomData<&'a mut [u8]>, -} - -impl<'a> IoSliceMut<'a> { - #[inline] - pub fn new(buf: &'a mut [u8]) -> IoSliceMut<'a> { - IoSliceMut { - vec: wasi::Iovec { buf: buf.as_mut_ptr(), buf_len: buf.len() }, - _p: PhantomData, - } - } - - #[inline] - pub fn advance(&mut self, n: usize) { - if self.vec.buf_len < n { - panic!("advancing IoSlice beyond its length"); - } - - unsafe { - self.vec.buf_len -= n; - self.vec.buf = self.vec.buf.add(n); - } - } - - #[inline] - pub fn as_slice(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.vec.buf as *const u8, self.vec.buf_len) } - } - - #[inline] - pub const fn into_slice(self) -> &'a mut [u8] { - unsafe { slice::from_raw_parts_mut(self.vec.buf as *mut u8, self.vec.buf_len) } - } - - #[inline] - pub fn as_mut_slice(&mut self) -> &mut [u8] { - unsafe { slice::from_raw_parts_mut(self.vec.buf as *mut u8, self.vec.buf_len) } - } -} diff --git a/library/std/src/sys/io/mod.rs b/library/std/src/sys/io/mod.rs index e2c5e7f88d492..b4e4e6ec73123 100644 --- a/library/std/src/sys/io/mod.rs +++ b/library/std/src/sys/io/mod.rs @@ -2,7 +2,7 @@ mod io_slice { cfg_select! { - any(target_family = "unix", target_os = "hermit", target_os = "solid_asp3", target_os = "trusty") => { + any(target_family = "unix", target_os = "hermit", target_os = "solid_asp3", target_os = "trusty", target_os = "wasi") => { mod iovec; pub use iovec::*; } @@ -10,10 +10,6 @@ mod io_slice { mod windows; pub use windows::*; } - target_os = "wasi" => { - mod wasi; - pub use wasi::*; - } target_os = "uefi" => { mod uefi; pub use uefi::*; diff --git a/library/std/src/sys/net/connection/socket/mod.rs b/library/std/src/sys/net/connection/socket/mod.rs index 1d941dec1b792..63d5c1d23124e 100644 --- a/library/std/src/sys/net/connection/socket/mod.rs +++ b/library/std/src/sys/net/connection/socket/mod.rs @@ -22,14 +22,10 @@ cfg_select! { mod solid; pub use solid::*; } - target_family = "unix" => { + any(target_family = "unix", target_os = "wasi") => { mod unix; pub use unix::*; } - all(target_os = "wasi", any(target_env = "p2", target_env = "p3")) => { - mod wasip2; - pub use wasip2::*; - } target_os = "windows" => { mod windows; pub use windows::*; diff --git a/library/std/src/sys/net/connection/socket/unix.rs b/library/std/src/sys/net/connection/socket/unix.rs index c892daf0d39c3..d30f6054077d9 100644 --- a/library/std/src/sys/net/connection/socket/unix.rs +++ b/library/std/src/sys/net/connection/socket/unix.rs @@ -4,7 +4,7 @@ use libc::{MSG_PEEK, c_int, c_void, size_t, sockaddr, socklen_t}; use crate::ffi::CStr; use crate::io::{self, BorrowedBuf, BorrowedCursor, IoSlice, IoSliceMut}; use crate::net::{Shutdown, SocketAddr}; -use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; +use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; use crate::sys::fd::FileDesc; use crate::sys::net::{getsockopt, setsockopt}; use crate::sys::pal::IsMinusOne; @@ -107,7 +107,7 @@ impl Socket { } } - #[cfg(not(target_os = "vxworks"))] + #[cfg(not(any(target_os = "vxworks", target_os = "wasi")))] pub fn new_pair(fam: c_int, ty: c_int) -> io::Result<(Socket, Socket)> { unsafe { let mut fds = [0, 0]; @@ -275,6 +275,7 @@ impl Socket { self.0.duplicate().map(Socket) } + #[cfg(not(target_os = "wasi"))] pub fn send_with_flags(&self, buf: &[u8], flags: c_int) -> io::Result { let len = cmp::min(buf.len(), ::MAX as usize) as wrlen_t; let ret = cvt(unsafe { @@ -361,6 +362,7 @@ impl Socket { self.recv_from_with_flags(buf, MSG_PEEK) } + #[cfg(not(target_os = "wasi"))] pub fn write(&self, buf: &[u8]) -> io::Result { self.0.write(buf) } diff --git a/library/std/src/sys/net/connection/socket/wasip2.rs b/library/std/src/sys/net/connection/socket/wasip2.rs deleted file mode 100644 index f86034266c26a..0000000000000 --- a/library/std/src/sys/net/connection/socket/wasip2.rs +++ /dev/null @@ -1,408 +0,0 @@ -#![deny(unsafe_op_in_unsafe_fn)] - -pub(super) use libc as netc; -use libc::{c_int, c_void, size_t}; - -use super::{getsockopt, setsockopt, socket_addr_from_c, socket_addr_to_c}; -use crate::ffi::CStr; -use crate::io::{self, BorrowedBuf, BorrowedCursor, IoSlice, IoSliceMut}; -use crate::net::{Shutdown, SocketAddr}; -use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; -use crate::sys::unsupported; -use crate::sys_common::{AsInner, FromInner, IntoInner}; -use crate::time::{Duration, Instant}; -use crate::{cmp, mem, str}; - -#[allow(non_camel_case_types)] -pub type wrlen_t = size_t; - -#[doc(hidden)] -pub trait IsMinusOne { - fn is_minus_one(&self) -> bool; -} - -macro_rules! impl_is_minus_one { - ($($t:ident)*) => ($(impl IsMinusOne for $t { - fn is_minus_one(&self) -> bool { - *self == -1 - } - })*) -} - -impl_is_minus_one! { i8 i16 i32 i64 isize } - -pub fn cvt(t: T) -> crate::io::Result { - if t.is_minus_one() { Err(crate::io::Error::last_os_error()) } else { Ok(t) } -} - -pub fn cvt_r(mut f: F) -> crate::io::Result -where - T: IsMinusOne, - F: FnMut() -> T, -{ - loop { - match cvt(f()) { - Err(ref e) if e.is_interrupted() => {} - other => return other, - } - } -} - -pub fn cvt_gai(err: c_int) -> io::Result<()> { - if err == 0 { - return Ok(()); - } - - if err == netc::EAI_SYSTEM { - return Err(io::Error::last_os_error()); - } - - let detail = unsafe { - str::from_utf8(CStr::from_ptr(netc::gai_strerror(err)).to_bytes()).unwrap().to_owned() - }; - - Err(io::Error::new( - io::ErrorKind::Uncategorized, - &format!("failed to lookup address information: {detail}")[..], - )) -} - -pub fn init() {} - -pub struct WasiSocket(OwnedFd); - -pub struct Socket(WasiSocket); - -impl Socket { - pub fn new(family: c_int, ty: c_int) -> io::Result { - let fd = cvt(unsafe { netc::socket(family, ty, 0) })?; - Ok(unsafe { Self::from_raw_fd(fd) }) - } - - pub fn connect(&self, addr: &SocketAddr) -> io::Result<()> { - let (addr, len) = socket_addr_to_c(addr); - cvt_r(|| unsafe { netc::connect(self.as_raw_fd(), addr.as_ptr(), len) })?; - Ok(()) - } - - pub fn connect_timeout(&self, addr: &SocketAddr, timeout: Duration) -> io::Result<()> { - self.set_nonblocking(true)?; - let r = self.connect(addr); - self.set_nonblocking(false)?; - - match r { - Ok(_) => return Ok(()), - // there's no ErrorKind for EINPROGRESS - Err(ref e) if e.raw_os_error() == Some(netc::EINPROGRESS) => {} - Err(e) => return Err(e), - } - - let mut pollfd = netc::pollfd { fd: self.as_raw_fd(), events: netc::POLLOUT, revents: 0 }; - - if timeout.as_secs() == 0 && timeout.subsec_nanos() == 0 { - return Err(io::Error::ZERO_TIMEOUT); - } - - let start = Instant::now(); - - loop { - let elapsed = start.elapsed(); - if elapsed >= timeout { - return Err(io::const_error!(io::ErrorKind::TimedOut, "connection timed out")); - } - - let timeout = timeout - elapsed; - let mut timeout = timeout - .as_secs() - .saturating_mul(1_000) - .saturating_add(timeout.subsec_nanos() as u64 / 1_000_000); - if timeout == 0 { - timeout = 1; - } - - let timeout = cmp::min(timeout, c_int::MAX as u64) as c_int; - - match unsafe { netc::poll(&mut pollfd, 1, timeout) } { - -1 => { - let err = io::Error::last_os_error(); - if !err.is_interrupted() { - return Err(err); - } - } - 0 => {} - _ => { - // WASI poll does not return POLLHUP or POLLERR in revents. Check if the - // connection actually succeeded and return ok only when the socket is - // ready and no errors were found. - if let Some(e) = self.take_error()? { - return Err(e); - } - - return Ok(()); - } - } - } - } - - pub fn accept( - &self, - storage: *mut netc::sockaddr, - len: *mut netc::socklen_t, - ) -> io::Result { - let fd = cvt_r(|| unsafe { netc::accept(self.as_raw_fd(), storage, len) })?; - Ok(unsafe { Self::from_raw_fd(fd) }) - } - - pub fn duplicate(&self) -> io::Result { - unsupported() - } - - fn recv_with_flags(&self, mut buf: BorrowedCursor<'_>, flags: c_int) -> io::Result<()> { - let ret = cvt(unsafe { - netc::recv( - self.as_raw_fd(), - buf.as_mut().as_mut_ptr() as *mut c_void, - buf.capacity(), - flags, - ) - })?; - unsafe { - buf.advance(ret as usize); - } - Ok(()) - } - - pub fn read(&self, buf: &mut [u8]) -> io::Result { - let mut buf = BorrowedBuf::from(buf); - self.recv_with_flags(buf.unfilled(), 0)?; - Ok(buf.len()) - } - - pub fn peek(&self, buf: &mut [u8]) -> io::Result { - let mut buf = BorrowedBuf::from(buf); - self.recv_with_flags(buf.unfilled(), netc::MSG_PEEK)?; - Ok(buf.len()) - } - - pub fn read_buf(&self, buf: BorrowedCursor<'_>) -> io::Result<()> { - self.recv_with_flags(buf, 0) - } - - pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { - io::default_read_vectored(|b| self.read(b), bufs) - } - - #[inline] - pub fn is_read_vectored(&self) -> bool { - false - } - - fn recv_from_with_flags( - &self, - buf: &mut [u8], - flags: c_int, - ) -> io::Result<(usize, SocketAddr)> { - let mut storage: netc::sockaddr_storage = unsafe { mem::zeroed() }; - let mut addrlen = size_of_val(&storage) as netc::socklen_t; - - let n = cvt(unsafe { - netc::recvfrom( - self.as_raw_fd(), - buf.as_mut_ptr() as *mut c_void, - buf.len(), - flags, - core::ptr::addr_of_mut!(storage) as *mut _, - &mut addrlen, - ) - })?; - Ok((n as usize, unsafe { socket_addr_from_c(&storage, addrlen as usize)? })) - } - - pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { - self.recv_from_with_flags(buf, 0) - } - - pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { - self.recv_from_with_flags(buf, netc::MSG_PEEK) - } - - fn write(&self, buf: &[u8]) -> io::Result { - let len = cmp::min(buf.len(), ::MAX as usize) as wrlen_t; - let ret = cvt(unsafe { - netc::send(self.as_raw(), buf.as_ptr() as *const c_void, len, netc::MSG_NOSIGNAL) - })?; - Ok(ret as usize) - } - - pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { - io::default_write_vectored(|b| self.write(b), bufs) - } - - #[inline] - pub fn is_write_vectored(&self) -> bool { - false - } - - pub fn set_timeout(&self, dur: Option, kind: c_int) -> io::Result<()> { - let timeout = match dur { - Some(dur) => { - if dur.as_secs() == 0 && dur.subsec_nanos() == 0 { - return Err(io::Error::ZERO_TIMEOUT); - } - - let secs = dur.as_secs().try_into().unwrap_or(netc::time_t::MAX); - let mut timeout = netc::timeval { - tv_sec: secs, - tv_usec: dur.subsec_micros() as netc::suseconds_t, - }; - if timeout.tv_sec == 0 && timeout.tv_usec == 0 { - timeout.tv_usec = 1; - } - timeout - } - None => netc::timeval { tv_sec: 0, tv_usec: 0 }, - }; - unsafe { setsockopt(self, netc::SOL_SOCKET, kind, timeout) } - } - - pub fn timeout(&self, kind: c_int) -> io::Result> { - let raw: netc::timeval = unsafe { getsockopt(self, netc::SOL_SOCKET, kind)? }; - if raw.tv_sec == 0 && raw.tv_usec == 0 { - Ok(None) - } else { - let sec = raw.tv_sec as u64; - let nsec = (raw.tv_usec as u32) * 1000; - Ok(Some(Duration::new(sec, nsec))) - } - } - - pub fn shutdown(&self, how: Shutdown) -> io::Result<()> { - let how = match how { - Shutdown::Write => netc::SHUT_WR, - Shutdown::Read => netc::SHUT_RD, - Shutdown::Both => netc::SHUT_RDWR, - }; - cvt(unsafe { netc::shutdown(self.as_raw_fd(), how) })?; - Ok(()) - } - - pub fn set_linger(&self, _linger: Option) -> io::Result<()> { - unsupported() - } - - pub fn linger(&self) -> io::Result> { - unsupported() - } - - pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> { - unsafe { setsockopt(self, netc::IPPROTO_TCP, netc::TCP_NODELAY, nodelay as c_int) } - } - - pub fn nodelay(&self) -> io::Result { - let raw: c_int = unsafe { getsockopt(self, netc::IPPROTO_TCP, netc::TCP_NODELAY)? }; - Ok(raw != 0) - } - - pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { - let mut nonblocking = nonblocking as c_int; - cvt(unsafe { netc::ioctl(self.as_raw_fd(), netc::FIONBIO, &mut nonblocking) }).map(drop) - } - - pub fn take_error(&self) -> io::Result> { - let raw: c_int = unsafe { getsockopt(self, netc::SOL_SOCKET, netc::SO_ERROR)? }; - if raw == 0 { Ok(None) } else { Ok(Some(io::Error::from_raw_os_error(raw as i32))) } - } - - // This is used by sys_common code to abstract over Windows and Unix. - pub fn as_raw(&self) -> RawFd { - self.as_raw_fd() - } -} - -impl AsInner for WasiSocket { - #[inline] - fn as_inner(&self) -> &OwnedFd { - &self.0 - } -} - -impl IntoInner for WasiSocket { - fn into_inner(self) -> OwnedFd { - self.0 - } -} - -impl FromInner for WasiSocket { - fn from_inner(owned_fd: OwnedFd) -> Self { - Self(owned_fd) - } -} - -impl AsFd for WasiSocket { - fn as_fd(&self) -> BorrowedFd<'_> { - self.0.as_fd() - } -} - -impl AsRawFd for WasiSocket { - #[inline] - fn as_raw_fd(&self) -> RawFd { - self.0.as_raw_fd() - } -} - -impl IntoRawFd for WasiSocket { - fn into_raw_fd(self) -> RawFd { - self.0.into_raw_fd() - } -} - -impl FromRawFd for WasiSocket { - unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { - unsafe { Self(FromRawFd::from_raw_fd(raw_fd)) } - } -} - -impl AsInner for Socket { - #[inline] - fn as_inner(&self) -> &WasiSocket { - &self.0 - } -} - -impl IntoInner for Socket { - fn into_inner(self) -> WasiSocket { - self.0 - } -} - -impl FromInner for Socket { - fn from_inner(sock: WasiSocket) -> Socket { - Socket(sock) - } -} - -impl AsFd for Socket { - fn as_fd(&self) -> BorrowedFd<'_> { - self.0.as_fd() - } -} - -impl AsRawFd for Socket { - #[inline] - fn as_raw_fd(&self) -> RawFd { - self.0.as_raw_fd() - } -} - -impl IntoRawFd for Socket { - fn into_raw_fd(self) -> RawFd { - self.0.into_raw_fd() - } -} - -impl FromRawFd for Socket { - unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { - unsafe { Self(FromRawFd::from_raw_fd(raw_fd)) } - } -} diff --git a/library/std/src/sys/net/connection/wasip1.rs b/library/std/src/sys/net/connection/wasip1.rs index 048dafdcd7f7c..3a0ac5552a64b 100644 --- a/library/std/src/sys/net/connection/wasip1.rs +++ b/library/std/src/sys/net/connection/wasip1.rs @@ -4,32 +4,32 @@ use crate::fmt; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, ToSocketAddrs}; use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; -use crate::sys::fd::WasiFd; +use crate::sys::fd::FileDesc; use crate::sys::{err2io, unsupported}; use crate::sys_common::{AsInner, FromInner, IntoInner}; use crate::time::Duration; -pub struct Socket(WasiFd); +pub struct Socket(FileDesc); pub struct TcpStream { inner: Socket, } -impl AsInner for Socket { +impl AsInner for Socket { #[inline] - fn as_inner(&self) -> &WasiFd { + fn as_inner(&self) -> &FileDesc { &self.0 } } -impl IntoInner for Socket { - fn into_inner(self) -> WasiFd { +impl IntoInner for Socket { + fn into_inner(self) -> FileDesc { self.0 } } -impl FromInner for Socket { - fn from_inner(inner: WasiFd) -> Socket { +impl FromInner for Socket { + fn from_inner(inner: FileDesc) -> Socket { Socket(inner) } } @@ -97,7 +97,7 @@ impl TcpStream { } pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { - self.socket().as_inner().read(bufs) + self.socket().as_inner().read_vectored(bufs) } pub fn is_read_vectored(&self) -> bool { @@ -109,7 +109,7 @@ impl TcpStream { } pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { - self.socket().as_inner().write(bufs) + self.socket().as_inner().write_vectored(bufs) } pub fn is_write_vectored(&self) -> bool { diff --git a/library/std/src/sys/pal/mod.rs b/library/std/src/sys/pal/mod.rs index e11df38a8ee68..2bd3ec8188a7f 100644 --- a/library/std/src/sys/pal/mod.rs +++ b/library/std/src/sys/pal/mod.rs @@ -53,13 +53,9 @@ cfg_select! { mod vexos; pub use self::vexos::*; } - all(target_os = "wasi", any(target_env = "p2", target_env = "p3")) => { - mod wasip2; - pub use self::wasip2::*; - } - all(target_os = "wasi", target_env = "p1") => { - mod wasip1; - pub use self::wasip1::*; + target_os = "wasi" => { + mod wasi; + pub use self::wasi::*; } target_family = "wasm" => { mod wasm; diff --git a/library/std/src/sys/pal/unix/time.rs b/library/std/src/sys/pal/unix/time.rs index c207f41cad4b5..24f13853b96b3 100644 --- a/library/std/src/sys/pal/unix/time.rs +++ b/library/std/src/sys/pal/unix/time.rs @@ -1,5 +1,6 @@ use core::num::niche_types::Nanoseconds; +use crate::sys_common::AsInner; use crate::time::Duration; use crate::{fmt, io}; @@ -298,6 +299,12 @@ impl Instant { } } +impl AsInner for Instant { + fn as_inner(&self) -> &Timespec { + &self.t + } +} + impl fmt::Debug for Instant { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Instant") diff --git a/library/std/src/sys/pal/wasip2/cabi_realloc.rs b/library/std/src/sys/pal/wasi/cabi_realloc.rs similarity index 100% rename from library/std/src/sys/pal/wasip2/cabi_realloc.rs rename to library/std/src/sys/pal/wasi/cabi_realloc.rs diff --git a/library/std/src/sys/pal/wasi/helpers.rs b/library/std/src/sys/pal/wasi/helpers.rs new file mode 100644 index 0000000000000..6bc41d469584d --- /dev/null +++ b/library/std/src/sys/pal/wasi/helpers.rs @@ -0,0 +1,63 @@ +#![forbid(unsafe_op_in_unsafe_fn)] + +use crate::io as std_io; + +#[inline] +pub fn is_interrupted(errno: i32) -> bool { + errno == libc::EINTR +} + +pub fn decode_error_kind(errno: i32) -> std_io::ErrorKind { + use std_io::ErrorKind::*; + match errno as libc::c_int { + libc::E2BIG => ArgumentListTooLong, + libc::EADDRINUSE => AddrInUse, + libc::EADDRNOTAVAIL => AddrNotAvailable, + libc::EBUSY => ResourceBusy, + libc::ECONNABORTED => ConnectionAborted, + libc::ECONNREFUSED => ConnectionRefused, + libc::ECONNRESET => ConnectionReset, + libc::EDEADLK => Deadlock, + libc::EDQUOT => QuotaExceeded, + libc::EEXIST => AlreadyExists, + libc::EFBIG => FileTooLarge, + libc::EHOSTUNREACH => HostUnreachable, + libc::EINTR => Interrupted, + libc::EINVAL => InvalidInput, + libc::EISDIR => IsADirectory, + libc::ELOOP => FilesystemLoop, + libc::ENOENT => NotFound, + libc::ENOMEM => OutOfMemory, + libc::ENOSPC => StorageFull, + libc::ENOSYS => Unsupported, + libc::EMLINK => TooManyLinks, + libc::ENAMETOOLONG => InvalidFilename, + libc::ENETDOWN => NetworkDown, + libc::ENETUNREACH => NetworkUnreachable, + libc::ENOTCONN => NotConnected, + libc::ENOTDIR => NotADirectory, + libc::EPIPE => BrokenPipe, + libc::EROFS => ReadOnlyFilesystem, + libc::ESPIPE => NotSeekable, + libc::ESTALE => StaleNetworkFileHandle, + libc::ETIMEDOUT => TimedOut, + libc::ETXTBSY => ExecutableFileBusy, + libc::EXDEV => CrossesDevices, + libc::EINPROGRESS => InProgress, + libc::EOPNOTSUPP => Unsupported, + libc::EACCES | libc::EPERM => PermissionDenied, + libc::EWOULDBLOCK => WouldBlock, + + _ => Uncategorized, + } +} + +pub fn abort_internal() -> ! { + unsafe { libc::abort() } +} + +#[inline] +#[cfg(target_env = "p1")] +pub(crate) fn err2io(err: wasi::Errno) -> std_io::Error { + std_io::Error::from_raw_os_error(err.raw().into()) +} diff --git a/library/std/src/sys/pal/wasi/mod.rs b/library/std/src/sys/pal/wasi/mod.rs new file mode 100644 index 0000000000000..42629a91e1afe --- /dev/null +++ b/library/std/src/sys/pal/wasi/mod.rs @@ -0,0 +1,39 @@ +//! System bindings for the WASI platforms. +//! +//! This module contains the facade (aka platform-specific) implementations of +//! OS level functionality for WASI. Currently this includes both WASIp1 and +//! WASIp2. + +#[allow(unused)] +#[path = "../wasm/atomics/futex.rs"] +pub mod futex; + +pub mod os; +#[path = "../unsupported/pipe.rs"] +pub mod pipe; +pub mod stack_overflow; +#[path = "../unix/time.rs"] +pub mod time; + +#[path = "../unsupported/common.rs"] +#[deny(unsafe_op_in_unsafe_fn)] +#[allow(unused)] +mod common; + +pub use common::*; + +mod helpers; + +// The following exports are listed individually to work around Rust's glob +// import conflict rules. If we glob export `helpers` and `common` together, +// then the compiler complains about conflicts. + +#[cfg(target_env = "p1")] +pub(crate) use helpers::err2io; +pub(crate) use helpers::{abort_internal, decode_error_kind, is_interrupted}; +#[cfg(not(target_env = "p1"))] +pub use os::IsMinusOne; +pub use os::{cvt, cvt_r}; + +#[cfg(not(target_env = "p1"))] +mod cabi_realloc; diff --git a/library/std/src/sys/pal/wasip1/os.rs b/library/std/src/sys/pal/wasi/os.rs similarity index 88% rename from library/std/src/sys/pal/wasip1/os.rs rename to library/std/src/sys/pal/wasi/os.rs index 151ba254ec4cf..367c63dfc59e7 100644 --- a/library/std/src/sys/pal/wasip1/os.rs +++ b/library/std/src/sys/pal/wasi/os.rs @@ -19,13 +19,20 @@ pub mod libc { } } +unsafe extern "C" { + #[thread_local] + #[link_name = "errno"] + static mut libc_errno: libc::c_int; +} + pub fn errno() -> i32 { - unsafe extern "C" { - #[thread_local] - static errno: libc::c_int; - } + unsafe { libc_errno as i32 } +} - unsafe { errno as i32 } +pub fn set_errno(val: i32) { + unsafe { + libc_errno = val; + } } pub fn error_string(errno: i32) -> String { @@ -149,3 +156,16 @@ impl_is_minus_one! { i8 i16 i32 i64 isize } pub fn cvt(t: T) -> io::Result { if t.is_minus_one() { Err(io::Error::last_os_error()) } else { Ok(t) } } + +pub fn cvt_r(mut f: F) -> crate::io::Result +where + T: IsMinusOne, + F: FnMut() -> T, +{ + loop { + match cvt(f()) { + Err(ref e) if e.is_interrupted() => {} + other => return other, + } + } +} diff --git a/library/std/src/sys/pal/wasi/stack_overflow.rs b/library/std/src/sys/pal/wasi/stack_overflow.rs new file mode 100644 index 0000000000000..9fef1ef5ab58f --- /dev/null +++ b/library/std/src/sys/pal/wasi/stack_overflow.rs @@ -0,0 +1,7 @@ +pub struct Handler; + +impl Handler { + pub unsafe fn new() -> Handler { + Handler + } +} diff --git a/library/std/src/sys/pal/wasip1/helpers.rs b/library/std/src/sys/pal/wasip1/helpers.rs deleted file mode 100644 index 404747f0dc756..0000000000000 --- a/library/std/src/sys/pal/wasip1/helpers.rs +++ /dev/null @@ -1,114 +0,0 @@ -#![forbid(unsafe_op_in_unsafe_fn)] - -use crate::io as std_io; - -#[inline] -pub fn is_interrupted(errno: i32) -> bool { - errno == wasi::ERRNO_INTR.raw().into() -} - -pub fn decode_error_kind(errno: i32) -> std_io::ErrorKind { - use std_io::ErrorKind; - - let Ok(errno) = u16::try_from(errno) else { - return ErrorKind::Uncategorized; - }; - - macro_rules! match_errno { - ($($($errno:ident)|+ => $errkind:ident),*, _ => $wildcard:ident $(,)?) => { - match errno { - $(e if $(e == ::wasi::$errno.raw())||+ => ErrorKind::$errkind),*, - _ => ErrorKind::$wildcard, - } - }; - } - - match_errno! { - ERRNO_2BIG => ArgumentListTooLong, - ERRNO_ACCES => PermissionDenied, - ERRNO_ADDRINUSE => AddrInUse, - ERRNO_ADDRNOTAVAIL => AddrNotAvailable, - ERRNO_AFNOSUPPORT => Unsupported, - ERRNO_AGAIN => WouldBlock, - // ALREADY => "connection already in progress", - // BADF => "bad file descriptor", - // BADMSG => "bad message", - ERRNO_BUSY => ResourceBusy, - // CANCELED => "operation canceled", - // CHILD => "no child processes", - ERRNO_CONNABORTED => ConnectionAborted, - ERRNO_CONNREFUSED => ConnectionRefused, - ERRNO_CONNRESET => ConnectionReset, - ERRNO_DEADLK => Deadlock, - // DESTADDRREQ => "destination address required", - ERRNO_DOM => InvalidInput, - // DQUOT => /* reserved */, - ERRNO_EXIST => AlreadyExists, - // FAULT => "bad address", - ERRNO_FBIG => FileTooLarge, - ERRNO_HOSTUNREACH => HostUnreachable, - // IDRM => "identifier removed", - // ILSEQ => "illegal byte sequence", - // INPROGRESS => "operation in progress", - ERRNO_INTR => Interrupted, - ERRNO_INVAL => InvalidInput, - ERRNO_IO => Uncategorized, - // ISCONN => "socket is connected", - ERRNO_ISDIR => IsADirectory, - ERRNO_LOOP => FilesystemLoop, - // MFILE => "file descriptor value too large", - ERRNO_MLINK => TooManyLinks, - // MSGSIZE => "message too large", - // MULTIHOP => /* reserved */, - ERRNO_NAMETOOLONG => InvalidFilename, - ERRNO_NETDOWN => NetworkDown, - // NETRESET => "connection aborted by network", - ERRNO_NETUNREACH => NetworkUnreachable, - // NFILE => "too many files open in system", - // NOBUFS => "no buffer space available", - ERRNO_NODEV => NotFound, - ERRNO_NOENT => NotFound, - // NOEXEC => "executable file format error", - // NOLCK => "no locks available", - // NOLINK => /* reserved */, - ERRNO_NOMEM => OutOfMemory, - // NOMSG => "no message of the desired type", - // NOPROTOOPT => "protocol not available", - ERRNO_NOSPC => StorageFull, - ERRNO_NOSYS => Unsupported, - ERRNO_NOTCONN => NotConnected, - ERRNO_NOTDIR => NotADirectory, - ERRNO_NOTEMPTY => DirectoryNotEmpty, - // NOTRECOVERABLE => "state not recoverable", - // NOTSOCK => "not a socket", - ERRNO_NOTSUP => Unsupported, - // NOTTY => "inappropriate I/O control operation", - ERRNO_NXIO => NotFound, - // OVERFLOW => "value too large to be stored in data type", - // OWNERDEAD => "previous owner died", - ERRNO_PERM => PermissionDenied, - ERRNO_PIPE => BrokenPipe, - // PROTO => "protocol error", - ERRNO_PROTONOSUPPORT => Unsupported, - // PROTOTYPE => "protocol wrong type for socket", - // RANGE => "result too large", - ERRNO_ROFS => ReadOnlyFilesystem, - ERRNO_SPIPE => NotSeekable, - ERRNO_SRCH => NotFound, - // STALE => /* reserved */, - ERRNO_TIMEDOUT => TimedOut, - ERRNO_TXTBSY => ResourceBusy, - ERRNO_XDEV => CrossesDevices, - ERRNO_NOTCAPABLE => PermissionDenied, - _ => Uncategorized, - } -} - -pub fn abort_internal() -> ! { - unsafe { libc::abort() } -} - -#[inline] -pub(crate) fn err2io(err: wasi::Errno) -> std_io::Error { - std_io::Error::from_raw_os_error(err.raw().into()) -} diff --git a/library/std/src/sys/pal/wasip1/mod.rs b/library/std/src/sys/pal/wasip1/mod.rs deleted file mode 100644 index ae5da3c1f77be..0000000000000 --- a/library/std/src/sys/pal/wasip1/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! System bindings for the wasm/web platform -//! -//! This module contains the facade (aka platform-specific) implementations of -//! OS level functionality for wasm. -//! -//! This is all super highly experimental and not actually intended for -//! wide/production use yet, it's still all in the experimental category. This -//! will likely change over time. -//! -//! Currently all functions here are basically stubs that immediately return -//! errors. The hope is that with a portability lint we can turn actually just -//! remove all this and just omit parts of the standard library if we're -//! compiling for wasm. That way it's a compile time error for something that's -//! guaranteed to be a runtime error! - -#[allow(unused)] -#[path = "../wasm/atomics/futex.rs"] -pub mod futex; - -pub mod os; -#[path = "../unsupported/pipe.rs"] -pub mod pipe; -pub mod time; - -#[path = "../unsupported/common.rs"] -#[deny(unsafe_op_in_unsafe_fn)] -#[allow(unused)] -mod common; - -pub use common::*; - -mod helpers; - -// The following exports are listed individually to work around Rust's glob -// import conflict rules. If we glob export `helpers` and `common` together, -// then the compiler complains about conflicts. - -pub(crate) use helpers::{abort_internal, decode_error_kind, err2io, is_interrupted}; diff --git a/library/std/src/sys/pal/wasip1/time.rs b/library/std/src/sys/pal/wasip1/time.rs deleted file mode 100644 index 0d8d0b59ac14a..0000000000000 --- a/library/std/src/sys/pal/wasip1/time.rs +++ /dev/null @@ -1,65 +0,0 @@ -#![forbid(unsafe_op_in_unsafe_fn)] - -use crate::time::Duration; - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] -pub struct Instant(Duration); - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] -pub struct SystemTime(Duration); - -pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::from_secs(0)); - -fn current_time(clock: wasi::Clockid) -> Duration { - let ts = unsafe { - wasi::clock_time_get( - clock, 1, // precision... seems ignored though? - ) - .unwrap() - }; - Duration::new((ts / 1_000_000_000) as u64, (ts % 1_000_000_000) as u32) -} - -impl Instant { - pub fn now() -> Instant { - Instant(current_time(wasi::CLOCKID_MONOTONIC)) - } - - pub fn checked_sub_instant(&self, other: &Instant) -> Option { - self.0.checked_sub(other.0) - } - - pub fn checked_add_duration(&self, other: &Duration) -> Option { - Some(Instant(self.0.checked_add(*other)?)) - } - - pub fn checked_sub_duration(&self, other: &Duration) -> Option { - Some(Instant(self.0.checked_sub(*other)?)) - } -} - -impl SystemTime { - pub fn now() -> SystemTime { - SystemTime(current_time(wasi::CLOCKID_REALTIME)) - } - - pub fn from_wasi_timestamp(ts: wasi::Timestamp) -> SystemTime { - SystemTime(Duration::from_nanos(ts)) - } - - pub fn to_wasi_timestamp(&self) -> Option { - self.0.as_nanos().try_into().ok() - } - - pub fn sub_time(&self, other: &SystemTime) -> Result { - self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0) - } - - pub fn checked_add_duration(&self, other: &Duration) -> Option { - Some(SystemTime(self.0.checked_add(*other)?)) - } - - pub fn checked_sub_duration(&self, other: &Duration) -> Option { - Some(SystemTime(self.0.checked_sub(*other)?)) - } -} diff --git a/library/std/src/sys/pal/wasip2/mod.rs b/library/std/src/sys/pal/wasip2/mod.rs deleted file mode 100644 index c1d89da2677c9..0000000000000 --- a/library/std/src/sys/pal/wasip2/mod.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! System bindings for the wasi preview 2 target. -//! -//! This is the next evolution of the original wasi target, and is intended to -//! replace that target over time. -//! -//! To begin with, this target mirrors the wasi target 1 to 1, but over -//! time this will change significantly. - -#[allow(unused)] -#[path = "../wasm/atomics/futex.rs"] -pub mod futex; - -#[path = "../wasip1/os.rs"] -pub mod os; -#[path = "../unsupported/pipe.rs"] -pub mod pipe; -pub mod time; - -#[path = "../unsupported/common.rs"] -#[deny(unsafe_op_in_unsafe_fn)] -#[allow(unused)] -mod common; - -pub use common::*; - -#[path = "../wasip1/helpers.rs"] -mod helpers; - -// The following exports are listed individually to work around Rust's glob -// import conflict rules. If we glob export `helpers` and `common` together, -// then the compiler complains about conflicts. - -pub(crate) use helpers::{abort_internal, decode_error_kind, err2io, is_interrupted}; - -mod cabi_realloc; diff --git a/library/std/src/sys/pal/wasip2/time.rs b/library/std/src/sys/pal/wasip2/time.rs deleted file mode 100644 index 434891839944d..0000000000000 --- a/library/std/src/sys/pal/wasip2/time.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::time::Duration; - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] -pub struct Instant(Duration); - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] -pub struct SystemTime(Duration); - -pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::from_secs(0)); - -impl Instant { - pub fn now() -> Instant { - Instant(Duration::from_nanos(wasip2::clocks::monotonic_clock::now())) - } - - pub fn checked_sub_instant(&self, other: &Instant) -> Option { - self.0.checked_sub(other.0) - } - - pub fn checked_add_duration(&self, other: &Duration) -> Option { - Some(Instant(self.0.checked_add(*other)?)) - } - - pub fn checked_sub_duration(&self, other: &Duration) -> Option { - Some(Instant(self.0.checked_sub(*other)?)) - } - - pub(crate) fn as_duration(&self) -> &Duration { - &self.0 - } -} - -impl SystemTime { - pub fn now() -> SystemTime { - let now = wasip2::clocks::wall_clock::now(); - SystemTime(Duration::new(now.seconds, now.nanoseconds)) - } - - pub fn from_wasi_timestamp(ts: wasi::Timestamp) -> SystemTime { - SystemTime(Duration::from_nanos(ts)) - } - - pub fn to_wasi_timestamp(&self) -> Option { - self.0.as_nanos().try_into().ok() - } - - pub fn sub_time(&self, other: &SystemTime) -> Result { - self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0) - } - - pub fn checked_add_duration(&self, other: &Duration) -> Option { - Some(SystemTime(self.0.checked_add(*other)?)) - } - - pub fn checked_sub_duration(&self, other: &Duration) -> Option { - Some(SystemTime(self.0.checked_sub(*other)?)) - } -} diff --git a/library/std/src/sys/stdio/mod.rs b/library/std/src/sys/stdio/mod.rs index d51ea9ad726b5..86d0f3fe49cb3 100644 --- a/library/std/src/sys/stdio/mod.rs +++ b/library/std/src/sys/stdio/mod.rs @@ -1,7 +1,7 @@ #![forbid(unsafe_op_in_unsafe_fn)] cfg_select! { - any(target_family = "unix", target_os = "hermit") => { + any(target_family = "unix", target_os = "hermit", target_os = "wasi") => { mod unix; pub use unix::*; } @@ -37,14 +37,6 @@ cfg_select! { mod vexos; pub use vexos::*; } - all(target_os = "wasi", target_env = "p1") => { - mod wasip1; - pub use wasip1::*; - } - all(target_os = "wasi", any(target_env = "p2", target_env = "p3")) => { - mod wasip2; - pub use wasip2::*; - } target_os = "xous" => { mod xous; pub use xous::*; diff --git a/library/std/src/sys/stdio/unix.rs b/library/std/src/sys/stdio/unix.rs index 8535e3539e9fd..ffed376a8e96e 100644 --- a/library/std/src/sys/stdio/unix.rs +++ b/library/std/src/sys/stdio/unix.rs @@ -1,14 +1,11 @@ #[cfg(target_os = "hermit")] use hermit_abi::{EBADF, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO}; -#[cfg(target_family = "unix")] +#[cfg(any(target_family = "unix", target_os = "wasi"))] use libc::{EBADF, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO}; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; use crate::mem::ManuallyDrop; -#[cfg(target_os = "hermit")] -use crate::os::hermit::io::FromRawFd; -#[cfg(target_family = "unix")] -use crate::os::unix::io::FromRawFd; +use crate::os::fd::FromRawFd; use crate::sys::fd::FileDesc; pub struct Stdin; diff --git a/library/std/src/sys/stdio/wasip1.rs b/library/std/src/sys/stdio/wasip1.rs deleted file mode 100644 index b70efd026f945..0000000000000 --- a/library/std/src/sys/stdio/wasip1.rs +++ /dev/null @@ -1,117 +0,0 @@ -#![forbid(unsafe_op_in_unsafe_fn)] - -use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; -use crate::mem::ManuallyDrop; -use crate::os::raw; -use crate::os::wasi::io::{AsRawFd, FromRawFd}; -use crate::sys::fd::WasiFd; - -pub struct Stdin; -pub struct Stdout; -pub struct Stderr; - -impl Stdin { - pub const fn new() -> Stdin { - Stdin - } -} - -impl AsRawFd for Stdin { - #[inline] - fn as_raw_fd(&self) -> raw::c_int { - 0 - } -} - -impl io::Read for Stdin { - fn read(&mut self, data: &mut [u8]) -> io::Result { - self.read_vectored(&mut [IoSliceMut::new(data)]) - } - - fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> io::Result<()> { - ManuallyDrop::new(unsafe { WasiFd::from_raw_fd(self.as_raw_fd()) }).read_buf(buf) - } - - fn read_vectored(&mut self, data: &mut [IoSliceMut<'_>]) -> io::Result { - ManuallyDrop::new(unsafe { WasiFd::from_raw_fd(self.as_raw_fd()) }).read(data) - } - - #[inline] - fn is_read_vectored(&self) -> bool { - true - } -} - -impl Stdout { - pub const fn new() -> Stdout { - Stdout - } -} - -impl AsRawFd for Stdout { - #[inline] - fn as_raw_fd(&self) -> raw::c_int { - 1 - } -} - -impl io::Write for Stdout { - fn write(&mut self, data: &[u8]) -> io::Result { - self.write_vectored(&[IoSlice::new(data)]) - } - - fn write_vectored(&mut self, data: &[IoSlice<'_>]) -> io::Result { - ManuallyDrop::new(unsafe { WasiFd::from_raw_fd(self.as_raw_fd()) }).write(data) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl Stderr { - pub const fn new() -> Stderr { - Stderr - } -} - -impl AsRawFd for Stderr { - #[inline] - fn as_raw_fd(&self) -> raw::c_int { - 2 - } -} - -impl io::Write for Stderr { - fn write(&mut self, data: &[u8]) -> io::Result { - self.write_vectored(&[IoSlice::new(data)]) - } - - fn write_vectored(&mut self, data: &[IoSlice<'_>]) -> io::Result { - ManuallyDrop::new(unsafe { WasiFd::from_raw_fd(self.as_raw_fd()) }).write(data) - } - - #[inline] - fn is_write_vectored(&self) -> bool { - true - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; - -pub fn is_ebadf(err: &io::Error) -> bool { - err.raw_os_error() == Some(wasi::ERRNO_BADF.raw().into()) -} - -pub fn panic_output() -> Option { - Some(Stderr::new()) -} diff --git a/library/std/src/sys/stdio/wasip2.rs b/library/std/src/sys/stdio/wasip2.rs deleted file mode 100644 index 1fcb49a083dd0..0000000000000 --- a/library/std/src/sys/stdio/wasip2.rs +++ /dev/null @@ -1,120 +0,0 @@ -use wasip2::cli; -use wasip2::io::streams::{Error, InputStream, OutputStream, StreamError}; - -use crate::io::{self, BorrowedBuf, BorrowedCursor}; - -pub struct Stdin(Option); -pub struct Stdout(Option); -pub struct Stderr(Option); - -fn error_to_io(err: Error) -> io::Error { - // There exists a function in `wasi:filesystem` to optionally acquire an - // error code from an error, but the streams in use in this module are - // exclusively used with stdio meaning that a filesystem error is not - // possible here. - // - // In lieu of an error code, which WASIp2 does not specify, this instead - // carries along the `to_debug_string` implementation that the host - // supplies. If this becomes too expensive in the future this could also - // become `io::Error::from_raw_os_error(libc::EIO)` or similar. - io::Error::new(io::ErrorKind::Other, err.to_debug_string()) -} - -impl Stdin { - pub const fn new() -> Stdin { - Stdin(None) - } - - fn stream(&mut self) -> &InputStream { - self.0.get_or_insert_with(cli::stdin::get_stdin) - } -} - -impl io::Read for Stdin { - fn read(&mut self, data: &mut [u8]) -> io::Result { - let mut buf = BorrowedBuf::from(data); - self.read_buf(buf.unfilled())?; - Ok(buf.len()) - } - - fn read_buf(&mut self, mut buf: BorrowedCursor<'_>) -> io::Result<()> { - match self.stream().blocking_read(u64::try_from(buf.capacity()).unwrap()) { - Ok(result) => { - buf.append(&result); - Ok(()) - } - Err(StreamError::Closed) => Ok(()), - Err(StreamError::LastOperationFailed(e)) => Err(error_to_io(e)), - } - } -} - -impl Stdout { - pub const fn new() -> Stdout { - Stdout(None) - } - - fn stream(&mut self) -> &OutputStream { - self.0.get_or_insert_with(cli::stdout::get_stdout) - } -} - -fn write(stream: &OutputStream, buf: &[u8]) -> io::Result { - // WASIp2's `blocking_write_and_flush` function is defined as accepting no - // more than 4096 bytes. Larger writes can be issued by manually using - // `check_write`, `write`, and `blocking_flush`, but for now just go ahead - // and use `blocking_write_and_flush` and report a short write and let a - // higher level loop over the result. - const MAX: usize = 4096; - let buf = &buf[..buf.len().min(MAX)]; - match stream.blocking_write_and_flush(buf) { - Ok(()) => Ok(buf.len()), - Err(StreamError::Closed) => Ok(0), - Err(StreamError::LastOperationFailed(e)) => Err(error_to_io(e)), - } -} - -impl io::Write for Stdout { - fn write(&mut self, data: &[u8]) -> io::Result { - write(self.stream(), data) - } - - fn flush(&mut self) -> io::Result<()> { - // Note that `OutputStream` has a `flush` function but for stdio all - // writes are accompanied with a flush which means that this flush - // doesn't need to do anything. - Ok(()) - } -} - -impl Stderr { - pub const fn new() -> Stderr { - Stderr(None) - } - - fn stream(&mut self) -> &OutputStream { - self.0.get_or_insert_with(cli::stderr::get_stderr) - } -} - -impl io::Write for Stderr { - fn write(&mut self, data: &[u8]) -> io::Result { - write(self.stream(), data) - } - - fn flush(&mut self) -> io::Result<()> { - // See `Stdout::flush` for why this is a noop. - Ok(()) - } -} - -pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; - -pub fn is_ebadf(_err: &io::Error) -> bool { - // WASIp2 stdio streams are always available so ebadf never shows up. - false -} - -pub fn panic_output() -> Option { - Some(Stderr::new()) -} diff --git a/library/std/src/sys/thread/mod.rs b/library/std/src/sys/thread/mod.rs index b98be62be0ad2..cb6bf6518f81e 100644 --- a/library/std/src/sys/thread/mod.rs +++ b/library/std/src/sys/thread/mod.rs @@ -48,7 +48,7 @@ cfg_select! { mod unsupported; pub use unsupported::{Thread, current_os_id, set_name, yield_now, DEFAULT_MIN_STACK_SIZE}; } - target_family = "unix" => { + any(target_family = "unix", target_os = "wasi") => { mod unix; pub use unix::{Thread, available_parallelism, current_os_id, sleep, yield_now, DEFAULT_MIN_STACK_SIZE}; #[cfg(not(any( @@ -58,6 +58,7 @@ cfg_select! { target_os = "redox", target_os = "hurd", target_os = "aix", + target_os = "wasi", )))] pub use unix::set_name; #[cfg(any( @@ -71,6 +72,7 @@ cfg_select! { target_os = "hurd", target_os = "fuchsia", target_os = "vxworks", + target_os = "wasi", ))] pub use unix::sleep_until; #[expect(dead_code)] @@ -82,6 +84,7 @@ cfg_select! { target_os = "redox", target_os = "hurd", target_os = "aix", + target_os = "wasi", ))] pub use unsupported::set_name; } @@ -92,27 +95,6 @@ cfg_select! { mod unsupported; pub use unsupported::{Thread, available_parallelism, current_os_id, set_name, DEFAULT_MIN_STACK_SIZE}; } - all(target_os = "wasi", target_env = "p1") => { - mod wasip1; - pub use wasip1::{DEFAULT_MIN_STACK_SIZE, sleep, yield_now}; - #[cfg(target_feature = "atomics")] - pub use wasip1::{Thread, available_parallelism}; - #[expect(dead_code)] - mod unsupported; - pub use unsupported::{current_os_id, set_name}; - #[cfg(not(target_feature = "atomics"))] - pub use unsupported::{Thread, available_parallelism}; - } - all(target_os = "wasi", any(target_env = "p2", target_env = "p3")) => { - mod wasip2; - pub use wasip2::{sleep, sleep_until}; - #[expect(dead_code)] - mod unsupported; - // Note that unlike WASIp1 even if the wasm `atomics` feature is enabled - // there is no support for threads, not even experimentally, not even in - // wasi-libc. Thus this is unconditionally unsupported. - pub use unsupported::{Thread, available_parallelism, current_os_id, set_name, yield_now, DEFAULT_MIN_STACK_SIZE}; - } all(target_family = "wasm", target_feature = "atomics") => { mod wasm; pub use wasm::sleep; @@ -150,7 +132,7 @@ cfg_select! { target_os = "hurd", target_os = "fuchsia", target_os = "vxworks", - all(target_os = "wasi", not(target_env = "p1")), + target_os = "wasi", )))] pub fn sleep_until(deadline: crate::time::Instant) { use crate::time::Instant; diff --git a/library/std/src/sys/thread/unix.rs b/library/std/src/sys/thread/unix.rs index 4adc94d0e5c9e..18cdea0e1b1b3 100644 --- a/library/std/src/sys/thread/unix.rs +++ b/library/std/src/sys/thread/unix.rs @@ -5,6 +5,7 @@ target_os = "redox", target_os = "hurd", target_os = "aix", + target_os = "wasi", )))] use crate::ffi::CStr; use crate::mem::{self, DropGuard, ManuallyDrop}; @@ -127,6 +128,7 @@ impl Thread { assert!(ret == 0, "failed to join thread: {}", io::Error::from_raw_os_error(ret)); } + #[cfg(not(target_os = "wasi"))] pub fn id(&self) -> libc::pthread_t { self.id } @@ -588,6 +590,7 @@ pub fn sleep(dur: Duration) { target_os = "hurd", target_os = "fuchsia", target_os = "vxworks", + target_os = "wasi", ))] pub fn sleep_until(deadline: crate::time::Instant) { use crate::time::Instant; diff --git a/library/std/src/sys/thread/wasip1.rs b/library/std/src/sys/thread/wasip1.rs deleted file mode 100644 index 9287a9c5485cc..0000000000000 --- a/library/std/src/sys/thread/wasip1.rs +++ /dev/null @@ -1,183 +0,0 @@ -#![forbid(unsafe_op_in_unsafe_fn)] - -#[cfg(target_feature = "atomics")] -use crate::io; -use crate::mem; -#[cfg(target_feature = "atomics")] -use crate::num::NonZero; -#[cfg(target_feature = "atomics")] -use crate::sys::os; -#[cfg(target_feature = "atomics")] -use crate::thread::ThreadInit; -use crate::time::Duration; -#[cfg(target_feature = "atomics")] -use crate::{cmp, ptr}; - -// Add a few symbols not in upstream `libc` just yet. -#[cfg(target_feature = "atomics")] -mod libc { - pub use libc::*; - - pub use crate::ffi; - - // defined in wasi-libc - // https://github.com/WebAssembly/wasi-libc/blob/a6f871343313220b76009827ed0153586361c0d5/libc-top-half/musl/include/alltypes.h.in#L108 - #[repr(C)] - union pthread_attr_union { - __i: [ffi::c_int; if size_of::() == 8 { 14 } else { 9 }], - __vi: [ffi::c_int; if size_of::() == 8 { 14 } else { 9 }], - __s: [ffi::c_ulong; if size_of::() == 8 { 7 } else { 9 }], - } - - #[repr(C)] - pub struct pthread_attr_t { - __u: pthread_attr_union, - } - - #[allow(non_camel_case_types)] - pub type pthread_t = *mut ffi::c_void; - - pub const _SC_NPROCESSORS_ONLN: ffi::c_int = 84; - - unsafe extern "C" { - pub fn pthread_create( - native: *mut pthread_t, - attr: *const pthread_attr_t, - f: extern "C" fn(*mut ffi::c_void) -> *mut ffi::c_void, - value: *mut ffi::c_void, - ) -> ffi::c_int; - pub fn pthread_join(native: pthread_t, value: *mut *mut ffi::c_void) -> ffi::c_int; - pub fn pthread_attr_init(attrp: *mut pthread_attr_t) -> ffi::c_int; - pub fn pthread_attr_setstacksize( - attr: *mut pthread_attr_t, - stack_size: libc::size_t, - ) -> ffi::c_int; - pub fn pthread_attr_destroy(attr: *mut pthread_attr_t) -> ffi::c_int; - pub fn pthread_detach(thread: pthread_t) -> ffi::c_int; - } -} - -#[cfg(target_feature = "atomics")] -pub struct Thread { - id: libc::pthread_t, -} - -#[cfg(target_feature = "atomics")] -impl Drop for Thread { - fn drop(&mut self) { - let ret = unsafe { libc::pthread_detach(self.id) }; - debug_assert_eq!(ret, 0); - } -} - -pub const DEFAULT_MIN_STACK_SIZE: usize = 1024 * 1024; - -#[cfg(target_feature = "atomics")] -impl Thread { - // unsafe: see thread::Builder::spawn_unchecked for safety requirements - pub unsafe fn new(stack: usize, init: Box) -> io::Result { - let data = Box::into_raw(init); - let mut native: libc::pthread_t = unsafe { mem::zeroed() }; - let mut attr: libc::pthread_attr_t = unsafe { mem::zeroed() }; - assert_eq!(unsafe { libc::pthread_attr_init(&mut attr) }, 0); - - let stack_size = cmp::max(stack, DEFAULT_MIN_STACK_SIZE); - - match unsafe { libc::pthread_attr_setstacksize(&mut attr, stack_size) } { - 0 => {} - n => { - assert_eq!(n, libc::EINVAL); - // EINVAL means |stack_size| is either too small or not a - // multiple of the system page size. Because it's definitely - // >= PTHREAD_STACK_MIN, it must be an alignment issue. - // Round up to the nearest page and try again. - let page_size = os::page_size(); - let stack_size = - (stack_size + page_size - 1) & (-(page_size as isize - 1) as usize - 1); - assert_eq!(unsafe { libc::pthread_attr_setstacksize(&mut attr, stack_size) }, 0); - } - }; - - let ret = unsafe { libc::pthread_create(&mut native, &attr, thread_start, data as *mut _) }; - // Note: if the thread creation fails and this assert fails, then data will - // be leaked. However, an alternative design could cause double-free - // which is clearly worse. - assert_eq!(unsafe { libc::pthread_attr_destroy(&mut attr) }, 0); - - return if ret != 0 { - // The thread failed to start and as a result data was not consumed. Therefore, it is - // safe to reconstruct the box so that it gets deallocated. - unsafe { - drop(Box::from_raw(data)); - } - Err(io::Error::from_raw_os_error(ret)) - } else { - Ok(Thread { id: native }) - }; - - extern "C" fn thread_start(data: *mut libc::c_void) -> *mut libc::c_void { - // SAFETY: we are simply recreating the box that was leaked earlier. - let init = unsafe { Box::from_raw(data as *mut ThreadInit) }; - let rust_start = init.init(); - rust_start(); - ptr::null_mut() - } - } - - pub fn join(self) { - let id = mem::ManuallyDrop::new(self).id; - let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; - if ret != 0 { - rtabort!("failed to join thread: {}", io::Error::from_raw_os_error(ret)); - } - } -} - -#[cfg(target_feature = "atomics")] -pub fn available_parallelism() -> io::Result> { - match unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) } { - -1 => Err(io::Error::last_os_error()), - cpus => NonZero::new(cpus as usize).ok_or(io::Error::UNKNOWN_THREAD_COUNT), - } -} - -pub fn yield_now() { - let ret = unsafe { wasi::sched_yield() }; - debug_assert_eq!(ret, Ok(())); -} - -pub fn sleep(dur: Duration) { - let mut nanos = dur.as_nanos(); - while nanos > 0 { - const USERDATA: wasi::Userdata = 0x0123_45678; - - let clock = wasi::SubscriptionClock { - id: wasi::CLOCKID_MONOTONIC, - timeout: u64::try_from(nanos).unwrap_or(u64::MAX), - precision: 0, - flags: 0, - }; - nanos -= u128::from(clock.timeout); - - let in_ = wasi::Subscription { - userdata: USERDATA, - u: wasi::SubscriptionU { tag: 0, u: wasi::SubscriptionUU { clock } }, - }; - unsafe { - let mut event: wasi::Event = mem::zeroed(); - let res = wasi::poll_oneoff(&in_, &mut event, 1); - match (res, event) { - ( - Ok(1), - wasi::Event { - userdata: USERDATA, - error: wasi::ERRNO_SUCCESS, - type_: wasi::EVENTTYPE_CLOCK, - .. - }, - ) => {} - _ => panic!("thread::sleep(): unexpected result of poll_oneoff"), - } - } - } -} diff --git a/library/std/src/sys/thread/wasip2.rs b/library/std/src/sys/thread/wasip2.rs deleted file mode 100644 index 420cad2a5e4ab..0000000000000 --- a/library/std/src/sys/thread/wasip2.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::time::{Duration, Instant}; - -pub fn sleep(dur: Duration) { - // Sleep in increments of `u64::MAX` nanoseconds until the `dur` is - // entirely drained. - let mut remaining = dur.as_nanos(); - while remaining > 0 { - let amt = u64::try_from(remaining).unwrap_or(u64::MAX); - wasip2::clocks::monotonic_clock::subscribe_duration(amt).block(); - remaining -= u128::from(amt); - } -} - -pub fn sleep_until(deadline: Instant) { - match u64::try_from(deadline.into_inner().as_duration().as_nanos()) { - // If the point in time we're sleeping to fits within a 64-bit - // number of nanoseconds then directly use `subscribe_instant`. - Ok(deadline) => { - wasip2::clocks::monotonic_clock::subscribe_instant(deadline).block(); - } - // ... otherwise we're sleeping for 500+ years relative to the - // "start" of what the system is using as a clock so speed/accuracy - // is not so much of a concern. Use `sleep` instead. - Err(_) => { - let now = Instant::now(); - - if let Some(delay) = deadline.checked_duration_since(now) { - sleep(delay); - } - } - } -} diff --git a/tests/rustdoc-js-std/unbox-type-result.js b/tests/rustdoc-js-std/unbox-type-result.js index 1f5cba58adf6d..e4765d87637c3 100644 --- a/tests/rustdoc-js-std/unbox-type-result.js +++ b/tests/rustdoc-js-std/unbox-type-result.js @@ -8,7 +8,6 @@ const EXPECTED = [ query: "File -> Metadata", others: [ { path: "std::fs::File", name: "metadata" }, - { path: "std::fs::File", name: "metadata_at" }, ] }, { diff --git a/triagebot.toml b/triagebot.toml index 6ad8dd6b4c886..db859bd54efb8 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -455,8 +455,7 @@ trigger_files = [ [autolabel."O-wasi"] trigger_files = [ - "library/std/src/sys/pal/wasip1", - "library/std/src/sys/pal/wasip2", + "library/std/src/sys/pal/wasi", "library/std/src/os/wasi" ] From 8cf942d89fcaef9eea4a472cd2213856ba27ccc5 Mon Sep 17 00:00:00 2001 From: aerooneqq Date: Mon, 8 Dec 2025 19:56:47 +0300 Subject: [PATCH 13/24] Add inline attribute to generated delegation function if needed --- compiler/rustc_ast_lowering/src/delegation.rs | 28 +++++ tests/pretty/delegation_inline_attribute.pp | 94 ++++++++++++++++ tests/pretty/delegation_inline_attribute.rs | 104 ++++++++++++++++++ tests/pretty/hir-delegation.pp | 2 + 4 files changed, 228 insertions(+) create mode 100644 tests/pretty/delegation_inline_attribute.pp create mode 100644 tests/pretty/delegation_inline_attribute.rs diff --git a/compiler/rustc_ast_lowering/src/delegation.rs b/compiler/rustc_ast_lowering/src/delegation.rs index e6e88eff2d5bc..0e7db7c9503cb 100644 --- a/compiler/rustc_ast_lowering/src/delegation.rs +++ b/compiler/rustc_ast_lowering/src/delegation.rs @@ -44,6 +44,7 @@ use hir::{BodyId, HirId}; use rustc_abi::ExternAbi; use rustc_ast::*; use rustc_errors::ErrorGuaranteed; +use rustc_hir::attrs::{AttributeKind, InlineAttr}; use rustc_hir::def_id::DefId; use rustc_middle::span_bug; use rustc_middle::ty::{Asyncness, ResolverAstLowering}; @@ -87,6 +88,8 @@ impl<'hir> LoweringContext<'_, 'hir> { let sig_id = self.get_delegation_sig_id(item_id, delegation.id, span, is_in_trait_impl); match sig_id { Ok(sig_id) => { + self.add_inline_attribute_if_needed(span); + let is_method = self.is_method(sig_id, span); let (param_count, c_variadic) = self.param_count(sig_id); let decl = self.lower_delegation_decl(sig_id, param_count, c_variadic, span); @@ -100,6 +103,31 @@ impl<'hir> LoweringContext<'_, 'hir> { } } + fn add_inline_attribute_if_needed(&mut self, span: Span) { + const PARENT_ID: hir::ItemLocalId = hir::ItemLocalId::ZERO; + let create_inline_attr_slice = + || [hir::Attribute::Parsed(AttributeKind::Inline(InlineAttr::Hint, span))]; + + let new_attributes = match self.attrs.get(&PARENT_ID) { + Some(attrs) => { + // Check if reuse already specifies any inline attribute, if so, do nothing + if attrs + .iter() + .any(|a| matches!(a, hir::Attribute::Parsed(AttributeKind::Inline(..)))) + { + return; + } + + self.arena.alloc_from_iter( + attrs.into_iter().map(|a| a.clone()).chain(create_inline_attr_slice()), + ) + } + None => self.arena.alloc_from_iter(create_inline_attr_slice()), + }; + + self.attrs.insert(PARENT_ID, new_attributes); + } + fn get_delegation_sig_id( &self, item_id: NodeId, diff --git a/tests/pretty/delegation_inline_attribute.pp b/tests/pretty/delegation_inline_attribute.pp new file mode 100644 index 0000000000000..4b3b2aa8f80a2 --- /dev/null +++ b/tests/pretty/delegation_inline_attribute.pp @@ -0,0 +1,94 @@ +//@ pretty-compare-only +//@ pretty-mode:hir +//@ pp-exact:delegation_inline_attribute.pp + +#![allow(incomplete_features)] +#![feature(fn_delegation)] +#[attr = MacroUse {arguments: UseAll}] +extern crate std; +#[prelude_import] +use ::std::prelude::rust_2015::*; + +mod to_reuse { + fn foo(x: usize) -> usize { x } +} + +// Check that #[inline(hint)] is added to foo reuse +#[attr = Inline(Hint)] +fn bar(arg0: _) -> _ { to_reuse::foo(self + 1) } + +trait Trait { + fn foo(&self) { } + fn foo1(&self) { } + fn foo2(&self) { } + fn foo3(&self) { } + fn foo4(&self) { } +} + +impl Trait for u8 { } + +struct S(u8); + +mod to_import { + fn check(arg: &'_ u8) -> &'_ u8 { arg } +} + +impl Trait for S { + // Check that #[inline(hint)] is added to foo reuse + #[attr = Inline(Hint)] + fn foo(self: _) + -> + _ { + { + // Check that #[inline(hint)] is added to foo0 reuse inside another reuse + #[attr = Inline(Hint)] + fn foo0(arg0: _) -> _ { to_reuse::foo(self + 1) } + + // Check that #[inline(hint)] is added when other attributes present in inner reuse + #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] + #[attr = MustUse] + #[attr = Cold] + #[attr = Inline(Hint)] + fn foo1(arg0: _) -> _ { to_reuse::foo(self / 2) } + + // Check that #[inline(never)] is preserved in inner reuse + #[attr = Inline(Never)] + fn foo2(arg0: _) -> _ { to_reuse::foo(self / 2) } + + // Check that #[inline(always)] is preserved in inner reuse + #[attr = Inline(Always)] + fn foo3(arg0: _) -> _ { to_reuse::foo(self / 2) } + + // Check that #[inline(never)] is preserved when there are other attributes in inner reuse + #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] + #[attr = Inline(Never)] + #[attr = MustUse] + #[attr = Cold] + fn foo4(arg0: _) -> _ { to_reuse::foo(self / 2) } + }.foo() + } + + // Check that #[inline(hint)] is added when there are other attributes present in trait reuse + #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] + #[attr = MustUse] + #[attr = Cold] + #[attr = Inline(Hint)] + fn foo1(self: _) -> _ { self.0.foo1() } + + // Check that #[inline(never)] is preserved in trait reuse + #[attr = Inline(Never)] + fn foo2(self: _) -> _ { self.0.foo2() } + + // Check that #[inline(always)] is preserved in trait reuse + #[attr = Inline(Always)] + fn foo3(self: _) -> _ { self.0.foo3() } + + // Check that #[inline(never)] is preserved when there are other attributes in trait reuse + #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] + #[attr = Inline(Never)] + #[attr = MustUse] + #[attr = Cold] + fn foo4(self: _) -> _ { self.0.foo4() } +} + +fn main() { } diff --git a/tests/pretty/delegation_inline_attribute.rs b/tests/pretty/delegation_inline_attribute.rs new file mode 100644 index 0000000000000..0716cfc51f5d9 --- /dev/null +++ b/tests/pretty/delegation_inline_attribute.rs @@ -0,0 +1,104 @@ +//@ pretty-compare-only +//@ pretty-mode:hir +//@ pp-exact:delegation_inline_attribute.pp + +#![allow(incomplete_features)] +#![feature(fn_delegation)] + +mod to_reuse { + pub fn foo(x: usize) -> usize { + x + } +} + +// Check that #[inline(hint)] is added to foo reuse +reuse to_reuse::foo as bar { + self + 1 +} + +trait Trait { + fn foo(&self) {} + fn foo1(&self) {} + fn foo2(&self) {} + fn foo3(&self) {} + fn foo4(&self) {} +} + +impl Trait for u8 {} + +struct S(u8); + +mod to_import { + pub fn check(arg: &u8) -> &u8 { arg } +} + +impl Trait for S { + // Check that #[inline(hint)] is added to foo reuse + reuse Trait::foo { + // Check that #[inline(hint)] is added to foo0 reuse inside another reuse + reuse to_reuse::foo as foo0 { + self + 1 + } + + // Check that #[inline(hint)] is added when other attributes present in inner reuse + #[cold] + #[must_use] + #[deprecated] + reuse to_reuse::foo as foo1 { + self / 2 + } + + // Check that #[inline(never)] is preserved in inner reuse + #[inline(never)] + reuse to_reuse::foo as foo2 { + self / 2 + } + + // Check that #[inline(always)] is preserved in inner reuse + #[inline(always)] + reuse to_reuse::foo as foo3 { + self / 2 + } + + // Check that #[inline(never)] is preserved when there are other attributes in inner reuse + #[cold] + #[must_use] + #[inline(never)] + #[deprecated] + reuse to_reuse::foo as foo4 { + self / 2 + } + } + + // Check that #[inline(hint)] is added when there are other attributes present in trait reuse + #[cold] + #[must_use] + #[deprecated] + reuse Trait::foo1 { + self.0 + } + + // Check that #[inline(never)] is preserved in trait reuse + #[inline(never)] + reuse Trait::foo2 { + self.0 + } + + // Check that #[inline(always)] is preserved in trait reuse + #[inline(always)] + reuse Trait::foo3 { + self.0 + } + + // Check that #[inline(never)] is preserved when there are other attributes in trait reuse + #[cold] + #[must_use] + #[inline(never)] + #[deprecated] + reuse Trait::foo4 { + self.0 + } +} + +fn main() { +} diff --git a/tests/pretty/hir-delegation.pp b/tests/pretty/hir-delegation.pp index f8ad02f2fccce..b5f7a14eb2fc0 100644 --- a/tests/pretty/hir-delegation.pp +++ b/tests/pretty/hir-delegation.pp @@ -12,6 +12,7 @@ fn b(e: C) { } trait G { + #[attr = Inline(Hint)] fn b(arg0: _) -> _ { b({ }) } } @@ -19,6 +20,7 @@ fn add(a: u32, b: u32) -> u32 { a + b } } +#[attr = Inline(Hint)] fn add(arg0: _, arg1: _) -> _ { m::add(arg0, arg1) } fn main() { { let _ = add(1, 2); }; } From 27a4aabffc40beda87c5990cb9b3c95e56177da9 Mon Sep 17 00:00:00 2001 From: Tobias Bucher Date: Mon, 8 Dec 2025 18:52:46 +0100 Subject: [PATCH 14/24] =?UTF-8?q?Fix=20typo=20"an"=20=E2=86=92=20"and"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/std/src/fs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 8b3e943b4ccd0..d14d0ad2f0ea6 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -1324,7 +1324,7 @@ impl Read for &File { /// /// # Platform-specific behavior /// - /// This function currently returns `true` on Unix an `false` on Windows. + /// This function currently returns `true` on Unix and `false` on Windows. /// Note that this [may change in the future][changes]. /// /// [changes]: io#platform-specific-behavior @@ -1385,7 +1385,7 @@ impl Write for &File { /// /// # Platform-specific behavior /// - /// This function currently returns `true` on Unix an `false` on Windows. + /// This function currently returns `true` on Unix and `false` on Windows. /// Note that this [may change in the future][changes]. /// /// [changes]: io#platform-specific-behavior From b8581ddecc2adb994f8ddd54a8c3dc3205dec547 Mon Sep 17 00:00:00 2001 From: Urgau <3616612+Urgau@users.noreply.github.com> Date: Mon, 8 Dec 2025 19:34:19 +0100 Subject: [PATCH 15/24] Remove `[no-mentions]` handler in the triagebot config https://github.blog/changelog/2025-11-07-removing-notifications-for-mentions-in-commit-messages/ --- triagebot.toml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/triagebot.toml b/triagebot.toml index 6ad8dd6b4c886..c459f69d06bb6 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -1680,12 +1680,6 @@ days-threshold = 28 # Documentation at: https://forge.rust-lang.org/triagebot/issue-links.html [issue-links] -# Prevents mentions in commits to avoid users being spammed -# Documentation at: https://forge.rust-lang.org/triagebot/no-mentions.html -[no-mentions] -# Subtree update authors can't fix it, no point in warning. -exclude-titles = ["subtree update"] - # Allow members to formally register concerns (`@rustbot concern my concern`) # Documentation at: https://forge.rust-lang.org/triagebot/concern.html [concern] From 2ff91ca11e270494bbd0f1b5cf3eca5b0b4feb13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Tue, 4 Nov 2025 18:00:38 +0000 Subject: [PATCH 16/24] Use return type `Span` on async fns instead of whole fn def `Span` --- compiler/rustc_ast_lowering/src/lib.rs | 5 ++--- .../ui/async-await/async-await-let-else.stderr | 2 +- .../async-fn/recurse-ice-129215.stderr | 4 ++-- ...ync-example-desugared-boxed-in-trait.stderr | 4 ++-- .../async-example-desugared-boxed.stderr | 2 +- .../async-example-desugared-manual.stderr | 2 +- .../in-trait/async-generics-and-bounds.stderr | 18 ++++++++---------- .../async-await/in-trait/async-generics.stderr | 18 ++++++++---------- ...-project-to-specializable-projection.stderr | 8 ++++---- .../inference_var_self_argument.stderr | 4 ++-- .../ui/async-await/issue-64130-3-other.stderr | 2 +- tests/ui/async-await/issues/issue-67893.stderr | 4 ++-- .../partial-drop-partial-reinit.stderr | 2 +- tests/ui/c-variadic/not-async.stderr | 12 ++++++------ .../cmse-nonsecure-entry/c-variadic.stderr | 4 ++-- .../in-trait/async-and-ret-ref.stderr | 10 +++++----- .../note-and-explain-ReVar-124973.stderr | 6 +++--- .../lifetimes/issue-76168-hr-outlives-3.stderr | 18 ++++++------------ .../type-match-with-late-bound.stderr | 15 ++++++--------- .../hkl_forbidden4.stderr | 4 ++-- 20 files changed, 65 insertions(+), 79 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index ab5caff86204d..2f0c96618af06 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -1677,7 +1677,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { let output = match coro { Some(coro) => { let fn_def_id = self.local_def_id(fn_node_id); - self.lower_coroutine_fn_ret_ty(&decl.output, fn_def_id, coro, kind, fn_span) + self.lower_coroutine_fn_ret_ty(&decl.output, fn_def_id, coro, kind) } None => match &decl.output { FnRetTy::Ty(ty) => { @@ -1762,9 +1762,8 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { fn_def_id: LocalDefId, coro: CoroutineKind, fn_kind: FnDeclKind, - fn_span: Span, ) -> hir::FnRetTy<'hir> { - let span = self.lower_span(fn_span); + let span = self.lower_span(output.span()); let (opaque_ty_node_id, allowed_features) = match coro { CoroutineKind::Async { return_impl_trait_id, .. } => (return_impl_trait_id, None), diff --git a/tests/ui/async-await/async-await-let-else.stderr b/tests/ui/async-await/async-await-let-else.stderr index 5883f34f87de6..b5b56d3242f4d 100644 --- a/tests/ui/async-await/async-await-let-else.stderr +++ b/tests/ui/async-await/async-await-let-else.stderr @@ -22,7 +22,7 @@ error[E0277]: `Rc<()>` cannot be sent between threads safely --> $DIR/async-await-let-else.rs:47:13 | LL | async fn foo2(x: Option) { - | ------------------------------ within this `impl Future` + | - within this `impl Future` ... LL | is_send(foo2(Some(true))); | ------- ^^^^^^^^^^^^^^^^ `Rc<()>` cannot be sent between threads safely diff --git a/tests/ui/async-await/async-fn/recurse-ice-129215.stderr b/tests/ui/async-await/async-fn/recurse-ice-129215.stderr index 98c7be2a5a3f2..232246590cf10 100644 --- a/tests/ui/async-await/async-fn/recurse-ice-129215.stderr +++ b/tests/ui/async-await/async-fn/recurse-ice-129215.stderr @@ -7,10 +7,10 @@ LL | a() = help: the trait `Future` is not implemented for `()` error[E0277]: `()` is not a future - --> $DIR/recurse-ice-129215.rs:3:1 + --> $DIR/recurse-ice-129215.rs:3:13 | LL | async fn a() { - | ^^^^^^^^^^^^ `()` is not a future + | ^ `()` is not a future | = help: the trait `Future` is not implemented for `()` diff --git a/tests/ui/async-await/in-trait/async-example-desugared-boxed-in-trait.stderr b/tests/ui/async-await/in-trait/async-example-desugared-boxed-in-trait.stderr index 54df0edf5a8e4..c98df134072eb 100644 --- a/tests/ui/async-await/in-trait/async-example-desugared-boxed-in-trait.stderr +++ b/tests/ui/async-await/in-trait/async-example-desugared-boxed-in-trait.stderr @@ -1,8 +1,8 @@ error[E0053]: method `foo` has an incompatible type for trait - --> $DIR/async-example-desugared-boxed-in-trait.rs:11:5 + --> $DIR/async-example-desugared-boxed-in-trait.rs:11:28 | LL | async fn foo(&self) -> i32 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Pin>>`, found future + | ^^^ expected `Pin>>`, found future | note: type in trait --> $DIR/async-example-desugared-boxed-in-trait.rs:7:22 diff --git a/tests/ui/async-await/in-trait/async-example-desugared-boxed.stderr b/tests/ui/async-await/in-trait/async-example-desugared-boxed.stderr index b7f2879727f24..d3765a7e6e6fd 100644 --- a/tests/ui/async-await/in-trait/async-example-desugared-boxed.stderr +++ b/tests/ui/async-await/in-trait/async-example-desugared-boxed.stderr @@ -2,7 +2,7 @@ warning: impl trait in impl method signature does not match trait method signatu --> $DIR/async-example-desugared-boxed.rs:14:22 | LL | async fn foo(&self) -> i32; - | --------------------------- return type from trait method defined here + | --- return type from trait method defined here ... LL | fn foo(&self) -> Pin + '_>> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/async-await/in-trait/async-example-desugared-manual.stderr b/tests/ui/async-await/in-trait/async-example-desugared-manual.stderr index 86546df88c175..3328dea37fe4d 100644 --- a/tests/ui/async-await/in-trait/async-example-desugared-manual.stderr +++ b/tests/ui/async-await/in-trait/async-example-desugared-manual.stderr @@ -2,7 +2,7 @@ warning: impl trait in impl method signature does not match trait method signatu --> $DIR/async-example-desugared-manual.rs:22:22 | LL | async fn foo(&self) -> i32; - | --------------------------- return type from trait method defined here + | --- return type from trait method defined here ... LL | fn foo(&self) -> MyFuture { | ^^^^^^^^ diff --git a/tests/ui/async-await/in-trait/async-generics-and-bounds.stderr b/tests/ui/async-await/in-trait/async-generics-and-bounds.stderr index 183b0fa152aa3..52fd887b296f4 100644 --- a/tests/ui/async-await/in-trait/async-generics-and-bounds.stderr +++ b/tests/ui/async-await/in-trait/async-generics-and-bounds.stderr @@ -1,11 +1,10 @@ error[E0311]: the parameter type `T` may not live long enough - --> $DIR/async-generics-and-bounds.rs:8:5 + --> $DIR/async-generics-and-bounds.rs:8:28 | LL | async fn foo(&self) -> &(T, U) where T: Debug + Sized, U: Hash; - | ^^^^^^^^^^^^^-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | | - | | the parameter type `T` must be valid for the anonymous lifetime as defined here... - | ...so that the reference type `&(T, U)` does not outlive the data it points at + | - ^^^^^^^ ...so that the reference type `&(T, U)` does not outlive the data it points at + | | + | the parameter type `T` must be valid for the anonymous lifetime as defined here... | help: consider adding an explicit lifetime bound | @@ -13,13 +12,12 @@ LL | async fn foo<'a>(&'a self) -> &'a (T, U) where T: Debug + Sized, U: Has | ++++ ++ ++ +++++++ error[E0311]: the parameter type `U` may not live long enough - --> $DIR/async-generics-and-bounds.rs:8:5 + --> $DIR/async-generics-and-bounds.rs:8:28 | LL | async fn foo(&self) -> &(T, U) where T: Debug + Sized, U: Hash; - | ^^^^^^^^^^^^^-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | | - | | the parameter type `U` must be valid for the anonymous lifetime as defined here... - | ...so that the reference type `&(T, U)` does not outlive the data it points at + | - ^^^^^^^ ...so that the reference type `&(T, U)` does not outlive the data it points at + | | + | the parameter type `U` must be valid for the anonymous lifetime as defined here... | help: consider adding an explicit lifetime bound | diff --git a/tests/ui/async-await/in-trait/async-generics.stderr b/tests/ui/async-await/in-trait/async-generics.stderr index 8916ef5ab6830..3f44e4cdb6778 100644 --- a/tests/ui/async-await/in-trait/async-generics.stderr +++ b/tests/ui/async-await/in-trait/async-generics.stderr @@ -1,11 +1,10 @@ error[E0311]: the parameter type `T` may not live long enough - --> $DIR/async-generics.rs:5:5 + --> $DIR/async-generics.rs:5:28 | LL | async fn foo(&self) -> &(T, U); - | ^^^^^^^^^^^^^-^^^^^^^^^^^^^^^^^ - | | | - | | the parameter type `T` must be valid for the anonymous lifetime as defined here... - | ...so that the reference type `&(T, U)` does not outlive the data it points at + | - ^^^^^^^ ...so that the reference type `&(T, U)` does not outlive the data it points at + | | + | the parameter type `T` must be valid for the anonymous lifetime as defined here... | help: consider adding an explicit lifetime bound | @@ -13,13 +12,12 @@ LL | async fn foo<'a>(&'a self) -> &'a (T, U) where T: 'a; | ++++ ++ ++ +++++++++++ error[E0311]: the parameter type `U` may not live long enough - --> $DIR/async-generics.rs:5:5 + --> $DIR/async-generics.rs:5:28 | LL | async fn foo(&self) -> &(T, U); - | ^^^^^^^^^^^^^-^^^^^^^^^^^^^^^^^ - | | | - | | the parameter type `U` must be valid for the anonymous lifetime as defined here... - | ...so that the reference type `&(T, U)` does not outlive the data it points at + | - ^^^^^^^ ...so that the reference type `&(T, U)` does not outlive the data it points at + | | + | the parameter type `U` must be valid for the anonymous lifetime as defined here... | help: consider adding an explicit lifetime bound | diff --git a/tests/ui/async-await/in-trait/dont-project-to-specializable-projection.stderr b/tests/ui/async-await/in-trait/dont-project-to-specializable-projection.stderr index 823d8d5b92fce..d0c11565f4e90 100644 --- a/tests/ui/async-await/in-trait/dont-project-to-specializable-projection.stderr +++ b/tests/ui/async-await/in-trait/dont-project-to-specializable-projection.stderr @@ -1,14 +1,14 @@ error[E0053]: method `foo` has an incompatible type for trait - --> $DIR/dont-project-to-specializable-projection.rs:14:5 + --> $DIR/dont-project-to-specializable-projection.rs:14:35 | LL | default async fn foo(_: T) -> &'static str { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected associated type, found future + | ^^^^^^^^^^^^ expected associated type, found future | note: type in trait - --> $DIR/dont-project-to-specializable-projection.rs:10:5 + --> $DIR/dont-project-to-specializable-projection.rs:10:27 | LL | async fn foo(_: T) -> &'static str; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ = note: expected signature `fn(_) -> impl Future` found signature `fn(_) -> impl Future` diff --git a/tests/ui/async-await/inference_var_self_argument.stderr b/tests/ui/async-await/inference_var_self_argument.stderr index 1fccc32470ff6..c4240a095e685 100644 --- a/tests/ui/async-await/inference_var_self_argument.stderr +++ b/tests/ui/async-await/inference_var_self_argument.stderr @@ -8,10 +8,10 @@ LL | async fn foo(self: &dyn Foo) { = help: consider changing to `self`, `&self`, `&mut self`, `self: Box`, `self: Rc`, `self: Arc`, or `self: Pin

` (where P is one of the previous types except `Self`) error[E0038]: the trait `Foo` is not dyn compatible - --> $DIR/inference_var_self_argument.rs:5:5 + --> $DIR/inference_var_self_argument.rs:5:33 | LL | async fn foo(self: &dyn Foo) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Foo` is not dyn compatible + | ^ `Foo` is not dyn compatible | note: for a trait to be dyn compatible it needs to allow building a vtable for more information, visit diff --git a/tests/ui/async-await/issue-64130-3-other.stderr b/tests/ui/async-await/issue-64130-3-other.stderr index d683366ed4737..531a7028f7e0e 100644 --- a/tests/ui/async-await/issue-64130-3-other.stderr +++ b/tests/ui/async-await/issue-64130-3-other.stderr @@ -2,7 +2,7 @@ error[E0277]: the trait bound `Foo: Qux` is not satisfied in `impl Future $DIR/issue-64130-3-other.rs:25:12 | LL | async fn bar() { - | -------------- within this `impl Future` + | - within this `impl Future` ... LL | is_qux(bar()); | ^^^^^ unsatisfied trait bound diff --git a/tests/ui/async-await/issues/issue-67893.stderr b/tests/ui/async-await/issues/issue-67893.stderr index 34f28dd53c7b9..610ed60bc8fda 100644 --- a/tests/ui/async-await/issues/issue-67893.stderr +++ b/tests/ui/async-await/issues/issue-67893.stderr @@ -6,10 +6,10 @@ LL | g(issue_67893::run()) | | | required by a bound introduced by this call | - ::: $DIR/auxiliary/issue_67893.rs:9:1 + ::: $DIR/auxiliary/issue_67893.rs:9:19 | LL | pub async fn run() { - | ------------------ within this `impl Future` + | - within this `impl Future` | = help: within `impl Future`, the trait `Send` is not implemented for `std::sync::MutexGuard<'_, ()>` note: required because it's used within this `async` fn body diff --git a/tests/ui/async-await/partial-drop-partial-reinit.stderr b/tests/ui/async-await/partial-drop-partial-reinit.stderr index cef835f7aed8b..cf4b408ad12b8 100644 --- a/tests/ui/async-await/partial-drop-partial-reinit.stderr +++ b/tests/ui/async-await/partial-drop-partial-reinit.stderr @@ -7,7 +7,7 @@ LL | gimme_send(foo()); | required by a bound introduced by this call ... LL | async fn foo() { - | -------------- within this `impl Future` + | - within this `impl Future` | help: within `impl Future`, the trait `Send` is not implemented for `NotSend` --> $DIR/partial-drop-partial-reinit.rs:19:1 diff --git a/tests/ui/c-variadic/not-async.stderr b/tests/ui/c-variadic/not-async.stderr index fc5eb10bec4f7..bb8cc64e15fa4 100644 --- a/tests/ui/c-variadic/not-async.stderr +++ b/tests/ui/c-variadic/not-async.stderr @@ -14,9 +14,9 @@ error[E0700]: hidden type for `impl Future` captures lifetime that --> $DIR/not-async.rs:5:65 | LL | async unsafe extern "C" fn fn_cannot_be_async(x: isize, _: ...) {} - | --------------------------------------------------------------- ^^ - | | - | opaque type defined here + | -^^ + | | + | opaque type defined here | = note: hidden type `{async fn body of fn_cannot_be_async()}` captures lifetime `'_` @@ -24,9 +24,9 @@ error[E0700]: hidden type for `impl Future` captures lifetime that --> $DIR/not-async.rs:12:73 | LL | async unsafe extern "C" fn method_cannot_be_async(x: isize, _: ...) {} - | ------------------------------------------------------------------- ^^ - | | - | opaque type defined here + | -^^ + | | + | opaque type defined here | = note: hidden type `{async fn body of S::method_cannot_be_async()}` captures lifetime `'_` diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/c-variadic.stderr b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/c-variadic.stderr index 5b0924d6f2aff..2395ed75d3a15 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/c-variadic.stderr +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-entry/c-variadic.stderr @@ -25,10 +25,10 @@ LL | async unsafe extern "cmse-nonsecure-entry" fn async_and_c_variadic(_: ...) = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list error[E0798]: `impl Trait` is not allowed in `extern "cmse-nonsecure-entry"` signatures - --> $DIR/c-variadic.rs:25:1 + --> $DIR/c-variadic.rs:25:69 | LL | async unsafe extern "cmse-nonsecure-entry" fn async_is_not_allowed() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^ error: aborting due to 4 previous errors diff --git a/tests/ui/impl-trait/in-trait/async-and-ret-ref.stderr b/tests/ui/impl-trait/in-trait/async-and-ret-ref.stderr index 19ffff9d3f2bd..75d7224020969 100644 --- a/tests/ui/impl-trait/in-trait/async-and-ret-ref.stderr +++ b/tests/ui/impl-trait/in-trait/async-and-ret-ref.stderr @@ -1,11 +1,11 @@ error[E0310]: the associated type `impl T` may not live long enough - --> $DIR/async-and-ret-ref.rs:7:5 + --> $DIR/async-and-ret-ref.rs:7:23 | LL | async fn foo() -> &'static impl T; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | the associated type `impl T` must be valid for the static lifetime... - | ...so that the reference type `&'static impl T` does not outlive the data it points at + | ^^^^^^^^^^^^^^^ + | | + | the associated type `impl T` must be valid for the static lifetime... + | ...so that the reference type `&'static impl T` does not outlive the data it points at error: aborting due to 1 previous error diff --git a/tests/ui/inference/note-and-explain-ReVar-124973.stderr b/tests/ui/inference/note-and-explain-ReVar-124973.stderr index bb37e6231a323..2b5e79e9a1c64 100644 --- a/tests/ui/inference/note-and-explain-ReVar-124973.stderr +++ b/tests/ui/inference/note-and-explain-ReVar-124973.stderr @@ -8,9 +8,9 @@ error[E0700]: hidden type for `impl Future` captures lifetime that --> $DIR/note-and-explain-ReVar-124973.rs:5:76 | LL | async unsafe extern "C" fn multiple_named_lifetimes<'a, 'b>(_: u8, _: ...) {} - | -------------------------------------------------------------------------- ^^ - | | - | opaque type defined here + | -^^ + | | + | opaque type defined here | = note: hidden type `{async fn body of multiple_named_lifetimes<'a, 'b>()}` captures lifetime `'_` diff --git a/tests/ui/lifetimes/issue-76168-hr-outlives-3.stderr b/tests/ui/lifetimes/issue-76168-hr-outlives-3.stderr index 945d38d17f63d..bb8e847242571 100644 --- a/tests/ui/lifetimes/issue-76168-hr-outlives-3.stderr +++ b/tests/ui/lifetimes/issue-76168-hr-outlives-3.stderr @@ -34,24 +34,18 @@ LL | for<'a> >::Output: Future + 'a, = help: the trait `for<'a> FnOnce(&'a mut i32)` is not implemented for `i32` error[E0277]: expected a `FnOnce(&'a mut i32)` closure, found `i32` - --> $DIR/issue-76168-hr-outlives-3.rs:6:1 + --> $DIR/issue-76168-hr-outlives-3.rs:6:26 | -LL | / async fn wrapper(f: F) -... | -LL | | F:, -LL | | for<'a> >::Output: Future + 'a, - | |__________________________________________________________________________^ expected an `FnOnce(&'a mut i32)` closure, found `i32` +LL | async fn wrapper(f: F) + | ^ expected an `FnOnce(&'a mut i32)` closure, found `i32` | = help: the trait `for<'a> FnOnce(&'a mut i32)` is not implemented for `i32` error[E0277]: expected a `FnOnce(&'a mut i32)` closure, found `i32` - --> $DIR/issue-76168-hr-outlives-3.rs:6:1 + --> $DIR/issue-76168-hr-outlives-3.rs:6:26 | -LL | / async fn wrapper(f: F) -... | -LL | | F:, -LL | | for<'a> >::Output: Future + 'a, - | |__________________________________________________________________________^ expected an `FnOnce(&'a mut i32)` closure, found `i32` +LL | async fn wrapper(f: F) + | ^ expected an `FnOnce(&'a mut i32)` closure, found `i32` | = help: the trait `for<'a> FnOnce(&'a mut i32)` is not implemented for `i32` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` diff --git a/tests/ui/traits/non_lifetime_binders/type-match-with-late-bound.stderr b/tests/ui/traits/non_lifetime_binders/type-match-with-late-bound.stderr index 15902bf16de5c..c325718b3033e 100644 --- a/tests/ui/traits/non_lifetime_binders/type-match-with-late-bound.stderr +++ b/tests/ui/traits/non_lifetime_binders/type-match-with-late-bound.stderr @@ -8,15 +8,12 @@ LL | #![feature(non_lifetime_binders)] = note: `#[warn(incomplete_features)]` on by default error[E0309]: the placeholder type `F` may not live long enough - --> $DIR/type-match-with-late-bound.rs:8:1 - | -LL | async fn walk2<'a, T: 'a>(_: T) - | ^ -- the placeholder type `F` must be valid for the lifetime `'a` as defined here... - | _| - | | -LL | | where -LL | | for F: 'a, - | |_________________^ ...so that the type `F` will meet its required lifetime bounds... + --> $DIR/type-match-with-late-bound.rs:8:32 + | +LL | async fn walk2<'a, T: 'a>(_: T) + | -- ^ ...so that the type `F` will meet its required lifetime bounds... + | | + | the placeholder type `F` must be valid for the lifetime `'a` as defined here... | note: ...that is required by this bound --> $DIR/type-match-with-late-bound.rs:10:15 diff --git a/tests/ui/type-alias-impl-trait/hkl_forbidden4.stderr b/tests/ui/type-alias-impl-trait/hkl_forbidden4.stderr index 2aacf9698379b..87313e3495249 100644 --- a/tests/ui/type-alias-impl-trait/hkl_forbidden4.stderr +++ b/tests/ui/type-alias-impl-trait/hkl_forbidden4.stderr @@ -18,10 +18,10 @@ LL | call(operation).await | ^^^^^^^^^^^^^^^ expected `{async fn body of operation()}`, got `FutNothing<'_>` | note: previous use here - --> $DIR/hkl_forbidden4.rs:12:1 + --> $DIR/hkl_forbidden4.rs:12:35 | LL | async fn operation(_: &mut ()) -> () { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^ error: aborting due to 2 previous errors From e6c4d4e22e934e7ba07fed820cd03bbeb896f420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Tue, 4 Nov 2025 18:08:36 +0000 Subject: [PATCH 17/24] Add test for `dyn Trait` in `async fn` return type --- .../async-fn/dyn-in-return-type.rs | 10 ++++++++ .../async-fn/dyn-in-return-type.stderr | 25 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/ui/async-await/async-fn/dyn-in-return-type.rs create mode 100644 tests/ui/async-await/async-fn/dyn-in-return-type.stderr diff --git a/tests/ui/async-await/async-fn/dyn-in-return-type.rs b/tests/ui/async-await/async-fn/dyn-in-return-type.rs new file mode 100644 index 0000000000000..ec793bf80f287 --- /dev/null +++ b/tests/ui/async-await/async-fn/dyn-in-return-type.rs @@ -0,0 +1,10 @@ +//@ edition:2024 + +async fn f() -> dyn core::fmt::Debug { +//~^ ERROR return type cannot be a trait object without pointer indirection +//~| HELP consider returning an `impl Trait` instead of a `dyn Trait` +//~| HELP alternatively, box the return type, and wrap all of the returned values in `Box::new` + loop {} +} + +fn main() {} diff --git a/tests/ui/async-await/async-fn/dyn-in-return-type.stderr b/tests/ui/async-await/async-fn/dyn-in-return-type.stderr new file mode 100644 index 0000000000000..6b1b092f5555c --- /dev/null +++ b/tests/ui/async-await/async-fn/dyn-in-return-type.stderr @@ -0,0 +1,25 @@ +error[E0746]: return type cannot be a trait object without pointer indirection + --> $DIR/dyn-in-return-type.rs:3:38 + | +LL | async fn f() -> dyn core::fmt::Debug { + | ______________________________________^ +... | +LL | | } + | |_^ doesn't have a size known at compile-time + | +help: consider returning an `impl Trait` instead of a `dyn Trait` + | +LL | async fn f() -> dyn core::fmt::Debug impl { + | ++++ +help: alternatively, box the return type, and wrap all of the returned values in `Box::new` + | +LL ~ async fn f() -> dyn core::fmt::Debug Box + | + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0746`. From f8afd7543ed00cff4535545aaa7be27381acaa73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Tue, 4 Nov 2025 18:11:51 +0000 Subject: [PATCH 18/24] Account for `async fn` with `dyn Trait` return type in `impl Trait` suggestion --- .../src/error_reporting/traits/suggestions.rs | 16 +++++++++++++++- .../async-fn/dyn-in-return-type.stderr | 13 +++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 1094f00e42f29..16bb4c186447f 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -1891,7 +1891,21 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { err.primary_message("return type cannot be a trait object without pointer indirection"); err.children.clear(); - let span = obligation.cause.span; + let mut span = obligation.cause.span; + if let DefKind::Closure = self.tcx.def_kind(obligation.cause.body_id) + && let parent = self.tcx.parent(obligation.cause.body_id.into()) + && let DefKind::Fn = self.tcx.def_kind(parent) + && self.tcx.asyncness(parent).is_async() + && let Some(parent) = parent.as_local() + && let Node::Item(hir::Item { kind: hir::ItemKind::Fn { sig: fn_sig, .. }, .. }) = + self.tcx.hir_node_by_def_id(parent) + { + // Do not suggest (#147894) + // async fn foo() -> dyn Display impl { .. } + // and + // async fn foo() -> dyn Display Box + span = fn_sig.decl.output.span(); + } let body = self.tcx.hir_body_owned_by(obligation.cause.body_id); let mut visitor = ReturnsVisitor::default(); diff --git a/tests/ui/async-await/async-fn/dyn-in-return-type.stderr b/tests/ui/async-await/async-fn/dyn-in-return-type.stderr index 6b1b092f5555c..bd2ee4c208cad 100644 --- a/tests/ui/async-await/async-fn/dyn-in-return-type.stderr +++ b/tests/ui/async-await/async-fn/dyn-in-return-type.stderr @@ -9,16 +9,13 @@ LL | | } | help: consider returning an `impl Trait` instead of a `dyn Trait` | -LL | async fn f() -> dyn core::fmt::Debug impl { - | ++++ -help: alternatively, box the return type, and wrap all of the returned values in `Box::new` +LL - async fn f() -> dyn core::fmt::Debug { +LL + async fn f() -> impl core::fmt::Debug { | -LL ~ async fn f() -> dyn core::fmt::Debug Box +help: alternatively, box the return type, and wrap all of the returned values in `Box::new` | +LL | async fn f() -> Box { + | ++++ + error: aborting due to 1 previous error From e6b921af1668e83d6b032f56d5bdfc93c191f6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Tue, 4 Nov 2025 18:31:31 +0000 Subject: [PATCH 19/24] Add test for `type Alias = dyn Trait` in return type --- .../dyn-trait-type-alias-return-type.rs | 8 ++++++++ .../dyn-trait-type-alias-return-type.stderr | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/ui/type-alias/dyn-trait-type-alias-return-type.rs create mode 100644 tests/ui/type-alias/dyn-trait-type-alias-return-type.stderr diff --git a/tests/ui/type-alias/dyn-trait-type-alias-return-type.rs b/tests/ui/type-alias/dyn-trait-type-alias-return-type.rs new file mode 100644 index 0000000000000..f96cd989a4ce2 --- /dev/null +++ b/tests/ui/type-alias/dyn-trait-type-alias-return-type.rs @@ -0,0 +1,8 @@ +type T = dyn core::fmt::Debug; + +fn f() -> T { loop {} } +//~^ ERROR return type cannot be a trait object without pointer indirection +//~| HELP +//~| HELP + +fn main() {} diff --git a/tests/ui/type-alias/dyn-trait-type-alias-return-type.stderr b/tests/ui/type-alias/dyn-trait-type-alias-return-type.stderr new file mode 100644 index 0000000000000..d67ba77595512 --- /dev/null +++ b/tests/ui/type-alias/dyn-trait-type-alias-return-type.stderr @@ -0,0 +1,18 @@ +error[E0746]: return type cannot be a trait object without pointer indirection + --> $DIR/dyn-trait-type-alias-return-type.rs:3:11 + | +LL | fn f() -> T { loop {} } + | ^ doesn't have a size known at compile-time + | +help: consider returning an `impl Trait` instead of a `dyn Trait` + | +LL | fn f() -> impl T { loop {} } + | ++++ +help: alternatively, box the return type, and wrap all of the returned values in `Box::new` + | +LL | fn f() -> Box { Box::new(loop {}) } + | +++++++ + +++++++++ + + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0746`. From b00e7224d3fb9ab1e5adec4876bf59a21c055c2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Tue, 4 Nov 2025 18:41:04 +0000 Subject: [PATCH 20/24] Recognize `type Alias = dyn Trait` in `fn` return types ``` error[E0277]: the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time --> $DIR/dyn-trait-type-alias-return-type.rs:4:11 | LL | fn f() -> T { loop {} } | ^ doesn't have a size known at compile-time | = help: the trait `Sized` is not implemented for `(dyn Debug + 'static)` note: this type alias is unsized --> $DIR/dyn-trait-type-alias-return-type.rs:1:1 | LL | type T = dyn core::fmt::Debug; | ^^^^^^ = note: the return type of a function must have a statically known size ``` --- .../src/error_reporting/traits/suggestions.rs | 13 +++++++ .../clippy/tests/ui/future_not_send.stderr | 37 +++++++++---------- .../dyn-trait-type-alias-return-type.rs | 10 +++-- .../dyn-trait-type-alias-return-type.stderr | 19 +++++----- tests/ui/unsized/issue-91801.rs | 2 +- tests/ui/unsized/issue-91801.stderr | 17 ++++----- 6 files changed, 54 insertions(+), 44 deletions(-) diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 16bb4c186447f..822025b986b83 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -1886,6 +1886,19 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let ty::Dynamic(_, _) = trait_pred.self_ty().skip_binder().kind() else { return false; }; + if let Node::Item(hir::Item { kind: hir::ItemKind::Fn { sig: fn_sig, .. }, .. }) = + self.tcx.hir_node_by_def_id(obligation.cause.body_id) + && let hir::FnRetTy::Return(ty) = fn_sig.decl.output + && let hir::TyKind::Path(qpath) = ty.kind + && let hir::QPath::Resolved(None, path) = qpath + && let Res::Def(DefKind::TyAlias, def_id) = path.res + { + // Do not suggest + // type T = dyn Trait; + // fn foo() -> impl T { .. } + err.span_note(self.tcx.def_span(def_id), "this type alias is unsized"); + return false; + } err.code(E0746); err.primary_message("return type cannot be a trait object without pointer indirection"); diff --git a/src/tools/clippy/tests/ui/future_not_send.stderr b/src/tools/clippy/tests/ui/future_not_send.stderr index e366dc2d21958..8b8af1ebaed39 100644 --- a/src/tools/clippy/tests/ui/future_not_send.stderr +++ b/src/tools/clippy/tests/ui/future_not_send.stderr @@ -1,8 +1,8 @@ error: future cannot be sent between threads safely - --> tests/ui/future_not_send.rs:8:1 + --> tests/ui/future_not_send.rs:8:62 | LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell) -> bool { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `private_future` is not `Send` + | ^^^^ future returned by `private_future` is not `Send` | note: future is not `Send` as this value is used across an await --> tests/ui/future_not_send.rs:11:20 @@ -23,10 +23,10 @@ LL | async fn private_future(rc: Rc<[u8]>, cell: &Cell) -> bool { = help: to override `-D warnings` add `#[allow(clippy::future_not_send)]` error: future cannot be sent between threads safely - --> tests/ui/future_not_send.rs:14:1 + --> tests/ui/future_not_send.rs:14:41 | LL | pub async fn public_future(rc: Rc<[u8]>) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `public_future` is not `Send` + | ^ future returned by `public_future` is not `Send` | note: future is not `Send` as this value is used across an await --> tests/ui/future_not_send.rs:17:20 @@ -39,10 +39,10 @@ LL | async { true }.await; = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` error: future cannot be sent between threads safely - --> tests/ui/future_not_send.rs:24:1 + --> tests/ui/future_not_send.rs:24:63 | LL | async fn private_future2(rc: Rc<[u8]>, cell: &Cell) -> bool { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `private_future2` is not `Send` + | ^^^^ future returned by `private_future2` is not `Send` | note: captured value is not `Send` --> tests/ui/future_not_send.rs:24:26 @@ -58,10 +58,10 @@ LL | async fn private_future2(rc: Rc<[u8]>, cell: &Cell) -> bool { = note: `std::cell::Cell` doesn't implement `std::marker::Sync` error: future cannot be sent between threads safely - --> tests/ui/future_not_send.rs:30:1 + --> tests/ui/future_not_send.rs:30:42 | LL | pub async fn public_future2(rc: Rc<[u8]>) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `public_future2` is not `Send` + | ^ future returned by `public_future2` is not `Send` | note: captured value is not `Send` --> tests/ui/future_not_send.rs:30:29 @@ -71,10 +71,10 @@ LL | pub async fn public_future2(rc: Rc<[u8]>) {} = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` error: future cannot be sent between threads safely - --> tests/ui/future_not_send.rs:42:5 + --> tests/ui/future_not_send.rs:42:39 | LL | async fn private_future(&self) -> usize { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `private_future` is not `Send` + | ^^^^^ future returned by `private_future` is not `Send` | note: future is not `Send` as this value is used across an await --> tests/ui/future_not_send.rs:45:24 @@ -87,10 +87,10 @@ LL | async { true }.await; = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Sync` error: future cannot be sent between threads safely - --> tests/ui/future_not_send.rs:49:5 + --> tests/ui/future_not_send.rs:49:38 | LL | pub async fn public_future(&self) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `public_future` is not `Send` + | ^ future returned by `public_future` is not `Send` | note: captured value is not `Send` because `&` references cannot be sent unless their referent is `Sync` --> tests/ui/future_not_send.rs:49:32 @@ -100,13 +100,10 @@ LL | pub async fn public_future(&self) { = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Sync` error: future cannot be sent between threads safely - --> tests/ui/future_not_send.rs:61:1 + --> tests/ui/future_not_send.rs:61:37 | -LL | / async fn generic_future(t: T) -> T -LL | | -LL | | where -LL | | T: Send, - | |____________^ future returned by `generic_future` is not `Send` +LL | async fn generic_future(t: T) -> T + | ^ future returned by `generic_future` is not `Send` | note: future is not `Send` as this value is used across an await --> tests/ui/future_not_send.rs:67:20 @@ -118,10 +115,10 @@ LL | async { true }.await; = note: `T` doesn't implement `std::marker::Sync` error: future cannot be sent between threads safely - --> tests/ui/future_not_send.rs:83:1 + --> tests/ui/future_not_send.rs:83:51 | LL | async fn generic_future_always_unsend(_: Rc) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `generic_future_always_unsend` is not `Send` + | ^ future returned by `generic_future_always_unsend` is not `Send` | note: future is not `Send` as this value is used across an await --> tests/ui/future_not_send.rs:86:20 diff --git a/tests/ui/type-alias/dyn-trait-type-alias-return-type.rs b/tests/ui/type-alias/dyn-trait-type-alias-return-type.rs index f96cd989a4ce2..fe1322a48d65e 100644 --- a/tests/ui/type-alias/dyn-trait-type-alias-return-type.rs +++ b/tests/ui/type-alias/dyn-trait-type-alias-return-type.rs @@ -1,8 +1,10 @@ type T = dyn core::fmt::Debug; - +//~^ NOTE this type alias is unsized + fn f() -> T { loop {} } -//~^ ERROR return type cannot be a trait object without pointer indirection -//~| HELP -//~| HELP +//~^ ERROR the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time +//~| HELP the trait `Sized` is not implemented for `(dyn Debug + 'static)` +//~| NOTE doesn't have a size known at compile-time +//~| NOTE the return type of a function must have a statically known size fn main() {} diff --git a/tests/ui/type-alias/dyn-trait-type-alias-return-type.stderr b/tests/ui/type-alias/dyn-trait-type-alias-return-type.stderr index d67ba77595512..fc7f9f49d1652 100644 --- a/tests/ui/type-alias/dyn-trait-type-alias-return-type.stderr +++ b/tests/ui/type-alias/dyn-trait-type-alias-return-type.stderr @@ -1,18 +1,17 @@ -error[E0746]: return type cannot be a trait object without pointer indirection - --> $DIR/dyn-trait-type-alias-return-type.rs:3:11 +error[E0277]: the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time + --> $DIR/dyn-trait-type-alias-return-type.rs:4:11 | LL | fn f() -> T { loop {} } | ^ doesn't have a size known at compile-time | -help: consider returning an `impl Trait` instead of a `dyn Trait` + = help: the trait `Sized` is not implemented for `(dyn Debug + 'static)` +note: this type alias is unsized + --> $DIR/dyn-trait-type-alias-return-type.rs:1:1 | -LL | fn f() -> impl T { loop {} } - | ++++ -help: alternatively, box the return type, and wrap all of the returned values in `Box::new` - | -LL | fn f() -> Box { Box::new(loop {}) } - | +++++++ + +++++++++ + +LL | type T = dyn core::fmt::Debug; + | ^^^^^^ + = note: the return type of a function must have a statically known size error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0746`. +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/unsized/issue-91801.rs b/tests/ui/unsized/issue-91801.rs index d906a08a55a21..8b4a3d214d5e7 100644 --- a/tests/ui/unsized/issue-91801.rs +++ b/tests/ui/unsized/issue-91801.rs @@ -6,7 +6,7 @@ pub static ALL_VALIDATORS: &[(&'static str, &'static Validator)] = &[("validate that credits and debits balance", &validate_something)]; fn or<'a>(first: &'static Validator<'a>, second: &'static Validator<'a>) -> Validator<'a> { - //~^ ERROR return type cannot be a trait object without pointer indirection + //~^ ERROR E0277 return Box::new(move |something: &'_ Something| -> Result<(), ()> { first(something).or_else(|_| second(something)) }); diff --git a/tests/ui/unsized/issue-91801.stderr b/tests/ui/unsized/issue-91801.stderr index 28e10f9caa41a..73f9de92458eb 100644 --- a/tests/ui/unsized/issue-91801.stderr +++ b/tests/ui/unsized/issue-91801.stderr @@ -1,18 +1,17 @@ -error[E0746]: return type cannot be a trait object without pointer indirection +error[E0277]: the size for values of type `(dyn Fn(&'a Something) -> Result<(), ()> + Send + Sync + 'a)` cannot be known at compilation time --> $DIR/issue-91801.rs:8:77 | LL | fn or<'a>(first: &'static Validator<'a>, second: &'static Validator<'a>) -> Validator<'a> { | ^^^^^^^^^^^^^ doesn't have a size known at compile-time | -help: consider returning an `impl Trait` instead of a `dyn Trait` + = help: the trait `Sized` is not implemented for `(dyn Fn(&'a Something) -> Result<(), ()> + Send + Sync + 'a)` +note: this type alias is unsized + --> $DIR/issue-91801.rs:3:1 | -LL | fn or<'a>(first: &'static Validator<'a>, second: &'static Validator<'a>) -> impl Validator<'a> { - | ++++ -help: alternatively, box the return type, and wrap all of the returned values in `Box::new` - | -LL | fn or<'a>(first: &'static Validator<'a>, second: &'static Validator<'a>) -> Box> { - | +++++++ + +LL | type Validator<'a> = dyn 'a + Send + Sync + Fn(&'a Something) -> Result<(), ()>; + | ^^^^^^^^^^^^^^^^^^ + = note: the return type of a function must have a statically known size error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0746`. +For more information about this error, try `rustc --explain E0277`. From b5790d7b58f8972d056e3ae9ac422bc1bd443351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 5 Nov 2025 18:28:44 +0000 Subject: [PATCH 21/24] Point at async fn return type instead of body in E0746 --- .../src/error_reporting/traits/suggestions.rs | 1 + tests/ui/async-await/async-fn/dyn-in-return-type.stderr | 9 +++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 822025b986b83..e7f588a77f770 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -1918,6 +1918,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { // and // async fn foo() -> dyn Display Box span = fn_sig.decl.output.span(); + err.span(span); } let body = self.tcx.hir_body_owned_by(obligation.cause.body_id); diff --git a/tests/ui/async-await/async-fn/dyn-in-return-type.stderr b/tests/ui/async-await/async-fn/dyn-in-return-type.stderr index bd2ee4c208cad..341d9561fd7ae 100644 --- a/tests/ui/async-await/async-fn/dyn-in-return-type.stderr +++ b/tests/ui/async-await/async-fn/dyn-in-return-type.stderr @@ -1,11 +1,8 @@ error[E0746]: return type cannot be a trait object without pointer indirection - --> $DIR/dyn-in-return-type.rs:3:38 + --> $DIR/dyn-in-return-type.rs:3:17 | -LL | async fn f() -> dyn core::fmt::Debug { - | ______________________________________^ -... | -LL | | } - | |_^ doesn't have a size known at compile-time +LL | async fn f() -> dyn core::fmt::Debug { + | ^^^^^^^^^^^^^^^^^^^^ | help: consider returning an `impl Trait` instead of a `dyn Trait` | From 9b42166ce5328f68ab659d642c5cca9b488c68e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 5 Nov 2025 18:48:40 +0000 Subject: [PATCH 22/24] Provide boxing suggestion for `type Alias = dyn Trait` return type --- .../src/error_reporting/traits/suggestions.rs | 11 +++++++++++ .../dyn-trait-type-alias-return-type.fixed | 18 ++++++++++++++++++ .../dyn-trait-type-alias-return-type.rs | 10 +++++++++- .../dyn-trait-type-alias-return-type.stderr | 19 ++++++++++++++++--- tests/ui/unsized/issue-91801.stderr | 4 ++++ 5 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 tests/ui/type-alias/dyn-trait-type-alias-return-type.fixed diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index e7f588a77f770..5ef6d05c419c4 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -1897,6 +1897,17 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { // type T = dyn Trait; // fn foo() -> impl T { .. } err.span_note(self.tcx.def_span(def_id), "this type alias is unsized"); + err.multipart_suggestion( + format!( + "consider boxing the return type, and wrapping all of the returned values in \ + `Box::new`", + ), + vec![ + (ty.span.shrink_to_lo(), "Box<".to_string()), + (ty.span.shrink_to_hi(), ">".to_string()), + ], + Applicability::MaybeIncorrect, + ); return false; } diff --git a/tests/ui/type-alias/dyn-trait-type-alias-return-type.fixed b/tests/ui/type-alias/dyn-trait-type-alias-return-type.fixed new file mode 100644 index 0000000000000..93472f808a8a7 --- /dev/null +++ b/tests/ui/type-alias/dyn-trait-type-alias-return-type.fixed @@ -0,0 +1,18 @@ +//@ run-rustfix +type T = dyn core::fmt::Debug; +//~^ NOTE this type alias is unsized + +fn f() -> Box { loop {} } +//~^ ERROR the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time +//~| HELP the trait `Sized` is not implemented for `(dyn Debug + 'static)` +//~| NOTE doesn't have a size known at compile-time +//~| NOTE the return type of a function must have a statically known size +//~| HELP consider boxing the return type + +fn main() { + f(); + //~^ ERROR the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time + //~| HELP the trait `Sized` is not implemented for `(dyn Debug + 'static)` + //~| NOTE doesn't have a size known at compile-time + //~| NOTE the return type of a function must have a statically known size +} diff --git a/tests/ui/type-alias/dyn-trait-type-alias-return-type.rs b/tests/ui/type-alias/dyn-trait-type-alias-return-type.rs index fe1322a48d65e..da9f8d07f8e05 100644 --- a/tests/ui/type-alias/dyn-trait-type-alias-return-type.rs +++ b/tests/ui/type-alias/dyn-trait-type-alias-return-type.rs @@ -1,3 +1,4 @@ +//@ run-rustfix type T = dyn core::fmt::Debug; //~^ NOTE this type alias is unsized @@ -6,5 +7,12 @@ fn f() -> T { loop {} } //~| HELP the trait `Sized` is not implemented for `(dyn Debug + 'static)` //~| NOTE doesn't have a size known at compile-time //~| NOTE the return type of a function must have a statically known size +//~| HELP consider boxing the return type -fn main() {} +fn main() { + f(); + //~^ ERROR the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time + //~| HELP the trait `Sized` is not implemented for `(dyn Debug + 'static)` + //~| NOTE doesn't have a size known at compile-time + //~| NOTE the return type of a function must have a statically known size +} diff --git a/tests/ui/type-alias/dyn-trait-type-alias-return-type.stderr b/tests/ui/type-alias/dyn-trait-type-alias-return-type.stderr index fc7f9f49d1652..658abbe8f780f 100644 --- a/tests/ui/type-alias/dyn-trait-type-alias-return-type.stderr +++ b/tests/ui/type-alias/dyn-trait-type-alias-return-type.stderr @@ -1,17 +1,30 @@ error[E0277]: the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time - --> $DIR/dyn-trait-type-alias-return-type.rs:4:11 + --> $DIR/dyn-trait-type-alias-return-type.rs:5:11 | LL | fn f() -> T { loop {} } | ^ doesn't have a size known at compile-time | = help: the trait `Sized` is not implemented for `(dyn Debug + 'static)` note: this type alias is unsized - --> $DIR/dyn-trait-type-alias-return-type.rs:1:1 + --> $DIR/dyn-trait-type-alias-return-type.rs:2:1 | LL | type T = dyn core::fmt::Debug; | ^^^^^^ = note: the return type of a function must have a statically known size +help: consider boxing the return type, and wrapping all of the returned values in `Box::new` + | +LL | fn f() -> Box { loop {} } + | ++++ + + +error[E0277]: the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time + --> $DIR/dyn-trait-type-alias-return-type.rs:13:5 + | +LL | f(); + | ^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `(dyn Debug + 'static)` + = note: the return type of a function must have a statically known size -error: aborting due to 1 previous error +error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/unsized/issue-91801.stderr b/tests/ui/unsized/issue-91801.stderr index 73f9de92458eb..192cdc767dd90 100644 --- a/tests/ui/unsized/issue-91801.stderr +++ b/tests/ui/unsized/issue-91801.stderr @@ -11,6 +11,10 @@ note: this type alias is unsized LL | type Validator<'a> = dyn 'a + Send + Sync + Fn(&'a Something) -> Result<(), ()>; | ^^^^^^^^^^^^^^^^^^ = note: the return type of a function must have a statically known size +help: consider boxing the return type, and wrapping all of the returned values in `Box::new` + | +LL | fn or<'a>(first: &'static Validator<'a>, second: &'static Validator<'a>) -> Box> { + | ++++ + error: aborting due to 1 previous error From fa40a0655d535fae3738818bef4f13123a74bd0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 5 Nov 2025 19:59:08 +0000 Subject: [PATCH 23/24] Account for async fn in traits and impls --- .../src/error_reporting/traits/suggestions.rs | 13 ++++-- .../async-fn/dyn-in-return-type.fixed | 30 +++++++++++++ .../async-fn/dyn-in-return-type.rs | 24 ++++++++++- .../async-fn/dyn-in-return-type.stderr | 36 +++++++++++++++- .../dyn-trait-type-alias-return-type.fixed | 21 ++++++++++ .../dyn-trait-type-alias-return-type.rs | 21 ++++++++++ .../dyn-trait-type-alias-return-type.stderr | 42 +++++++++++++++++-- 7 files changed, 176 insertions(+), 11 deletions(-) create mode 100644 tests/ui/async-await/async-fn/dyn-in-return-type.fixed diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 5ef6d05c419c4..fd845e31c9093 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -1886,7 +1886,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let ty::Dynamic(_, _) = trait_pred.self_ty().skip_binder().kind() else { return false; }; - if let Node::Item(hir::Item { kind: hir::ItemKind::Fn { sig: fn_sig, .. }, .. }) = + if let Node::Item(hir::Item { kind: hir::ItemKind::Fn { sig: fn_sig, .. }, .. }) + | Node::ImplItem(hir::ImplItem { kind: hir::ImplItemKind::Fn(fn_sig, _), .. }) + | Node::TraitItem(hir::TraitItem { kind: hir::TraitItemKind::Fn(fn_sig, _), .. }) = self.tcx.hir_node_by_def_id(obligation.cause.body_id) && let hir::FnRetTy::Return(ty) = fn_sig.decl.output && let hir::TyKind::Path(qpath) = ty.kind @@ -1918,11 +1920,14 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let mut span = obligation.cause.span; if let DefKind::Closure = self.tcx.def_kind(obligation.cause.body_id) && let parent = self.tcx.parent(obligation.cause.body_id.into()) - && let DefKind::Fn = self.tcx.def_kind(parent) + && let DefKind::Fn | DefKind::AssocFn = self.tcx.def_kind(parent) && self.tcx.asyncness(parent).is_async() && let Some(parent) = parent.as_local() - && let Node::Item(hir::Item { kind: hir::ItemKind::Fn { sig: fn_sig, .. }, .. }) = - self.tcx.hir_node_by_def_id(parent) + && let Node::Item(hir::Item { kind: hir::ItemKind::Fn { sig: fn_sig, .. }, .. }) + | Node::ImplItem(hir::ImplItem { kind: hir::ImplItemKind::Fn(fn_sig, _), .. }) + | Node::TraitItem(hir::TraitItem { + kind: hir::TraitItemKind::Fn(fn_sig, _), .. + }) = self.tcx.hir_node_by_def_id(parent) { // Do not suggest (#147894) // async fn foo() -> dyn Display impl { .. } diff --git a/tests/ui/async-await/async-fn/dyn-in-return-type.fixed b/tests/ui/async-await/async-fn/dyn-in-return-type.fixed new file mode 100644 index 0000000000000..6a717f628460e --- /dev/null +++ b/tests/ui/async-await/async-fn/dyn-in-return-type.fixed @@ -0,0 +1,30 @@ +//@ edition:2024 +//@ run-rustfix + +async fn f() -> Box { +//~^ ERROR return type cannot be a trait object without pointer indirection +//~| HELP consider returning an `impl Trait` instead of a `dyn Trait` +//~| HELP alternatively, box the return type, and wrap all of the returned values in `Box::new` + Box::new("") +} +trait T { + async fn f(&self) -> Box { + //~^ ERROR return type cannot be a trait object without pointer indirection + //~| HELP consider returning an `impl Trait` instead of a `dyn Trait` + //~| HELP alternatively, box the return type, and wrap all of the returned values in `Box::new` + Box::new("") + } +} +impl T for () { + async fn f(&self) -> Box { + //~^ ERROR return type cannot be a trait object without pointer indirection + //~| HELP consider returning an `impl Trait` instead of a `dyn Trait` + //~| HELP alternatively, box the return type, and wrap all of the returned values in `Box::new` + Box::new("") + } +} + +fn main() { + let _ = f(); + let _ = ().f(); +} diff --git a/tests/ui/async-await/async-fn/dyn-in-return-type.rs b/tests/ui/async-await/async-fn/dyn-in-return-type.rs index ec793bf80f287..47d5e371f55f6 100644 --- a/tests/ui/async-await/async-fn/dyn-in-return-type.rs +++ b/tests/ui/async-await/async-fn/dyn-in-return-type.rs @@ -1,10 +1,30 @@ //@ edition:2024 +//@ run-rustfix async fn f() -> dyn core::fmt::Debug { //~^ ERROR return type cannot be a trait object without pointer indirection //~| HELP consider returning an `impl Trait` instead of a `dyn Trait` //~| HELP alternatively, box the return type, and wrap all of the returned values in `Box::new` - loop {} + Box::new("") +} +trait T { + async fn f(&self) -> dyn core::fmt::Debug { + //~^ ERROR return type cannot be a trait object without pointer indirection + //~| HELP consider returning an `impl Trait` instead of a `dyn Trait` + //~| HELP alternatively, box the return type, and wrap all of the returned values in `Box::new` + Box::new("") + } +} +impl T for () { + async fn f(&self) -> dyn core::fmt::Debug { + //~^ ERROR return type cannot be a trait object without pointer indirection + //~| HELP consider returning an `impl Trait` instead of a `dyn Trait` + //~| HELP alternatively, box the return type, and wrap all of the returned values in `Box::new` + Box::new("") + } } -fn main() {} +fn main() { + let _ = f(); + let _ = ().f(); +} diff --git a/tests/ui/async-await/async-fn/dyn-in-return-type.stderr b/tests/ui/async-await/async-fn/dyn-in-return-type.stderr index 341d9561fd7ae..0d8a92d33cf9a 100644 --- a/tests/ui/async-await/async-fn/dyn-in-return-type.stderr +++ b/tests/ui/async-await/async-fn/dyn-in-return-type.stderr @@ -1,5 +1,5 @@ error[E0746]: return type cannot be a trait object without pointer indirection - --> $DIR/dyn-in-return-type.rs:3:17 + --> $DIR/dyn-in-return-type.rs:4:17 | LL | async fn f() -> dyn core::fmt::Debug { | ^^^^^^^^^^^^^^^^^^^^ @@ -14,6 +14,38 @@ help: alternatively, box the return type, and wrap all of the returned values in LL | async fn f() -> Box { | ++++ + -error: aborting due to 1 previous error +error[E0746]: return type cannot be a trait object without pointer indirection + --> $DIR/dyn-in-return-type.rs:11:26 + | +LL | async fn f(&self) -> dyn core::fmt::Debug { + | ^^^^^^^^^^^^^^^^^^^^ + | +help: consider returning an `impl Trait` instead of a `dyn Trait` + | +LL - async fn f(&self) -> dyn core::fmt::Debug { +LL + async fn f(&self) -> impl core::fmt::Debug { + | +help: alternatively, box the return type, and wrap all of the returned values in `Box::new` + | +LL | async fn f(&self) -> Box { + | ++++ + + +error[E0746]: return type cannot be a trait object without pointer indirection + --> $DIR/dyn-in-return-type.rs:19:26 + | +LL | async fn f(&self) -> dyn core::fmt::Debug { + | ^^^^^^^^^^^^^^^^^^^^ + | +help: consider returning an `impl Trait` instead of a `dyn Trait` + | +LL - async fn f(&self) -> dyn core::fmt::Debug { +LL + async fn f(&self) -> impl core::fmt::Debug { + | +help: alternatively, box the return type, and wrap all of the returned values in `Box::new` + | +LL | async fn f(&self) -> Box { + | ++++ + + +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0746`. diff --git a/tests/ui/type-alias/dyn-trait-type-alias-return-type.fixed b/tests/ui/type-alias/dyn-trait-type-alias-return-type.fixed index 93472f808a8a7..ba94e1967df39 100644 --- a/tests/ui/type-alias/dyn-trait-type-alias-return-type.fixed +++ b/tests/ui/type-alias/dyn-trait-type-alias-return-type.fixed @@ -1,6 +1,8 @@ //@ run-rustfix type T = dyn core::fmt::Debug; //~^ NOTE this type alias is unsized +//~| NOTE this type alias is unsized +//~| NOTE this type alias is unsized fn f() -> Box { loop {} } //~^ ERROR the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time @@ -9,10 +11,29 @@ fn f() -> Box { loop {} } //~| NOTE the return type of a function must have a statically known size //~| HELP consider boxing the return type +trait X { + fn f(&self) -> Box { loop {} } + //~^ ERROR the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time + //~| HELP the trait `Sized` is not implemented for `(dyn Debug + 'static)` + //~| NOTE doesn't have a size known at compile-time + //~| NOTE the return type of a function must have a statically known size + //~| HELP consider boxing the return type +} + +impl X for () { + fn f(&self) -> Box { loop {} } + //~^ ERROR the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time + //~| HELP the trait `Sized` is not implemented for `(dyn Debug + 'static)` + //~| NOTE doesn't have a size known at compile-time + //~| NOTE the return type of a function must have a statically known size + //~| HELP consider boxing the return type +} + fn main() { f(); //~^ ERROR the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time //~| HELP the trait `Sized` is not implemented for `(dyn Debug + 'static)` //~| NOTE doesn't have a size known at compile-time //~| NOTE the return type of a function must have a statically known size + ().f(); } diff --git a/tests/ui/type-alias/dyn-trait-type-alias-return-type.rs b/tests/ui/type-alias/dyn-trait-type-alias-return-type.rs index da9f8d07f8e05..76f6e7c443424 100644 --- a/tests/ui/type-alias/dyn-trait-type-alias-return-type.rs +++ b/tests/ui/type-alias/dyn-trait-type-alias-return-type.rs @@ -1,6 +1,8 @@ //@ run-rustfix type T = dyn core::fmt::Debug; //~^ NOTE this type alias is unsized +//~| NOTE this type alias is unsized +//~| NOTE this type alias is unsized fn f() -> T { loop {} } //~^ ERROR the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time @@ -9,10 +11,29 @@ fn f() -> T { loop {} } //~| NOTE the return type of a function must have a statically known size //~| HELP consider boxing the return type +trait X { + fn f(&self) -> T { loop {} } + //~^ ERROR the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time + //~| HELP the trait `Sized` is not implemented for `(dyn Debug + 'static)` + //~| NOTE doesn't have a size known at compile-time + //~| NOTE the return type of a function must have a statically known size + //~| HELP consider boxing the return type +} + +impl X for () { + fn f(&self) -> T { loop {} } + //~^ ERROR the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time + //~| HELP the trait `Sized` is not implemented for `(dyn Debug + 'static)` + //~| NOTE doesn't have a size known at compile-time + //~| NOTE the return type of a function must have a statically known size + //~| HELP consider boxing the return type +} + fn main() { f(); //~^ ERROR the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time //~| HELP the trait `Sized` is not implemented for `(dyn Debug + 'static)` //~| NOTE doesn't have a size known at compile-time //~| NOTE the return type of a function must have a statically known size + ().f(); } diff --git a/tests/ui/type-alias/dyn-trait-type-alias-return-type.stderr b/tests/ui/type-alias/dyn-trait-type-alias-return-type.stderr index 658abbe8f780f..bb6951687048f 100644 --- a/tests/ui/type-alias/dyn-trait-type-alias-return-type.stderr +++ b/tests/ui/type-alias/dyn-trait-type-alias-return-type.stderr @@ -1,5 +1,5 @@ error[E0277]: the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time - --> $DIR/dyn-trait-type-alias-return-type.rs:5:11 + --> $DIR/dyn-trait-type-alias-return-type.rs:7:11 | LL | fn f() -> T { loop {} } | ^ doesn't have a size known at compile-time @@ -17,7 +17,43 @@ LL | fn f() -> Box { loop {} } | ++++ + error[E0277]: the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time - --> $DIR/dyn-trait-type-alias-return-type.rs:13:5 + --> $DIR/dyn-trait-type-alias-return-type.rs:24:20 + | +LL | fn f(&self) -> T { loop {} } + | ^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `(dyn Debug + 'static)` +note: this type alias is unsized + --> $DIR/dyn-trait-type-alias-return-type.rs:2:1 + | +LL | type T = dyn core::fmt::Debug; + | ^^^^^^ + = note: the return type of a function must have a statically known size +help: consider boxing the return type, and wrapping all of the returned values in `Box::new` + | +LL | fn f(&self) -> Box { loop {} } + | ++++ + + +error[E0277]: the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time + --> $DIR/dyn-trait-type-alias-return-type.rs:15:20 + | +LL | fn f(&self) -> T { loop {} } + | ^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `(dyn Debug + 'static)` +note: this type alias is unsized + --> $DIR/dyn-trait-type-alias-return-type.rs:2:1 + | +LL | type T = dyn core::fmt::Debug; + | ^^^^^^ + = note: the return type of a function must have a statically known size +help: consider boxing the return type, and wrapping all of the returned values in `Box::new` + | +LL | fn f(&self) -> Box { loop {} } + | ++++ + + +error[E0277]: the size for values of type `(dyn Debug + 'static)` cannot be known at compilation time + --> $DIR/dyn-trait-type-alias-return-type.rs:33:5 | LL | f(); | ^^^ doesn't have a size known at compile-time @@ -25,6 +61,6 @@ LL | f(); = help: the trait `Sized` is not implemented for `(dyn Debug + 'static)` = note: the return type of a function must have a statically known size -error: aborting due to 2 previous errors +error: aborting due to 4 previous errors For more information about this error, try `rustc --explain E0277`. From 90d2477e4e35927b20237b27869200fb1d47e7bf Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 4 Dec 2025 09:49:31 -0800 Subject: [PATCH 24/24] Add release notes for 1.92.0 --- RELEASES.md | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index b14cc499b46d0..0dffe931e6eb1 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,88 @@ +Version 1.92.0 (2025-12-11) +========================== + + + +Language +-------- +- [Document `MaybeUninit` representation and validity](https://github.com/rust-lang/rust/pull/140463) +- [Allow `&raw [mut | const]` for union field in safe code](https://github.com/rust-lang/rust/pull/141469) +- [Prefer item bounds of associated types over where-bounds for auto-traits and `Sized`](https://github.com/rust-lang/rust/pull/144064) +- [Do not materialize `X` in `[X; 0]` when `X` is unsizing a const](https://github.com/rust-lang/rust/pull/145277) +- [Support combining `#[track_caller]` and `#[no_mangle]` (requires every declaration specifying `#[track_caller]` as well)](https://github.com/rust-lang/rust/pull/145724) +- [Make never type lints `never_type_fallback_flowing_into_unsafe` and `dependency_on_unit_never_type_fallback` deny-by-default](https://github.com/rust-lang/rust/pull/146167) +- [Allow specifying multiple bounds for same associated item, except in trait objects](https://github.com/rust-lang/rust/pull/146593) +- [Slightly strengthen higher-ranked region handling in coherence](https://github.com/rust-lang/rust/pull/146725) +- [The `unused_must_use` lint no longer warns on `Result<(), Uninhabited>` (for instance, `Result<(), !>`), or `ControlFlow`](https://github.com/rust-lang/rust/pull/147382). This avoids having to check for an error that can never happen. + + + +Compiler +-------- +- [Make `mips64el-unknown-linux-muslabi64` link dynamically](https://github.com/rust-lang/rust/pull/146858) +- [Remove current code for embedding command-line args in PDB](https://github.com/rust-lang/rust/pull/147022) + Command-line information is typically not needed by debugging tools, and the removed code + was causing problems for incremental builds even on targets that don't use PDB debuginfo. + + + +Libraries +--------- +- [Specialize `Iterator::eq{_by}` for `TrustedLen` iterators](https://github.com/rust-lang/rust/pull/137122) +- [Simplify `Extend` for tuples](https://github.com/rust-lang/rust/pull/138799) +- [Added details to `Debug` for `EncodeWide`](https://github.com/rust-lang/rust/pull/140153). +- [`iter::Repeat::last`](https://github.com/rust-lang/rust/pull/147258) and [`count`](https://github.com/rust-lang/rust/pull/146410) will now panic, rather than looping infinitely. + + + +Stabilized APIs +--------------- + +- [`NonZero::div_ceil`](https://doc.rust-lang.org/stable/std/num/struct.NonZero.html#method.div_ceil) +- [`Location::file_as_c_str`](https://doc.rust-lang.org/stable/std/panic/struct.Location.html#method.file_as_c_str) +- [`RwLockWriteGuard::downgrade`](https://doc.rust-lang.org/stable/std/sync/struct.RwLockWriteGuard.html#method.downgrade) +- [`Box::new_zeroed`](https://doc.rust-lang.org/stable/std/boxed/struct.Box.html#method.new_zeroed) +- [`Box::new_zeroed_slice`](https://doc.rust-lang.org/stable/std/boxed/struct.Box.html#method.new_zeroed_slice) +- [`Rc::new_zeroed`](https://doc.rust-lang.org/stable/std/rc/struct.Rc.html#method.new_zeroed) +- [`Rc::new_zeroed_slice`](https://doc.rust-lang.org/stable/std/rc/struct.Rc.html#method.new_zeroed_slice) +- [`Arc::new_zeroed`](https://doc.rust-lang.org/stable/std/sync/struct.Arc.html#method.new_zeroed) +- [`Arc::new_zeroed_slice`](https://doc.rust-lang.org/stable/std/sync/struct.Arc.html#method.new_zeroed_slice) +- [`btree_map::Entry::insert_entry`](https://doc.rust-lang.org/stable/std/collections/btree_map/enum.Entry.html#method.insert_entry) +- [`btree_map::VacantEntry::insert_entry`](https://doc.rust-lang.org/stable/std/collections/btree_map/struct.VacantEntry.html#method.insert_entry) +- [`impl Extend for proc_macro::TokenStream`](https://doc.rust-lang.org/stable/proc_macro/struct.TokenStream.html#impl-Extend%3CGroup%3E-for-TokenStream) +- [`impl Extend for proc_macro::TokenStream`](https://doc.rust-lang.org/stable/proc_macro/struct.TokenStream.html#impl-Extend%3CLiteral%3E-for-TokenStream) +- [`impl Extend for proc_macro::TokenStream`](https://doc.rust-lang.org/stable/proc_macro/struct.TokenStream.html#impl-Extend%3CPunct%3E-for-TokenStream) +- [`impl Extend for proc_macro::TokenStream`](https://doc.rust-lang.org/stable/proc_macro/struct.TokenStream.html#impl-Extend%3CIdent%3E-for-TokenStream) + +These previously stable APIs are now stable in const contexts: + +- [`<[_]>::rotate_left`](https://doc.rust-lang.org/stable/std/primitive.slice.html#method.rotate_left) +- [`<[_]>::rotate_right`](https://doc.rust-lang.org/stable/std/primitive.slice.html#method.rotate_right) + + + +Cargo +----- +- [Added a new chapter](https://github.com/rust-lang/cargo/issues/16119) to the Cargo book, ["Optimizing Build Performance"](https://doc.rust-lang.org/stable/cargo/guide/build-performance.html). + + + +Rustdoc +----- +- [If a trait item appears in rustdoc search, hide the corresponding impl items](https://github.com/rust-lang/rust/pull/145898). Previously a search for "last" would show both `Iterator::last` as well as impl methods like `std::vec::IntoIter::last`. Now these impl methods will be hidden, freeing up space for inherent methods like `BTreeSet::last`. +- [Relax rules for identifiers in search](https://github.com/rust-lang/rust/pull/147860). Previously you could only search for identifiers that were valid in rust code, now searches only need to be valid as part of an identifier. For example, you can now perform a search that starts with a digit. + + + +Compatibility Notes +------------------- +* [Fix backtraces with `-C panic=abort` on Linux by generating unwind tables by default](https://github.com/rust-lang/rust/pull/143613). Build with `-C force-unwind-tables=no` to keep omitting unwind tables. +- As part of the larger effort refactoring compiler built-in attributes and their diagnostics, [the future-compatibility lint `invalid_macro_export_arguments` is upgraded to deny-by-default and will be reported in dependencies too.](https://github.com/rust-lang/rust/pull/143857) +- [Update the minimum external LLVM to 20](https://github.com/rust-lang/rust/pull/145071) +- [Prevent downstream `impl DerefMut for Pin`](https://github.com/rust-lang/rust/pull/145608) +- [Don't apply temporary lifetime extension rules to the arguments of non-extended `pin!` and formatting macros](https://github.com/rust-lang/rust/pull/145838) + + Version 1.91.1 (2025-11-10) ===========================