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
141 changes: 93 additions & 48 deletions compiler/rustc_ast_lowering/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1481,12 +1481,23 @@ impl<'hir> LoweringContext<'_, 'hir> {
TyKind::DirectConstArg(expr)
if self.tcx.features().min_generic_const_args() =>
{
let ct = match self.can_lower_expr_to_const_arg_direct(expr) {
let ct = match self.can_lower_expr_to_const_arg_direct(
expr,
DirectConstArgContext::MacrolessMinGenericConstArgs,
) {
Ok(()) => self.lower_expr_to_const_arg_direct(expr, None),
Err(e) => e.emit(self),
};
let ct = self.arena.alloc(ct);
return GenericArg::Const(ct.try_as_ambig_ct().unwrap());
// note: this allows direct_const_arg!(_) to be inferred to a type. a little
// wonky.
return match ct.try_as_ambig_ct() {
Some(ct) => GenericArg::Const(ct),
None => GenericArg::Infer(hir::InferArg {
hir_id: ct.hir_id,
span: ct.span,
}),
};
}
_ => {}
}
Expand Down Expand Up @@ -2612,7 +2623,7 @@ impl<'hir> LoweringContext<'_, 'hir> {

let is_trivial_path = path.is_potential_trivial_const_arg()
&& matches!(res, Res::Def(DefKind::ConstParam, _));
let ct_kind = if is_trivial_path || tcx.features().min_generic_const_args() {
let ct_kind = if is_trivial_path || tcx.features().macroless_generic_const_args() {
let qpath = self.lower_qpath(
ty_id,
&None,
Expand Down Expand Up @@ -2675,7 +2686,15 @@ impl<'hir> LoweringContext<'_, 'hir> {
hir::ConstItemRhs::Body(self.lower_const_body(span, None))
}
ConstItemRhsKind::TypeConst { rhs: Some(anon) } => {
hir::ConstItemRhs::TypeConst(self.lower_anon_const_to_const_arg_and_alloc(anon))
hir::ConstItemRhs::TypeConst(self.arena.alloc(
match self.can_lower_expr_to_const_arg_direct(
&anon.value,
DirectConstArgContext::MacrolessMinGenericConstArgs,
) {
Ok(()) => self.lower_expr_to_const_arg_direct(&anon.value, Some(anon.id)),
Err(err) => err.emit(self),
},
))
}
Comment on lines 2688 to 2698

@khyperia khyperia Jul 10, 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.

ConstItemRhsKind::TypeConst no longer needs to contain an AnonConst, it can parse identically to the rhs of a regular const, as a normal expr now.

Doing so I've left undone for now, will do so in a followup, it gets a bit more involved (and it's related to this #158617 (comment) that I might do at the same time)

View changes since the review

ConstItemRhsKind::TypeConst { rhs: None } => {
let const_arg = ConstArg {
Expand All @@ -2694,63 +2713,67 @@ impl<'hir> LoweringContext<'_, 'hir> {
fn can_lower_expr_to_const_arg_direct(
&mut self,
expr: &Expr,
context: DirectConstArgContext,
) -> Result<(), UnrepresentableConstArgError> {
let is_mgca = self.tcx.features().min_generic_const_args();
// Note the only stable case is currently ExprKind::Path. All others have an is_mgca guard.
match &expr.kind {
ExprKind::Call(func, args)
if is_mgca && let ExprKind::Path(_qself, _path) = &func.kind =>
{
use DirectConstArgContext::*;
// Note the only stable case is currently ExprKind::Path
match (&expr.kind, context) {
(
ExprKind::Call(Expr { kind: ExprKind::Path(_, _), .. }, args),
MacrolessMinGenericConstArgs,
) => {
for arg in args {
self.can_lower_expr_to_const_arg_direct(arg)?;
self.can_lower_expr_to_const_arg_direct(arg, context)?;
}
Ok(())
}
ExprKind::Tup(exprs) if is_mgca => {
(ExprKind::Tup(exprs), MacrolessMinGenericConstArgs) => {
for expr in exprs {
self.can_lower_expr_to_const_arg_direct(expr)?;
self.can_lower_expr_to_const_arg_direct(expr, context)?;
}
Ok(())
}
ExprKind::Path(qself, path)
if is_mgca
|| path.is_potential_trivial_const_arg()
&& matches!(
self.get_partial_res(expr.id)
.and_then(|partial_res| partial_res.full_res()),
Some(Res::Def(DefKind::ConstParam, _))
) =>
{
Ok(())
(ExprKind::Path(_, _), MacrolessMinGenericConstArgs) => Ok(()),
(ExprKind::Path(_, path), _) => {
if path.is_potential_trivial_const_arg()
&& matches!(
self.get_partial_res(expr.id)
.and_then(|partial_res| partial_res.full_res()),
Some(Res::Def(DefKind::ConstParam, _))
)
{
Ok(())
} else {
Err(UnrepresentableConstArgError::new(expr))
}
}
ExprKind::Struct(se) if is_mgca => {
(ExprKind::Struct(se), MacrolessMinGenericConstArgs) => {
for f in &se.fields {
self.can_lower_expr_to_const_arg_direct(&f.expr)?;
self.can_lower_expr_to_const_arg_direct(&f.expr, context)?;
}
Ok(())
}
ExprKind::Array(elements) if is_mgca => {
(ExprKind::Array(elements), MacrolessMinGenericConstArgs) => {
for element in elements {
self.can_lower_expr_to_const_arg_direct(element)?;
self.can_lower_expr_to_const_arg_direct(element, context)?;
}
Ok(())
}
ExprKind::Underscore if is_mgca => Ok(()),
ExprKind::Block(block, _)
if is_mgca
&& let [stmt] = block.stmts.as_slice()
(ExprKind::Underscore, MacrolessMinGenericConstArgs) => Ok(()),
(ExprKind::Block(block, _), MacrolessMinGenericConstArgs)
if let [stmt] = block.stmts.as_slice()
&& let StmtKind::Expr(expr) = &stmt.kind =>
{
self.can_lower_expr_to_const_arg_direct(expr)
self.can_lower_expr_to_const_arg_direct(expr, context)
}
ExprKind::Lit(literal) if is_mgca => Ok(()),
ExprKind::Unary(UnOp::Neg, inner_expr)
if is_mgca && let ExprKind::Lit(_) = &inner_expr.kind =>
(ExprKind::Lit(_), MacrolessMinGenericConstArgs) => Ok(()),
(ExprKind::Unary(UnOp::Neg, inner_expr), MacrolessMinGenericConstArgs)
if let ExprKind::Lit(_) = &inner_expr.kind =>
{
Ok(())
}
ExprKind::ConstBlock(anon) if is_mgca => Ok(()),
ExprKind::DirectConstArg(expr) if is_mgca => {
(ExprKind::ConstBlock(_), MacrolessMinGenericConstArgs) => Ok(()),
(ExprKind::DirectConstArg(_), MacrolessMinGenericConstArgs | MinGenericConstArgs) => {
// Always report this as able to be represented directly. If it turns out not to be,
// `lower_expr_to_const_arg_direct` will report an error.
Ok(())
Expand All @@ -2767,8 +2790,6 @@ impl<'hir> LoweringContext<'_, 'hir> {
expr: &Expr,
id_override: Option<NodeId>,
) -> hir::ConstArg<'hir> {
debug_assert!(self.can_lower_expr_to_const_arg_direct(expr).is_ok());

let span = self.lower_span(expr.span);
let node_id = id_override.unwrap_or(expr.id);
match &expr.kind {
Expand Down Expand Up @@ -2929,7 +2950,12 @@ impl<'hir> LoweringContext<'_, 'hir> {
// ExprKind::DirectConstArg, which effectively forces the expression to be lowered
// as a direct arg. If it actually turns out to not be possible, emit an error
// instead.
match self.can_lower_expr_to_const_arg_direct(expr) {
// Always use MacrolessMinGenericConstArgs, even if we're under regular GCA, because
// that's what the macro means: to enter a context that is like macroless GCA.
match self.can_lower_expr_to_const_arg_direct(
expr,
DirectConstArgContext::MacrolessMinGenericConstArgs,
) {
Ok(()) => self.lower_expr_to_const_arg_direct(expr, id_override),
Err(err) => err.emit(self),
}
Expand All @@ -2951,24 +2977,28 @@ impl<'hir> LoweringContext<'_, 'hir> {
&mut self,
anon: &AnonConst,
) -> &'hir hir::ConstArg<'hir> {
self.arena.alloc(self.lower_anon_const_to_const_arg(anon, anon.value.span))
self.arena.alloc(self.lower_anon_const_to_const_arg(anon))
}

#[instrument(level = "debug", skip(self))]
fn lower_anon_const_to_const_arg(
&mut self,
anon: &AnonConst,
span: Span,
) -> hir::ConstArg<'hir> {
fn lower_anon_const_to_const_arg(&mut self, anon: &AnonConst) -> hir::ConstArg<'hir> {
// Stable only allows one nesting of blocks for directly represented paths. mGCA allows
// arbitrarily many, and are handled inside lower_expr_to_const_arg_direct for consistency.
let expr = if self.tcx.features().min_generic_const_args() {
let expr = if self.tcx.features().macroless_generic_const_args() {
&anon.value
} else {
anon.value.maybe_unwrap_block()
};

if self.can_lower_expr_to_const_arg_direct(expr).is_ok() {
let context = if self.tcx.features().macroless_generic_const_args() {
DirectConstArgContext::MacrolessMinGenericConstArgs
} else if self.tcx.features().min_generic_const_args() {
DirectConstArgContext::MinGenericConstArgs
} else {
DirectConstArgContext::Stable
};

if self.can_lower_expr_to_const_arg_direct(expr, context).is_ok() {
return self.lower_expr_to_const_arg_direct(expr, Some(anon.id));
}

Expand Down Expand Up @@ -3273,6 +3303,21 @@ impl<'hir> GenericArgsCtor<'hir> {
}
}

#[derive(Copy, Clone, Debug)]
enum DirectConstArgContext {
/// The only allowed direct const arg representation is simple paths that nameres to generic
/// const parameters.
Stable,
/// The allowed representations are what is allowed on stable, plus the `direct_const_arg!` macro.
MinGenericConstArgs,
/// Expressions attempt to be lowered directly, and if that fails, the expression falls back to
/// being represented as an anon const.
///
/// This context is also used under MinGenericConstArgs inside a `direct_const_arg!` macro, for
/// simplicity, as they allow the same code.
MacrolessMinGenericConstArgs,
}

#[derive(Debug)]
struct UnrepresentableConstArgError {
span: Span,
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,8 @@ declare_features! (
(unstable, macro_metavar_expr, "1.61.0", Some(83527)),
/// Provides a way to concatenate identifiers using metavariable expressions.
(unstable, macro_metavar_expr_concat, "1.81.0", Some(124225)),
/// Allows directly represented generic_const_args without the `direct_const_arg!` macro.
(incomplete, macroless_generic_const_args, "CURRENT_RUSTC_VERSION", Some(159006)),
/// Allows `#[marker]` on certain traits allowing overlapping implementations.
(unstable, marker_trait_attr, "1.30.0", Some(29864)),
/// Enable mgca `type const` syntax before expansion.
Expand Down Expand Up @@ -875,5 +877,6 @@ pub const INCOMPATIBLE_FEATURES: &[(Symbol, Symbol)] = &[
/// Some features require one or more other features to be enabled.
pub const DEPENDENT_FEATURES: &[(Symbol, &[Symbol])] = &[
(sym::generic_const_args, &[sym::min_generic_const_args]),
(sym::macroless_generic_const_args, &[sym::min_generic_const_args]),
(sym::unsized_const_params, &[sym::adt_const_params]),
];
5 changes: 2 additions & 3 deletions compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,15 +483,14 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
let tcx = self.tcx();
let parent_def_id = self.item_def_id();
if let Res::Def(DefKind::ConstParam, _) = res
&& matches!(tcx.def_kind(parent_def_id), DefKind::AnonConst | DefKind::InlineConst)
&& let ty::AnonConstKind::MCG = tcx.anon_const_kind(parent_def_id)
&& let Some(context) = self.anon_const_forbids_generic_params()
{
let folder = ForbidParamUsesFolder {
tcx,
anon_const_def_id: parent_def_id,
span,
is_self_alias: false,
context: ForbidParamContext::ConstArgument,
context,
};
return Err(folder.error());
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1250,6 +1250,7 @@ symbols! {
macro_reexport,
macro_use,
macro_vis_matcher,
macroless_generic_const_args,
macros_in_extern,
main,
managed_boxes,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# macroless_generic_const_args

Enables using `#![feature(min_generic_const_args)]` without the `direct_const_arg!` macro.

The tracking issue for this feature is: [#159006]

[#159006]: https://github.com/rust-lang/rust/issues/159006

------------------------

Warning: This feature is incomplete; its design and syntax may change.

Related features: [min_generic_const_args]. See that doc for what the `direct_const_arg!` is. This feature enables
support for directly represented const arguments without the macro.

[min_generic_const_args]: min-generic-const-args.md

## Examples

Here is an example from [min_generic_const_args]:

[min_generic_const_args]: min-generic-const-args.md

```rust
#![allow(incomplete_features)]
#![feature(min_generic_const_args)]

trait Bar {
type const VAL: usize;
type const VAL2: usize;
}

struct Baz;

impl Bar for Baz {
type const VAL: usize = 2;
type const VAL2: usize = const { Self::VAL * 2 };
}

struct Foo<B: Bar> {
arr1: [usize; core::direct_const_arg!(B::VAL)],
arr2: [usize; core::direct_const_arg!(B::VAL2)],
}
```

Using `#![macroless_generic_const_args]` enables you to write the above without the macro:

@kn1g78 kn1g78 Jul 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this should be #![feature(macroless_generic_const_args)], as shown in the example below.

View changes since the review


```rust
#![allow(incomplete_features)]
#![feature(min_generic_const_args, macroless_generic_const_args)]

trait Bar {
type const VAL: usize;
type const VAL2: usize;
}

struct Baz;

impl Bar for Baz {
type const VAL: usize = 2;
type const VAL2: usize = const { Self::VAL * 2 };
}

struct Foo<B: Bar> {
arr1: [usize; B::VAL],
arr2: [usize; B::VAL2],
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,29 @@ and uses a different approach for implementation. It is intentionally more restr
cases that make the `generic_const_exprs` hard to implement properly. See [Feature background][feature_background]
for more details.

Related features: [generic_const_args], [generic_const_items].
Related features: [macroless_generic_const_args], [generic_const_args], [generic_const_items].

[feature_background]: https://github.com/rust-lang/project-const-generics/blob/main/documents/min_const_generics_plan.md
[generic_const_exprs]: generic-const-exprs.md
[macroless_generic_const_args]: macroless-generic-const-args.md
[generic_const_args]: generic-const-args.md
[generic_const_items]: generic-const-items.md

## `direct_const_arg!` macro

This feature introduces a new macro: `direct_const_arg!`.

When an expression is used as a generic argument, it is typically lowered as an "anon const", which is an expression
that is opaque to the type system and cannot contain generics. Using `direct_const_arg!` instead represents the
expression "directly", i.e. without an anon const, in a way that is visible to the type system.

(Note that plain paths to generic parameters are always represented directly, without `direct_const_arg!`, as this
already works on stable)

See [macroless_generic_const_args] as a feature to disable the requirement of writing `direct_const_arg!`.

[macroless_generic_const_args]: macroless-generic-const-args.md

## `type const` syntax

This feature introduces new syntax: `type const`.
Expand All @@ -35,8 +51,8 @@ type const X: usize = 1;
const Y: usize = 1;

struct Foo {
good_arr: [(); X], // Allowed
bad_arr: [(); Y], // Will not compile, `Y` must be `type const`.
good_arr: [(); core::direct_const_arg!(X)], // Allowed
bad_arr: [(); core::direct_const_arg!(Y)], // Will not compile, `Y` must be `type const`.
}
```

Expand All @@ -59,8 +75,8 @@ impl Bar for Baz {
}

struct Foo<B: Bar> {
arr1: [usize; B::VAL],
arr2: [usize; B::VAL2],
arr1: [usize; core::direct_const_arg!(B::VAL)],
arr2: [usize; core::direct_const_arg!(B::VAL2)],
}
```

Expand Down
Loading
Loading