Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
830a06e
Parse const params in fn parameter lists behind function_arg_const_ge…
bit-aloo Oct 9, 2026
a6a02a2
Resolve arg position const params from function parameters
bit-aloo Oct 9, 2026
76bf900
Lower arg position const params to const generic params
bit-aloo Oct 9, 2026
ad4db6c
Lower call arguments for arg position const params on path calls
bit-aloo Oct 9, 2026
7e570be
Lower call arguments for arg position const params on method calls
bit-aloo Oct 9, 2026
3e1d350
Check arg position const arguments against the param type and report …
bit-aloo Oct 9, 2026
ae0be40
Drop arg position const arguments when building THIR
bit-aloo Oct 9, 2026
e1ecbcf
Add tests for arg position const param lowering
bit-aloo Oct 9, 2026
7750562
carry more info with wf obligation
bit-aloo Oct 9, 2026
624858f
Tighten const argument lookahead to preserve Rust 2015 syntax
bit-aloo Oct 10, 2026
153aa1a
Lets not allocate in lower_fn_decl, and have iterator from inputs
bit-aloo Oct 10, 2026
66b0ea0
use is-keyword instead of check keyword
bit-aloo Oct 10, 2026
c25232f
Reflow a comment with overly long lines
nnethercote Oct 12, 2026
b444573
Inline and remove `fully_perform_into`
nnethercote Oct 12, 2026
631c647
Move `fully_perform` early return earlier
nnethercote Oct 13, 2026
cb2c10a
perf: drop error in `unwrap_failed` instead of `unwrap`
jnkel Oct 9, 2026
afa50b9
Address review comments
nnethercote Oct 13, 2026
df331dc
Rename allow_const to allow_const_params in FnParseMode
bit-aloo Oct 13, 2026
8104522
Add a page about function-arg-const-generics in unstable book
bit-aloo Oct 13, 2026
49c9894
Rollup merge of #162727 - bit-aloo:2026-09-06-function-argument-const…
JonathanBrouwer Oct 13, 2026
3e443d8
Rollup merge of #164059 - jnkel:outline-result-unwrap-drop, r=scottmcm
JonathanBrouwer Oct 13, 2026
480c8c2
Rollup merge of #164236 - nnethercote:fully_perform-early-return, r=lcnr
JonathanBrouwer Oct 13, 2026
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
23 changes: 23 additions & 0 deletions compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2935,6 +2935,9 @@ pub struct Param {
pub id: NodeId,
pub span: Span,
pub is_placeholder: bool,
/// Records whether `const` was written before the parameter.
/// This only tracks syntax, not whether it's a valid const generic param.
pub has_const_keyword: bool,
}

/// Alternative representation for `Arg`s describing `self` parameter of methods.
Expand Down Expand Up @@ -3032,8 +3035,28 @@ impl Param {
ty,
id: DUMMY_NODE_ID,
is_placeholder: false,
has_const_keyword: false,
}
}

/// Returns the ident if this param is a valid arg position const param.
/// Unlike has_const_keyword, this also checks the pats
pub fn const_param_ident(&self) -> Option<Ident> {
if !self.has_const_keyword {
return None;
}

match self.pat.kind {
PatKind::Ident(BindingMode::NONE, ident, None) => Some(ident),
_ => None,
}
}

/// Checks if this is an arg position const param.
/// Use this instead of has_const_keyword, which only checks for the keyword.
pub fn is_const_param(&self) -> bool {
self.const_param_ident().is_some()
}
}

/// A signature (not the body) of a function declaration.
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_ast_lowering/src/delegation/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
hir::GenericParamKind::Const {
ty: self.arena.alloc(hir::Ty { kind, hir_id, span }),
default: None,
arg_pos: None,
}
}
};
Expand Down
16 changes: 13 additions & 3 deletions compiler/rustc_ast_lowering/src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1254,8 +1254,12 @@ impl<'hir> LoweringContext<'_, 'hir> {
body: impl FnOnce(&mut Self) -> hir::Expr<'hir>,
) -> hir::BodyId {
self.lower_body(|this| {
let params =
this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x)));
let params = this.arena.alloc_from_iter(
decl.inputs
.iter()
.filter(|param| !param.is_const_param())
.map(|x| this.lower_param(x)),
);

