Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 38 additions & 9 deletions compiler/rustc_attr_parsing/src/attributes/link_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Span>,
}

impl AttributeParser for FfiPureParser {
const ATTRIBUTES: AcceptMapping<Self> =
&[(&[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<AttributeKind> {
let attr_span = self.span?;

// `#[ffi_const]` functions cannot be `#[ffi_pure]`.
for other_attr in cx.all_attrs {
Copy link
Copy Markdown
Contributor

@JonathanBrouwer JonathanBrouwer May 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think putting this check in the finalize of an attribute is not really nice, because:

  • You need to make an arbitrary decision of whether it's in the ffi_const or in the ffi_pure parser
  • The code just gets a lot longer from this
  • We still need custom errors for any attributes that conflict

I'd love to see a better way of handling attribute conflicts, but I don't really like this solution. This is why in #153101 I mentioned that this requires larger refactoring

View changes since the review

if other_attr.word_is(sym::ffi_const) {
cx.emit_err(BothFfiConstAndPure { attr_span });
break;
}
}

Some(AttributeKind::FfiPure(attr_span))
}
}

pub(crate) struct RustcStdInternalSymbolParser;
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ attribute_parsers!(
ConfusablesParser,
ConstStabilityParser,
DocParser,
FfiPureParser,
MacroUseParser,
NakedParser,
OnConstParser,
Expand Down Expand Up @@ -234,7 +235,6 @@ attribute_parsers!(
Single<WithoutArgs<DefaultLibAllocatorParser>>,
Single<WithoutArgs<ExportStableParser>>,
Single<WithoutArgs<FfiConstParser>>,
Single<WithoutArgs<FfiPureParser>>,
Single<WithoutArgs<FundamentalParser>>,
Single<WithoutArgs<LoopMatchParser>>,
Single<WithoutArgs<MacroEscapeParser>>,
Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_attr_parsing/src/session_diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1156,3 +1156,12 @@ 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)]
#[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,
}
11 changes: 1 addition & 10 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -229,6 +226,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| AttributeKind::ExportStable
| AttributeKind::Feature(..)
| AttributeKind::FfiConst
| AttributeKind::FfiPure(..)
| AttributeKind::Fundamental
| AttributeKind::Ignore { .. }
| AttributeKind::InstructionSet(..)
Expand Down Expand Up @@ -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)
Expand Down
7 changes: 0 additions & 7 deletions compiler/rustc_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
4 changes: 3 additions & 1 deletion tests/ui/ffi-attrs/ffi_const2.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading