From c65551e835f42d6b3cc28ebe37257ae7cfcbfe06 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Thu, 20 Nov 2025 02:16:43 +0000 Subject: [PATCH] Introduce `hir::ConstArgKind::Struct` --- compiler/rustc_ast_lowering/src/index.rs | 7 ++ compiler/rustc_ast_lowering/src/lib.rs | 41 +++++++++ compiler/rustc_hir/src/hir.rs | 13 +++ compiler/rustc_hir/src/intravisit.rs | 23 +++++ .../src/hir_ty_lowering/mod.rs | 3 + compiler/rustc_hir_pretty/src/lib.rs | 4 + compiler/rustc_metadata/src/rmeta/encoder.rs | 1 + compiler/rustc_middle/src/hir/map.rs | 2 + compiler/rustc_middle/src/hir/mod.rs | 1 + compiler/rustc_resolve/src/def_collector.rs | 89 +++++++++++++++---- compiler/rustc_resolve/src/lib.rs | 9 ++ .../mgca/struct_expr_with_macros.rs | 31 +++++++ 12 files changed, 205 insertions(+), 19 deletions(-) create mode 100644 tests/ui/const-generics/mgca/struct_expr_with_macros.rs diff --git a/compiler/rustc_ast_lowering/src/index.rs b/compiler/rustc_ast_lowering/src/index.rs index 8d7351d3a510..f6edcaa64dfe 100644 --- a/compiler/rustc_ast_lowering/src/index.rs +++ b/compiler/rustc_ast_lowering/src/index.rs @@ -281,6 +281,13 @@ impl<'a, 'hir> Visitor<'hir> for NodeCollector<'a, 'hir> { }); } + fn visit_const_arg_expr_field(&mut self, field: &'hir ConstArgExprField<'hir>) { + self.insert(field.span, field.hir_id, Node::ConstArgExprField(field)); + self.with_parent(field.hir_id, |this| { + intravisit::walk_const_arg_expr_field(this, field); + }) + } + fn visit_stmt(&mut self, stmt: &'hir Stmt<'hir>) { self.insert(stmt.span, stmt.hir_id, Node::Stmt(stmt)); diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 1bc8d7c25bb5..416fef8e3af3 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -2410,6 +2410,47 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { ConstArg { hir_id: self.next_id(), kind: hir::ConstArgKind::Path(qpath) } } + ExprKind::Struct(se) => { + let path = self.lower_qpath( + expr.id, + &se.qself, + &se.path, + // FIXME(mgca): we may want this to be `Optional` instead, but + // we would also need to make sure that HIR ty lowering errors + // when these paths wind up in signatures. + ParamMode::Explicit, + AllowReturnTypeNotation::No, + ImplTraitContext::Disallowed(ImplTraitPosition::Path), + None, + ); + + let fields = self.arena.alloc_from_iter(se.fields.iter().map(|f| { + let hir_id = self.lower_node_id(f.id); + // FIXME(mgca): This might result in lowering attributes that + // then go unused as the `Target::ExprField` is not actually + // corresponding to `Node::ExprField`. + self.lower_attrs(hir_id, &f.attrs, f.span, Target::ExprField); + + let expr = if let ExprKind::ConstBlock(anon_const) = &f.expr.kind { + let def_id = self.local_def_id(anon_const.id); + let def_kind = self.tcx.def_kind(def_id); + assert_eq!(DefKind::AnonConst, def_kind); + + self.lower_anon_const_to_const_arg_direct(anon_const) + } else { + self.lower_expr_to_const_arg_direct(&f.expr) + }; + + &*self.arena.alloc(hir::ConstArgExprField { + hir_id, + field: self.lower_ident(f.ident), + expr: self.arena.alloc(expr), + span: self.lower_span(f.span), + }) + })); + + ConstArg { hir_id: self.next_id(), kind: hir::ConstArgKind::Struct(path, fields) } + } ExprKind::Underscore => ConstArg { hir_id: self.lower_node_id(expr.id), kind: hir::ConstArgKind::Infer(expr.span, ()), diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index c60471848c89..e176c703b33e 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -494,6 +494,7 @@ impl<'hir, Unambig> ConstArg<'hir, Unambig> { pub fn span(&self) -> Span { match self.kind { + ConstArgKind::Struct(path, _) => path.span(), ConstArgKind::Path(path) => path.span(), ConstArgKind::Anon(anon) => anon.span, ConstArgKind::Error(span, _) => span, @@ -513,6 +514,8 @@ pub enum ConstArgKind<'hir, Unambig = ()> { /// However, in the future, we'll be using it for all of those. Path(QPath<'hir>), Anon(&'hir AnonConst), + /// Represents construction of struct/struct variants + Struct(QPath<'hir>, &'hir [&'hir ConstArgExprField<'hir>]), /// Error const Error(Span, ErrorGuaranteed), /// This variant is not always used to represent inference consts, sometimes @@ -520,6 +523,14 @@ pub enum ConstArgKind<'hir, Unambig = ()> { Infer(Span, Unambig), } +#[derive(Clone, Copy, Debug, HashStable_Generic)] +pub struct ConstArgExprField<'hir> { + pub hir_id: HirId, + pub span: Span, + pub field: Ident, + pub expr: &'hir ConstArg<'hir>, +} + #[derive(Clone, Copy, Debug, HashStable_Generic)] pub struct InferArg { #[stable_hasher(ignore)] @@ -4714,6 +4725,7 @@ pub enum Node<'hir> { ConstArg(&'hir ConstArg<'hir>), Expr(&'hir Expr<'hir>), ExprField(&'hir ExprField<'hir>), + ConstArgExprField(&'hir ConstArgExprField<'hir>), Stmt(&'hir Stmt<'hir>), PathSegment(&'hir PathSegment<'hir>), Ty(&'hir Ty<'hir>), @@ -4773,6 +4785,7 @@ impl<'hir> Node<'hir> { Node::AssocItemConstraint(c) => Some(c.ident), Node::PatField(f) => Some(f.ident), Node::ExprField(f) => Some(f.ident), + Node::ConstArgExprField(f) => Some(f.field), Node::PreciseCapturingNonLifetimeArg(a) => Some(a.ident), Node::Param(..) | Node::AnonConst(..) diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index be3cab6461ef..e63f51f8e6a4 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -396,6 +396,9 @@ pub trait Visitor<'v>: Sized { fn visit_expr_field(&mut self, field: &'v ExprField<'v>) -> Self::Result { walk_expr_field(self, field) } + fn visit_const_arg_expr_field(&mut self, field: &'v ConstArgExprField<'v>) -> Self::Result { + walk_const_arg_expr_field(self, field) + } fn visit_pattern_type_pattern(&mut self, p: &'v TyPat<'v>) -> Self::Result { walk_ty_pat(self, p) } @@ -954,6 +957,17 @@ pub fn walk_expr_field<'v, V: Visitor<'v>>(visitor: &mut V, field: &'v ExprField try_visit!(visitor.visit_ident(*ident)); visitor.visit_expr(*expr) } + +pub fn walk_const_arg_expr_field<'v, V: Visitor<'v>>( + visitor: &mut V, + field: &'v ConstArgExprField<'v>, +) -> V::Result { + let ConstArgExprField { hir_id, field, expr, span: _ } = field; + try_visit!(visitor.visit_id(*hir_id)); + try_visit!(visitor.visit_ident(*field)); + visitor.visit_const_arg_unambig(*expr) +} + /// We track whether an infer var is from a [`Ty`], [`ConstArg`], or [`GenericArg`] so that /// HIR visitors overriding [`Visitor::visit_infer`] can determine what kind of infer is being visited pub enum InferKind<'hir> { @@ -1068,6 +1082,15 @@ pub fn walk_const_arg<'v, V: Visitor<'v>>( let ConstArg { hir_id, kind } = const_arg; try_visit!(visitor.visit_id(*hir_id)); match kind { + ConstArgKind::Struct(qpath, field_exprs) => { + try_visit!(visitor.visit_qpath(qpath, *hir_id, qpath.span())); + + for field_expr in *field_exprs { + try_visit!(visitor.visit_const_arg_expr_field(field_expr)); + } + + V::Result::output() + } ConstArgKind::Path(qpath) => visitor.visit_qpath(qpath, *hir_id, qpath.span()), ConstArgKind::Anon(anon) => visitor.visit_anon_const(*anon), ConstArgKind::Error(_, _) => V::Result::output(), // errors and spans are not important diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index f7c7cf6634fd..3c1200c812bc 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -2263,6 +2263,9 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ) .unwrap_or_else(|guar| Const::new_error(tcx, guar)) } + hir::ConstArgKind::Struct(..) => { + span_bug!(const_arg.span(), "lowering `{:?}` is not yet implemented", const_arg) + } hir::ConstArgKind::Anon(anon) => self.lower_anon_const(anon), hir::ConstArgKind::Infer(span, ()) => self.ct_infer(None, span), hir::ConstArgKind::Error(_, e) => ty::Const::new_error(tcx, e), diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index b3b416955230..f99533922110 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -180,6 +180,8 @@ impl<'a> State<'a> { Node::ConstArg(a) => self.print_const_arg(a), Node::Expr(a) => self.print_expr(a), Node::ExprField(a) => self.print_expr_field(a), + // FIXME(mgca): proper printing for struct exprs + Node::ConstArgExprField(_) => self.word("/* STRUCT EXPR */"), Node::Stmt(a) => self.print_stmt(a), Node::PathSegment(a) => self.print_path_segment(a), Node::Ty(a) => self.print_type(a), @@ -1135,6 +1137,8 @@ impl<'a> State<'a> { fn print_const_arg(&mut self, const_arg: &hir::ConstArg<'_>) { match &const_arg.kind { + // FIXME(mgca): proper printing for struct exprs + ConstArgKind::Struct(..) => self.word("/* STRUCT EXPR */"), ConstArgKind::Path(qpath) => self.print_qpath(qpath, true), ConstArgKind::Anon(anon) => self.print_anon_const(anon), ConstArgKind::Error(_, _) => self.word("/*ERROR*/"), diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 94bc4d3fa530..920c896d5a47 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1440,6 +1440,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { hir::Node::ConstArg(hir::ConstArg { kind, .. }) => match kind { // Skip encoding defs for these as they should not have had a `DefId` created hir::ConstArgKind::Error(..) + | hir::ConstArgKind::Struct(..) | hir::ConstArgKind::Path(..) | hir::ConstArgKind::Infer(..) => true, hir::ConstArgKind::Anon(..) => false, diff --git a/compiler/rustc_middle/src/hir/map.rs b/compiler/rustc_middle/src/hir/map.rs index 9e639b243f28..d62ddc915d17 100644 --- a/compiler/rustc_middle/src/hir/map.rs +++ b/compiler/rustc_middle/src/hir/map.rs @@ -739,6 +739,7 @@ impl<'tcx> TyCtxt<'tcx> { Node::ConstArg(_) => node_str("const"), Node::Expr(_) => node_str("expr"), Node::ExprField(_) => node_str("expr field"), + Node::ConstArgExprField(_) => node_str("const arg expr field"), Node::Stmt(_) => node_str("stmt"), Node::PathSegment(_) => node_str("path segment"), Node::Ty(_) => node_str("type"), @@ -1007,6 +1008,7 @@ impl<'tcx> TyCtxt<'tcx> { Node::ConstArg(const_arg) => const_arg.span(), Node::Expr(expr) => expr.span, Node::ExprField(field) => field.span, + Node::ConstArgExprField(field) => field.span, Node::Stmt(stmt) => stmt.span, Node::PathSegment(seg) => { let ident_span = seg.ident.span; diff --git a/compiler/rustc_middle/src/hir/mod.rs b/compiler/rustc_middle/src/hir/mod.rs index 217ecbab059e..ba2d8febad7c 100644 --- a/compiler/rustc_middle/src/hir/mod.rs +++ b/compiler/rustc_middle/src/hir/mod.rs @@ -359,6 +359,7 @@ impl<'tcx> TyCtxt<'tcx> { | Node::Infer(_) | Node::WherePredicate(_) | Node::PreciseCapturingNonLifetimeArg(_) + | Node::ConstArgExprField(_) | Node::OpaqueTy(_) => { unreachable!("no sub-expr expected for {parent_node:?}") } diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index 71bbd64ddc6c..b50fc201bdb8 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -11,9 +11,9 @@ use rustc_hir::def_id::LocalDefId; use rustc_middle::span_bug; use rustc_span::hygiene::LocalExpnId; use rustc_span::{Span, Symbol, sym}; -use tracing::debug; +use tracing::{debug, instrument}; -use crate::{ImplTraitContext, InvocationParent, Resolver}; +use crate::{ConstArgContext, ImplTraitContext, InvocationParent, Resolver}; pub(crate) fn collect_definitions( resolver: &mut Resolver<'_, '_>, @@ -21,6 +21,7 @@ pub(crate) fn collect_definitions( expansion: LocalExpnId, ) { let invocation_parent = resolver.invocation_parents[&expansion]; + debug!("new fragment to visit with invocation_parent: {invocation_parent:?}"); let mut visitor = DefCollector { resolver, expansion, invocation_parent }; fragment.visit_with(&mut visitor); } @@ -74,6 +75,12 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { self.invocation_parent.impl_trait_context = orig_itc; } + fn with_const_arg(&mut self, ctxt: ConstArgContext, f: F) { + let orig = mem::replace(&mut self.invocation_parent.const_arg_context, ctxt); + f(self); + self.invocation_parent.const_arg_context = orig; + } + fn collect_field(&mut self, field: &'a FieldDef, index: Option) { let index = |this: &Self| { index.unwrap_or_else(|| { @@ -93,7 +100,10 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { } } + #[instrument(level = "debug", skip(self))] fn visit_macro_invoc(&mut self, id: NodeId) { + debug!(?self.invocation_parent); + let id = id.placeholder_to_expn_id(); let old_parent = self.resolver.invocation_parents.insert(id, self.invocation_parent); assert!(old_parent.is_none(), "parent `LocalDefId` is reset for an invocation"); @@ -360,36 +370,77 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> { // `MgcaDisambiguation::Direct` is set even when MGCA is disabled, so // to avoid affecting stable we have to feature gate the not creating // anon consts - if let MgcaDisambiguation::Direct = constant.mgca_disambiguation - && self.resolver.tcx.features().min_generic_const_args() - { - visit::walk_anon_const(self, constant); - return; + if !self.resolver.tcx.features().min_generic_const_args() { + let parent = + self.create_def(constant.id, None, DefKind::AnonConst, constant.value.span); + return self.with_parent(parent, |this| visit::walk_anon_const(this, constant)); } - let parent = self.create_def(constant.id, None, DefKind::AnonConst, constant.value.span); - self.with_parent(parent, |this| visit::walk_anon_const(this, constant)); + match constant.mgca_disambiguation { + MgcaDisambiguation::Direct => self.with_const_arg(ConstArgContext::Direct, |this| { + visit::walk_anon_const(this, constant); + }), + MgcaDisambiguation::AnonConst => { + self.with_const_arg(ConstArgContext::NonDirect, |this| { + let parent = + this.create_def(constant.id, None, DefKind::AnonConst, constant.value.span); + this.with_parent(parent, |this| visit::walk_anon_const(this, constant)); + }) + } + }; } + #[instrument(level = "debug", skip(self))] fn visit_expr(&mut self, expr: &'a Expr) { - let parent_def = match expr.kind { + debug!(?self.invocation_parent); + + let parent_def = match &expr.kind { ExprKind::MacCall(..) => return self.visit_macro_invoc(expr.id), ExprKind::Closure(..) | ExprKind::Gen(..) => { self.create_def(expr.id, None, DefKind::Closure, expr.span) } - ExprKind::ConstBlock(ref constant) => { - for attr in &expr.attrs { - visit::walk_attribute(self, attr); - } - let def = - self.create_def(constant.id, None, DefKind::InlineConst, constant.value.span); - self.with_parent(def, |this| visit::walk_anon_const(this, constant)); - return; + ExprKind::ConstBlock(constant) => { + // Under `min_generic_const_args` a `const { }` block sometimes + // corresponds to an anon const rather than an inline const. + let def_kind = match self.invocation_parent.const_arg_context { + ConstArgContext::Direct => DefKind::AnonConst, + ConstArgContext::NonDirect => DefKind::InlineConst, + }; + + return self.with_const_arg(ConstArgContext::NonDirect, |this| { + for attr in &expr.attrs { + visit::walk_attribute(this, attr); + } + + let def = this.create_def(constant.id, None, def_kind, constant.value.span); + this.with_parent(def, |this| visit::walk_anon_const(this, constant)); + }); } + + // Avoid overwriting `const_arg_context` as we may want to treat const blocks + // as being anon consts if we are inside a const argument. + ExprKind::Struct(_) => return visit::walk_expr(self, expr), + // FIXME(mgca): we may want to handle block labels in some manner + ExprKind::Block(block, _) if let [stmt] = block.stmts.as_slice() => match stmt.kind { + // FIXME(mgca): this probably means that mac calls that expand + // to semi'd const blocks are handled differently to just writing + // out a semi'd const block. + StmtKind::Expr(..) | StmtKind::MacCall(..) => return visit::walk_expr(self, expr), + + // Fallback to normal behaviour + StmtKind::Let(..) | StmtKind::Item(..) | StmtKind::Semi(..) | StmtKind::Empty => { + self.invocation_parent.parent_def + } + }, + _ => self.invocation_parent.parent_def, }; - self.with_parent(parent_def, |this| visit::walk_expr(this, expr)) + self.with_const_arg(ConstArgContext::NonDirect, |this| { + // Note in some cases the `parent_def` here may be the existing parent + // and this is actually a no-op `with_parent` call. + this.with_parent(parent_def, |this| visit::walk_expr(this, expr)) + }) } fn visit_ty(&mut self, ty: &'a Ty) { diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index c981abe67c1f..57f19f7ea398 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -187,6 +187,7 @@ struct InvocationParent { parent_def: LocalDefId, impl_trait_context: ImplTraitContext, in_attr: bool, + const_arg_context: ConstArgContext, } impl InvocationParent { @@ -194,6 +195,7 @@ impl InvocationParent { parent_def: CRATE_DEF_ID, impl_trait_context: ImplTraitContext::Existential, in_attr: false, + const_arg_context: ConstArgContext::NonDirect, }; } @@ -204,6 +206,13 @@ enum ImplTraitContext { InBinding, } +#[derive(Copy, Clone, Debug)] +enum ConstArgContext { + Direct, + /// Either inside of an `AnonConst` or not inside a const argument at all. + NonDirect, +} + /// Used for tracking import use types which will be used for redundant import checking. /// /// ### Used::Scope Example diff --git a/tests/ui/const-generics/mgca/struct_expr_with_macros.rs b/tests/ui/const-generics/mgca/struct_expr_with_macros.rs new file mode 100644 index 000000000000..b59a73066488 --- /dev/null +++ b/tests/ui/const-generics/mgca/struct_expr_with_macros.rs @@ -0,0 +1,31 @@ +//@ check-pass + +// Test that the def collector makes `AnonConst`s not `InlineConst`s even +// when the const block is obscured via macros. + +#![feature(min_generic_const_args, adt_const_params)] +#![expect(incomplete_features)] + +macro_rules! const_block { + ($e:expr) => { const { + $e + } } +} + +macro_rules! foo_expr { + ($e:expr) => { Foo { + field: $e, + } } +} + +use std::marker::ConstParamTy; + +#[derive(PartialEq, Eq, ConstParamTy)] +struct Foo { field: u32 } + +fn foo() {} + +fn main() { + foo::<{ Foo { field: const_block!{ 1 + 1 }} }>(); + foo::<{ foo_expr! { const_block! { 1 + 1 }} }>(); +}