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
2 changes: 1 addition & 1 deletion compiler/rustc_ast_lowering/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@ fn index_ast<'tcx>(
mut_visit::walk_assoc_item(self, item, ctxt);
match ctxt {
visit::AssocCtxt::Trait => {
self.replace_with_dummy(item, AssocItemKind::MacCall, AstOwner::TraitItem)
self.insert(item.id, AstOwner::TraitItem(Box::new(item.clone())));

@darkraider01 darkraider01 Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The old replace_with_dummy was replacing the trait items with MacCall placeholders, so finalize_check couldn't actually inspect them through target_item.

This keeps the original items in the trait while still indexing the cloned ones separately for lowering.

The downside is that we're cloning trait items even when the attribute isn't used. I couldn't find a simpler way to reuse the existing infrastructure here, but I'm open to suggestions.

View changes since the review

@darkraider01 darkraider01 Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Edit: (This approach is suggested by llm, since i couldn't come with a better/simplied solution) Maybe another option could be to keep the original trait items only for traits with #[rustc_must_implement_one_of] , and use the existing placeholders everywhere else. That would still involve cloning, but limit it to traits that actually need the check. Not sure how neatly it would fit into the indexer, especially with nested traits, but it might be worth exploring.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think both options might be terrible for performance, let's check.
It also just doesn't feel like a great solution, it might be better to just keep the check in rustc_passes if this is needed

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I've started a small experiment here that might make this code nice if perf allows: #164980

I'm not expecting that PR to be accepted tho

@darkraider01 darkraider01 Oct 26, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Tried this with #163980 locally and it works without cloning the trait items. I just dropped the cloning hunk and kept your indexer as-is. Thank you!!!
Edit: heres the branch if you'd like to inspect https://github.com/darkraider01/rust/tree/experiment-ast-retention-164980

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sadly the perf effect of #164980 is not acceptable

}
visit::AssocCtxt::Impl { .. } => {
self.replace_with_dummy(item, AssocItemKind::MacCall, AstOwner::ImplItem)
Expand Down
49 changes: 47 additions & 2 deletions compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use std::path::PathBuf;

use rustc_ast::{GenericParamKind, ItemKind, LitIntType, LitKind, MetaItemLit};
use rustc_ast::{AssocItemKind, GenericParamKind, ItemKind, LitIntType, LitKind, MetaItemLit};
use rustc_attr_ir::lang_items::LangItem;
use rustc_attr_ir::{
BorrowckGraphvizFormatKind, CguFields, CguKind, RustcAssertVarianceKind, RustcCleanAttribute,
RustcCleanQueries, RustcMirKind,
RustcCleanQueries, RustcMirKind, find_attr,
};
use rustc_data_structures::fx::FxHashMap;
use rustc_feature::AttributeStability;
Expand Down Expand Up @@ -86,6 +86,51 @@ impl SingleAttributeParser for RustcMustImplementOneOfParser {

Some(AttributeKind::RustcMustImplementOneOf { attr_span: cx.attr_span, fn_names })
}

fn finalize_check(cx: &mut FinalizeCheckContext<'_, '_>, attr_span: Span) {
if cx.target != Target::Trait {
return;
}

let item = cx.target_item.expect("missing AST target item for Target::Trait");
let ItemKind::Trait(trait_) = &item.kind else {
panic!("expected trait AST target item for Target::Trait");
};

let fn_names =

@darkraider01 darkraider01 Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Kept the full Ident comparison here rather than just matching names, since the old check did the same.

Added a couple of macro tests to make sure the hygiene behavior stays the same.

View changes since the review

find_attr!(cx.parsed_attrs, RustcMustImplementOneOf { fn_names, .. } => fn_names)
.expect("missing parsed RustcMustImplementOneOf attribute in finalize_check");

for ident in fn_names {
let item = trait_.items.iter().find(|item| item.kind.ident() == Some(*ident));

match item {
Some(item) => match &item.kind {
AssocItemKind::Fn(f) => {
if f.body.is_none() {
cx.emit_err(diagnostics::FunctionNotHaveDefaultImplementation {
span: item.span,
note_span: attr_span,
});
}
}
AssocItemKind::Delegation(_) => {}
_ => {
cx.emit_err(diagnostics::MustImplementNotFunction {
span: item.span,
span_note: diagnostics::MustImplementNotFunctionSpanNote {

@darkraider01 darkraider01 Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Keeping delegation as a valid case here since it lowers to TraitFn::Provided, which the old check already accepted.

I added tests for both normal and renamed delegation as well.

View changes since the review

span: attr_span,
},
note: diagnostics::MustImplementNotFunctionNote {},
});
}
},
None => {
cx.emit_err(diagnostics::FunctionNotFoundInTrait { span: ident.span });
}
}
}
}
}

pub(crate) struct RustcNeverReturnsNullPtrParser;
Expand Down
38 changes: 38 additions & 0 deletions compiler/rustc_attr_parsing/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,44 @@ pub(crate) struct FunctionNamesDuplicated {
pub spans: Vec<Span>,
}

#[derive(Diagnostic)]
#[diag("function doesn't have a default implementation")]
pub(crate) struct FunctionNotHaveDefaultImplementation {
#[primary_span]
pub span: Span,
#[note("required by this annotation")]
pub note_span: Span,
}

#[derive(Diagnostic)]
#[diag("not a function")]
pub(crate) struct MustImplementNotFunction {
#[primary_span]
pub span: Span,
#[subdiagnostic]
pub span_note: MustImplementNotFunctionSpanNote,
#[subdiagnostic]
pub note: MustImplementNotFunctionNote,
}

#[derive(Subdiagnostic)]
#[note("required by this annotation")]
pub(crate) struct MustImplementNotFunctionSpanNote {
#[primary_span]
pub span: Span,
}

#[derive(Subdiagnostic)]
#[note("all `#[rustc_must_implement_one_of]` arguments must be associated function names")]
pub(crate) struct MustImplementNotFunctionNote {}

#[derive(Diagnostic)]
#[diag("function not found in this trait")]
pub(crate) struct FunctionNotFoundInTrait {
#[primary_span]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag("unsafe attribute used without unsafe")]
pub(crate) struct UnsafeAttrOutsideUnsafeLint {
Expand Down
57 changes: 2 additions & 55 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ use rustc_attr_ir::{
OptimizeAttr, ReprAttr, RustcAssertVarianceKind, find_attr,
};
use rustc_attr_parsing::AttributeParser;
use rustc_data_structures::thin_vec::ThinVec;
use rustc_errors::{DiagCtxtHandle, IntoDiagArg};
use rustc_feature::BUILTIN_ATTRIBUTE_SET;
use rustc_hir::def::DefKind;
Expand All @@ -40,7 +39,7 @@ use rustc_middle::ty::error::{ExpectedFound, TypeError};
use rustc_middle::ty::{self, TyCtxt, TypingMode, Unnormalized, Variance};
use rustc_session::diagnostics::feature_err;
use rustc_span::edition::Edition;
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, bug, kw, span_bug, sym};
use rustc_span::{DUMMY_SP, Span, Symbol, bug, kw, span_bug, sym};
use rustc_structures::CrateType;
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
use rustc_trait_selection::infer::{TyCtxtInferExt, ValuePairs};
Expand Down Expand Up @@ -199,9 +198,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
AttributeKind::MayDangle(attr_span) => self.check_may_dangle(hir_id, *attr_span),
AttributeKind::Doc(attr) => self.check_doc_attrs(attr, hir_id, target),
AttributeKind::EiiImpl(eii_impl) => self.check_eii_impl(eii_impl),
AttributeKind::RustcMustImplementOneOf { attr_span, fn_names } => {
self.check_rustc_must_implement_one_of(*attr_span, fn_names, hir_id, target)
}
AttributeKind::OnUnimplemented { directive } => {
self.check_diagnostic_on_unimplemented(hir_id, directive.as_deref())
}
Expand Down Expand Up @@ -358,6 +354,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
AttributeKind::RustcMacroTransparency(_) => (),
AttributeKind::RustcMain => (),
AttributeKind::RustcMir(_) => (),
AttributeKind::RustcMustImplementOneOf { .. } => (),
AttributeKind::RustcMustMatchExhaustively(..) => (),
AttributeKind::RustcNeverReturnsNullPtr => (),
AttributeKind::RustcNoImplicitAutorefs => (),
Expand Down Expand Up @@ -456,56 +453,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
})
}

