Skip to content

Commit 9eef092

Browse files
committed
rustc_attr_parsing: allow const item paths in selected builtin attrs
Add gated support for const item paths in selected builtin attributes, including `repr(align)`, `repr(packed)`, `rustc_align`, and `rustc_align_static`. Resolve attribute const paths during late resolution, carry them through HIR as attr int values, and evaluate them at the layout/codegen use sites. This keeps the feature scoped to const item paths without adding general expression support in attributes. Also adds UI/codegen/incremental coverage and fixes the gated-syntax diagnostics so unresolved names do not leak before the feature gate.
1 parent 5bbdeaa commit 9eef092

35 files changed

Lines changed: 881 additions & 97 deletions

compiler/rustc_ast_lowering/src/lib.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,13 @@ use rustc_ast::node_id::NodeMap;
4242
use rustc_ast::{self as ast, *};
4343
use rustc_attr_parsing::{AttributeParser, Late, OmitDoc};
4444
use rustc_data_structures::fingerprint::Fingerprint;
45-
use rustc_data_structures::fx::FxIndexSet;
45+
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
4646
use rustc_data_structures::sorted_map::SortedMap;
4747
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
4848
use rustc_data_structures::steal::Steal;
4949
use rustc_data_structures::tagged_ptr::TaggedRef;
5050
use rustc_errors::{DiagArgFromDisplay, DiagCtxtHandle};
51+
use rustc_hir::attrs::AttrConstResolution;
5152
use rustc_hir::def::{DefKind, LifetimeRes, Namespace, PartialRes, PerNS, Res};
5253
use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE, LocalDefId};
5354
use rustc_hir::definitions::{DefPathData, DisambiguatorState};
@@ -155,6 +156,7 @@ struct LoweringContext<'a, 'hir, R> {
155156
impl<'a, 'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'a, 'hir, R> {
156157
fn new(tcx: TyCtxt<'hir>, resolver: &'a mut R) -> Self {
157158
let registered_tools = tcx.registered_tools(()).iter().map(|x| x.name).collect();
159+
let attr_const_res_map = resolver.all_attr_const_resolutions();
158160
Self {
159161
tcx,
160162
resolver,
@@ -209,6 +211,7 @@ impl<'a, 'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'a, 'hir, R> {
209211
tcx.sess,
210212
tcx.features(),
211213
registered_tools,
214+
attr_const_res_map,
212215
Late,
213216
),
214217
delayed_lints: Vec::new(),
@@ -239,6 +242,7 @@ impl SpanLowerer {
239242
struct ResolverDelayedAstLowering<'a, 'tcx> {
240243
node_id_to_def_id: NodeMap<LocalDefId>,
241244
partial_res_map: NodeMap<PartialRes>,
245+
attr_const_res_map: FxIndexMap<rustc_span::AttrId, Vec<AttrConstResolution<ast::NodeId>>>,
242246
next_node_id: NodeId,
243247
base: &'a ResolverAstLowering<'tcx>,
244248
}
@@ -253,6 +257,16 @@ impl<'a, 'tcx> ResolverAstLoweringExt<'tcx> for ResolverDelayedAstLowering<'a, '
253257
self.partial_res_map.get(&id).copied().or_else(|| self.base.get_partial_res(id))
254258
}
255259

260+
fn all_attr_const_resolutions(
261+
&self,
262+
) -> FxIndexMap<rustc_span::AttrId, Vec<AttrConstResolution<ast::NodeId>>> {
263+
let mut map = self.base.all_attr_const_resolutions();
264+
for (attr_id, resolutions) in &self.attr_const_res_map {
265+
map.entry(*attr_id).or_default().extend(resolutions.iter().copied());
266+
}
267+
map
268+
}
269+
256270
fn get_import_res(&self, id: NodeId) -> PerNS<Option<Res<NodeId>>> {
257271
self.base.get_import_res(id)
258272
}
@@ -345,6 +359,12 @@ impl<'tcx> ResolverAstLowering<'tcx> {
345359
self.partial_res_map.get(&id).copied()
346360
}
347361

362+
fn all_attr_const_resolutions(
363+
&self,
364+
) -> FxIndexMap<rustc_span::AttrId, Vec<AttrConstResolution<ast::NodeId>>> {
365+
self.attr_const_res_map.clone()
366+
}
367+
348368
/// Obtains per-namespace resolutions for `use` statement with the given `NodeId`.
349369
fn get_import_res(&self, id: NodeId) -> PerNS<Option<Res<NodeId>>> {
350370
self.import_res_map.get(&id).copied().unwrap_or_default()
@@ -663,6 +683,7 @@ pub fn lower_delayed_owner(tcx: TyCtxt<'_>, def_id: LocalDefId) {
663683
let mut resolver = ResolverDelayedAstLowering {
664684
next_node_id: resolver.next_node_id,
665685
partial_res_map: Default::default(),
686+
attr_const_res_map: Default::default(),
666687
node_id_to_def_id: Default::default(),
667688
base: resolver,
668689
};

compiler/rustc_attr_parsing/src/attributes/repr.rs

Lines changed: 96 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
use rustc_abi::{Align, Size};
22
use rustc_ast::{IntTy, LitIntType, LitKind, UintTy};
3-
use rustc_hir::attrs::{IntType, ReprAttr};
3+
use rustc_hir::attrs::{AttrConstResolved, AttrIntValue, IntType, ReprAttr};
4+
use rustc_hir::def::{DefKind, Res};
5+
use rustc_session::parse::feature_err;
46

57
use super::prelude::*;
6-
use crate::session_diagnostics::{self, IncorrectReprFormatGenericCause};
8+
use crate::ShouldEmit;
9+
use crate::session_diagnostics::{
10+
self, AttrConstGenericNotSupported, AttrConstPathNotConst, IncorrectReprFormatGenericCause,
11+
};
712

813
/// Parse #[repr(...)] forms.
914
///
@@ -122,7 +127,7 @@ fn parse_repr<S: Stage>(cx: &AcceptContext<'_, '_, S>, param: &MetaItemParser) -
122127
parse_repr_align(cx, l, param.span(), AlignKind::Align)
123128
}
124129

125-
(Some(sym::packed), ArgParser::NoArgs) => Some(ReprPacked(Align::ONE)),
130+
(Some(sym::packed), ArgParser::NoArgs) => Some(ReprPacked(AttrIntValue::Lit(1))),
126131
(Some(sym::packed), ArgParser::List(l)) => {
127132
parse_repr_align(cx, l, param.span(), AlignKind::Packed)
128133
}
@@ -189,6 +194,11 @@ enum AlignKind {
189194
Align,
190195
}
191196

197+
enum AlignmentParseError {
198+
Message(String),
199+
AlreadyErrored,
200+
}
201+
192202
fn parse_repr_align<S: Stage>(
193203
cx: &AcceptContext<'_, '_, S>,
194204
list: &MetaItemListParser,
@@ -214,31 +224,21 @@ fn parse_repr_align<S: Stage>(
214224
return None;
215225
};
216226

217-
let Some(lit) = align.lit() else {
227+
match parse_alignment_or_const_path(
228+
cx,
229+
align,
218230
match align_kind {
219-
Packed => {
220-
cx.emit_err(session_diagnostics::IncorrectReprFormatPackedExpectInteger {
221-
span: align.span(),
222-
});
223-
}
224-
Align => {
225-
cx.emit_err(session_diagnostics::IncorrectReprFormatExpectInteger {
226-
span: align.span(),
227-
});
228-
}
229-
}
230-
231-
return None;
232-
};
233-
234-
match parse_alignment(&lit.kind, cx) {
235-
Ok(literal) => Some(match align_kind {
236-
AlignKind::Packed => ReprAttr::ReprPacked(literal),
237-
AlignKind::Align => ReprAttr::ReprAlign(literal),
231+
Packed => "repr(packed)",
232+
Align => "repr(align)",
233+
},
234+
) {
235+
Ok(value) => Some(match align_kind {
236+
AlignKind::Packed => ReprAttr::ReprPacked(value),
237+
AlignKind::Align => ReprAttr::ReprAlign(value),
238238
}),
239-
Err(message) => {
239+
Err(AlignmentParseError::Message(message)) => {
240240
cx.emit_err(session_diagnostics::InvalidReprGeneric {
241-
span: lit.span,
241+
span: align.span(),
242242
repr_arg: match align_kind {
243243
Packed => "packed".to_string(),
244244
Align => "align".to_string(),
@@ -247,6 +247,7 @@ fn parse_repr_align<S: Stage>(
247247
});
248248
None
249249
}
250+
Err(AlignmentParseError::AlreadyErrored) => None,
250251
}
251252
}
252253

@@ -281,9 +282,71 @@ fn parse_alignment<S: Stage>(
281282
Ok(align)
282283
}
283284

285+
fn parse_alignment_or_const_path<S: Stage>(
286+
cx: &AcceptContext<'_, '_, S>,
287+
arg: &MetaItemOrLitParser,
288+
attr_name: &'static str,
289+
) -> Result<AttrIntValue, AlignmentParseError> {
290+
if let Some(lit) = arg.lit() {
291+
return parse_alignment(&lit.kind, cx)
292+
.map(|align| AttrIntValue::Lit(u128::from(align.bytes())))
293+
.map_err(AlignmentParseError::Message);
294+
}
295+
296+
let Some(meta) = arg.meta_item() else {
297+
return Err(AlignmentParseError::Message("not an unsuffixed integer".to_string()));
298+
};
299+
300+
if !matches!(meta.args(), ArgParser::NoArgs) {
301+
return Err(AlignmentParseError::Message("not an unsuffixed integer".to_string()));
302+
}
303+
304+
if let Some(features) = cx.features_option()
305+
&& !features.const_attr_paths()
306+
&& !meta.span().allows_unstable(sym::const_attr_paths)
307+
{
308+
feature_err(
309+
cx.sess(),
310+
sym::const_attr_paths,
311+
meta.span(),
312+
"const item paths in builtin attributes are experimental",
313+
)
314+
.emit();
315+
return Err(AlignmentParseError::AlreadyErrored);
316+
}
317+
318+
let Some(resolution) = cx.attr_const_resolution(meta.path().span()) else {
319+
// `parse_limited(sym::repr)` runs before lowering for callers that only care whether
320+
// `repr(packed(...))` exists at all.
321+
if matches!(cx.stage.should_emit(), ShouldEmit::Nothing) {
322+
return Ok(AttrIntValue::Lit(1));
323+
}
324+
return Err(AlignmentParseError::Message("not an unsuffixed integer".to_string()));
325+
};
326+
327+
match resolution {
328+
AttrConstResolved::Resolved(Res::Def(DefKind::Const { .. }, def_id)) => {
329+
Ok(AttrIntValue::Const { def_id, span: meta.path().span() })
330+
}
331+
AttrConstResolved::Resolved(Res::Def(DefKind::ConstParam, _)) => {
332+
cx.emit_err(AttrConstGenericNotSupported { span: meta.path().span(), attr_name });
333+
Err(AlignmentParseError::AlreadyErrored)
334+
}
335+
AttrConstResolved::Resolved(res) => {
336+
cx.emit_err(AttrConstPathNotConst {
337+
span: meta.path().span(),
338+
attr_name,
339+
thing: res.descr(),
340+
});
341+
Err(AlignmentParseError::AlreadyErrored)
342+
}
343+
AttrConstResolved::Error => Err(AlignmentParseError::AlreadyErrored),
344+
}
345+
}
346+
284347
/// Parse #[align(N)].
285348
#[derive(Default)]
286-
pub(crate) struct RustcAlignParser(Option<(Align, Span)>);
349+
pub(crate) struct RustcAlignParser(ThinVec<(AttrIntValue, Span)>);
287350

288351
impl RustcAlignParser {
289352
const PATH: &[Symbol] = &[sym::rustc_align];
@@ -301,22 +364,15 @@ impl RustcAlignParser {
301364
return;
302365
};
303366

304-
let Some(lit) = align.lit() else {
305-
cx.emit_err(session_diagnostics::IncorrectReprFormatExpectInteger {
306-
span: align.span(),
307-
});
308-
309-
return;
310-
};
311-
312-
match parse_alignment(&lit.kind, cx) {
313-
Ok(literal) => self.0 = Ord::max(self.0, Some((literal, cx.attr_span))),
314-
Err(message) => {
367+
match parse_alignment_or_const_path(cx, align, "rustc_align") {
368+
Ok(literal) => self.0.push((literal, cx.attr_span)),
369+
Err(AlignmentParseError::Message(message)) => {
315370
cx.emit_err(session_diagnostics::InvalidAlignmentValue {
316-
span: lit.span,
371+
span: align.span(),
317372
error_part: message,
318373
});
319374
}
375+
Err(AlignmentParseError::AlreadyErrored) => {}
320376
}
321377
}
322378
}
@@ -335,8 +391,7 @@ impl<S: Stage> AttributeParser<S> for RustcAlignParser {
335391
]);
336392

337393
fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
338-
let (align, span) = self.0?;
339-
Some(AttributeKind::RustcAlign { align, span })
394+
(!self.0.is_empty()).then_some(AttributeKind::RustcAlign { aligns: self.0 })
340395
}
341396
}
342397

@@ -358,7 +413,6 @@ impl<S: Stage> AttributeParser<S> for RustcAlignStaticParser {
358413
AllowedTargets::AllowList(&[Allow(Target::Static), Allow(Target::ForeignStatic)]);
359414

360415
fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
361-
let (align, span) = self.0.0?;
362-
Some(AttributeKind::RustcAlign { align, span })
416+
(!self.0.0.is_empty()).then_some(AttributeKind::RustcAlign { aligns: self.0.0 })
363417
}
364418
}

compiler/rustc_attr_parsing/src/context.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use rustc_hir::{AttrPath, HirId};
1515
use rustc_parse::parser::Recovery;
1616
use rustc_session::Session;
1717
use rustc_session::lint::{Lint, LintId};
18-
use rustc_span::{ErrorGuaranteed, Span, Symbol};
18+
use rustc_span::{AttrId, ErrorGuaranteed, Span, Symbol};
1919

2020
use crate::AttributeParser;
2121
// Glob imports to avoid big, bitrotty import lists
@@ -414,6 +414,7 @@ pub struct Late;
414414
/// Gives [`AttributeParser`]s enough information to create errors, for example.
415415
pub struct AcceptContext<'f, 'sess, S: Stage> {
416416
pub(crate) shared: SharedContext<'f, 'sess, S>,
417+
pub(crate) attr_id: Option<AttrId>,
417418

418419
/// The outer span of the attribute currently being parsed
419420
///
@@ -497,6 +498,13 @@ impl<'f, 'sess: 'f, S: Stage> SharedContext<'f, 'sess, S> {
497498
impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
498499
pub(crate) fn adcx(&mut self) -> AttributeDiagnosticContext<'_, 'f, 'sess, S> {
499500
AttributeDiagnosticContext { ctx: self, custom_suggestions: Vec::new() }
501+
502+
pub(crate) fn attr_const_resolution(
503+
&self,
504+
path_span: Span,
505+
) -> Option<rustc_hir::attrs::AttrConstResolved<NodeId>> {
506+
self.attr_id.and_then(|attr_id| self.shared.cx.attr_const_resolution(attr_id, path_span))
507+
}
500508
}
501509
}
502510

0 commit comments

Comments
 (0)