Skip to content

Commit a8028ab

Browse files
authored
Rollup merge of #150822 - fix-149981, r=@Kivooeo
Fix for ICE: eii: fn / macro rules None in find_attr() Closes #149981 This used to ICE: ```rust macro_rules! foo_impl {} #[eii] fn foo_impl() {} ``` `#[eii]` generates a macro (called `foo_impl`) and a default impl. So the partial expansion used to roughly look like the following: ```rust macro_rules! foo_impl {} // actually resolves here extern "Rust" { fn foo_impl(); } #[eii_extern_target(foo_impl)] macro foo_impl { () => {}; } const _: () = { #[implements_eii(foo_impl)] // assumed to name resolve to the macro v2 above fn foo_impl() {} }; ``` Now, shadowing rules for macrov2 and macrov1 are super weird! Take a look at this: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=23f21421921360478b0ec0276711ad36 So instead of resolving to the macrov2, we resolve the macrov1 named the same thing. A regression test was added to this, and some span_delayed_bugs were added to make sure we catch this in the right places. But that didn't fix the root cause. To make sure this simply cannot happen again, I made it so that we don't even need to do a name resolution for the default. In other words, the new partial expansion looks more like: ```rust macro_rules! foo_impl {} extern "Rust" { fn foo_impl(); // resolves to here now!!! } #[eii_extern_target(foo_impl)] macro foo_impl { () => {}; } const _: () = { #[implements_eii(known_extern_target=foo_impl)] // still name resolved, but directly to the foreign function. fn foo_impl() {} }; ``` The reason this helps is that name resolution for non-macros is much more predictable. It's not possible to have two functions like that with the same name in scope. We used to key externally implementable items off of the defid of the macro, but now the unique identifier is the foreign function's defid which seems much more sane. Finally, I lied a tiny bit because the above partial expansion doesn't actually work. ```rust extern "Rust" { fn foo_impl(); // not to here } const _: () = { #[implements_eii(known_extern_target=foo_impl)] // actually resolves to this function itself fn foo_impl() {} // <--- so to here }; ``` So the last few commits change the expansion to actually be this: ```rust macro_rules! foo_impl {} extern "Rust" { fn foo_impl(); // resolves to here now!!! } #[eii_extern_target(foo_impl)] macro foo_impl { () => {}; } const _: () = { mod dflt { // necessary, otherwise `super` doesn't work use super::*; #[implements_eii(known_extern_target=super::foo_impl)] // now resolves to outside the `dflt` module, so the foreign item. fn foo_impl() {} } }; ``` I apologize to whoever needs to review this, this is very subtle and I hope this makes it clear enough 😭.
2 parents 3bb7de6 + 7791bc2 commit a8028ab

15 files changed

Lines changed: 280 additions & 115 deletions

File tree