fn check_rustc_must_implement_one_of(
&self,
attr_span: Span,
list: &ThinVec<Ident>,
hir_id: HirId,
target: Target,
) {
// Ignoring invalid targets because TyCtxt::associated_items emits bug if the target isn't valid
// the parser has already produced an error for the target being invalid
if !matches!(target, Target::Trait) {
return;
}

let def_id = hir_id.owner.def_id;

let items = self.tcx.associated_items(def_id);
// Check that all arguments of `#[rustc_must_implement_one_of]` reference
// functions in the trait with default implementations
for ident in list {
let item = items
.filter_by_name_unhygienic(ident.name)
.find(|item| item.ident(self.tcx) == *ident);

match item {
Some(item) if matches!(item.kind, ty::AssocKind::Fn { .. }) => {
if !item.defaultness(self.tcx).has_value() {
self.tcx.dcx().emit_err(
diagnostics::FunctionNotHaveDefaultImplementation {
span: self.tcx.def_span(item.def_id),
note_span: attr_span,
},
);
}
}
Some(item) => {
self.dcx().emit_err(diagnostics::MustImplementNotFunction {
span: self.tcx.def_span(item.def_id),
span_note: diagnostics::MustImplementNotFunctionSpanNote {
span: attr_span,
},
note: diagnostics::MustImplementNotFunctionNote {},
});
}
None => {
self.dcx().emit_err(diagnostics::FunctionNotFoundInTrait { span: ident.span });
}
}
}
}