// Optionally lower the fn contract
if let Some(contract) = contract {
Expand Down Expand Up @@ -1398,7 +1402,9 @@ impl<'hir> LoweringContext<'_, 'hir> {
// let-bound variables and temporaries created in the body
// (and its tail expression!) before we drop the
// parameters (c.f. rust-lang/rust#64512).
for (index, parameter) in decl.inputs.iter().enumerate() {
for (index, parameter) in
decl.inputs.iter().filter(|param| !param.is_const_param()).enumerate()
{
let parameter = self.lower_param(parameter);
let span = parameter.pat.span;

Expand Down Expand Up @@ -1771,6 +1777,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
) -> (&'hir hir::Generics<'hir>, T) {
assert!(self.curr_owner.impl_trait_defs.is_empty());
assert!(self.curr_owner.impl_trait_bounds.is_empty());
assert!(self.curr_owner.const_defs.is_empty());

let mut predicates: SmallVec<[hir::WherePredicate<'hir>; 4]> = SmallVec::new();
// We need to make sure that generic params don't have multiple relaxed bounds for the same trait
Expand Down Expand Up @@ -1815,6 +1822,9 @@ impl<'hir> LoweringContext<'_, 'hir> {
let span = self.lower_span(generics.span);
let res = f(self);

let const_defs = std::mem::take(&mut self.curr_owner.const_defs);
params.extend(const_defs.into_iter());

let impl_trait_defs = std::mem::take(&mut self.curr_owner.impl_trait_defs);
params.extend(impl_trait_defs.into_iter());

Expand Down
83 changes: 64 additions & 19 deletions compiler/rustc_ast_lowering/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ struct PerOwnerLoweringState<'a, 'hir> {
// -- Transient --
impl_trait_defs: Vec<hir::GenericParam<'hir>>,
impl_trait_bounds: Vec<hir::WherePredicate<'hir>>,
const_defs: Vec<hir::GenericParam<'hir>>,
}

impl<'a, 'hir> PerOwnerLoweringState<'a, 'hir> {
Expand Down Expand Up @@ -211,6 +212,7 @@ impl<'a, 'hir> PerOwnerLoweringState<'a, 'hir> {
children: LocalDefIdMap::default(),
impl_trait_defs: Vec::new(),
impl_trait_bounds: Vec::new(),
const_defs: Vec::new(),
}
}

Expand All @@ -226,6 +228,7 @@ impl<'a, 'hir> PerOwnerLoweringState<'a, 'hir> {
assert_eq!(self.owner_id(), node.def_id());
assert!(self.impl_trait_defs.is_empty());
assert!(self.impl_trait_bounds.is_empty());
assert!(self.const_defs.is_empty());

let attrs = self.attrs;
let mut bodies = self.bodies;
Expand Down Expand Up @@ -1900,18 +1903,20 @@ impl<'hir> LoweringContext<'_, 'hir> {
}

fn lower_fn_params_to_idents(&mut self, decl: &FnDecl) -> &'hir [Option<Ident>] {
self.arena.alloc_from_iter(decl.inputs.iter().map(|param| match param.pat.kind {
PatKind::Missing => None,
PatKind::Ident(_, ident, _) => Some(self.lower_ident(ident)),
PatKind::Wild => Some(Ident::new(kw::Underscore, self.lower_span(param.pat.span))),
_ => {
self.dcx().span_delayed_bug(
param.pat.span,
"non-missing/ident/wild param pat must trigger an error",
);
None
}
}))
self.arena.alloc_from_iter(decl.inputs.iter().filter(|param| !param.is_const_param()).map(
|param| match param.pat.kind {
PatKind::Missing => None,
PatKind::Ident(_, ident, _) => Some(self.lower_ident(ident)),
PatKind::Wild => Some(Ident::new(kw::Underscore, self.lower_span(param.pat.span))),
_ => {
self.dcx().span_delayed_bug(
param.pat.span,
"non-missing/ident/wild param pat must trigger an error",
);
None
}
},
))
}

/// Lowers a function declaration.
Expand Down Expand Up @@ -1945,13 +1950,53 @@ impl<'hir> LoweringContext<'_, 'hir> {
// Skip the `...` (`CVarArgs`) trailing arguments from the AST,
// as they are not explicit in HIR/Ty function signatures.
// (instead, the `c_variadic` flag is set to `true`)
let mut inputs = &decl.inputs[..];
if decl.c_variadic() {
let input_count = if decl.c_variadic() {
// Splat + variadic errors in AST validation, so just ignore one of them here.
splatted = None;
inputs = &inputs[..inputs.len() - 1];
}
let inputs = self.arena.alloc_from_iter(inputs.iter().map(|param| {
decl.inputs.len() - 1
} else {
decl.inputs.len()
};

let inputs = decl.inputs.iter().enumerate().take(input_count);
// Function argument const params are moved from `inputs` into the function's
// generics. Their position is taken from the original `decl.inputs` (before
// removing `...`), so it matches the argument position at the call site.
let inputs = self.arena.alloc_from_iter(inputs.filter_map(|(param_idx, param)| {
if let Some(ident) = param.const_param_ident() {
if !matches!(
kind,
FnDeclKind::Fn | FnDeclKind::Inherent | FnDeclKind::Impl | FnDeclKind::Trait
) {
self.dcx().span_delayed_bug(
param.span,
"Const args only allowed for Free/Inherent/Impl/Trait methods",
);
return None;
}
let ty = self.lower_ty_alloc(
&param.ty,
ImplTraitContext::Disallowed(ImplTraitPosition::GenericDefault),
);
let hir_id = self.lower_node_id(param.id);
let generic_param = hir::GenericParam {
hir_id,
def_id: self.local_def_id(param.id),
name: ParamName::Plain(self.lower_ident(ident)),
span: self.lower_span(param.span),
pure_wrt_drop: false,
kind: hir::GenericParamKind::Const {
ty,
default: None,
arg_pos: Some(param_idx as u16),
},
colon_span: None,
source: hir::GenericParamSource::Generics,
};
self.lower_attrs(hir_id, &param.attrs, param.span, Target::from(&generic_param));
self.curr_owner.const_defs.push(generic_param);
return None;
}
if let DiscardParams::Yes = discard_params {
// FIXME This uses `fn_hir_id`, which is not correct, it should use the parameter hir id instead
// The parameter is currently not lowered for functions without bodies, so there is no place to store the lowered hir id
Expand All @@ -1972,7 +2017,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
ImplTraitContext::Disallowed(ImplTraitPosition::PointerParam)
}
};
self.lower_ty(&param.ty, itctx)
Some(self.lower_ty(&param.ty, itctx))
}));

let output = match coro {
Expand Down Expand Up @@ -2368,7 +2413,7 @@ impl<'hir> LoweringContext<'_, 'hir> {

(
hir::ParamName::Plain(self.lower_ident(param.ident)),
hir::GenericParamKind::Const { ty, default },
hir::GenericParamKind::Const { ty, default, arg_pos: None },
)
}
}
Expand Down
15 changes: 15 additions & 0 deletions compiler/rustc_ast_passes/src/ast_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ impl<'a> AstValidator<'a> {
self.check_decl_splatting(fn_decl, c_variadic_span, splat_semantic);
self.check_decl_attrs(fn_decl);
self.check_decl_self_param(&fn_decl.inputs, self_semantic);
self.check_decl_const_param(&fn_decl.inputs);
}

/// Emits fatal error if function declaration has more than `u16::MAX` arguments
Expand Down Expand Up @@ -555,6 +556,20 @@ impl<'a> AstValidator<'a> {
}
}

fn check_decl_const_param(&self, fn_inputs: &[Param]) {
for param in fn_inputs {
if param.has_const_keyword && !param.is_const_param() {
let span = if matches!(param.pat.kind, PatKind::Missing) {
param.span
} else {
param.pat.span
};

self.dcx().emit_err(diagnostics::InvalidConstParam { span });
}
}
}

/// Check that the signature of this function does not violate the constraints of its ABI.
fn check_extern_fn_signature(
&self,
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_ast_passes/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,14 @@ pub(crate) struct FnParamForbiddenSelf {
pub span: Span,
}

#[derive(Diagnostic)]
#[diag("`const` parameter is only allowed in free/trait/impl functions")]
pub(crate) struct InvalidConstParam {
#[primary_span]
#[label("not semantically valid as function parameter")]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag("`default` is only allowed on items in trait impls")]
pub(crate) struct ForbiddenDefault {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_ast_passes/src/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
gate_all!(fn_delegation, "functions delegation is not yet fully implemented");
gate_all!(forced_keywords, "forced keywords are experimental");
gate_all!(frontmatter, "frontmatters are experimental");
gate_all!(function_arg_const_generics, "function arg const generics are experimental");
gate_all!(gca_min_const_items, "unbraced const blocks as const args are experimental");
gate_all!(gen_blocks, "gen blocks are experimental");
gate_all!(generic_const_items, "generic const items are experimental");
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_ast_pretty/src/pprust/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2244,6 +2244,9 @@ impl<'a> State<'a> {
self.print_explicit_self(&eself);
} else {
if !matches!(input.pat.kind, PatKind::Missing) {
if input.has_const_keyword {
self.word_nbsp("const");
}
self.print_pat(&input.pat);
self.word(":");
self.space();
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_builtin_macros/src/autodiff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,7 @@ mod llvm_enzyme {
id: ast::DUMMY_NODE_ID,
span,
is_placeholder: false,
has_const_keyword: false,
}
})
.collect();
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_expand/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@ impl<'a> ExtCtxt<'a> {
span,
ty,
is_placeholder: false,
has_const_keyword: false,
}
}

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_expand/src/placeholders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ pub(crate) fn placeholder(
span,
ty: ty(),
is_placeholder: true,
has_const_keyword: false,
}]),
AstFragmentKind::FieldDefs => AstFragment::FieldDefs(smallvec![ast::FieldDef {
attrs: Default::default(),
Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,8 @@ declare_features! (
(internal, freeze_impls, "1.78.0", Some(121675)),
/// Frontmatter `---` blocks for use by external tools.
(unstable, frontmatter, "1.88.0", Some(136889)),
/// Allow function args to be const generics
(incomplete, function_arg_const_generics, "CURRENT_RUSTC_VERSION", Some(163261)),
/// Allows using ADTs in directly represented generic const args.
(incomplete, gca_adts, "CURRENT_RUSTC_VERSION", Some(163420)),
/// Allows using generics in more complex const expressions, based on definitional equality.
Expand Down Expand Up @@ -879,6 +881,13 @@ pub enum DependentFeature {
/// Some features require one or more other features to be enabled.
pub const DEPENDENT_FEATURES: &[(Symbol, DependentFeature)] = &[
// tidy-alphabetical-start
(
sym::function_arg_const_generics,
DependentFeature::Or(&[
DependentFeature::Leaf(sym::gca_min_const_items),
DependentFeature::Leaf(sym::gca_adts),
]),
),
(
sym::gca_adts,
DependentFeature::Or(&[
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,9 @@ pub enum GenericParamKind<'hir> {
ty: &'hir Ty<'hir>,
/// Optional default value for the const generic param
default: Option<&'hir ConstArg<'hir>>,
/// Position of the const parameter in the function's argument list,
/// used by `function_arg_const_generics`.
arg_pos: Option<u16>,
},
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir/src/intravisit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1170,7 +1170,7 @@ pub fn walk_generic_param<'v, V: Visitor<'v>>(
GenericParamKind::Type { ref default, .. } => {
visit_opt!(visitor, visit_ty_unambig, default)
}
GenericParamKind::Const { ref ty, ref default } => {
GenericParamKind::Const { ref ty, ref default, arg_pos: _ } => {
try_visit!(visitor.visit_ty_unambig(ty));
if let Some(default) = default {
try_visit!(visitor.visit_const_param_default(*hir_id, default));
Expand Down
Loading
Loading