compiler/rustc_ast/src/ast.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2117,10 +2117,9 @@ pub struct MacroDef {
21172117

21182118
#[derive(Clone, Encodable, Decodable, Debug, HashStable_Generic, Walkable)]
21192119
pub struct EiiExternTarget {
2120-
/// path to the extern item we're targetting
2120+
/// path to the extern item we're targeting
21212121
pub extern_item_path: Path,
21222122
pub impl_unsafe: bool,
2123-
pub span: Span,
21242123
}
21252124

21262125
#[derive(Clone, Encodable, Decodable, Debug, Copy, Hash, Eq, PartialEq)]
@@ -3813,6 +3812,19 @@ pub struct Fn {
38133812
pub struct EiiImpl {
38143813
pub node_id: NodeId,
38153814
pub eii_macro_path: Path,
3815+
/// This field is an implementation detail that prevents a lot of bugs.
3816+
/// See <https://github.com/rust-lang/rust/issues/149981> for an example.
3817+
///
3818+
/// The problem is, that if we generate a declaration *together* with its default,
3819+
/// we generate both a declaration and an implementation. The generated implementation
3820+
/// uses the same mechanism to register itself as a user-defined implementation would,
3821+
/// despite being invisible to users. What does happen is a name resolution step.
3822+
/// The invisible default implementation has to find the declaration.
3823+
/// Both are generated at the same time, so we can skip that name resolution step.
3824+
///
3825+
/// This field is that shortcut: we prefill the extern target to skip a name resolution step,
3826+
/// making sure it never fails. It'd be awful UX if we fail name resolution in code invisible to the user.
3827+
pub known_eii_macro_resolution: Option<EiiExternTarget>,
38163828
pub impl_safety: Safety,
38173829
pub span: Span,
38183830
pub inner_span: Span,

compiler/rustc_ast_lowering/src/item.rs

Lines changed: 56 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use rustc_abi::ExternAbi;
22
use rustc_ast::visit::AssocCtxt;
33
use rustc_ast::*;
44
use rustc_errors::{E0570, ErrorGuaranteed, struct_span_code_err};
5-
use rustc_hir::attrs::{AttributeKind, EiiDecl};
5+
use rustc_hir::attrs::{AttributeKind, EiiDecl, EiiImplResolution};
66
use rustc_hir::def::{DefKind, PerNS, Res};
77
use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId};
88
use rustc_hir::{
@@ -134,6 +134,56 @@ impl<'hir> LoweringContext<'_, 'hir> {
134134
}
135135
}
136136

137+
fn lower_eii_extern_target(
138+
&mut self,
139+
id: NodeId,
140+
eii_name: Ident,
141+
EiiExternTarget { extern_item_path, impl_unsafe }: &EiiExternTarget,
142+
) -> Option<EiiDecl> {
143+
self.lower_path_simple_eii(id, extern_item_path).map(|did| EiiDecl {
144+
eii_extern_target: did,
145+
impl_unsafe: *impl_unsafe,
146+
name: eii_name,
147+
})
148+
}
149+
150+
fn lower_eii_impl(
151+
&mut self,
152+
EiiImpl {
153+
node_id,
154+
eii_macro_path,
155+
impl_safety,
156+
span,
157+
inner_span,
158+
is_default,
159+
known_eii_macro_resolution,
160+
}: &EiiImpl,
161+
) -> hir::attrs::EiiImpl {
162+
let resolution = if let Some(target) = known_eii_macro_resolution
163+
&& let Some(decl) = self.lower_eii_extern_target(
164+
*node_id,
165+
// the expect is ok here since we always generate this path in the eii macro.
166+
eii_macro_path.segments.last().expect("at least one segment").ident,
167+
target,
168+
) {
169+
EiiImplResolution::Known(decl)
170+
} else if let Some(macro_did) = self.lower_path_simple_eii(*node_id, eii_macro_path) {
171+
EiiImplResolution::Macro(macro_did)
172+
} else {
173+
EiiImplResolution::Error(
174+
self.dcx().span_delayed_bug(*span, "eii never resolved without errors given"),
175+
)
176+
};
177+
178+
hir::attrs::EiiImpl {
179+
span: self.lower_span(*span),
180+
inner_span: self.lower_span(*inner_span),
181+
impl_marked_unsafe: self.lower_safety(*impl_safety, hir::Safety::Safe).is_unsafe(),
182+
is_default: *is_default,
183+
resolution,
184+
}
185+
}
186+
137187
fn generate_extra_attrs_for_item_kind(
138188
&mut self,
139189
id: NodeId,
@@ -143,49 +193,14 @@ impl<'hir> LoweringContext<'_, 'hir> {
143193
ItemKind::Fn(box Fn { eii_impls, .. }) if eii_impls.is_empty() => Vec::new(),
144194
ItemKind::Fn(box Fn { eii_impls, .. }) => {
145195
vec![hir::Attribute::Parsed(AttributeKind::EiiImpls(
146-
eii_impls
147-
.iter()
148-
.flat_map(
149-
|EiiImpl {
150-
node_id,
151-
eii_macro_path,
152-
impl_safety,
153-
span,
154-
inner_span,
155-
is_default,
156-
}| {
157-
self.lower_path_simple_eii(*node_id, eii_macro_path).map(|did| {
158-
hir::attrs::EiiImpl {
159-
eii_macro: did,
160-
span: self.lower_span(*span),
161-
inner_span: self.lower_span(*inner_span),
162-
impl_marked_unsafe: self
163-
.lower_safety(*impl_safety, hir::Safety::Safe)
164-
.is_unsafe(),
165-
is_default: *is_default,
166-
}
167-
})
168-
},
169-
)
170-
.collect(),
196+
eii_impls.iter().map(|i| self.lower_eii_impl(i)).collect(),
171197
))]
172198
}
173-
ItemKind::MacroDef(
174-
_,
175-
MacroDef {
176-
eii_extern_target: Some(EiiExternTarget { extern_item_path, impl_unsafe, span }),
177-
..
178-
},
179-
) => self
180-
.lower_path_simple_eii(id, extern_item_path)
181-
.map(|did| {
182-
vec![hir::Attribute::Parsed(AttributeKind::EiiExternTarget(EiiDecl {
183-
eii_extern_target: did,
184-
impl_unsafe: *impl_unsafe,
185-
span: self.lower_span(*span),
186-
}))]
187-
})
199+
ItemKind::MacroDef(name, MacroDef { eii_extern_target: Some(target), .. }) => self
200+
.lower_eii_extern_target(id, *name, target)
201+
.map(|decl| vec![hir::Attribute::Parsed(AttributeKind::EiiExternTarget(decl))])
188202
.unwrap_or_default(),
203+
189204
ItemKind::ExternCrate(..)
190205
| ItemKind::Use(..)
191206
| ItemKind::Static(..)

compiler/rustc_builtin_macros/src/eii.rs

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,19 +103,19 @@ fn eii_(
103103

104104
// span of the declaring item without attributes
105105
let item_span = func.sig.span;
106-
// span of the eii attribute and the item below it, i.e. the full declaration
107-
let decl_span = eii_attr_span.to(item_span);
108106
let foreign_item_name = func.ident;
109107

110108
let mut return_items = Vec::new();
111109

112110
if func.body.is_some() {
113111
return_items.push(Box::new(generate_default_impl(
112+
ecx,
114113
&func,
115114
impl_unsafe,
116115
macro_name,
117116
eii_attr_span,
118117
item_span,
118+
foreign_item_name,
119119
)))
120120
}
121121

@@ -133,7 +133,6 @@ fn eii_(
133133
macro_name,
134134
foreign_item_name,
135135
impl_unsafe,
136-
decl_span,
137136
)));
138137

139138
return_items.into_iter().map(wrap_item).collect()
@@ -187,11 +186,13 @@ fn filter_attrs_for_multiple_eii_attr(
187186
}
188187

189188
fn generate_default_impl(
189+
ecx: &mut ExtCtxt<'_>,
190190
func: &ast::Fn,
191191
impl_unsafe: bool,
192192
macro_name: Ident,
193193
eii_attr_span: Span,
194194
item_span: Span,
195+
foreign_item_name: Ident,
195196
) -> ast::Item {
196197
// FIXME: re-add some original attrs
197198
let attrs = ThinVec::new();
@@ -208,6 +209,21 @@ fn generate_default_impl(
208209
},
209210
span: eii_attr_span,
210211
is_default: true,
212+
known_eii_macro_resolution: Some(ast::EiiExternTarget {
213+
extern_item_path: ast::Path {
214+
span: foreign_item_name.span,
215+
segments: thin_vec![
216+
ast::PathSegment {
217+
ident: Ident::from_str_and_span("super", foreign_item_name.span,),
218+
id: DUMMY_NODE_ID,
219+
args: None
220+
},
221+
ast::PathSegment { ident: foreign_item_name, id: DUMMY_NODE_ID, args: None },
222+
],
223+
tokens: None,
224+
},
225+
impl_unsafe,
226+
}),
211227
});
212228