/// Checks that each externally implementable item (EII) implementation uses `unsafe`
/// exactly when its declaration requires it.
fn check_eii_impl(&self, eii_impl: &EiiImpl) {
Expand Down
38 changes: 0 additions & 38 deletions compiler/rustc_passes/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -955,44 +955,6 @@ pub(crate) struct EiiWithoutImpl {
pub help: (),
}

#[derive(Diagnostic)]
#[diag("function doesn't have a default implementation")]
pub(crate) struct FunctionNotHaveDefaultImplementation {
#[primary_span]
pub span: Span,
#[note("required by this annotation")]
pub note_span: Span,
}

#[derive(Diagnostic)]
#[diag("not a function")]
pub(crate) struct MustImplementNotFunction {
#[primary_span]
pub span: Span,
#[subdiagnostic]
pub span_note: MustImplementNotFunctionSpanNote,
#[subdiagnostic]
pub note: MustImplementNotFunctionNote,
}

#[derive(Subdiagnostic)]
#[note("required by this annotation")]
pub(crate) struct MustImplementNotFunctionSpanNote {
#[primary_span]
pub span: Span,
}

#[derive(Subdiagnostic)]
#[note("all `#[rustc_must_implement_one_of]` arguments must be associated function names")]
pub(crate) struct MustImplementNotFunctionNote {}

#[derive(Diagnostic)]
#[diag("function not found in this trait")]
pub(crate) struct FunctionNotFoundInTrait {
#[primary_span]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag("there is no parameter `{$argument_name}` on trait `{$trait_name}`")]
pub(crate) struct UnknownFormatParameterForOnUnimplementedAttr {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//@ check-pass

#![feature(rustc_attrs)]
#![feature(fn_delegation)]

mod to_reuse {
pub fn b() {}
pub fn c() {}
}

#[rustc_must_implement_one_of(a, b)]
trait TraitDelegation {
fn a() {}
reuse to_reuse::b;
}

#[rustc_must_implement_one_of(a, ren)]
trait TraitDelegationRenamed {
fn a() {}
reuse to_reuse::c as ren;
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#![feature(rustc_attrs)]

macro_rules! define_method {
($name:ident) => {
fn $name() {}
};
}

#[rustc_must_implement_one_of(a, b)]
trait TraitMacro {
fn a() {}
define_method!(b);
}

macro_rules! define_trait {
($trait_name:ident, $m1:ident, $m2:ident) => {
#[rustc_must_implement_one_of($m1, $m2)]
trait $trait_name {
fn $m1() {}
fn $m2() {}
}
};
}

define_trait!(TraitMacroWhole, foo, bar);

macro_rules! define_internal_b {
() => {
fn b() {}
};
}

#[rustc_must_implement_one_of(a, b)]
//~^ ERROR function not found in this trait
trait TraitHygiene {
fn a() {}
define_internal_b!();
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
error: function not found in this trait
--> $DIR/rustc_must_implement_one_of_macros.rs:33:34
|
LL | #[rustc_must_implement_one_of(a, b)]
| ^

error: aborting due to 1 previous error

Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ trait TrTwoDefaults {
fn c(); //~ ERROR function doesn't have a default implementation
}

#[rustc_must_implement_one_of(a, b, C, D, missing)]
//~^ ERROR function not found in this trait
trait TrMixed {
fn a() {}
fn b(); //~ ERROR function doesn't have a default implementation
const C: u8 = 1; //~ ERROR not a function
type D; //~ ERROR not a function
}

#[rustc_must_implement_one_of(abc, abc)]
//~^ ERROR the `rustc_must_implement_one_of` attribute cannot be used on functions
fn function() {}
Expand Down
Loading
Loading