From 69f75dbd9240907f15b49d8079a529a3b4fa500d Mon Sep 17 00:00:00 2001 From: hizyyo Date: Sat, 5 Sep 2026 21:33:26 +0500 Subject: [PATCH 1/8] Move the foreign module #[link] ABI check to attribute parsing --- compiler/rustc_ast_lowering/src/lib.rs | 1 + .../src/attributes/allow_unstable.rs | 2 +- .../src/attributes/codegen_attrs.rs | 4 +- .../src/attributes/inline.rs | 2 +- .../src/attributes/link_attrs.rs | 19 ++++++-- .../src/attributes/lint_helpers.rs | 2 +- .../src/attributes/macro_attrs.rs | 2 +- .../rustc_attr_parsing/src/attributes/mod.rs | 8 ++-- compiler/rustc_attr_parsing/src/context.rs | 2 +- .../rustc_attr_parsing/src/diagnostics.rs | 7 +++ compiler/rustc_attr_parsing/src/interface.rs | 2 +- compiler/rustc_passes/src/check_attr.rs | 19 +------- compiler/rustc_passes/src/diagnostics.rs | 7 --- tests/ui/attributes/link-foreign-mod-abi.rs | 45 +++++++++++++++++++ tests/ui/attributes/link-invalid-abi.rs | 13 ++++++ tests/ui/attributes/link-invalid-abi.stderr | 24 ++++++++++ ...issue-43106-gating-of-builtin-attrs.stderr | 16 +++---- 17 files changed, 127 insertions(+), 48 deletions(-) create mode 100644 tests/ui/attributes/link-foreign-mod-abi.rs create mode 100644 tests/ui/attributes/link-invalid-abi.rs create mode 100644 tests/ui/attributes/link-invalid-abi.stderr diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 8513ee39855d0..efb434c921653 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -41,6 +41,7 @@ use std::mem; use std::sync::{Arc, LazyLock}; +use rustc_abi::ExternAbi; use rustc_ast::mut_visit::{self, MutVisitor}; use rustc_ast::node_id::NodeMap; use rustc_ast::visit::{self, Visitor}; diff --git a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs index 667c2d0838a25..4a6e57b12233a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs +++ b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs @@ -26,7 +26,7 @@ impl CombineAttributeParser for AllowInternalUnstableParser { .zip(iter::repeat(cx.attr_span)) } - fn finalize_check(cx: &FinalizeCheckContext<'_, '_>, attr_span: Span) { + fn finalize_check(cx: &mut FinalizeCheckContext<'_, '_>, attr_span: Span) { check_macro_only(cx, attr_span); } } diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index bff7d7ad81cb9..2553829f8c5ce 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -349,7 +349,7 @@ impl NoArgsAttributeParser for TrackCallerParser { const STABILITY: AttributeStability = AttributeStability::Stable; const CREATE: fn(Span) -> AttributeKind = AttributeKind::TrackCaller; - fn finalize_check(cx: &FinalizeCheckContext<'_, '_>, attr_span: Span) { + fn finalize_check(cx: &mut FinalizeCheckContext<'_, '_>, attr_span: Span) { match cx.target { Target::Fn => { // `#[track_caller]` is not valid on weak lang items because they are called via @@ -571,7 +571,7 @@ impl CombineAttributeParser for TargetFeatureParser { parse_tf_attribute(cx, args) } - fn finalize_check(cx: &FinalizeCheckContext<'_, '_>, attr_span: Span) { + fn finalize_check(cx: &mut FinalizeCheckContext<'_, '_>, attr_span: Span) { // `#[target_feature]` is incompatible with lang item functions, // except on WASM where calling target-feature functions is safe (see #84988). if !cx.sess().target.is_like_wasm && !cx.sess().opts.actually_rustdoc { diff --git a/compiler/rustc_attr_parsing/src/attributes/inline.rs b/compiler/rustc_attr_parsing/src/attributes/inline.rs index f3e305ea266a1..78a989fdfdba6 100644 --- a/compiler/rustc_attr_parsing/src/attributes/inline.rs +++ b/compiler/rustc_attr_parsing/src/attributes/inline.rs @@ -94,7 +94,7 @@ impl SingleAttributeParser for RustcForceInlineParser { )) } - fn finalize_check(cx: &FinalizeCheckContext<'_, '_>, attr_span: Span) { + fn finalize_check(cx: &mut FinalizeCheckContext<'_, '_>, attr_span: Span) { let Some(inline_span) = find_attr!(cx.parsed_attrs, Inline(attr, span) if !matches!(attr, InlineAttr::Force { .. }) => span) else { return; diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index a00e5af00c2eb..e3e74971e99db 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -1,8 +1,10 @@ +use rustc_abi::ExternAbi; +use rustc_ast::ItemKind; use rustc_attr_ir::AttributeKind::{LinkName, LinkOrdinal, LinkSection}; use rustc_attr_ir::*; use rustc_errors::msg; use rustc_feature::{AttributeStability, Features}; -use rustc_lint_defs::builtin::ILL_FORMED_ATTRIBUTE_INPUT; +use rustc_lint_defs::builtin::{ILL_FORMED_ATTRIBUTE_INPUT, UNUSED_ATTRIBUTES}; use rustc_session::Session; use rustc_session::diagnostics::feature_err; use rustc_span::edition::Edition::Edition2024; @@ -17,7 +19,7 @@ use crate::attributes::cfg::parse_cfg_entry; use crate::diagnostics::{ AsNeededCompatibility, BothFfiConstAndPure, BundleNeedsStatic, EmptyLinkName, ExportSymbolsNeedsStatic, ImportNameTypeRaw, ImportNameTypeX86, IncompatibleWasmLink, - InvalidLinkModifier, InvalidMachoSection, InvalidMachoSectionReason, LinkFrameworkApple, + InvalidLinkModifier, InvalidMachoSection, InvalidMachoSectionReason, Link, LinkFrameworkApple, LinkOrdinalOutOfRange, LinkRequiresName, MultipleModifiers, NullOnLinkName, NullOnLinkSection, RawDylibOnlyWindows, WholeArchiveNeedsStatic, }; @@ -258,6 +260,17 @@ impl CombineAttributeParser for LinkParser { import_name_type, }) } + + fn finalize_check(cx: &mut FinalizeCheckContext<'_, '_>, attr_span: Span) { + let Some(item) = cx.target_item else { return }; + let ItemKind::ForeignMod(fm) = &item.kind else { return }; + let abi = fm.abi.map_or(ExternAbi::FALLBACK, |abi| { + abi.symbol_unescaped.as_str().parse().unwrap_or(ExternAbi::Rust) + }); + if matches!(abi, ExternAbi::Rust) { + cx.emit_lint(UNUSED_ATTRIBUTES, Link, attr_span); + } + } } impl LinkParser { @@ -584,7 +597,7 @@ impl NoArgsAttributeParser for FfiPureParser { const STABILITY: AttributeStability = unstable!(ffi_pure); const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiPure; - fn finalize_check(cx: &FinalizeCheckContext<'_, '_>, attr_span: Span) { + fn finalize_check(cx: &mut FinalizeCheckContext<'_, '_>, attr_span: Span) { // `#[ffi_const]` functions cannot be `#[ffi_pure]`. if cx.all_attrs.iter().any(|a| a.word_is(sym::ffi_const)) { cx.emit_err(BothFfiConstAndPure { attr_span }); diff --git a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs index 4ead90af8d630..10e42ced30705 100644 --- a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs +++ b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs @@ -29,7 +29,7 @@ impl NoArgsAttributeParser for RustcPubTransparentParser { const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcPubTransparent; - fn finalize_check(cx: &FinalizeCheckContext<'_, '_>, attr_span: Span) { + fn finalize_check(cx: &mut FinalizeCheckContext<'_, '_>, attr_span: Span) { // `#[rustc_pub_transparent]` may only be applied to `#[repr(transparent)]` types. let is_transparent = find_attr!( cx.parsed_attrs, diff --git a/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs index 92b61fe00b1b9..dde10225e652f 100644 --- a/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs @@ -136,7 +136,7 @@ impl NoArgsAttributeParser for AllowInternalUnsafeParser { const STABILITY: AttributeStability = unstable!(allow_internal_unsafe); const CREATE: fn(Span) -> AttributeKind = |span| AttributeKind::AllowInternalUnsafe(span); - fn finalize_check(cx: &FinalizeCheckContext<'_, '_>, attr_span: Span) { + fn finalize_check(cx: &mut FinalizeCheckContext<'_, '_>, attr_span: Span) { check_macro_only(cx, attr_span); } } diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 242b4a73b06a6..8e47414f6c72a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -169,7 +169,7 @@ pub(crate) trait SingleAttributeParser: 'static { /// combinations. `attr_span` is the span of this attribute. /// /// Defaults to a no-op. - fn finalize_check(_cx: &FinalizeCheckContext<'_, '_>, _attr_span: Span) {} + fn finalize_check(_cx: &mut FinalizeCheckContext<'_, '_>, _attr_span: Span) {} } /// Use in combination with [`SingleAttributeParser`]. @@ -287,7 +287,7 @@ pub(crate) trait NoArgsAttributeParser: 'static { /// `attr_span` is the span of this attribute. /// /// Defaults to a no-op. - fn finalize_check(_cx: &FinalizeCheckContext<'_, '_>, _attr_span: Span) {} + fn finalize_check(_cx: &mut FinalizeCheckContext<'_, '_>, _attr_span: Span) {} } pub(crate) struct WithoutArgs(PhantomData); @@ -311,7 +311,7 @@ impl SingleAttributeParser for WithoutArgs { Some(T::CREATE(cx.attr_span)) } - fn finalize_check(cx: &FinalizeCheckContext<'_, '_>, attr_span: Span) { + fn finalize_check(cx: &mut FinalizeCheckContext<'_, '_>, attr_span: Span) { T::finalize_check(cx, attr_span) } } @@ -355,7 +355,7 @@ pub(crate) trait CombineAttributeParser: 'static { /// `attr_span` is the span of the first attribute that was encountered. /// /// Defaults to a no-op. - fn finalize_check(_cx: &FinalizeCheckContext<'_, '_>, _attr_span: Span) {} + fn finalize_check(_cx: &mut FinalizeCheckContext<'_, '_>, _attr_span: Span) {} } /// Use in combination with [`CombineAttributeParser`]. diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 22a4a069b75c7..72839643b4dc3 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -102,7 +102,7 @@ pub(crate) type FinalizeFn = fn(&mut FinalizeContext<'_, '_>) -> FinalizeOutput; /// finalized, so it can inspect the fully parsed attributes via /// [`FinalizeCheckContext::parsed_attrs`]. The [`Span`] is the span of the attribute the /// check is associated with, used for diagnostics. -pub(crate) type FinalizeCheckFn = fn(&FinalizeCheckContext<'_, '_>, Span); +pub(crate) type FinalizeCheckFn = fn(&mut FinalizeCheckContext<'_, '_>, Span); /// The result of finalizing a single attribute parser. pub(crate) struct FinalizeOutput { diff --git a/compiler/rustc_attr_parsing/src/diagnostics.rs b/compiler/rustc_attr_parsing/src/diagnostics.rs index 44e405fcf5269..bf630c6bf91a0 100644 --- a/compiler/rustc_attr_parsing/src/diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/diagnostics.rs @@ -1825,6 +1825,13 @@ pub(crate) struct EmptyLinkName { pub span: Span, } +#[derive(Diagnostic)] +#[diag("attribute should be applied to an `extern` block with non-Rust ABI")] +#[warning( + "this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!" +)] +pub(crate) struct Link; + #[derive(Diagnostic)] #[diag("link kind `framework` is only supported on Apple targets", code = E0455)] pub(crate) struct LinkFrameworkApple { diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index 48083683b6a7a..df89ffe57b9a0 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -512,7 +512,7 @@ impl<'sess> AttributeParser<'sess> { // inspect the fully parsed attributes via `FinalizeCheckContext::parsed_attrs`. for (check, attr_span) in deferred_checks { check( - &FinalizeCheckContext { + &mut FinalizeCheckContext { shared: SharedContext { cx: self, target_span, diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 4926ec9872d3b..2c9a18239665e 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -8,7 +8,6 @@ use std::cell::Cell; use std::slice; -use rustc_abi::ExternAbi; use rustc_ast::MetaItemKind; use rustc_attr_ir::diagnostic::Directive; use rustc_attr_ir::lang_items::LangItem; @@ -208,7 +207,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } AttributeKind::Naked(..) => self.check_naked(hir_id, target), AttributeKind::MayDangle(attr_span) => self.check_may_dangle(hir_id, *attr_span), - AttributeKind::Link(_, attr_span) => self.check_link(hir_id, *attr_span, target), AttributeKind::MacroExport { span, .. } => { self.check_macro_export(hir_id, *span, target) } @@ -271,6 +269,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::InstructionSet(..) => (), AttributeKind::InstrumentFn(..) => (), AttributeKind::Lang(..) => (), + AttributeKind::Link(..) => (), AttributeKind::LinkName { .. } => (), AttributeKind::LinkOrdinal { .. } => (), AttributeKind::LinkSection { .. } => (), @@ -1100,22 +1099,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { self.dcx().emit_err(diagnostics::InvalidMayDangle { attr_span }); } - /// Checks if `#[link]` is applied to an item other than a foreign module. - fn check_link(&self, hir_id: HirId, attr_span: Span, target: Target) { - if target != Target::ForeignMod { - return; // Checked by attribute parser - } - - if let hir::Node::Item(item) = self.tcx.hir_node(hir_id) - && let Item { kind: ItemKind::ForeignMod { abi, .. }, .. } = item - && !matches!(abi, ExternAbi::Rust) - { - return; - } - - self.tcx.emit_node_span_lint(UNUSED_ATTRIBUTES, hir_id, attr_span, diagnostics::Link); - } - /// Checks if `#[rustc_legacy_const_generics]` is applied to a function and has a valid argument. fn check_rustc_legacy_const_generics( &self, diff --git a/compiler/rustc_passes/src/diagnostics.rs b/compiler/rustc_passes/src/diagnostics.rs index 59c1005885903..7c82811f99276 100644 --- a/compiler/rustc_passes/src/diagnostics.rs +++ b/compiler/rustc_passes/src/diagnostics.rs @@ -126,13 +126,6 @@ pub(crate) struct BothOptimizeNoneAndInline { pub inline_span: Span, } -#[derive(Diagnostic)] -#[diag("attribute should be applied to an `extern` block with non-Rust ABI")] -#[warning( - "this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!" -)] -pub(crate) struct Link; - #[derive(Diagnostic)] #[diag("#[rustc_legacy_const_generics] functions must only have const generics")] pub(crate) struct RustcLegacyConstGenericsOnly { diff --git a/tests/ui/attributes/link-foreign-mod-abi.rs b/tests/ui/attributes/link-foreign-mod-abi.rs new file mode 100644 index 0000000000000..83d0a2dd47f0b --- /dev/null +++ b/tests/ui/attributes/link-foreign-mod-abi.rs @@ -0,0 +1,45 @@ +//@ check-pass +//@ compile-flags: --crate-type=lib + +#![no_core] +#![feature(no_core, rust_cold_cc, rust_preserve_none_cc, rust_tail_cc, unboxed_closures)] +#![allow(missing_abi)] +#![deny(unfulfilled_lint_expectations, unused_attributes)] + +#[link(name = "omitted")] +extern {} + +#[link(name = "c")] +extern "C" {} + +#[link(name = "rust-call")] +extern "rust-call" {} + +#[link(name = "rust-cold")] +extern "rust-cold" {} + +#[link(name = "rust-preserve-none")] +extern "rust-preserve-none" {} + +#[link(name = "tail")] +extern "tail" {} + +#[cfg_attr(any(), link(name = "disabled"))] +extern "Rust" {} + +#[expect(unused_attributes)] +#[cfg_attr(all(), link(name = "enabled"))] +extern "Rust" {} + +#[allow(unused_attributes)] +#[link(name = "allowed")] +extern "Rust" {} + +#[expect(unused_attributes)] +#[link(name = "expected")] +extern "Rust" {} + +#[expect(unused_attributes)] +#[link(name = "first")] +#[link(name = "second")] +extern "Rust" {} diff --git a/tests/ui/attributes/link-invalid-abi.rs b/tests/ui/attributes/link-invalid-abi.rs new file mode 100644 index 0000000000000..49f91236c1a2b --- /dev/null +++ b/tests/ui/attributes/link-invalid-abi.rs @@ -0,0 +1,13 @@ +//@ check-fail +//@ compile-flags: --crate-type=lib + +#![no_core] +#![feature(no_core)] +#![warn(unused_attributes)] + +#[link(name = "first")] +//~^ WARN +//~| WARN +#[link(name = "second")] +extern "invalid" {} +//~^ ERROR diff --git a/tests/ui/attributes/link-invalid-abi.stderr b/tests/ui/attributes/link-invalid-abi.stderr new file mode 100644 index 0000000000000..add2ebcb7c186 --- /dev/null +++ b/tests/ui/attributes/link-invalid-abi.stderr @@ -0,0 +1,24 @@ +error[E0703]: invalid ABI: found `invalid` + --> $DIR/link-invalid-abi.rs:12:8 + | +LL | extern "invalid" {} + | ^^^^^^^^^ invalid ABI + | + = note: invoke `rustc --print=calling-conventions` for a full list of supported calling conventions + +warning: attribute should be applied to an `extern` block with non-Rust ABI + --> $DIR/link-invalid-abi.rs:8:1 + | +LL | #[link(name = "first")] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! +note: the lint level is defined here + --> $DIR/link-invalid-abi.rs:6:9 + | +LL | #![warn(unused_attributes)] + | ^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error; 1 warning emitted + +For more information about this error, try `rustc --explain E0703`. diff --git a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr index a677339551153..2d3b7658f9aae 100644 --- a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr +++ b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr @@ -207,14 +207,6 @@ note: the lint level is defined here LL | #![warn(unused_attributes, unknown_lints)] | ^^^^^^^^^^^^^^^^^ -warning: attribute should be applied to an `extern` block with non-Rust ABI - --> $DIR/issue-43106-gating-of-builtin-attrs.rs:729:5 - | -LL | #[link(name = "x")] extern "Rust" {} - | ^^^^^^^^^^^^^^^^^^^ - | - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - warning: the `macro_use` attribute cannot be used on crates --> $DIR/issue-43106-gating-of-builtin-attrs.rs:45:4 | @@ -978,6 +970,14 @@ LL | #[link(name = "x")] impl S { } = help: the `link` attribute can only be applied to foreign modules = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! +warning: attribute should be applied to an `extern` block with non-Rust ABI + --> $DIR/issue-43106-gating-of-builtin-attrs.rs:729:5 + | +LL | #[link(name = "x")] extern "Rust" {} + | ^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + warning: the `must_use` attribute cannot be used on modules --> $DIR/issue-43106-gating-of-builtin-attrs.rs:749:3 | From 0c182d66b47f8203e551f0302022bcd9ea5c5277 Mon Sep 17 00:00:00 2001 From: hizyyo Date: Sat, 12 Sep 2026 16:00:46 +0500 Subject: [PATCH 2/8] Fix finalize_check signatures --- compiler/rustc_ast_lowering/src/lib.rs | 1 - compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index efb434c921653..8513ee39855d0 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -41,7 +41,6 @@ use std::mem; use std::sync::{Arc, LazyLock}; -use rustc_abi::ExternAbi; use rustc_ast::mut_visit::{self, MutVisitor}; use rustc_ast::node_id::NodeMap; use rustc_ast::visit::{self, Visitor}; diff --git a/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs b/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs index 30fdadf95e4ce..2acba84956e67 100644 --- a/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs +++ b/compiler/rustc_attr_parsing/src/attributes/non_exhaustive.rs @@ -21,7 +21,7 @@ impl NoArgsAttributeParser for NonExhaustiveParser { const STABILITY: AttributeStability = AttributeStability::Stable; const CREATE: fn(Span) -> AttributeKind = AttributeKind::NonExhaustive; - fn finalize_check(cx: &FinalizeCheckContext<'_, '_>, attr_span: Span) { + fn finalize_check(cx: &mut FinalizeCheckContext<'_, '_>, attr_span: Span) { if cx.target != Target::Struct { return; } From 89da8633dd9a996e633aae359cc100a009e98ae4 Mon Sep 17 00:00:00 2001 From: hizyyo Date: Sat, 19 Sep 2026 18:23:47 +0500 Subject: [PATCH 3/8] Address link attribute finalization invariants --- .../src/attributes/link_attrs.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index e3e74971e99db..f4d0fa35e3f47 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -262,10 +262,22 @@ impl CombineAttributeParser for LinkParser { } fn finalize_check(cx: &mut FinalizeCheckContext<'_, '_>, attr_span: Span) { - let Some(item) = cx.target_item else { return }; - let ItemKind::ForeignMod(fm) = &item.kind else { return }; + if cx.target != Target::ForeignMod { + return; + } + + let item = cx.target_item.expect("missing AST target item for Target::ForeignMod"); + let ItemKind::ForeignMod(fm) = &item.kind else { + panic!("expected foreign module AST target item for Target::ForeignMod"); + }; let abi = fm.abi.map_or(ExternAbi::FALLBACK, |abi| { - abi.symbol_unescaped.as_str().parse().unwrap_or(ExternAbi::Rust) + abi.symbol_unescaped.as_str().parse().unwrap_or_else(|_| { + cx.dcx().span_delayed_bug( + abi.span, + "LinkParser::finalize_check was unable to pre-detect the ABI, so it continues to use the recovery value solely to check for unused_attributes in the lint; a user error E0703 will be reported later in lower_abi", + ); + ExternAbi::Rust + }) }); if matches!(abi, ExternAbi::Rust) { cx.emit_lint(UNUSED_ATTRIBUTES, Link, attr_span); From 999b4aaa959fc886e272249c63bd6cdc85a119bf Mon Sep 17 00:00:00 2001 From: Rachel Barker Date: Thu, 17 Sep 2026 16:57:10 +0100 Subject: [PATCH 4/8] Fix maximum read/write/send length on non-{Mac, Windows} platforms On most platforms, `send` takes a `size_t` length, but returns an `ssize_t` count of bytes written. This means that the actual limit is `ssize_t::MAX`, not `size_t::MAX`. QNX has a bug on top of this, where the returned length appears to be converted to a C `int` and back, resulting in the wrong return value if the length is >= 2^31 bytes. --- library/std/src/sys/fd/unix.rs | 16 ++++++---- .../std/src/sys/net/connection/socket/mod.rs | 24 +++++++++++--- .../src/sys/net/connection/socket/tests.rs | 31 +++++++++++++++---- .../src/sys/net/connection/socket/windows.rs | 2 +- 4 files changed, 55 insertions(+), 18 deletions(-) diff --git a/library/std/src/sys/fd/unix.rs b/library/std/src/sys/fd/unix.rs index aa84f6bc28023..6ebbc6afc1661 100644 --- a/library/std/src/sys/fd/unix.rs +++ b/library/std/src/sys/fd/unix.rs @@ -72,12 +72,16 @@ pub struct FileDesc(OwnedFd); // // On Apple targets however, apparently the 64-bit libc is either buggy or // intentionally showing odd behavior by rejecting any read with a size -// larger than INT_MAX. To handle both of these the read size is capped on -// both platforms. -const READ_LIMIT: usize = if cfg!(target_vendor = "apple") { - libc::c_int::MAX as usize -} else { - libc::ssize_t::MAX as usize +// larger than INT_MAX. +// +// Meanwhile on QNX, reads/writes/sends larger than INT_MAX return the wrong +// number of bytes written (eg, writing 2^31 bytes returns (2^64 - 2^31) instead +// of the correct byte count). +const READ_LIMIT: usize = cfg_select! { + any(target_vendor = "apple", target_os = "nto", target_os = "qnx") => { + libc::c_int::MAX as usize + } + _ => libc::ssize_t::MAX as usize, }; #[cfg(any( diff --git a/library/std/src/sys/net/connection/socket/mod.rs b/library/std/src/sys/net/connection/socket/mod.rs index 4e5de0209ad24..55db60fde694d 100644 --- a/library/std/src/sys/net/connection/socket/mod.rs +++ b/library/std/src/sys/net/connection/socket/mod.rs @@ -36,8 +36,26 @@ cfg_select! { use netc as c; +// Similarly to reads (see READ_LIMIT), the `send` syscall on most platforms +// takes a `size_t` length, but returns an `ssize_t` of bytes written. So the +// actual maximum number of bytes we can send in one call is SSIZE_MAX. +// +// On Apple targets however, apparently the 64-bit libc is either buggy or +// intentionally showing odd behavior by rejecting send calls with a size +// larger than INT_MAX. So cap the send size to INT_MAX. +// +// Meanwhile on QNX, reads/writes/sends larger than INT_MAX return the wrong +// number of bytes written (eg, writing 2^31 bytes returns (2^64 - 2^31) instead +// of the correct byte count). +// +// On Windows, the relevant syscall takes an `i32` (unlike for read/write!), +// so we need to clamp to i32::MAX. const MAX_SEND_LEN: usize = - if cfg!(target_vendor = "apple") { c_int::MAX as usize } else { ::MAX as usize }; + cfg_select! { + any(target_vendor = "apple", target_os = "nto", target_os = "qnx") => c_int::MAX as usize, + target_os = "windows" => i32::MAX as usize, + _ => libc::ssize_t::MAX as usize, + }; cfg_select! { any( @@ -717,8 +735,6 @@ impl UdpSocket { self.inner.peek_from(buf) } - // `MAX_SEND_LEN` is `usize::MAX` off Apple/Windows, where the guard is a no-op. - #[allow(clippy::absurd_extreme_comparisons)] pub fn send_to(&self, buf: &[u8], dst: &SocketAddr) -> io::Result { if buf.len() > MAX_SEND_LEN { return Err(io::Error::from_raw_os_error(c::EMSGSIZE)); @@ -872,8 +888,6 @@ impl UdpSocket { self.inner.peek(buf) } - // `MAX_SEND_LEN` is `usize::MAX` off Apple/Windows, where the guard is a no-op. - #[allow(clippy::absurd_extreme_comparisons)] pub fn send(&self, buf: &[u8]) -> io::Result { if buf.len() > MAX_SEND_LEN { return Err(io::Error::from_raw_os_error(c::EMSGSIZE)); diff --git a/library/std/src/sys/net/connection/socket/tests.rs b/library/std/src/sys/net/connection/socket/tests.rs index e6f02d7a93859..cc072420b754b 100644 --- a/library/std/src/sys/net/connection/socket/tests.rs +++ b/library/std/src/sys/net/connection/socket/tests.rs @@ -18,13 +18,32 @@ fn no_lookup_host_duplicates() { ); } -// #115325: on Apple, `send` rejects a length > `c_int::MAX` with `EINVAL`, so -// the clamp must not regress to the unbounded `wrlen_t::MAX`. +// On non-Windows platforms, the maximum valid length to pass into `send` is +// not the same as the maximum of the *type* used for the length. Ensure that +// the proper maximum is used, and that we do not regress to simply clamping to +// `wrlen_t::MAX` +// +// On Apple (per 115325), `send` takes a `size_t` length, but rejects any +// length > `c_int::MAX` with `EINVAL`. +// +// On QNX, `send` with length > `c_int::MAX` returns an incorrect count of bytes +// written. +// +// On Windows, `send` takes an `i32` length and returns an `i32`. +// +// On other platforms, `send` takes a `size_t` and returns an `ssize_t`, so +// sends larger then `ssize_t::MAX` will (maybe silently) return bad lengths. #[test] fn max_send_len_within_platform_limit() { - if cfg!(target_vendor = "apple") { - assert_eq!(MAX_SEND_LEN, c_int::MAX as usize); - } else { - assert_eq!(MAX_SEND_LEN, ::MAX as usize); + cfg_select! { + any(target_vendor = "apple", target_os = "nto", target_os = "qnx") => { + assert_eq!(MAX_SEND_LEN, c_int::MAX as usize); + } + target_os = "windows" => { + assert_eq!(MAX_SEND_LEN, i32::MAX as usize); + } + _ => { + assert_eq!(MAX_SEND_LEN, libc::ssize_t::MAX as usize); + } } } diff --git a/library/std/src/sys/net/connection/socket/windows.rs b/library/std/src/sys/net/connection/socket/windows.rs index 8b98d0d31e507..e9d5336001fd8 100644 --- a/library/std/src/sys/net/connection/socket/windows.rs +++ b/library/std/src/sys/net/connection/socket/windows.rs @@ -309,7 +309,7 @@ impl Socket { ) -> io::Result<(usize, SocketAddr)> { let mut storage = unsafe { mem::zeroed::() }; let mut addrlen = size_of_val(&storage) as netc::socklen_t; - let length = cmp::min(buf.len(), ::MAX as usize) as wrlen_t; + let length = cmp::min(buf.len(), super::MAX_SEND_LEN) as wrlen_t; // On unix when a socket is shut down all further reads return 0, so we // do the same on windows to map a shut down socket to returning EOF. From 6a6b9b49c022af25b3589078dd52cd747bc43d92 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sun, 27 Sep 2026 05:39:39 -0600 Subject: [PATCH 5/8] Add .seek_read_buf_exact() to std::os::windows::fs::FileExt --- library/std/src/fs/tests.rs | 36 ++++++++++++++++++++ library/std/src/os/windows/fs.rs | 57 ++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 250109f277dab..10eb600d64b31 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -1075,6 +1075,42 @@ fn test_seek_read_buf() { check!(fs::remove_file(&filename)); } +#[test] +#[cfg(windows)] +fn test_seek_read_buf_exact() { + use crate::os::windows::fs::FileExt; + + let tmpdir = tmpdir(); + let filename = tmpdir.join("file_rt_io_file_test_seek_read_buf_exact.txt"); + { + let oo = OpenOptions::new().create_new(true).write(true).read(true).clone(); + let mut file = check!(oo.open(&filename)); + check!(file.write_all(b"0123456789")); + } + { + let mut file = check!(File::open(&filename)); + let mut buf: [MaybeUninit; 5] = [MaybeUninit::uninit(); 5]; + let mut buf = BorrowedBuf::from(buf.as_mut_slice()); + + // Exact read + check!(file.seek_read_buf_exact(buf.unfilled(), 2)); + assert_eq!(buf.filled(), b"23456"); + assert_eq!(check!(file.stream_position()), 7); + + // Already full + check!(file.seek_read_buf_exact(buf.unfilled(), 3)); + check!(file.seek_read_buf_exact(buf.unfilled(), 10)); + assert_eq!(buf.filled(), b"23456"); + assert_eq!(check!(file.stream_position()), 10); + + // Non-empty exact read past eof fails + let err = file.seek_read_buf_exact(buf.clear().unfilled(), 6).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::UnexpectedEof); + assert_eq!(check!(file.stream_position()), 10); + } + check!(fs::remove_file(&filename)); +} + #[test] fn file_test_read_buf() { let tmpdir = tmpdir(); diff --git a/library/std/src/os/windows/fs.rs b/library/std/src/os/windows/fs.rs index 3e6a934f318b2..c8f70117e9a7e 100644 --- a/library/std/src/os/windows/fs.rs +++ b/library/std/src/os/windows/fs.rs @@ -152,6 +152,63 @@ pub trait FileExt { io::default_read_buf(|b| self.seek_read(b, offset), buf) } + /// Seeks to a given position and reads the exact number of bytes required to fill `buf`. + /// + /// This is equivalent to the [`seek_read_exact`](FileExt::seek_read_exact) 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`. + /// + /// Reading beyond the end of the file will always succeed without reading any bytes. + /// + /// # Examples + /// + #[cfg_attr(windows, doc = "```no_run")] + #[cfg_attr(not(windows), doc = "```ignore (needs windows)")] + /// #![feature(core_io_borrowed_buf)] + /// #![feature(read_buf_at)] + /// #![feature(seek_read_exact_seek_write_all)] + /// + /// use std::io; + /// use std::io::BorrowedBuf; + /// use std::fs::File; + /// use std::mem::MaybeUninit; + /// use std::os::windows::prelude::*; + /// + /// fn main() -> io::Result<()> { + /// let mut file = File::open("pi.txt")?; + /// + /// // Read some bytes starting from offset 2 + /// let mut buf: [MaybeUninit; 10] = [MaybeUninit::uninit(); 10]; + /// let mut buf = BorrowedBuf::from(buf.as_mut_slice()); + /// file.seek_read_buf_exact(buf.unfilled(), 2)?; + /// + /// assert!(buf.filled().starts_with(b"1")); + /// + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "seek_read_exact_seek_write_all", issue = "162868")] + fn seek_read_buf_exact( + &self, + mut buf: BorrowedCursor<'_, u8>, + mut offset: u64, + ) -> io::Result<()> { + while buf.capacity() > 0 { + let prev_written = buf.written(); + match self.seek_read_buf(buf.reborrow(), offset) { + Ok(()) => {} + Err(e) if e.is_interrupted() => {} + Err(e) => return Err(e), + } + let n = buf.written() - prev_written; + offset += n as u64; + if n == 0 { + return Err(io::Error::READ_EXACT_EOF); + } + } + Ok(()) + } + /// Seeks to a given position and writes a number of bytes. /// /// Returns the number of bytes written. From 7c10734455ee9fd1cb197f3986687d37f0ef609f Mon Sep 17 00:00:00 2001 From: malezjaa Date: Wed, 16 Sep 2026 00:20:01 +0200 Subject: [PATCH 6/8] regression test for inherent associated const ICE --- .../ice-normalize-inherent-assoc-const.rs | 19 +++++++++++++++++++ .../ice-normalize-inherent-assoc-const.stderr | 15 +++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 tests/ui/associated-inherent-types/ice-normalize-inherent-assoc-const.rs create mode 100644 tests/ui/associated-inherent-types/ice-normalize-inherent-assoc-const.stderr diff --git a/tests/ui/associated-inherent-types/ice-normalize-inherent-assoc-const.rs b/tests/ui/associated-inherent-types/ice-normalize-inherent-assoc-const.rs new file mode 100644 index 0000000000000..843e33bae0d81 --- /dev/null +++ b/tests/ui/associated-inherent-types/ice-normalize-inherent-assoc-const.rs @@ -0,0 +1,19 @@ +//! Regression test for: https://github.com/rust-lang/rust/issues/142722. + +#![feature(trivial_bounds)] +#![feature(generic_const_exprs)] +#![feature(min_generic_const_args)] +#![feature(inherent_associated_types)] + +struct Foo; +impl Foo { + const ASSOC_C: usize = todo!(); + //~^ ERROR evaluation panicked: not yet implemented + fn foo() + where + [u8; Self::ASSOC_C]:, + { + } +} + +pub fn main() {} diff --git a/tests/ui/associated-inherent-types/ice-normalize-inherent-assoc-const.stderr b/tests/ui/associated-inherent-types/ice-normalize-inherent-assoc-const.stderr new file mode 100644 index 0000000000000..5ce8a67bc7810 --- /dev/null +++ b/tests/ui/associated-inherent-types/ice-normalize-inherent-assoc-const.stderr @@ -0,0 +1,15 @@ +error[E0080]: evaluation panicked: not yet implemented + --> $DIR/ice-normalize-inherent-assoc-const.rs:10:28 + | +LL | const ASSOC_C: usize = todo!(); + | ^^^^^^^ evaluation of `Foo::ASSOC_C` failed here + +note: erroneous constant encountered + --> $DIR/ice-normalize-inherent-assoc-const.rs:14:14 + | +LL | [u8; Self::ASSOC_C]:, + | ^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0080`. From 01594298297ece4e1f671722da02ed8c0e445eef Mon Sep 17 00:00:00 2001 From: malezjaa Date: Mon, 28 Sep 2026 00:03:56 +0200 Subject: [PATCH 7/8] update feature name --- .../ice-normalize-inherent-assoc-const.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/associated-inherent-types/ice-normalize-inherent-assoc-const.rs b/tests/ui/associated-inherent-types/ice-normalize-inherent-assoc-const.rs index 843e33bae0d81..a7b430f85a9a2 100644 --- a/tests/ui/associated-inherent-types/ice-normalize-inherent-assoc-const.rs +++ b/tests/ui/associated-inherent-types/ice-normalize-inherent-assoc-const.rs @@ -2,7 +2,7 @@ #![feature(trivial_bounds)] #![feature(generic_const_exprs)] -#![feature(min_generic_const_args)] +#![feature(gca_min_const_items)] #![feature(inherent_associated_types)] struct Foo; From 4d0e645b0f85ba6dfa6027f41d8487f929ee63e0 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 28 Sep 2026 01:39:35 -0500 Subject: [PATCH 8/8] triagebot: Subscribe me to changes in test-float-parse --- triagebot.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/triagebot.toml b/triagebot.toml index 60098a038ea73..bad75dc51d740 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -1185,6 +1185,10 @@ cc = ["@rust-lang/miri", "@RalfJung", "@oli-obk", "@lcnr"] message = "Some changes occurred in float parsing" cc = ["@tgross35"] +[mentions."src/tools/test-float-parse"] +message = "Some changes occurred in float parsing" +cc = ["@tgross35"] + [mentions."library/core/src/fmt/num.rs"] message = "Some changes occurred in integer formatting" cc = ["@tgross35"]