213229
ast::Item {
@@ -236,18 +252,66 @@ fn generate_default_impl(
236252
stmts: thin_vec![ast::Stmt {
237253
id: DUMMY_NODE_ID,
238254
kind: ast::StmtKind::Item(Box::new(ast::Item {
239-
attrs,
255+
attrs: ThinVec::new(),
240256
id: DUMMY_NODE_ID,
241257
span: item_span,
242258
vis: ast::Visibility {
243-
span: eii_attr_span,
259+
span: item_span,
244260
kind: ast::VisibilityKind::Inherited,
245261
tokens: None
246262
},
247-
kind: ItemKind::Fn(Box::new(default_func)),
263+
kind: ItemKind::Mod(
264+
ast::Safety::Default,
265+
Ident::from_str_and_span("dflt", item_span),
266+
ast::ModKind::Loaded(
267+
thin_vec![
268+
Box::new(ast::Item {
269+
attrs: thin_vec![ecx.attr_nested_word(
270+
sym::allow,
271+
sym::unused_imports,
272+
item_span
273+
),],
274+
id: DUMMY_NODE_ID,
275+
span: item_span,
276+
vis: ast::Visibility {
277+
span: eii_attr_span,
278+
kind: ast::VisibilityKind::Inherited,
279+
tokens: None
280+
},
281+
kind: ItemKind::Use(ast::UseTree {
282+
prefix: ast::Path::from_ident(
283+
Ident::from_str_and_span(
284+
"super", item_span,
285+
)
286+
),
287+
kind: ast::UseTreeKind::Glob,
288+
span: item_span,
289+
}),
290+
tokens: None,
291+
}),
292+
Box::new(ast::Item {
293+
attrs,
294+
id: DUMMY_NODE_ID,
295+
span: item_span,
296+
vis: ast::Visibility {
297+
span: eii_attr_span,
298+
kind: ast::VisibilityKind::Inherited,
299+
tokens: None
300+
},
301+
kind: ItemKind::Fn(Box::new(default_func)),
302+
tokens: None,
303+
}),
304+
],
305+
ast::Inline::Yes,
306+
ast::ModSpans {
307+
inner_span: item_span,
308+
inject_use_span: item_span,
309+
}
310+
)
311+
),
248312
tokens: None,
249313
})),
250-
span: eii_attr_span
314+
span: eii_attr_span,
251315
}],
252316
id: DUMMY_NODE_ID,
253317
rules: ast::BlockCheckMode::Default,
@@ -352,7 +416,6 @@ fn generate_attribute_macro_to_implement(
352416
macro_name: Ident,
353417
foreign_item_name: Ident,
354418
impl_unsafe: bool,
355-
decl_span: Span,
356419
) -> ast::Item {
357420
let mut macro_attrs = ThinVec::new();
358421

@@ -394,7 +457,6 @@ fn generate_attribute_macro_to_implement(
394457
eii_extern_target: Some(ast::EiiExternTarget {
395458
extern_item_path: ast::Path::from_ident(foreign_item_name),
396459
impl_unsafe,
397-
span: decl_span,
398460
}),
399461
},
400462
),
@@ -451,7 +513,7 @@ pub(crate) fn eii_extern_target(
451513
false
452514
};
453515

454-
d.eii_extern_target = Some(EiiExternTarget { extern_item_path, impl_unsafe, span });
516+
d.eii_extern_target = Some(EiiExternTarget { extern_item_path, impl_unsafe });
455517

456518
// Return the original item and the new methods.
457519
vec![item]
@@ -508,6 +570,7 @@ pub(crate) fn eii_shared_macro(
508570
impl_safety: meta_item.unsafety,
509571
span,
510572
is_default,
573+
known_eii_macro_resolution: None,
511574
});
512575

513576
vec![item]

compiler/rustc_codegen_ssa/src/codegen_attrs.rs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use std::str::FromStr;
33
use rustc_abi::{Align, ExternAbi};
44
use rustc_ast::expand::autodiff_attrs::{AutoDiffAttrs, DiffActivity, DiffMode};
55
use rustc_ast::{LitKind, MetaItem, MetaItemInner, attr};
6-
use rustc_hir::attrs::{AttributeKind, InlineAttr, Linkage, RtsanSetting, UsedBy};
6+
use rustc_hir::attrs::{
7+
AttributeKind, EiiImplResolution, InlineAttr, Linkage, RtsanSetting, UsedBy,
8+
};
79
use rustc_hir::def::DefKind;
810
use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
911
use rustc_hir::{self as hir, Attribute, LangItem, find_attr, lang_items};
@@ -285,11 +287,23 @@ fn process_builtin_attrs(
285287
}
286288
AttributeKind::EiiImpls(impls) => {
287289
for i in impls {
288-
let extern_item = find_attr!(
289-
tcx.get_all_attrs(i.eii_macro),
290-
AttributeKind::EiiExternTarget(target) => target.eii_extern_target
291-
)
292-
.expect("eii should have declaration macro with extern target attribute");
290+
let extern_item = match i.resolution {
291+
EiiImplResolution::Macro(def_id) => {
292+
let Some(extern_item) = find_attr!(
293+
tcx.get_all_attrs(def_id),
294+
AttributeKind::EiiExternTarget(target) => target.eii_extern_target
295+
) else {
296+
tcx.dcx().span_delayed_bug(
297+
i.span,
298+
"resolved to something that's not an EII",
299+
);
300+
continue;
301+
};
302+
extern_item
303+
}
304+
EiiImplResolution::Known(decl) => decl.eii_extern_target,
305+
EiiImplResolution::Error(_eg) => continue,
306+
};
293307

294308
// this is to prevent a bug where a single crate defines both the default and explicit implementation
295309
// for an EII. In that case, both of them may be part of the same final object file. I'm not 100% sure
@@ -302,7 +316,7 @@ fn process_builtin_attrs(
302316
// iterate over all implementations *in the current crate*
303317
// (this is ok since we generate codegen fn attrs in the local crate)
304318
// if any of them is *not default* then don't emit the alias.
305-
&& tcx.externally_implementable_items(LOCAL_CRATE).get(&i.eii_macro).expect("at least one").1.iter().any(|(_, imp)| !imp.is_default)
319+
&& tcx.externally_implementable_items(LOCAL_CRATE).get(&extern_item).expect("at least one").1.iter().any(|(_, imp)| !imp.is_default)
306320
{
307321
continue;
308322
}

0 commit comments

Comments
 (0)