From ab6b2117eccf20ff1420cc75c9217f319deac7fb Mon Sep 17 00:00:00 2001 From: Iris Shi <0.0@owo.li> Date: Wed, 6 May 2026 11:48:47 +0800 Subject: [PATCH 1/2] move `check_ffi_pure` to attribute parser --- .../src/attributes/link_attrs.rs | 47 +++++++++++++++---- compiler/rustc_attr_parsing/src/context.rs | 2 +- .../src/session_diagnostics.rs | 7 +++ compiler/rustc_passes/src/check_attr.rs | 11 +---- compiler/rustc_passes/src/errors.rs | 7 --- 5 files changed, 47 insertions(+), 27 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index d9310906dfeef..3c3bb23a79295 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -14,11 +14,11 @@ use super::util::parse_single_integer; use crate::attributes::AttributeSafety; use crate::attributes::cfg::parse_cfg_entry; use crate::session_diagnostics::{ - AsNeededCompatibility, BundleNeedsStatic, EmptyLinkName, ExportSymbolsNeedsStatic, - ImportNameTypeRaw, ImportNameTypeX86, IncompatibleWasmLink, InvalidLinkModifier, - InvalidMachoSection, InvalidMachoSectionReason, LinkFrameworkApple, LinkOrdinalOutOfRange, - LinkRequiresName, MultipleModifiers, NullOnLinkSection, RawDylibNoNul, RawDylibOnlyWindows, - WholeArchiveNeedsStatic, + AsNeededCompatibility, BothFfiConstAndPure, BundleNeedsStatic, EmptyLinkName, + ExportSymbolsNeedsStatic, ImportNameTypeRaw, ImportNameTypeX86, IncompatibleWasmLink, + InvalidLinkModifier, InvalidMachoSection, InvalidMachoSectionReason, LinkFrameworkApple, + LinkOrdinalOutOfRange, LinkRequiresName, MultipleModifiers, NullOnLinkSection, RawDylibNoNul, + RawDylibOnlyWindows, UnusedMultiple, WholeArchiveNeedsStatic, }; pub(crate) struct LinkNameParser; @@ -540,12 +540,41 @@ impl NoArgsAttributeParser for FfiConstParser { const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::FfiConst; } -pub(crate) struct FfiPureParser; -impl NoArgsAttributeParser for FfiPureParser { - const PATH: &[Symbol] = &[sym::ffi_pure]; +#[derive(Default)] +pub(crate) struct FfiPureParser { + span: Option, +} + +impl AttributeParser for FfiPureParser { + const ATTRIBUTES: AcceptMapping = + &[(&[sym::ffi_pure], template!(Word), |this, cx, args| { + let Some(()) = cx.expect_no_args(args) else { + return; + }; + + if let Some(earlier) = this.span { + let span = cx.attr_span; + cx.emit_err(UnusedMultiple { this: span, other: earlier, name: sym::ffi_pure }); + } else { + this.span = Some(cx.attr_span); + } + })]; const SAFETY: AttributeSafety = AttributeSafety::Unsafe { unsafe_since: None }; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]); - const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiPure; + + fn finalize(self, cx: &FinalizeContext<'_, '_>) -> Option { + let attr_span = self.span?; + + // `#[ffi_const]` functions cannot be `#[ffi_pure]`. + for other_attr in cx.all_attrs { + if other_attr.word_is(sym::ffi_const) { + cx.emit_err(BothFfiConstAndPure { attr_span }); + break; + } + } + + Some(AttributeKind::FfiPure(attr_span)) + } } pub(crate) struct RustcStdInternalSymbolParser; diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index bf4989b83200b..00fd2e11f3a3a 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -135,6 +135,7 @@ attribute_parsers!( ConfusablesParser, ConstStabilityParser, DocParser, + FfiPureParser, MacroUseParser, NakedParser, OnConstParser, @@ -234,7 +235,6 @@ attribute_parsers!( Single>, Single>, Single>, - Single>, Single>, Single>, Single>, diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 0a9c96033257d..a55fc65f23234 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -1156,3 +1156,10 @@ pub(crate) enum InvalidMachoSectionReason { #[note("section name `{$section}` is longer than 16 bytes")] SectionTooLong { section: String }, } + +#[derive(Diagnostic)] +#[diag("`#[ffi_const]` function cannot be `#[ffi_pure]`", code = E0757)] +pub(crate) struct BothFfiConstAndPure { + #[primary_span] + pub attr_span: Span, +} diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index a978138e3e1ff..07ec06396d336 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -174,9 +174,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { Attribute::Parsed(AttributeKind::NonExhaustive(attr_span)) => { self.check_non_exhaustive(*attr_span, span, target, item) } - &Attribute::Parsed(AttributeKind::FfiPure(attr_span)) => { - self.check_ffi_pure(attr_span, attrs) - } Attribute::Parsed(AttributeKind::MayDangle(attr_span)) => { self.check_may_dangle(hir_id, *attr_span) } @@ -229,6 +226,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::ExportStable | AttributeKind::Feature(..) | AttributeKind::FfiConst + | AttributeKind::FfiPure(..) | AttributeKind::Fundamental | AttributeKind::Ignore { .. } | AttributeKind::InstructionSet(..) @@ -1090,13 +1088,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } - fn check_ffi_pure(&self, attr_span: Span, attrs: &[Attribute]) { - if find_attr!(attrs, FfiConst) { - // `#[ffi_const]` functions cannot be `#[ffi_pure]` - self.dcx().emit_err(errors::BothFfiConstAndPure { attr_span }); - } - } - /// Checks if `#[may_dangle]` is applied to a lifetime or type generic parameter in `Drop` impl. fn check_may_dangle(&self, hir_id: HirId, attr_span: Span) { if let hir::Node::GenericParam(param) = self.tcx.hir_node(hir_id) diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index a916b4670fded..35f7a8669ae8a 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -171,13 +171,6 @@ pub(crate) struct DocMaskedNotExternCrateSelf { pub item_span: Span, } -#[derive(Diagnostic)] -#[diag("`#[ffi_const]` function cannot be `#[ffi_pure]`", code = E0757)] -pub(crate) struct BothFfiConstAndPure { - #[primary_span] - pub attr_span: Span, -} - #[derive(Diagnostic)] #[diag("attribute should be applied to an `extern` block with non-Rust ABI")] #[warning( From 6efada081d5704a159b73f3e15771b8d5afb0f59 Mon Sep 17 00:00:00 2001 From: Iris Shi <0.0@owo.li> Date: Wed, 6 May 2026 20:28:18 +0800 Subject: [PATCH 2/2] add diagnostic note for `#[ffi_pure]` with `#[ffi_const]` --- compiler/rustc_attr_parsing/src/session_diagnostics.rs | 2 ++ tests/ui/ffi-attrs/ffi_const2.stderr | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index a55fc65f23234..d3c95b594e937 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -1159,7 +1159,9 @@ pub(crate) enum InvalidMachoSectionReason { #[derive(Diagnostic)] #[diag("`#[ffi_const]` function cannot be `#[ffi_pure]`", code = E0757)] +#[note("`#[ffi_pure]` is redundant here because `#[ffi_const]` provides stronger guarantees")] pub(crate) struct BothFfiConstAndPure { #[primary_span] + #[suggestion("remove `#[ffi_pure]`", code = "", applicability = "maybe-incorrect")] pub attr_span: Span, } diff --git a/tests/ui/ffi-attrs/ffi_const2.stderr b/tests/ui/ffi-attrs/ffi_const2.stderr index d4c9bc42ec9e4..8c890d02e19dd 100644 --- a/tests/ui/ffi-attrs/ffi_const2.stderr +++ b/tests/ui/ffi-attrs/ffi_const2.stderr @@ -2,7 +2,9 @@ error[E0757]: `#[ffi_const]` function cannot be `#[ffi_pure]` --> $DIR/ffi_const2.rs:4:5 | LL | #[unsafe(ffi_pure)] - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^ help: remove `#[ffi_pure]` + | + = note: `#[ffi_pure]` is redundant here because `#[ffi_const]` provides stronger guarantees error: aborting due to 1 previous error