From 830a06e3a869d957c92a33f74c78bb642d3dc4ab Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 9 Oct 2026 08:48:03 +0000 Subject: [PATCH 01/19] Parse const params in fn parameter lists behind function_arg_const_generics --- compiler/rustc_ast/src/ast.rs | 23 +++++++++++++ .../rustc_ast_passes/src/ast_validation.rs | 15 ++++++++ compiler/rustc_ast_passes/src/diagnostics.rs | 8 +++++ compiler/rustc_ast_passes/src/feature_gate.rs | 1 + compiler/rustc_ast_pretty/src/pprust/state.rs | 3 ++ compiler/rustc_builtin_macros/src/autodiff.rs | 1 + compiler/rustc_expand/src/build.rs | 1 + compiler/rustc_expand/src/placeholders.rs | 1 + compiler/rustc_feature/src/unstable.rs | 9 +++++ compiler/rustc_parse/src/parser/attr.rs | 7 +++- .../rustc_parse/src/parser/diagnostics.rs | 1 + compiler/rustc_parse/src/parser/expr.rs | 1 + compiler/rustc_parse/src/parser/function.rs | 34 ++++++++++++++++--- compiler/rustc_parse/src/parser/item.rs | 26 ++++++++++---- compiler/rustc_parse/src/parser/path.rs | 1 + compiler/rustc_parse/src/parser/stmt.rs | 7 +++- compiler/rustc_parse/src/parser/ty.rs | 18 +++++++--- compiler/rustc_span/src/symbol.rs | 1 + .../clippy/clippy_utils/src/ast_utils/mod.rs | 1 + ...eature-gate-function-arg-const-generics.rs | 4 +++ ...re-gate-function-arg-const-generics.stderr | 13 +++++++ 21 files changed, 160 insertions(+), 16 deletions(-) create mode 100644 tests/ui/feature-gates/feature-gate-function-arg-const-generics.rs create mode 100644 tests/ui/feature-gates/feature-gate-function-arg-const-generics.stderr diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 4c7ae87556f76..6850104f1cda6 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -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. @@ -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 { + 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. diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 010afc6be5534..d33c42132ecea 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -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 @@ -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, diff --git a/compiler/rustc_ast_passes/src/diagnostics.rs b/compiler/rustc_ast_passes/src/diagnostics.rs index bb006b32ac73e..6def9c6dfb98a 100644 --- a/compiler/rustc_ast_passes/src/diagnostics.rs +++ b/compiler/rustc_ast_passes/src/diagnostics.rs @@ -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 { diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 5feebcc0b8bdf..ed004d9e92a02 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -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"); diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index b76502814dbb7..e767f9623c2fd 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -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(); diff --git a/compiler/rustc_builtin_macros/src/autodiff.rs b/compiler/rustc_builtin_macros/src/autodiff.rs index 21f424836e476..b8002e73ae35c 100644 --- a/compiler/rustc_builtin_macros/src/autodiff.rs +++ b/compiler/rustc_builtin_macros/src/autodiff.rs @@ -529,6 +529,7 @@ mod llvm_enzyme { id: ast::DUMMY_NODE_ID, span, is_placeholder: false, + has_const_keyword: false, } }) .collect(); diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs index 2a4089b1213ab..5f0cb054d8024 100644 --- a/compiler/rustc_expand/src/build.rs +++ b/compiler/rustc_expand/src/build.rs @@ -600,6 +600,7 @@ impl<'a> ExtCtxt<'a> { span, ty, is_placeholder: false, + has_const_keyword: false, } } diff --git a/compiler/rustc_expand/src/placeholders.rs b/compiler/rustc_expand/src/placeholders.rs index ad6ae5481da39..371778f3abb01 100644 --- a/compiler/rustc_expand/src/placeholders.rs +++ b/compiler/rustc_expand/src/placeholders.rs @@ -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(), diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index a35ea37b3bd5f..3801fb2f5fa7c 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -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. @@ -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(&[ diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs index 4d6255aa08f8f..f7829b3fe12c3 100644 --- a/compiler/rustc_parse/src/parser/attr.rs +++ b/compiler/rustc_parse/src/parser/attr.rs @@ -211,7 +211,12 @@ impl<'a> Parser<'a> { AttrWrapper::empty(), true, false, - FnParseMode { req_name: |_, _| true, context: FnContext::Free, req_body: true }, + FnParseMode { + req_name: |_, _| true, + context: FnContext::Free, + req_body: true, + allow_const: true, + }, ForceCollect::No, AllowConstBlockItems::Yes, ) { diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 14ea328601a02..705aac9cb17a6 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -59,6 +59,7 @@ pub(super) fn dummy_arg(ident: Ident, guar: ErrorGuaranteed) -> Param { span: ident.span, ty: Box::new(ty), is_placeholder: false, + has_const_keyword: false, } } diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index bf99bbe26f087..d04d4c9818c5b 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -2440,6 +2440,7 @@ impl<'a> Parser<'a> { span: lo.to(this.prev_token.span), id: DUMMY_NODE_ID, is_placeholder: false, + has_const_keyword: false, }, Trailing::from(this.token == token::Comma), UsePreAttrPos::No, diff --git a/compiler/rustc_parse/src/parser/function.rs b/compiler/rustc_parse/src/parser/function.rs index d3aba0b98eb8b..4c3b06f58f307 100644 --- a/compiler/rustc_parse/src/parser/function.rs +++ b/compiler/rustc_parse/src/parser/function.rs @@ -92,6 +92,10 @@ pub(crate) struct FnParseMode { /// definition or extern block. Within an impl block or a module, it should /// always be set to true. pub(super) req_body: bool, + /// Whether function_arg_const_generics (`const N: T`) is allowed + /// in the parameter list. Enabled for free, impl, and trait functions. + /// Disabled for the rest. + pub(super) allow_const: bool, } /// The context in which a function is parsed. @@ -139,6 +143,8 @@ impl<'a> Parser<'a> { } }; + let decl = Box::new(decl); + // Store the end of function parameters to give better diagnostics // inside `parse_fn_body()`. let fn_params_end = self.prev_token.span.shrink_to_hi(); @@ -671,11 +677,11 @@ impl<'a> Parser<'a> { fn_parse_mode: &FnParseMode, ret_allow_plus: AllowPlus, recover_return_sign: RecoverReturnSign, - ) -> PResult<'a, Box> { - Ok(Box::new(FnDecl { + ) -> PResult<'a, FnDecl> { + Ok(FnDecl { inputs: self.parse_fn_params(fn_parse_mode)?, output: self.parse_ret_ty(ret_allow_plus, RecoverQPath::Yes, recover_return_sign)?, - })) + }) } /// Parses the parameter list of a function, including the `(` and `)` delimiters. @@ -763,6 +769,18 @@ impl<'a> Parser<'a> { } else { is_name_required }; + + let mut is_const = false; + if fn_parse_mode.allow_const + && this.check_keyword(exp!(Const)) + && this.look_ahead(1, |token| token.is_ident()) + { + this.bump(); + let span = this.prev_token.span; + is_const = true; + this.psess.gated_spans.gate(sym::function_arg_const_generics, span); + } + let (pat, ty) = if is_name_required || this.is_named_param() { debug!("parse_param_general parse_pat (is_name_required:{})", is_name_required); let (pat, colon) = this.parse_fn_param_pat_colon()?; @@ -841,7 +859,15 @@ impl<'a> Parser<'a> { let span = lo.to(this.prev_token.span); Ok(( - Param { attrs, id: ast::DUMMY_NODE_ID, is_placeholder: false, pat, span, ty }, + Param { + attrs, + id: ast::DUMMY_NODE_ID, + is_placeholder: false, + pat, + span, + ty, + has_const_keyword: is_const, + }, Trailing::No, UsePreAttrPos::No, )) diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 6d3d79bbe2b5c..b8509073a6a79 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -126,8 +126,12 @@ impl<'a> Parser<'a> { force_collect: ForceCollect, allow_const_block_items: AllowConstBlockItems, ) -> PResult<'a, Option>> { - let fn_parse_mode = - FnParseMode { req_name: |_, _| true, context: FnContext::Free, req_body: true }; + let fn_parse_mode = FnParseMode { + req_name: |_, _| true, + context: FnContext::Free, + req_body: true, + allow_const: true, + }; self.parse_item_(fn_parse_mode, force_collect, allow_const_block_items) .map(|i| i.map(Box::new)) } @@ -1235,8 +1239,12 @@ impl<'a> Parser<'a> { &mut self, force_collect: ForceCollect, ) -> PResult<'a, Option>>> { - let fn_parse_mode = - FnParseMode { req_name: |_, _| true, context: FnContext::Impl, req_body: true }; + let fn_parse_mode = FnParseMode { + req_name: |_, _| true, + context: FnContext::Impl, + req_body: true, + allow_const: true, + }; self.parse_assoc_item(fn_parse_mode, force_collect) } @@ -1248,6 +1256,7 @@ impl<'a> Parser<'a> { req_name: |edition, _| edition >= Edition::Edition2018, context: FnContext::Trait, req_body: false, + allow_const: true, }; self.parse_assoc_item(fn_parse_mode, force_collect) } @@ -1594,6 +1603,7 @@ impl<'a> Parser<'a> { req_name: |_, is_dot_dot_dot| is_dot_dot_dot == IsDotDotDot::No, context: FnContext::Free, req_body: false, + allow_const: false, }; Ok(self .parse_item_( @@ -2514,8 +2524,12 @@ impl<'a> Parser<'a> { let err = if self.check_fn_front_matter(false, Case::Sensitive) { let inherited_vis = Visibility { span: DUMMY_SP, kind: VisibilityKind::Inherited }; // We use `parse_fn` to get a span for the function - let fn_parse_mode = - FnParseMode { req_name: |_, _| true, context: FnContext::Free, req_body: true }; + let fn_parse_mode = FnParseMode { + req_name: |_, _| true, + context: FnContext::Free, + req_body: true, + allow_const: true, + }; match self.parse_fn( &mut AttrVec::new(), fn_parse_mode, diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs index 99bf592c859fb..c8aacd7718fcf 100644 --- a/compiler/rustc_parse/src/parser/path.rs +++ b/compiler/rustc_parse/src/parser/path.rs @@ -403,6 +403,7 @@ impl<'a> Parser<'a> { context: FnContext::ParenthesizedArgumentList, req_name: |_, _| false, req_body: false, + allow_const: false, }; let param = p.parse_param_general(&mode, first_param)?; first_param = false; diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 3cee15c5efa49..57141883c4726 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -159,7 +159,12 @@ impl<'a> Parser<'a> { attrs.clone(), // FIXME: unwanted clone of attrs false, true, - FnParseMode { req_name: |_, _| true, context: FnContext::Free, req_body: true }, + FnParseMode { + req_name: |_, _| true, + context: FnContext::Free, + req_body: true, + allow_const: true, + }, force_collect, AllowConstBlockItems::No, )? { diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index 9b7e8e38ed788..2f68d7723b6a1 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -886,8 +886,10 @@ impl<'a> Parser<'a> { req_name: |_, _| false, context: FnContext::FunctionPtrType, req_body: false, + allow_const: false, }; let decl = self.parse_fn_decl(&mode, AllowPlus::No, recover_return_sign)?; + let decl = Box::new(decl); let decl_span = span_start.to(self.prev_token.span); Ok(TyKind::FnPtr(Box::new(FnPtrTy { @@ -1449,8 +1451,12 @@ impl<'a> Parser<'a> { self.bump(); let args_lo = self.token.span; let snapshot = self.create_snapshot_for_diagnostic(); - let mode = - FnParseMode { req_name: |_, _| false, context: FnContext::Free, req_body: false }; + let mode = FnParseMode { + req_name: |_, _| false, + context: FnContext::Free, + req_body: false, + allow_const: false, + }; match self.parse_fn_decl(&mode, AllowPlus::No, RecoverReturnSign::OnlyFatArrow) { Ok(decl) => { self.dcx().emit_err(ExpectedFnPathFoundFnKeyword { fn_token_span }); @@ -1547,8 +1553,12 @@ impl<'a> Parser<'a> { }; // Parse `(T, U) -> R`. let inputs_lo = self.token.span; - let mode = - FnParseMode { req_name: |_, _| false, context: FnContext::Free, req_body: false }; + let mode = FnParseMode { + req_name: |_, _| false, + context: FnContext::Free, + req_body: false, + allow_const: false, + }; let inputs = match self.parse_fn_params(&mode) { Ok(params) => params, Err(err) => { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index b9e7a9f5f94cb..31e8058fdce88 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1071,6 +1071,7 @@ symbols! { fsub_algebraic, fsub_fast, full, + function_arg_const_generics, fundamental, fused_iterator, future_output, diff --git a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs index 0774fa729bc12..7081e3d874225 100644 --- a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs +++ b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs @@ -837,6 +837,7 @@ fn eq_param(l: &Param, r: &Param) -> bool { && eq_pat(&l.pat, &r.pat) && eq_ty(&l.ty, &r.ty) && over(&l.attrs, &r.attrs, eq_attr) + && l.has_const_keyword == r.has_const_keyword } fn eq_closure_binder(l: &ClosureBinder, r: &ClosureBinder) -> bool { diff --git a/tests/ui/feature-gates/feature-gate-function-arg-const-generics.rs b/tests/ui/feature-gates/feature-gate-function-arg-const-generics.rs new file mode 100644 index 0000000000000..582af74750d24 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-function-arg-const-generics.rs @@ -0,0 +1,4 @@ +fn foo(const N: usize) {} +//~^ ERROR function arg const generics are experimental + +fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-function-arg-const-generics.stderr b/tests/ui/feature-gates/feature-gate-function-arg-const-generics.stderr new file mode 100644 index 0000000000000..127fdf80e7999 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-function-arg-const-generics.stderr @@ -0,0 +1,13 @@ +error[E0658]: function arg const generics are experimental + --> $DIR/feature-gate-function-arg-const-generics.rs:1:8 + | +LL | fn foo(const N: usize) {} + | ^^^^^ + | + = note: see issue #163261 for more information + = help: add `#![feature(function_arg_const_generics)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. From a6a02a24d1c05440fd175ecb1bdf293629def3e8 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 9 Oct 2026 08:48:10 +0000 Subject: [PATCH 02/19] Resolve arg position const params from function parameters --- compiler/rustc_resolve/src/def_collector.rs | 3 + compiler/rustc_resolve/src/late.rs | 161 ++++++++++++++++---- 2 files changed, 132 insertions(+), 32 deletions(-) diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index f3329c3d0398e..3cf8be2fcbc0d 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -524,6 +524,9 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> { self.visit_macro_invoc(p.id); self.visit_invoc(p.id); } else { + if let Some(ident) = p.const_param_ident() { + self.create_def(p.id, Some(ident.name), DefKind::ConstParam, ident.span); + } self.with_impl_trait(ImplTraitContext::Universal, |this| visit::walk_param(this, p)) } } diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index f146a6ffeaa00..9e7e3edd46e0d 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -1159,10 +1159,26 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc self.visit_fn_header(&sig.header); self.visit_ident(ident); self.visit_generics(generics); + // Resolve arg position const param types using const param type rules. + self.with_lifetime_rib(LifetimeRibKind::AnonymousReportError, |this| { + for param in &sig.decl.inputs { + if !param.is_const_param() { + continue; + } + this.resolve_const_param_type(¶m.ty); + } + }); + + // Skip const params here since they're not locals and shouldn't + // participate in lifetime elision. self.resolve_fn_signature( fn_id, sig.decl.has_self(), - sig.decl.inputs.iter().map(|Param { ty, .. }| (None, &**ty)), + sig.decl + .inputs + .iter() + .filter(|param| !param.is_const_param()) + .map(|Param { ty, .. }| (None, &**ty)), &sig.decl.output, false, ); @@ -1187,6 +1203,14 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc match fn_kind { FnKind::Fn(_, _, Fn { sig, generics, contract, body, .. }) => { this.visit_generics(generics); + this.with_lifetime_rib(LifetimeRibKind::AnonymousReportError, |this| { + for param in &sig.decl.inputs { + if !param.is_const_param() { + continue; + } + this.resolve_const_param_type(¶m.ty); + } + }); let declaration = &sig.decl; this.resolve_fn_signature( @@ -1195,6 +1219,7 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc declaration .inputs .iter() + .filter(|param| !param.is_const_param()) .map(|Param { pat, ty, .. }| (Some(&**pat), &**ty)), &declaration.output, sig.header.coroutine_marker.is_some(), @@ -1691,6 +1716,18 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { ret } + fn resolve_const_param_type(&mut self, ty: &'ast Ty) { + if self.r.features.generic_const_parameter_types() { + self.visit_ty(ty) + } else { + self.ribs[TypeNS].push(Rib::new(RibKind::ConstParamTy)); + self.ribs[ValueNS].push(Rib::new(RibKind::ConstParamTy)); + self.with_lifetime_rib(LifetimeRibKind::ConstParamTy, |this| this.visit_ty(ty)); + self.ribs[TypeNS].pop().unwrap(); + self.ribs[ValueNS].pop().unwrap(); + } + } + fn visit_generic_params(&mut self, params: &'ast [GenericParam], add_self_upper: bool) { // For type parameter defaults, we have to ban access // to following type parameters, as the GenericArgs can only @@ -1782,17 +1819,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { this.ribs[TypeNS].push(forward_ty_ban_rib_const_param_ty); this.ribs[ValueNS].push(forward_const_ban_rib_const_param_ty); - if this.r.features.generic_const_parameter_types() { - this.visit_ty(ty) - } else { - this.ribs[TypeNS].push(Rib::new(RibKind::ConstParamTy)); - this.ribs[ValueNS].push(Rib::new(RibKind::ConstParamTy)); - this.with_lifetime_rib(LifetimeRibKind::ConstParamTy, |this| { - this.visit_ty(ty) - }); - this.ribs[TypeNS].pop().unwrap(); - this.ribs[ValueNS].pop().unwrap(); - } + this.resolve_const_param_type(ty); forward_const_ban_rib_const_param_ty = this.ribs[ValueNS].pop().unwrap(); forward_ty_ban_rib_const_param_ty = this.ribs[TypeNS].pop().unwrap(); @@ -2924,9 +2951,10 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { ); } - ItemKind::Fn(Fn { generics, define_opaque, .. }) => { - self.with_generic_param_rib( + ItemKind::Fn(Fn { generics, define_opaque, sig, .. }) => { + self.with_const_and_generic_param_rib( &generics.params, + &sig.decl.inputs, RibKind::Item(HasGenericParams::Yes(generics.span), def_kind), item.id, LifetimeBinderKind::Function, @@ -3157,9 +3185,12 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } } - fn with_generic_param_rib( + /// Same as `with_generic_param_rib`, but also registers arg position const + /// params from `fn_inputs` in the same rib. + fn with_const_and_generic_param_rib( &mut self, params: &[GenericParam], + fn_inputs: &[Param], kind: RibKind<'ra>, binder: NodeId, generics_kind: LifetimeBinderKind, @@ -3176,8 +3207,11 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { let mut function_value_rib = Rib::new(kind); let mut function_lifetime_rib = LifetimeRib::new(lifetime_kind); + let const_params = + fn_inputs.iter().filter(|param| param.is_const_param()).collect::>(); + // Only check for shadowed bindings if we're declaring new params. - if !params.is_empty() { + if !params.is_empty() || !const_params.is_empty() { let mut seen_bindings = FxHashMap::default(); // Store all seen lifetimes names from outer scopes. let mut seen_lifetimes = FxHashSet::default(); @@ -3317,6 +3351,36 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { self.r.record_partial_res(param.id, PartialRes::new(res)); rib.bindings.insert(ident, res); } + + for param in const_params { + let const_ident = param.const_param_ident().unwrap(); + let ident = const_ident.normalize_to_macros_2_0(); + debug!("with_generic_param_rib: {}", param.id); + + match seen_bindings.entry(ident) { + Entry::Occupied(entry) => { + let span = *entry.get(); + let err = ResolutionError::NameAlreadyUsedInParameterList(ident, span); + self.r.report_error(const_ident.span, err); + // Taint the resolution in case of errors to prevent follow up errors in typeck + self.r.record_partial_res(param.id, PartialRes::new(Res::Err)); + function_value_rib.bindings.insert(ident, Res::Err); + continue; + } + Entry::Vacant(entry) => { + entry.insert(const_ident.span); + } + } + let def_id = self.r.local_def_id(param.id); + let res = match kind { + RibKind::Item(..) | RibKind::AssocItem => { + Res::Def(DefKind::ConstParam, def_id.to_def_id()) + } + _ => span_bug!(const_ident.span, "Unexpected rib kind {:?}", kind), + }; + self.r.record_partial_res(param.id, PartialRes::new(res)); + function_value_rib.bindings.insert(ident, res); + } } self.lifetime_ribs.push(function_lifetime_rib); @@ -3345,6 +3409,28 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } } + fn with_generic_param_rib( + &mut self, + params: &[GenericParam], + kind: RibKind<'ra>, + binder: NodeId, + generics_kind: LifetimeBinderKind, + generics_span: Span, + f: F, + ) where + F: FnOnce(&mut Self), + { + self.with_const_and_generic_param_rib( + params, + &[], + kind, + binder, + generics_kind, + generics_span, + f, + ); + } + fn with_label_rib(&mut self, kind: RibKind<'ra>, f: impl FnOnce(&mut Self)) { self.label_ribs.push(Rib::new(kind)); f(self); @@ -3435,17 +3521,21 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } fn resolve_trait_item(&mut self, item: &'ast Item) { - let walk_assoc_item = - |this: &mut Self, generics: &Generics, kind, item: &'ast AssocItem| { - this.with_generic_param_rib( - &generics.params, - RibKind::AssocItem, - item.id, - kind, - generics.span, - |this| visit::walk_assoc_item(this, item, AssocCtxt::Trait), - ); - }; + let walk_assoc_item = |this: &mut Self, + generics: &Generics, + kind, + item: &'ast AssocItem, + fn_inputs: &[Param]| { + this.with_const_and_generic_param_rib( + &generics.params, + fn_inputs, + RibKind::AssocItem, + item.id, + kind, + generics.span, + |this| visit::walk_assoc_item(this, item, AssocCtxt::Trait), + ); + }; self.resolve_doc_links(&item.attrs, MaybeExported::Ok(item.id)); match &item.kind { @@ -3481,8 +3571,14 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { self.resolve_define_opaques(define_opaque); } - AssocItemKind::Fn(Fn { generics, define_opaque, .. }) => { - walk_assoc_item(self, generics, LifetimeBinderKind::Function, item); + AssocItemKind::Fn(Fn { generics, define_opaque, sig, .. }) => { + walk_assoc_item( + self, + generics, + LifetimeBinderKind::Function, + item, + &sig.decl.inputs, + ); self.resolve_define_opaques(define_opaque); } @@ -3498,7 +3594,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } AssocItemKind::Type(TyAlias { generics, .. }) => self .with_lifetime_rib(LifetimeRibKind::AnonymousReportError, |this| { - walk_assoc_item(this, generics, LifetimeBinderKind::Item, item) + walk_assoc_item(this, generics, LifetimeBinderKind::Item, item, &[]) }), AssocItemKind::MacCall(_) | AssocItemKind::DelegationMac(..) => { panic!("unexpanded macro in resolve!") @@ -3724,11 +3820,12 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { ); self.resolve_define_opaques(define_opaque); } - AssocItemKind::Fn(fn_kind @ Fn { ident, generics, define_opaque, .. }) => { + AssocItemKind::Fn(fn_kind @ Fn { ident, generics, define_opaque, sig, .. }) => { debug!("resolve_implementation AssocItemKind::Fn"); // We also need a new scope for the impl item type parameters. - self.with_generic_param_rib( + self.with_const_and_generic_param_rib( &generics.params, + &sig.decl.inputs, RibKind::AssocItem, item.id, LifetimeBinderKind::Function, From 76bf900459f5d52cbc736ce67d766c296848c853 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 9 Oct 2026 08:49:17 +0000 Subject: [PATCH 03/19] Lower arg position const params to const generic params --- .../src/delegation/generics.rs | 1 + compiler/rustc_ast_lowering/src/item.rs | 16 +++- compiler/rustc_ast_lowering/src/lib.rs | 76 ++++++++++++++----- compiler/rustc_hir/src/hir.rs | 3 + compiler/rustc_hir/src/intravisit.rs | 2 +- .../src/collect/generics_of.rs | 6 +- compiler/rustc_hir_pretty/src/lib.rs | 2 +- compiler/rustc_middle/src/ty/generics.rs | 26 ++++++- .../src/unstable/convert/stable/ty.rs | 2 +- compiler/rustc_resolve/src/lib.rs | 2 +- src/librustdoc/clean/mod.rs | 4 +- .../clippy/clippy_utils/src/hir_utils.rs | 2 + .../doc_examples/rustc_dump_generics.stderr | 1 + tests/ui/attributes/dump_generics.stderr | 9 +++ 14 files changed, 121 insertions(+), 31 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/delegation/generics.rs b/compiler/rustc_ast_lowering/src/delegation/generics.rs index cdec2f5312ae2..3bc98b26ab745 100644 --- a/compiler/rustc_ast_lowering/src/delegation/generics.rs +++ b/compiler/rustc_ast_lowering/src/delegation/generics.rs @@ -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, } } }; diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 9cea3519cf94e..c3809c17671ed 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -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 { @@ -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; @@ -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 @@ -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()); diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index a99db03007b51..137e4563a3902 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -182,6 +182,7 @@ struct PerOwnerLoweringState<'a, 'hir> { // -- Transient -- impl_trait_defs: Vec>, impl_trait_bounds: Vec>, + const_defs: Vec>, } impl<'a, 'hir> PerOwnerLoweringState<'a, 'hir> { @@ -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(), } } @@ -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; @@ -1900,18 +1903,20 @@ impl<'hir> LoweringContext<'_, 'hir> { } fn lower_fn_params_to_idents(&mut self, decl: &FnDecl) -> &'hir [Option] { - 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. @@ -1945,13 +1950,50 @@ 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[..]; + let mut inputs = decl.inputs.iter().enumerate().collect::>(); if decl.c_variadic() { // Splat + variadic errors in AST validation, so just ignore one of them here. splatted = None; - inputs = &inputs[..inputs.len() - 1]; + inputs.pop(); } - let inputs = self.arena.alloc_from_iter(inputs.iter().map(|param| { + // 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.iter().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( + ¶m.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 u32), + }, + colon_span: None, + source: hir::GenericParamSource::Generics, + }; + self.lower_attrs(hir_id, ¶m.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 @@ -1972,7 +2014,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ImplTraitContext::Disallowed(ImplTraitPosition::PointerParam) } }; - self.lower_ty(¶m.ty, itctx) + Some(self.lower_ty(¶m.ty, itctx)) })); let output = match coro { @@ -2368,7 +2410,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 }, ) } } diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index ceb7f38c2a416..a51ac29ba940d 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -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, }, } diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index b404d1708b85b..75d9f9f737677 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -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)); diff --git a/compiler/rustc_hir_analysis/src/collect/generics_of.rs b/compiler/rustc_hir_analysis/src/collect/generics_of.rs index 4d43a9ca5700e..1f3f8032a1301 100644 --- a/compiler/rustc_hir_analysis/src/collect/generics_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/generics_of.rs @@ -310,7 +310,7 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { ty::GenericParamDefKind::Type { has_default: default.is_some(), synthetic } } - GenericParamKind::Const { ty: _, default } => { + GenericParamKind::Const { ty: _, default, arg_pos } => { if default.is_some() { match param_default_policy.expect("no policy for generic param default") { ParamDefaultPolicy::Allowed => {} @@ -321,7 +321,7 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { } } - ty::GenericParamDefKind::Const { has_default: default.is_some() } + ty::GenericParamDefKind::Const { has_default: default.is_some(), arg_pos } } }; Some(ty::GenericParamDef { @@ -524,7 +524,7 @@ impl<'v> Visitor<'v> for AnonConstInParamTyDetector { type Result = ControlFlow<()>; fn visit_generic_param(&mut self, p: &'v hir::GenericParam<'v>) -> Self::Result { - if let GenericParamKind::Const { ty, default: _ } = p.kind { + if let GenericParamKind::Const { ty, default: _, arg_pos: _ } = p.kind { let prev = self.in_param_ty; self.in_param_ty = true; let res = self.visit_ty_unambig(ty); diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index d8c57bfbdb56b..c89cffda68d06 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -2490,7 +2490,7 @@ impl<'a> State<'a> { self.print_type(default); } } - GenericParamKind::Const { ty, ref default } => { + GenericParamKind::Const { ty, ref default, arg_pos: _ } => { self.word_space(":"); self.print_type(ty); if let Some(default) = default { diff --git a/compiler/rustc_middle/src/ty/generics.rs b/compiler/rustc_middle/src/ty/generics.rs index 353ac74bd576f..8d64ef22bcf2b 100644 --- a/compiler/rustc_middle/src/ty/generics.rs +++ b/compiler/rustc_middle/src/ty/generics.rs @@ -14,8 +14,18 @@ use crate::ty::{self, ClauseKind, EarlyBinder, GenericArgsRef, Region, RegionKin #[derive(Clone, Debug, TyEncodable, TyDecodable, StableHash)] pub enum GenericParamDefKind { Lifetime, - Type { has_default: bool, synthetic: bool }, - Const { has_default: bool }, + Type { + has_default: bool, + synthetic: bool, + }, + Const { + has_default: bool, + /// Used by `function_arg_const_generics` to track the position of + /// a const parameter in the function's argument list (including `self`). + /// `Some(i)` means the argument at index `i` supplies this const parameter, + /// while `None` indicates a regular const generic parameter. + arg_pos: Option, + }, } impl GenericParamDefKind { @@ -48,6 +58,14 @@ impl GenericParamDefKind { _ => false, } } + + /// Returns the position of a function argument const generic. + pub fn arg_pos(&self) -> Option { + match self { + GenericParamDefKind::Const { arg_pos, .. } => *arg_pos, + _ => None, + } + } } #[derive(Clone, Debug, TyEncodable, TyDecodable, StableHash)] @@ -300,6 +318,10 @@ impl<'tcx> Generics { self.own_params.iter().filter(|p| p.kind.is_synthetic()).count() } + pub fn own_arg_pos_consts(&'tcx self) -> impl Iterator { + self.own_params.iter().filter_map(|param| Some((param, param.kind.arg_pos()?))) + } + /// Returns the args corresponding to the generic parameters /// of this item, excluding `Self`. /// diff --git a/compiler/rustc_public/src/unstable/convert/stable/ty.rs b/compiler/rustc_public/src/unstable/convert/stable/ty.rs index c3eebccb6b762..51dd03a252693 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/ty.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/ty.rs @@ -705,7 +705,7 @@ impl<'tcx> Stable<'tcx> for rustc_middle::ty::GenericParamDefKind { ty::GenericParamDefKind::Type { has_default, synthetic } => { GenericParamDefKind::Type { has_default, synthetic } } - ty::GenericParamDefKind::Const { has_default } => { + ty::GenericParamDefKind::Const { has_default, arg_pos: _ } => { GenericParamDefKind::Const { has_default } } } diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 196a499d76baf..c460ecaa7a4aa 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1760,7 +1760,7 @@ impl<'tcx> Resolver<'_, 'tcx> { .filter_map(|param| match param.kind { ty::GenericParamDefKind::Lifetime => Some("'_"), ty::GenericParamDefKind::Type { has_default, .. } - | ty::GenericParamDefKind::Const { has_default } => { + | ty::GenericParamDefKind::Const { has_default, arg_pos: _ } => { if has_default { None } else { diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 1e03a0b13f79f..1b4fb883cbde8 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -638,7 +638,7 @@ fn clean_generic_param_def( }, ) } - ty::GenericParamDefKind::Const { has_default } => ( + ty::GenericParamDefKind::Const { has_default, arg_pos: _ } => ( def.name, GenericParamDefKind::Const { ty: Box::new(clean_middle_ty( @@ -717,7 +717,7 @@ fn clean_generic_param<'tcx>( }, ) } - hir::GenericParamKind::Const { ty, default } => ( + hir::GenericParamKind::Const { ty, default, arg_pos: _ } => ( param.name.ident().name, GenericParamDefKind::Const { ty: Box::new(clean_ty(ty, cx)), diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs index 4ef0a795b4766..06564d8f7ab7c 100644 --- a/src/tools/clippy/clippy_utils/src/hir_utils.rs +++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs @@ -378,10 +378,12 @@ impl HirEqInterExpr<'_, '_, '_> { GenericParamKind::Const { ty: l_ty, default: l_default, + arg_pos: _ }, GenericParamKind::Const { ty: r_ty, default: r_default, + arg_pos: _ }, ) => self.eq_ty(l_ty, r_ty) && both(*l_default, *r_default, |l, r| self.eq_const_arg(l, r)), _ => false, diff --git a/tests/ui/attributes/doc_examples/rustc_dump_generics.stderr b/tests/ui/attributes/doc_examples/rustc_dump_generics.stderr index b6b2eacb2c137..d40115642ce25 100644 --- a/tests/ui/attributes/doc_examples/rustc_dump_generics.stderr +++ b/tests/ui/attributes/doc_examples/rustc_dump_generics.stderr @@ -22,6 +22,7 @@ note: Generics { pure_wrt_drop: false, kind: Const { has_default: false, + arg_pos: None, }, }, GenericParamDef { diff --git a/tests/ui/attributes/dump_generics.stderr b/tests/ui/attributes/dump_generics.stderr index 5a212e01e8cbc..f966957055f81 100644 --- a/tests/ui/attributes/dump_generics.stderr +++ b/tests/ui/attributes/dump_generics.stderr @@ -39,6 +39,7 @@ note: Generics { pure_wrt_drop: false, kind: Const { has_default: false, + arg_pos: None, }, }, GenericParamDef { @@ -137,6 +138,7 @@ note: Generics { pure_wrt_drop: false, kind: Const { has_default: false, + arg_pos: None, }, }, GenericParamDef { @@ -225,6 +227,7 @@ note: Generics { pure_wrt_drop: false, kind: Const { has_default: false, + arg_pos: None, }, }, GenericParamDef { @@ -309,6 +312,7 @@ note: Generics { pure_wrt_drop: false, kind: Const { has_default: false, + arg_pos: None, }, }, GenericParamDef { @@ -393,6 +397,7 @@ note: Generics { pure_wrt_drop: false, kind: Const { has_default: false, + arg_pos: None, }, }, GenericParamDef { @@ -470,6 +475,7 @@ note: Generics { pure_wrt_drop: false, kind: Const { has_default: false, + arg_pos: None, }, }, GenericParamDef { @@ -562,6 +568,7 @@ note: Generics { pure_wrt_drop: false, kind: Const { has_default: false, + arg_pos: None, }, }, GenericParamDef { @@ -650,6 +657,7 @@ note: Generics { pure_wrt_drop: false, kind: Const { has_default: false, + arg_pos: None, }, }, GenericParamDef { @@ -803,6 +811,7 @@ note: Generics { pure_wrt_drop: false, kind: Const { has_default: false, + arg_pos: None, }, }, ], From ad4db6c220a8e76354db2ba2b117fc662d3f581b Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 9 Oct 2026 08:49:33 +0000 Subject: [PATCH 04/19] Lower call arguments for arg position const params on path calls --- .../src/hir_ty_lowering/generics.rs | 7 +++- .../src/hir_ty_lowering/mod.rs | 37 +++++++++++++++++++ compiler/rustc_hir_typeck/src/expr.rs | 2 +- .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 35 +++++++++++++++++- compiler/rustc_hir_typeck/src/pat.rs | 6 +-- 5 files changed, 80 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs index 08ddab617c385..e7bb779b233de 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/generics.rs @@ -451,8 +451,11 @@ pub(crate) fn check_generic_arg_count( .iter() .filter(|param| matches!(param.kind, ty::GenericParamDefKind::Type { synthetic: true, .. })) .count(); + // arg position const params are supplied through call arguments, so they + // cannot be explicitly specified via turbofish. + let arg_const_param_count = gen_params.own_arg_pos_consts().count(); let named_type_param_count = param_counts.types - has_self as usize - synth_type_param_count; - let named_const_param_count = param_counts.consts; + let named_const_param_count = param_counts.consts - arg_const_param_count; let infer_lifetimes = (gen_pos != GenericArgPosition::Type || seg.infer_args) && !gen_args.has_lifetime_args(); @@ -630,7 +633,7 @@ pub(crate) fn check_generic_arg_count( let expected_min = if seg.infer_args { 0 } else { - param_counts.consts + named_type_param_count + named_const_param_count + named_type_param_count - default_counts.types - default_counts.consts }; 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 4fa02926f15d8..010d2a2a565ba 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -2385,6 +2385,43 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { self.check_param_uses_if_mcg(ct, tcx.hir_span(path_hir_id), false) } + /// Lowers a call-argument expression into a ty::Const for an arg position + /// const param. + pub fn lower_const_arg_expr(&self, expr: &hir::Expr<'_>, ty: Ty<'tcx>) -> Const<'tcx> { + let tcx = self.tcx(); + match expr.kind { + hir::ExprKind::Lit(lit) => { + self.lower_const_arg_literal(&lit.node, false, ty, expr.span) + } + hir::ExprKind::Unary( + hir::UnOp::Neg, + hir::Expr { kind: hir::ExprKind::Lit(lit), .. }, + ) => self.lower_const_arg_literal(&lit.node, true, ty, expr.span), + hir::ExprKind::Path(hir::QPath::Resolved(_, hir::Path { res: Res::Local(_), .. })) => { + Const::new_error(tcx, self.dcx().span_err(expr.span, "Path error")) + } + hir::ExprKind::Path(hir::QPath::Resolved(maybe_qself, path)) => { + let opt_self_ty = maybe_qself.as_ref().map(|qself| self.lower_ty(qself)); + self.lower_resolved_const_path(opt_self_ty, path, expr.hir_id) + } + hir::ExprKind::Path(hir::QPath::TypeRelative(hir_self_ty, segment)) => { + let self_ty = self.lower_ty(hir_self_ty); + self.lower_type_relative_const_path( + self_ty, + hir_self_ty, + segment, + expr.hir_id, + expr.span, + ) + .unwrap_or_else(|guard| Const::new_error(tcx, guard)) + } + _ => Const::new_error( + tcx, + self.dcx().span_err(expr.span, "Some other variant we dont support"), + ), + } + } + /// Lower a [`hir::ConstArg`] to a (type-level) [`ty::Const`]. #[instrument(skip(self), level = "debug")] pub fn lower_const_arg(&self, const_arg: &hir::ConstArg<'_>, ty: Ty<'tcx>) -> Const<'tcx> { diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index a301cb6a397d9..64270c6b27222 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -625,7 +625,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { call_expr_and_args.map_or(expr.span, |(e, _)| e.span), expr.span, expr.hir_id, - call_expr_and_args.is_some(), + call_expr_and_args.map(|(_, args)| args), ) .0 } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index b0f940dd7a21a..f694467c3ec93 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -617,6 +617,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ct } + pub(crate) fn lower_const_arg_expr( + &self, + expr: &hir::Expr<'_>, + ty: Ty<'tcx>, + ) -> ty::Const<'tcx> { + let ct = self.lowerer().lower_const_arg_expr(expr, ty); + self.register_wf_obligation(ct.into(), expr.span, ObligationCauseCode::WellFormed(None)); + ct + } + // If the type given by the user has free regions, save it for later, since // NLL would like to enforce those. Also pass in types that involve // projections, since those can resolve to `'static` bounds (modulo #54940, @@ -1014,8 +1024,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { span: Span, path_span: Span, hir_id: HirId, - has_args: bool, + call_args: Option<&'tcx [hir::Expr<'tcx>]>, ) -> (Ty<'tcx>, Res) { + let has_args = call_args.is_some(); let tcx = self.tcx; let generic_segments = match res { @@ -1333,6 +1344,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { generic_segments: &'a [GenericPathSegment], infer_args_for_err: &'a FxHashSet, segments: &'tcx [hir::PathSegment<'tcx>], + call_args: Option<&'tcx [hir::Expr<'tcx>]>, } impl<'a, 'tcx> GenericArgsLowerer<'a, 'tcx> for CtorGenericArgsCtxt<'a, 'tcx> { fn args_for_def_id( @@ -1401,6 +1413,26 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { infer_args: bool, ) -> ty::GenericArg<'tcx> { let tcx = self.fcx.tcx(); + // For arg position params, the generic arg comes from the call argument + // rather than an inference variable. + if let Some(pos) = param.kind.arg_pos() { + let Some(args) = self.call_args else { + let guard = + self.fcx.dcx().span_err(self.span, "argument should be provided"); + return ty::Const::new_error(tcx, guard).into(); + }; + + let Some(arg) = args.get(pos as usize) else { + let guard = self + .fcx + .dcx() + .span_delayed_bug(self.span, "missing argument for const param"); + return ty::Const::new_error(tcx, guard).into(); + }; + let ty = + tcx.type_of(param.def_id).instantiate(tcx, preceding_args).skip_norm_wip(); + return self.fcx.lower_const_arg_expr(arg, ty).into(); + } if !infer_args && let Some(default) = param.default_value(tcx) { // If we have a default, then it doesn't matter that we're not inferring // the type/const arguments: We provide the default where any is missing. @@ -1428,6 +1460,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { generic_segments: &generic_segments, infer_args_for_err: &infer_args_for_err, segments, + call_args, }, ) }); diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index 16c51ca625dcd..65e03d9e7566f 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -913,7 +913,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let (res, opt_ty, segments) = self.resolve_ty_and_res_fully_qualified_call(qpath, lt.hir_id, lt.span); self.instantiate_value_path( - segments, opt_ty, res, lt.span, lt.span, lt.hir_id, false, + segments, opt_ty, res, lt.span, lt.span, lt.hir_id, None, ) .0 } @@ -1646,7 +1646,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Find the type of the path pattern, for later checking. let (pat_ty, pat_res) = - self.instantiate_value_path(segments, opt_ty, res, span, span, path_id, false); + self.instantiate_value_path(segments, opt_ty, res, span, span, path_id, None); Ok(ResolvedPat { ty: pat_ty, kind: ResolvedPatKind::Path { res, pat_res, segments } }) } @@ -1807,7 +1807,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Type-check the path. let (pat_ty, res) = self - .instantiate_value_path(segments, opt_ty, res, pat.span, pat.span, pat.hir_id, false); + .instantiate_value_path(segments, opt_ty, res, pat.span, pat.span, pat.hir_id, None); if !pat_ty.is_fn() { return report_unexpected_res(res); } From 7e570beffb6a5e186fb5eb87a75f4cf6f0181334 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 9 Oct 2026 08:49:37 +0000 Subject: [PATCH 05/19] Lower call arguments for arg position const params on method calls --- .../rustc_hir_typeck/src/method/confirm.rs | 23 +++++++++++++++++-- compiler/rustc_hir_typeck/src/method/mod.rs | 3 ++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/confirm.rs b/compiler/rustc_hir_typeck/src/method/confirm.rs index 721e9a38fc50c..aaa86a18a834f 100644 --- a/compiler/rustc_hir_typeck/src/method/confirm.rs +++ b/compiler/rustc_hir_typeck/src/method/confirm.rs @@ -40,6 +40,7 @@ pub(crate) struct ConfirmContext<'a, 'tcx> { self_expr: &'tcx hir::Expr<'tcx>, call_expr: &'tcx hir::Expr<'tcx>, skip_record_for_diagnostics: bool, + call_args: Option<&'tcx [hir::Expr<'tcx>]>, } impl<'a, 'tcx> Deref for ConfirmContext<'a, 'tcx> { @@ -64,6 +65,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { unadjusted_self_ty: Ty<'tcx>, pick: &probe::Pick<'tcx>, segment: &'tcx hir::PathSegment<'tcx>, + args: Option<&'tcx [hir::Expr<'tcx>]>, ) -> ConfirmResult<'tcx> { debug!( "confirm(unadjusted_self_ty={:?}, pick={:?}, generic_args={:?})", @@ -71,6 +73,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); let mut confirm_cx = ConfirmContext::new(self, span, self_expr, call_expr); + confirm_cx.call_args = args; confirm_cx.confirm(unadjusted_self_ty, pick, segment) } @@ -96,7 +99,14 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { self_expr: &'tcx hir::Expr<'tcx>, call_expr: &'tcx hir::Expr<'tcx>, ) -> ConfirmContext<'a, 'tcx> { - ConfirmContext { fcx, span, self_expr, call_expr, skip_record_for_diagnostics: false } + ConfirmContext { + fcx, + span, + self_expr, + call_expr, + skip_record_for_diagnostics: false, + call_args: None, + } } fn confirm( @@ -489,10 +499,19 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { fn inferred_kind( &mut self, - _preceding_args: &[ty::GenericArg<'tcx>], + preceding_args: &[ty::GenericArg<'tcx>], param: &ty::GenericParamDef, _infer_args: bool, ) -> ty::GenericArg<'tcx> { + if let Some(pos) = param.kind.arg_pos() + && let Some(args) = self.cfcx.call_args + && let Some(arg) = pos.checked_sub(1).and_then(|index| args.get(index as usize)) + { + let tcx = self.cfcx.tcx; + let ty = + tcx.type_of(param.def_id).instantiate(tcx, preceding_args).skip_norm_wip(); + return self.cfcx.lower_const_arg_expr(arg, ty).into(); + } self.cfcx.var_for_def(self.cfcx.span, param) } } diff --git a/compiler/rustc_hir_typeck/src/method/mod.rs b/compiler/rustc_hir_typeck/src/method/mod.rs index 16127b7d87f3d..690275faa31b9 100644 --- a/compiler/rustc_hir_typeck/src/method/mod.rs +++ b/compiler/rustc_hir_typeck/src/method/mod.rs @@ -202,7 +202,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.tcx.check_stability(pick.item.def_id, Some(call_expr.hir_id), span, None); - let result = self.confirm_method(span, self_expr, call_expr, self_ty, &pick, segment); + let result = + self.confirm_method(span, self_expr, call_expr, self_ty, &pick, segment, Some(args)); debug!("result = {:?}", result); if let Some(span) = result.illegal_sized_bound { From 3e1d350dfd2e570a4ff8db33c3dbef1072e6a3b5 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 9 Oct 2026 08:49:46 +0000 Subject: [PATCH 06/19] Check arg position const arguments against the param type and report them as arguments --- compiler/rustc_hir_typeck/src/callee.rs | 12 +++++++++++- compiler/rustc_hir_typeck/src/expr.rs | 17 +++++++++++++++-- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 19 +++++++++++++------ 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index b431e9951a87e..c4df239f85a94 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::iter; use rustc_abi::{CanonAbi, ExternAbi}; @@ -666,11 +667,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn_id: SplatLoweringInfo<'tcx>, callee_generic_args: Option>, ) { + let mut formal_inputs = Cow::Borrowed(fn_sig.inputs()); + if let (SplatLoweringInfo::FnDef(def_id), Some(args)) = (fn_id, callee_generic_args) { + for (param, pos) in self.tcx.generics_of(def_id).own_arg_pos_consts() { + let ty = self.tcx.type_of(param.def_id).instantiate(self.tcx, args).skip_norm_wip(); + let ty = self.normalize(call_expr.span, Unnormalized::new_wip(ty)); + formal_inputs.to_mut().insert(pos as usize, ty); + } + } + let do_check = || { self.check_argument_types( call_expr.span, call_expr, - fn_sig.inputs(), + &formal_inputs, fn_sig.output(), expected, arg_exprs, diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 64270c6b27222..7c92eaa14ea91 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -5,6 +5,8 @@ //! //! See [`rustc_hir_analysis::check`] for more context on type checking in general. +use std::borrow::Cow; + use rustc_abi::{FIRST_VARIANT, FieldIdx}; use rustc_ast as ast; use rustc_ast::util::parser::ExprPrecedence; @@ -1496,14 +1498,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Handle splatted method arguments // self is already handled as `rcvr`, so it's never splatted here - let method_inputs = &method.sig.inputs()[1..]; + let mut method_inputs = Cow::Borrowed(&method.sig.inputs()[1..]); + for (param, pos) in self.tcx.generics_of(method.def_id).own_arg_pos_consts() { + let ty = self + .tcx + .type_of(param.def_id) + .instantiate(self.tcx, method.args) + .skip_norm_wip(); + // Insert function argument const param types back into the inputs. + // We use pos - 1 since method_inputs skips self, but arg_pos counts it. + method_inputs.to_mut().insert(pos as usize - 1, ty); + } + let method_tuple_args_flag = TupleArgumentsFlag::with_fn_sig_kind(method.sig.fn_sig_kind, true); self.check_argument_types( segment.ident.span, expr, - method_inputs, + &method_inputs, method.sig.output(), expected, args, diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 8ecd7838a0e69..1f8174c1b1056 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -2108,25 +2108,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { None } }); - match (body_id, params) { + let mut params_with_generics: IndexVec = match (body_id, params) { (Some(_), Some(_)) | (None, None) => unreachable!(), (Some(body), None) => { let params = self.tcx.hir_body(body).params; let params = params .get(is_method as usize..params.len() - sig.decl.c_variadic() as usize)?; debug_assert_eq!(params.len(), fn_inputs.len()); - Some((fn_inputs.zip(params.iter().map(FnParam::Param)).collect(), generics)) + fn_inputs.zip(params.iter().map(FnParam::Param)).collect() } (None, Some(params)) => { let params = params .get(is_method as usize..params.len() - sig.decl.c_variadic() as usize)?; debug_assert_eq!(params.len(), fn_inputs.len()); - Some(( - fn_inputs.zip(params.iter().map(|&ident| FnParam::Ident(ident))).collect(), - generics, - )) + fn_inputs.zip(params.iter().map(|&ident| FnParam::Ident(ident))).collect() + } + }; + for param in generics.params { + if let hir::GenericParamKind::Const { arg_pos: Some(pos), .. } = param.kind + && let Some(idx) = (pos as usize).checked_sub(is_method as usize) + { + params_with_generics + .raw + .insert(idx, (None, FnParam::Ident(Some(param.name.ident())))); } } + Some((params_with_generics, generics)) } } From ae0be406754e4492279179bf9d6e9c8e2b2f6128 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 9 Oct 2026 08:49:50 +0000 Subject: [PATCH 07/19] Drop arg position const arguments when building THIR --- compiler/rustc_mir_build/src/thir/cx/expr.rs | 38 ++++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index e38e9293ba196..d7ceae9505f87 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -412,10 +412,24 @@ impl<'tcx> ThirBuildCx<'tcx> { let expr = self.method_callee(expr, segment.ident.span, None); info!("Using method span: {:?}", expr.span); + // Arg position const args are already part of the callee's generic args, + // so we skip them when building runtime arguments. + let generics = match *expr.ty.kind() { + ty::FnDef(def_id, _) => Some(tcx.generics_of(def_id)), + _ => None, + }; + let is_const_arg = |idx: usize| { + generics.is_some_and(|g| { + g.own_arg_pos_consts().any(|(_, pos)| pos as usize == idx) + }) + }; let args = std::iter::once(receiver) .chain(args.iter()) - .map(|expr| self.mirror_expr(expr)) + .enumerate() + .filter(|&(idx, _)| !is_const_arg(idx)) + .map(|(_, arg)| self.mirror_expr(arg)) .collect(); + ExprKind::Call { ty: expr.ty, fun: self.thir.exprs.push(expr), @@ -517,10 +531,28 @@ impl<'tcx> ThirBuildCx<'tcx> { base: AdtExprBase::None, })) } else { + // Arg position const args are already part of the callee's generic args, + // so we skip them when building runtime arguments. + let fn_ty = self.typeck_results.node_type(fun.hir_id); + let generics = match *fn_ty.kind() { + ty::FnDef(def_id, _) => Some(tcx.generics_of(def_id)), + _ => None, + }; + let is_const_arg = |idx: usize| { + generics.is_some_and(|g| { + g.own_arg_pos_consts().any(|(_, pos)| pos as usize == idx) + }) + }; + let args = args + .iter() + .enumerate() + .filter(|&(idx, _)| !is_const_arg(idx)) + .map(|(_, arg)| self.mirror_expr(arg)) + .collect(); ExprKind::Call { - ty: self.typeck_results.node_type(fun.hir_id), + ty: fn_ty, fun: self.mirror_expr(fun), - args: self.mirror_exprs(args), + args, from_hir_call: true, fn_span: expr.span, } From e1ecbcf810616c7f60eeba9b26c104e6b3604a8b Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 9 Oct 2026 08:49:55 +0000 Subject: [PATCH 08/19] Add tests for arg position const param lowering --- tests/pretty/function-arg-const-generics.rs | 19 ++++++ .../function_arg_const_generics/basic_test.rs | 18 ++++++ .../non-const-argument.rs | 10 +++ .../non-const-argument.stderr | 8 +++ .../parse-errors-types.rs | 7 +++ .../parse-errors-types.stderr | 14 +++++ .../function_arg_const_generics/pass.rs | 61 ++++++++++++++++++ .../typeck-errors.rs | 23 +++++++ .../typeck-errors.stderr | 62 +++++++++++++++++++ 9 files changed, 222 insertions(+) create mode 100644 tests/pretty/function-arg-const-generics.rs create mode 100644 tests/ui/const-generics/function_arg_const_generics/basic_test.rs create mode 100644 tests/ui/const-generics/function_arg_const_generics/non-const-argument.rs create mode 100644 tests/ui/const-generics/function_arg_const_generics/non-const-argument.stderr create mode 100644 tests/ui/const-generics/function_arg_const_generics/parse-errors-types.rs create mode 100644 tests/ui/const-generics/function_arg_const_generics/parse-errors-types.stderr create mode 100644 tests/ui/const-generics/function_arg_const_generics/pass.rs create mode 100644 tests/ui/const-generics/function_arg_const_generics/typeck-errors.rs create mode 100644 tests/ui/const-generics/function_arg_const_generics/typeck-errors.stderr diff --git a/tests/pretty/function-arg-const-generics.rs b/tests/pretty/function-arg-const-generics.rs new file mode 100644 index 0000000000000..af9772990bfe5 --- /dev/null +++ b/tests/pretty/function-arg-const-generics.rs @@ -0,0 +1,19 @@ +//@ pp-exact + +#![feature(function_arg_const_generics, gca_min_const_items)] + +fn first(const N: usize) {} + +fn middle(a: u8, const N: usize, b: u8) {} + +struct S; + +impl S { + fn method(&self, x: u8, const N: usize) {} +} + +trait Tr { + fn assoc(const N: usize); +} + +fn main() {} diff --git a/tests/ui/const-generics/function_arg_const_generics/basic_test.rs b/tests/ui/const-generics/function_arg_const_generics/basic_test.rs new file mode 100644 index 0000000000000..c95a7d84da785 --- /dev/null +++ b/tests/ui/const-generics/function_arg_const_generics/basic_test.rs @@ -0,0 +1,18 @@ +//@ check-pass +#![feature(function_arg_const_generics, gca_min_const_items)] + +fn foo(const N: usize) -> [u8; N] { [0; N] } + +fn forward() -> [u8; K] { foo(K) } + +struct S; + +impl S { + fn m(&self, x: u8, const N: usize) -> [u8; N] { [x; N] } +} + +fn main() { + let _: [u8; 3] = foo(3); + let _: [u8; 5] = forward::<5>(); + let _: [u8; 4] = S::m(&S, 1, 4); +} diff --git a/tests/ui/const-generics/function_arg_const_generics/non-const-argument.rs b/tests/ui/const-generics/function_arg_const_generics/non-const-argument.rs new file mode 100644 index 0000000000000..95c2aabd5f7d0 --- /dev/null +++ b/tests/ui/const-generics/function_arg_const_generics/non-const-argument.rs @@ -0,0 +1,10 @@ +#![feature(function_arg_const_generics, gca_min_const_items)] + +fn foo(const N: usize) -> [u8; N] { + [0; N] +} + +fn main() { + let x = 3; + let _ = foo(x); //~ ERROR +} diff --git a/tests/ui/const-generics/function_arg_const_generics/non-const-argument.stderr b/tests/ui/const-generics/function_arg_const_generics/non-const-argument.stderr new file mode 100644 index 0000000000000..7a59a15404eae --- /dev/null +++ b/tests/ui/const-generics/function_arg_const_generics/non-const-argument.stderr @@ -0,0 +1,8 @@ +error: Path error + --> $DIR/non-const-argument.rs:9:17 + | +LL | let _ = foo(x); + | ^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/const-generics/function_arg_const_generics/parse-errors-types.rs b/tests/ui/const-generics/function_arg_const_generics/parse-errors-types.rs new file mode 100644 index 0000000000000..ade3a5ed72641 --- /dev/null +++ b/tests/ui/const-generics/function_arg_const_generics/parse-errors-types.rs @@ -0,0 +1,7 @@ +#![feature(function_arg_const_generics, gca_min_const_items)] + +type FnPtr = fn(const N: usize); //~ ERROR + +fn fn_sugar() {} //~ ERROR + +fn main() {} diff --git a/tests/ui/const-generics/function_arg_const_generics/parse-errors-types.stderr b/tests/ui/const-generics/function_arg_const_generics/parse-errors-types.stderr new file mode 100644 index 0000000000000..f8ebb09c81470 --- /dev/null +++ b/tests/ui/const-generics/function_arg_const_generics/parse-errors-types.stderr @@ -0,0 +1,14 @@ +error: expected one of `(`, `+`, `::`, or `<`, found `:` + --> $DIR/parse-errors-types.rs:3:24 + | +LL | type FnPtr = fn(const N: usize); + | ^ expected one of `(`, `+`, `::`, or `<` + +error: expected one of `(`, `+`, `::`, or `<`, found `:` + --> $DIR/parse-errors-types.rs:5:26 + | +LL | fn fn_sugar() {} + | ^ expected one of `(`, `+`, `::`, or `<` + +error: aborting due to 2 previous errors + diff --git a/tests/ui/const-generics/function_arg_const_generics/pass.rs b/tests/ui/const-generics/function_arg_const_generics/pass.rs new file mode 100644 index 0000000000000..51424724fca37 --- /dev/null +++ b/tests/ui/const-generics/function_arg_const_generics/pass.rs @@ -0,0 +1,61 @@ +//@ run-pass +//@ edition: 2021 +#![feature(function_arg_const_generics, gca_min_const_items)] + +fn foo(const N: usize) -> [u8; N] { + [0; N] +} + +fn forward() -> [u8; K] { + foo(K) +} + +fn in_signature(const N: usize, x: [u8; N]) -> [u8; N] { + let y: [u8; N] = x; + y +} + +fn apit_before(x: impl Copy, const N: usize) -> [u8; N] { + let _ = x; + [0; N] +} + +fn apit_after(const N: usize, x: impl Copy) -> [u8; N] { + let _ = x; + [0; N] +} + +async fn in_async(const N: usize) -> [u8; N] { + [0; N] +} + +struct S; + +impl S { + fn method(&self, x: u8, const N: usize) -> [u8; N] { + [x; N] + } +} + +trait Tr { + fn assoc(const N: usize) -> [u8; N]; +} + +impl Tr for S { + fn assoc(const N: usize) -> [u8; N] { + [1; N] + } +} + +fn main() { + assert_eq!(foo(3).len(), 3); + assert_eq!(forward::<5>().len(), 5); + assert_eq!(in_signature(2, [4; 2]), [4, 4]); + assert_eq!(apit_before(1u8, 2).len(), 2); + assert_eq!(apit_after(2, 1u8).len(), 2); + let _ = in_async(3); + assert_eq!(S.method(7, 2), [7, 7]); + assert_eq!(S::method(&S, 9, 3), [9, 9, 9]); + assert_eq!(::assoc(2), [1, 1]); + assert_eq!(S::assoc(2), [1, 1]); +} diff --git a/tests/ui/const-generics/function_arg_const_generics/typeck-errors.rs b/tests/ui/const-generics/function_arg_const_generics/typeck-errors.rs new file mode 100644 index 0000000000000..a118644670444 --- /dev/null +++ b/tests/ui/const-generics/function_arg_const_generics/typeck-errors.rs @@ -0,0 +1,23 @@ +#![feature(function_arg_const_generics, gca_min_const_items)] + +fn foo(const N: usize) -> [u8; N] { + [0; N] +} + +trait Tr { + fn m(const N: usize); + fn n(x: usize); +} + +struct S; + +impl Tr for S { + fn m(x: usize) {} //~ ERROR E0049 + fn n(const N: usize) {} //~ ERROR E0049 +} + +fn main() { + let _ = foo::<3>(3); //~ ERROR E0107 + let _ = foo(true); //~ ERROR E0308 + //~^ ERROR +} diff --git a/tests/ui/const-generics/function_arg_const_generics/typeck-errors.stderr b/tests/ui/const-generics/function_arg_const_generics/typeck-errors.stderr new file mode 100644 index 0000000000000..7ee6c024a7b07 --- /dev/null +++ b/tests/ui/const-generics/function_arg_const_generics/typeck-errors.stderr @@ -0,0 +1,62 @@ +error[E0049]: associated function `m` has 0 const parameters but its trait declaration has 1 const parameter + --> $DIR/typeck-errors.rs:15:9 + | +LL | fn m(const N: usize); + | -------------- expected 1 const parameter +... +LL | fn m(x: usize) {} + | ^ found 0 const parameters + +error[E0049]: associated function `n` has 1 const parameter but its trait declaration has 0 const parameters + --> $DIR/typeck-errors.rs:16:10 + | +LL | fn n(x: usize); + | - expected 0 const parameters +... +LL | fn n(const N: usize) {} + | ^^^^^^^^^^^^^^ found 1 const parameter + +error[E0107]: function takes 0 generic arguments but 1 generic argument was supplied + --> $DIR/typeck-errors.rs:20:13 + | +LL | let _ = foo::<3>(3); + | ^^^----- help: remove the unnecessary generics + | | + | expected 0 generic arguments + | +note: function defined here, with 0 generic parameters + --> $DIR/typeck-errors.rs:3:4 + | +LL | fn foo(const N: usize) -> [u8; N] { + | ^^^ + +error: the constant `true` is not of type `usize` + --> $DIR/typeck-errors.rs:21:13 + | +LL | let _ = foo(true); + | ^^^^^^^^^ expected `usize`, found `bool` + | +note: required by a const generic parameter in `foo` + --> $DIR/typeck-errors.rs:3:8 + | +LL | fn foo(const N: usize) -> [u8; N] { + | ^^^^^^^^^^^^^^ required by this const generic parameter in `foo` + +error[E0308]: mismatched types + --> $DIR/typeck-errors.rs:21:17 + | +LL | let _ = foo(true); + | --- ^^^^ expected `usize`, found `bool` + | | + | arguments to this function are incorrect + | +note: function defined here + --> $DIR/typeck-errors.rs:3:4 + | +LL | fn foo(const N: usize) -> [u8; N] { + | ^^^ - + +error: aborting due to 5 previous errors + +Some errors have detailed explanations: E0049, E0107, E0308. +For more information about an error, try `rustc --explain E0049`. From 77505625c1ab9fd7495ccf0baf2cd7469ae39a51 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 9 Oct 2026 09:44:36 +0000 Subject: [PATCH 09/19] carry more info with wf obligation --- compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index f694467c3ec93..9e3e3d810345e 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -623,7 +623,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty: Ty<'tcx>, ) -> ty::Const<'tcx> { let ct = self.lowerer().lower_const_arg_expr(expr, ty); - self.register_wf_obligation(ct.into(), expr.span, ObligationCauseCode::WellFormed(None)); + self.register_wf_obligation( + ct.into(), + expr.span, + ObligationCauseCode::WellFormed(WellFormedLoc::None), + ); ct } From 624858fb06b755dd85e76cbc16892dbcaaf60070 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 10 Oct 2026 13:38:32 +0000 Subject: [PATCH 10/19] Tighten const argument lookahead to preserve Rust 2015 syntax --- compiler/rustc_parse/src/parser/function.rs | 3 +- .../const-keyword-in-anon-param-2015.rs | 27 ++++++++ .../const-keyword-in-anon-param-2015.stderr | 63 +++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 tests/ui/const-generics/function_arg_const_generics/const-keyword-in-anon-param-2015.rs create mode 100644 tests/ui/const-generics/function_arg_const_generics/const-keyword-in-anon-param-2015.stderr diff --git a/compiler/rustc_parse/src/parser/function.rs b/compiler/rustc_parse/src/parser/function.rs index 4c3b06f58f307..190047914b90d 100644 --- a/compiler/rustc_parse/src/parser/function.rs +++ b/compiler/rustc_parse/src/parser/function.rs @@ -773,7 +773,8 @@ impl<'a> Parser<'a> { let mut is_const = false; if fn_parse_mode.allow_const && this.check_keyword(exp!(Const)) - && this.look_ahead(1, |token| token.is_ident()) + && this.look_ahead(1, |token| token.is_non_reserved_ident()) + && this.look_ahead(2, |token| *token == token::Colon) { this.bump(); let span = this.prev_token.span; diff --git a/tests/ui/const-generics/function_arg_const_generics/const-keyword-in-anon-param-2015.rs b/tests/ui/const-generics/function_arg_const_generics/const-keyword-in-anon-param-2015.rs new file mode 100644 index 0000000000000..4ef7263af034b --- /dev/null +++ b/tests/ui/const-generics/function_arg_const_generics/const-keyword-in-anon-param-2015.rs @@ -0,0 +1,27 @@ +//@ edition: 2015 +#![feature(function_arg_const_generics, gca_min_const_items, const_trait_impl)] + +#[cfg(false)] +trait DynType { + fn f(const dyn Trait); //~ ERROR +} + +#[cfg(false)] +trait ImplType { + fn f(const impl Trait); //~ ERROR +} + +#[cfg(false)] +trait FnPtrType { + fn f(const fn()); //~ ERROR +} + +trait ConstTraitBound { + fn f(const Fn()); //~ ERROR const trait bounds are not allowed in trait object types + //~| WARN anonymous parameters are deprecated + //~| WARN this is accepted in the current edition + //~| WARN trait objects without an explicit `dyn` are deprecated + //~| WARN this is accepted in the current edition +} + +fn main() {} diff --git a/tests/ui/const-generics/function_arg_const_generics/const-keyword-in-anon-param-2015.stderr b/tests/ui/const-generics/function_arg_const_generics/const-keyword-in-anon-param-2015.stderr new file mode 100644 index 0000000000000..71367460ea8a9 --- /dev/null +++ b/tests/ui/const-generics/function_arg_const_generics/const-keyword-in-anon-param-2015.stderr @@ -0,0 +1,63 @@ +error: expected one of `(`, `+`, `::`, or `<`, found `Trait` + --> $DIR/const-keyword-in-anon-param-2015.rs:6:20 + | +LL | fn f(const dyn Trait); + | ^^^^^ expected one of `(`, `+`, `::`, or `<` + +error: expected a trait, found type + --> $DIR/const-keyword-in-anon-param-2015.rs:11:16 + | +LL | fn f(const impl Trait); + | ^^^^^^^^^^ + | +help: use the trait bounds directly + | +LL - fn f(const impl Trait); +LL + fn f(const Trait); + | + +error: an `fn` pointer type cannot be `const` + --> $DIR/const-keyword-in-anon-param-2015.rs:16:10 + | +LL | fn f(const fn()); + | ^^^^^ `const` because of this + | + = note: allowed qualifiers are: `unsafe` and `extern` +help: remove the `const` qualifier + | +LL - fn f(const fn()); +LL + fn f(fn()); + | + +error: const trait bounds are not allowed in trait object types + --> $DIR/const-keyword-in-anon-param-2015.rs:20:10 + | +LL | fn f(const Fn()); + | ^^^^^^^^^^ + +warning: anonymous parameters are deprecated and will be removed in the next edition + --> $DIR/const-keyword-in-anon-param-2015.rs:20:10 + | +LL | fn f(const Fn()); + | ^^^^^^^^^^ help: try naming the parameter or explicitly ignoring it: `_: const Fn()` + | + = warning: this is accepted in the current edition (Rust 2015) but is a hard error in Rust 2018! + = note: for more information, see + = note: `#[warn(anonymous_parameters)]` (part of `#[warn(rust_2018_compatibility)]`) on by default + +warning: trait objects without an explicit `dyn` are deprecated + --> $DIR/const-keyword-in-anon-param-2015.rs:20:10 + | +LL | fn f(const Fn()); + | ^^^^^^^^^^ + | + = warning: this is accepted in the current edition (Rust 2015) but is a hard error in Rust 2021! + = note: for more information, see + = note: `#[warn(bare_trait_objects)]` (part of `#[warn(rust_2021_compatibility)]`) on by default +help: if this is a dyn-compatible trait, use `dyn` + | +LL | fn f(dyn const Fn()); + | +++ + +error: aborting due to 4 previous errors; 2 warnings emitted + From 153aa1a61c2e76b6c51d1f0a351910ae43168205 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 10 Oct 2026 15:09:12 +0000 Subject: [PATCH 11/19] Lets not allocate in lower_fn_decl, and have iterator from inputs Make arg_pos to Option instead of Option And add a has_arg_pos_const in generics, so we can have shortcircuit --- compiler/rustc_ast_lowering/src/lib.rs | 15 +++--- compiler/rustc_hir/src/hir.rs | 2 +- .../src/collect/generics_of.rs | 11 +++- compiler/rustc_middle/src/ty/generics.rs | 11 ++-- compiler/rustc_mir_build/src/thir/cx/expr.rs | 52 ++++++++++++------- compiler/rustc_ty_utils/src/assoc.rs | 1 + 6 files changed, 60 insertions(+), 32 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 137e4563a3902..f8c33c36061d2 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -1950,16 +1950,19 @@ 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.iter().enumerate().collect::>(); - 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.pop(); - } + 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.iter().filter_map(|(param_idx, param)| { + let inputs = self.arena.alloc_from_iter(inputs.filter_map(|(param_idx, param)| { if let Some(ident) = param.const_param_ident() { if !matches!( kind, @@ -1985,7 +1988,7 @@ impl<'hir> LoweringContext<'_, 'hir> { kind: hir::GenericParamKind::Const { ty, default: None, - arg_pos: Some(*param_idx as u32), + arg_pos: Some(param_idx as u16), }, colon_span: None, source: hir::GenericParamSource::Generics, diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index a51ac29ba940d..1055d8da88800 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -850,7 +850,7 @@ pub enum GenericParamKind<'hir> { 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, + arg_pos: Option, }, } diff --git a/compiler/rustc_hir_analysis/src/collect/generics_of.rs b/compiler/rustc_hir_analysis/src/collect/generics_of.rs index 1f3f8032a1301..9732412418b72 100644 --- a/compiler/rustc_hir_analysis/src/collect/generics_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/generics_of.rs @@ -42,7 +42,9 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { let parent_generics = tcx.generics_of(trait_def_id); let parent_count = parent_generics.parent_count + parent_generics.own_params.len(); - let mut trait_fn_params = tcx.generics_of(fn_def_id).own_params.clone(); + let trait_fn_generics = tcx.generics_of(fn_def_id); + + let mut trait_fn_params = trait_fn_generics.own_params.clone(); for param in &mut own_params { param.index = param.index + parent_count as u32 + trait_fn_params.len() as u32 @@ -61,6 +63,8 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { own_params, param_def_id_to_index, has_self: opaque_ty_generics.has_self, + has_arg_pos_consts: opaque_ty_generics.has_arg_pos_consts + || trait_fn_generics.has_arg_pos_consts, has_late_bound_regions: opaque_ty_generics.has_late_bound_regions, }; } @@ -156,6 +160,7 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { own_params, param_def_id_to_index, has_self: generics.has_self, + has_arg_pos_consts: generics.has_arg_pos_consts, has_late_bound_regions: generics.has_late_bound_regions, }; } @@ -286,6 +291,8 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { prev + type_start }; + let mut has_arg_pos_consts = false; + own_params.extend(hir_generics.params.iter().filter_map(|param| { const MESSAGE: &str = "defaults for generic parameters are not allowed here"; let kind = match param.kind { @@ -321,6 +328,7 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { } } + has_arg_pos_consts |= arg_pos.is_some(); ty::GenericParamDefKind::Const { has_default: default.is_some(), arg_pos } } }; @@ -395,6 +403,7 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { own_params, param_def_id_to_index, has_self: has_self || parent_has_self, + has_arg_pos_consts, has_late_bound_regions: has_late_bound_regions(tcx, node), } } diff --git a/compiler/rustc_middle/src/ty/generics.rs b/compiler/rustc_middle/src/ty/generics.rs index 8d64ef22bcf2b..edddd7043077c 100644 --- a/compiler/rustc_middle/src/ty/generics.rs +++ b/compiler/rustc_middle/src/ty/generics.rs @@ -24,7 +24,7 @@ pub enum GenericParamDefKind { /// a const parameter in the function's argument list (including `self`). /// `Some(i)` means the argument at index `i` supplies this const parameter, /// while `None` indicates a regular const generic parameter. - arg_pos: Option, + arg_pos: Option, }, } @@ -60,7 +60,7 @@ impl GenericParamDefKind { } /// Returns the position of a function argument const generic. - pub fn arg_pos(&self) -> Option { + pub fn arg_pos(&self) -> Option { match self { GenericParamDefKind::Const { arg_pos, .. } => *arg_pos, _ => None, @@ -145,6 +145,7 @@ pub struct Generics { pub param_def_id_to_index: FxHashMap, pub has_self: bool, + pub has_arg_pos_consts: bool, pub has_late_bound_regions: Option, } @@ -318,8 +319,10 @@ impl<'tcx> Generics { self.own_params.iter().filter(|p| p.kind.is_synthetic()).count() } - pub fn own_arg_pos_consts(&'tcx self) -> impl Iterator { - self.own_params.iter().filter_map(|param| Some((param, param.kind.arg_pos()?))) + pub fn own_arg_pos_consts(&'tcx self) -> impl Iterator { + let params = if self.has_arg_pos_consts { &self.own_params[..] } else { &[] }; + + params.iter().filter_map(|param| Some((param, param.kind.arg_pos()?))) } /// Returns the args corresponding to the generic parameters diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index d7ceae9505f87..3730446af2c82 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -418,17 +418,25 @@ impl<'tcx> ThirBuildCx<'tcx> { ty::FnDef(def_id, _) => Some(tcx.generics_of(def_id)), _ => None, }; - let is_const_arg = |idx: usize| { - generics.is_some_and(|g| { - g.own_arg_pos_consts().any(|(_, pos)| pos as usize == idx) - }) + let args = if generics.is_some_and(|g| g.has_arg_pos_consts) { + let is_const_arg = |idx: usize| { + generics.is_some_and(|g| { + g.own_arg_pos_consts().any(|(_, pos)| pos as usize == idx) + }) + }; + + std::iter::once(receiver) + .chain(args.iter()) + .enumerate() + .filter(|&(idx, _)| !is_const_arg(idx)) + .map(|(_, arg)| self.mirror_expr(arg)) + .collect() + } else { + std::iter::once(receiver) + .chain(args.iter()) + .map(|arg| self.mirror_expr(arg)) + .collect() }; - let args = std::iter::once(receiver) - .chain(args.iter()) - .enumerate() - .filter(|&(idx, _)| !is_const_arg(idx)) - .map(|(_, arg)| self.mirror_expr(arg)) - .collect(); ExprKind::Call { ty: expr.ty, @@ -538,17 +546,21 @@ impl<'tcx> ThirBuildCx<'tcx> { ty::FnDef(def_id, _) => Some(tcx.generics_of(def_id)), _ => None, }; - let is_const_arg = |idx: usize| { - generics.is_some_and(|g| { - g.own_arg_pos_consts().any(|(_, pos)| pos as usize == idx) - }) + let args = if generics.is_some_and(|g| g.has_arg_pos_consts) { + let is_const_arg = |idx: usize| { + generics.is_some_and(|g| { + g.own_arg_pos_consts().any(|(_, pos)| pos as usize == idx) + }) + }; + args.iter() + .enumerate() + .filter(|&(idx, _)| !is_const_arg(idx)) + .map(|(_, arg)| self.mirror_expr(arg)) + .collect() + } else { + args.iter().map(|arg| self.mirror_expr(arg)).collect() }; - let args = args - .iter() - .enumerate() - .filter(|&(idx, _)| !is_const_arg(idx)) - .map(|(_, arg)| self.mirror_expr(arg)) - .collect(); + ExprKind::Call { ty: fn_ty, fun: self.mirror_expr(fun), diff --git a/compiler/rustc_ty_utils/src/assoc.rs b/compiler/rustc_ty_utils/src/assoc.rs index 941c158675548..bff2f1e6804af 100644 --- a/compiler/rustc_ty_utils/src/assoc.rs +++ b/compiler/rustc_ty_utils/src/assoc.rs @@ -353,6 +353,7 @@ fn associated_type_for_impl_trait_in_impl( own_params, param_def_id_to_index, has_self: false, + has_arg_pos_consts: trait_assoc_generics.has_arg_pos_consts, has_late_bound_regions: trait_assoc_generics.has_late_bound_regions, } }); From 66b0ea0b002d118675a465ec7eacef22d8cb6e5a Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 10 Oct 2026 19:50:46 +0000 Subject: [PATCH 12/19] use is-keyword instead of check keyword --- compiler/rustc_parse/src/parser/function.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_parse/src/parser/function.rs b/compiler/rustc_parse/src/parser/function.rs index 190047914b90d..b76ddf2a07295 100644 --- a/compiler/rustc_parse/src/parser/function.rs +++ b/compiler/rustc_parse/src/parser/function.rs @@ -772,7 +772,7 @@ impl<'a> Parser<'a> { let mut is_const = false; if fn_parse_mode.allow_const - && this.check_keyword(exp!(Const)) + && this.token.is_keyword(kw::Const) && this.look_ahead(1, |token| token.is_non_reserved_ident()) && this.look_ahead(2, |token| *token == token::Colon) { From c25232fe81687936b78bbe33c925d51b1794f643 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 13 Oct 2026 10:38:17 +1100 Subject: [PATCH 13/19] Reflow a comment with overly long lines --- .../src/traits/query/type_op/mod.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs index 250579a2b064a..46e1fb9f84388 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs @@ -137,10 +137,11 @@ where let mut error_info = None; let mut region_constraints = QueryRegionConstraints::default(); - // HACK(type_alias_impl_trait): When moving an opaque type to hidden type mapping from the query to the current inferctxt, - // we sometimes end up with `Opaque<'a> = Opaque<'b>` instead of an actual hidden type. In that case we don't register a - // hidden type but just equate the lifetimes. Thus we need to scrape the region constraints even though we're also manually - // collecting region constraints via `region_constraints`. + // HACK(type_alias_impl_trait): When moving an opaque type to hidden type mapping from the + // query to the current inferctxt, we sometimes end up with `Opaque<'a> = Opaque<'b>` + // instead of an actual hidden type. In that case we don't register a hidden type but just + // equate the lifetimes. Thus we need to scrape the region constraints even though we're + // also manually collecting region constraints via `region_constraints`. let (mut output, _) = scrape_region_constraints(infcx, root_def_id, "fully_perform", span, |ocx| { let (output, ei, obligations, _) = From b44457308ce3bc2ad9dacf32a27a3f1eb394f06f Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 13 Oct 2026 10:50:39 +1100 Subject: [PATCH 14/19] Inline and remove `fully_perform_into` It has a single call site. --- .../src/traits/query/type_op/mod.rs | 69 +++++++------------ 1 file changed, 24 insertions(+), 45 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs index 46e1fb9f84388..bfc5d39be2581 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs @@ -2,14 +2,12 @@ use std::fmt; use rustc_errors::ErrorGuaranteed; use rustc_hir::def_id::LocalDefId; -use rustc_infer::traits::PredicateObligations; use rustc_middle::traits::query::NoSolution; use rustc_middle::ty::{ParamEnvAnd, TyCtxt, TypeFoldable}; use rustc_span::Span; use crate::infer::canonical::{ - CanonicalQueryInput, CanonicalQueryResponse, Certainty, OriginalQueryValues, - QueryRegionConstraints, + CanonicalQueryInput, CanonicalQueryResponse, OriginalQueryValues, QueryRegionConstraints, }; use crate::infer::{InferCtxt, InferOk}; use crate::traits::ObligationCause; @@ -82,43 +80,6 @@ pub trait QueryTypeOp<'tcx>: fmt::Debug + Copy + TypeFoldable> + 't tcx: TyCtxt<'tcx>, canonicalized: CanonicalQueryInput<'tcx, ParamEnvAnd<'tcx, Self>>, ) -> Result, NoSolution>; - - fn fully_perform_into( - query_key: ParamEnvAnd<'tcx, Self>, - infcx: &InferCtxt<'tcx>, - output_query_region_constraints: &mut QueryRegionConstraints<'tcx>, - span: Span, - ) -> Result< - ( - Self::QueryResponse, - Option>>, - PredicateObligations<'tcx>, - Certainty, - ), - NoSolution, - > { - if !infcx.disable_trait_solver_fast_paths() - && let Some(result) = QueryTypeOp::try_fast_path(infcx.tcx, &query_key) - { - return Ok((result, None, PredicateObligations::new(), Certainty::Proven)); - } - - let mut canonical_var_values = OriginalQueryValues::default(); - let old_param_env = query_key.param_env; - let canonical_self = infcx.canonicalize_query(query_key, &mut canonical_var_values); - let canonical_result = Self::perform_query(infcx.tcx, canonical_self)?; - - let InferOk { value, obligations } = infcx - .instantiate_nll_query_response_and_region_obligations( - &ObligationCause::dummy_with_span(span), - old_param_env, - &canonical_var_values, - canonical_result, - output_query_region_constraints, - )?; - - Ok((value, Some(canonical_self), obligations, canonical_result.value.certainty)) - } } impl<'tcx, Q> TypeOp<'tcx> for ParamEnvAnd<'tcx, Q> @@ -144,14 +105,32 @@ where // also manually collecting region constraints via `region_constraints`. let (mut output, _) = scrape_region_constraints(infcx, root_def_id, "fully_perform", span, |ocx| { - let (output, ei, obligations, _) = - Q::fully_perform_into(self, infcx, &mut region_constraints, span)?; - error_info = ei; - + if !infcx.disable_trait_solver_fast_paths() + && let Some(value) = QueryTypeOp::try_fast_path(infcx.tcx, &self) + { + return Ok(value); + } + + let mut canonical_var_values = OriginalQueryValues::default(); + let old_param_env = self.param_env; + let canonical_self = infcx.canonicalize_query(self, &mut canonical_var_values); + let canonical_result = Q::perform_query(infcx.tcx, canonical_self)?; + + let InferOk { value, obligations } = infcx + .instantiate_nll_query_response_and_region_obligations( + &ObligationCause::dummy_with_span(span), + old_param_env, + &canonical_var_values, + canonical_result, + &mut region_constraints, + )?; + + error_info = Some(canonical_self); ocx.register_obligations(obligations); - Ok(output) + Ok(value) })?; output.error_info = error_info; + if let Some(QueryRegionConstraints { constraints, assumptions }) = output.constraints { region_constraints.constraints.extend(constraints.iter().cloned()); region_constraints.assumptions.extend(assumptions.iter().cloned()); From 631c64729b928d20f6d7c70fc168b49231c5860a Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 13 Oct 2026 11:17:11 +1100 Subject: [PATCH 15/19] Move `fully_perform` early return earlier Doing it before `scrape_region_constraints` avoids creating a snapshot and lots of other work that doesn't do anything. --- .../src/traits/query/type_op/mod.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs index bfc5d39be2581..1fbe5e93ae94d 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs @@ -95,6 +95,16 @@ where root_def_id: LocalDefId, span: Span, ) -> Result, ErrorGuaranteed> { + if !infcx.disable_trait_solver_fast_paths() + && let Some(value) = QueryTypeOp::try_fast_path(infcx.tcx, &self) + { + // In this case most of the work done by `scrape_region_constraints` becomes a no-op + // and can be skipped. This deep resolve is the only thing that isn't a no-op. + let value = infcx.deeply_resolve_ignoring_regions(value); + + return Ok(TypeOpOutput { output: value, constraints: None, error_info: None }); + } + let mut error_info = None; let mut region_constraints = QueryRegionConstraints::default(); @@ -105,12 +115,6 @@ where // also manually collecting region constraints via `region_constraints`. let (mut output, _) = scrape_region_constraints(infcx, root_def_id, "fully_perform", span, |ocx| { - if !infcx.disable_trait_solver_fast_paths() - && let Some(value) = QueryTypeOp::try_fast_path(infcx.tcx, &self) - { - return Ok(value); - } - let mut canonical_var_values = OriginalQueryValues::default(); let old_param_env = self.param_env; let canonical_self = infcx.canonicalize_query(self, &mut canonical_var_values); From cb2c10a09cc0ae420dc7f5dc3e531ffd6b6531f1 Mon Sep 17 00:00:00 2001 From: Jonathan Keller Date: Fri, 9 Oct 2026 14:02:41 -0700 Subject: [PATCH 16/19] perf: drop error in `unwrap_failed` instead of `unwrap` --- library/core/src/result.rs | 26 ++++--- .../issues/result-is-ok-unwrap.rs | 12 +++- .../iter-filter-count-assume.rs | 2 +- tests/codegen-llvm/range-len-try-from.rs | 8 +-- ...d_constant.main.GVN.32bit.panic-abort.diff | 11 +-- ..._constant.main.GVN.32bit.panic-unwind.diff | 67 +++++++++++++------ ...d_constant.main.GVN.64bit.panic-abort.diff | 11 +-- ..._constant.main.GVN.64bit.panic-unwind.diff | 67 +++++++++++++------ ...ap.runtime-optimized.after.panic-abort.mir | 43 ++++++++++++ ...p.runtime-optimized.after.panic-unwind.mir | 43 ++++++++++++ tests/mir-opt/pre-codegen/result_unwrap.rs | 15 +++++ 11 files changed, 229 insertions(+), 76 deletions(-) create mode 100644 tests/mir-opt/pre-codegen/result_unwrap.result_unwrap.runtime-optimized.after.panic-abort.mir create mode 100644 tests/mir-opt/pre-codegen/result_unwrap.result_unwrap.runtime-optimized.after.panic-unwind.mir create mode 100644 tests/mir-opt/pre-codegen/result_unwrap.rs diff --git a/library/core/src/result.rs b/library/core/src/result.rs index 9d898df3c6d3c..255c93bd8109c 100644 --- a/library/core/src/result.rs +++ b/library/core/src/result.rs @@ -1181,7 +1181,7 @@ impl Result { { match self { Ok(t) => t, - Err(e) => unwrap_failed(msg, &e), + Err(e) => unwrap_failed(msg, e), } } @@ -1229,7 +1229,7 @@ impl Result { { match self { Ok(t) => t, - Err(e) => unwrap_failed("called `Result::unwrap()` on an `Err` value", &e), + Err(e) => unwrap_failed("called `Result::unwrap()` on an `Err` value", e), } } @@ -1294,7 +1294,7 @@ impl Result { T: fmt::Debug, { match self { - Ok(t) => unwrap_failed(msg, &t), + Ok(t) => unwrap_failed(msg, t), Err(e) => e, } } @@ -1325,7 +1325,7 @@ impl Result { T: fmt::Debug, { match self { - Ok(t) => unwrap_failed("called `Result::unwrap_err()` on an `Ok` value", &t), + Ok(t) => unwrap_failed("called `Result::unwrap_err()` on an `Ok` value", t), Err(e) => e, } } @@ -1858,12 +1858,22 @@ impl Result, E> { } // This is a separate function to reduce the code size of the methods +// +// Take `error` by value so that `unwrap` doesn't need to include a landing pad to drop `error` +// during unwinding. This reduces the size of `unwrap` and allows it to be MIR inlined. #[cfg(not(panic = "immediate-abort"))] -#[inline(never)] +#[rustc_no_mir_inline] #[cold] #[track_caller] -fn unwrap_failed(msg: &str, error: &dyn fmt::Debug) -> ! { - panic!("{msg}: {error:?}"); +fn unwrap_failed(msg: &str, error: T) -> ! { + // Do the actual formatting with a `&dyn Debug` to reduce code size. + #[inline(never)] + #[cold] + #[track_caller] + fn unwrap_failed_inner(msg: &str, error: &dyn fmt::Debug) -> ! { + panic!("{msg}: {error:?}") + } + unwrap_failed_inner(msg, &error) } // This is a separate function to avoid constructing a `dyn Debug` @@ -1874,7 +1884,7 @@ fn unwrap_failed(msg: &str, error: &dyn fmt::Debug) -> ! { #[inline] #[cold] #[track_caller] -const fn unwrap_failed(_msg: &str, _error: &T) -> ! { +const fn unwrap_failed(_msg: &str, _error: T) -> ! { panic!() } diff --git a/tests/codegen-llvm/issues/result-is-ok-unwrap.rs b/tests/codegen-llvm/issues/result-is-ok-unwrap.rs index ffcbb0ae142d9..fc7d4129f5baa 100644 --- a/tests/codegen-llvm/issues/result-is-ok-unwrap.rs +++ b/tests/codegen-llvm/issues/result-is-ok-unwrap.rs @@ -10,7 +10,6 @@ use std::hint::black_box; #[no_mangle] pub fn unwrap_after_is_ok(arg: Result) { // CHECK-NOT: unwrap_failed - // CHECK-NOT: panic if arg.is_ok() { let value = arg.unwrap(); if value == 42 { @@ -18,3 +17,14 @@ pub fn unwrap_after_is_ok(arg: Result) { } } } + +// Ensure our `NOT` check above could actually ever trigger + +// CHECK-LABEL: @unguarded_unwrap_calls_unwrap_failed +#[no_mangle] +pub fn unguarded_unwrap_calls_unwrap_failed(arg: Result) -> u64 { + // CHECK: call{{.+}}unwrap_failed + arg.unwrap() +} + +// CHECK: declare{{.+}}unwrap_failed diff --git a/tests/codegen-llvm/lib-optimizations/iter-filter-count-assume.rs b/tests/codegen-llvm/lib-optimizations/iter-filter-count-assume.rs index 7588b23efc3af..6835abc87d354 100644 --- a/tests/codegen-llvm/lib-optimizations/iter-filter-count-assume.rs +++ b/tests/codegen-llvm/lib-optimizations/iter-filter-count-assume.rs @@ -11,7 +11,7 @@ #[unsafe(no_mangle)] pub fn filter_count_untrusted(bar: &[u8; 1234]) -> u16 { // CHECK-NOT: llvm.assume - // CHECK: call void @{{.+}}unwrap_failed + // CHECK: call {{.+}}unwrap_failed // CHECK-NOT: llvm.assume let mut iter = bar.iter(); let iter = std::iter::from_fn(|| iter.next()); // Make it not TrustedLen diff --git a/tests/codegen-llvm/range-len-try-from.rs b/tests/codegen-llvm/range-len-try-from.rs index 9e23fd67bcd9c..7fdf675e99191 100644 --- a/tests/codegen-llvm/range-len-try-from.rs +++ b/tests/codegen-llvm/range-len-try-from.rs @@ -15,9 +15,9 @@ use std::hint::black_box; #[no_mangle] pub fn score_round_enumerate(candidates: &[bool]) { // The length check itself can still fail. - // CHECK: call void {{.*}}unwrap_failed + // CHECK: tail call {{.*}}unwrap_failed // But the checked conversion inside the loop should not add another panic path. - // CHECK-NOT: call void {{.*}}unwrap_failed + // CHECK-NOT: tail call {{.*}}unwrap_failed // CHECK: ret void u32::try_from(candidates.len()).unwrap(); @@ -31,9 +31,9 @@ pub fn score_round_enumerate(candidates: &[bool]) { #[no_mangle] pub fn score_round_range(candidates: &[bool]) { // The length check itself can still fail. - // CHECK: call void {{.*}}unwrap_failed + // CHECK: tail call {{.*}}unwrap_failed // But the checked conversion inside the loop should not add another panic path. - // CHECK-NOT: call void {{.*}}unwrap_failed + // CHECK-NOT: tail call {{.*}}unwrap_failed // CHECK: ret void u32::try_from(candidates.len()).unwrap(); diff --git a/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.32bit.panic-abort.diff b/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.32bit.panic-abort.diff index a0526d279a863..9db63b97aa8a5 100644 --- a/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.32bit.panic-abort.diff +++ b/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.32bit.panic-abort.diff @@ -21,10 +21,7 @@ } scope 7 (inlined #[track_caller] Result::, std::alloc::AllocError>::unwrap) { let mut _10: isize; - let _11: std::alloc::AllocError; - let mut _12: !; - let mut _13: &dyn std::fmt::Debug; - let _14: &std::alloc::AllocError; + let mut _11: !; scope 8 { } scope 9 { @@ -84,11 +81,7 @@ } bb5: { - StorageLive(_13); - StorageLive(_14); - _14 = &_11; - _13 = copy _14 as &dyn std::fmt::Debug (PointerCoercion(Unsize, Implicit)); - _12 = result::unwrap_failed(const "called `Result::unwrap()` on an `Err` value", move _13) -> unwind unreachable; + _11 = result::unwrap_failed::(const "called `Result::unwrap()` on an `Err` value", const std::alloc::AllocError) -> unwind unreachable; } bb6: { diff --git a/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.32bit.panic-unwind.diff b/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.32bit.panic-unwind.diff index 3b5ecd3d2b16c..72ac52df3337f 100644 --- a/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.32bit.panic-unwind.diff +++ b/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.32bit.panic-unwind.diff @@ -19,7 +19,15 @@ scope 6 (inlined std::alloc::Global::alloc_impl) { } } - scope 7 (inlined NonNull::<[u8]>::as_ptr) { + scope 7 (inlined #[track_caller] Result::, std::alloc::AllocError>::unwrap) { + let mut _10: isize; + let mut _11: !; + scope 8 { + } + scope 9 { + } + } + scope 10 (inlined NonNull::<[u8]>::as_ptr) { } } scope 3 (inlined #[track_caller] Option::::unwrap) { @@ -36,31 +44,20 @@ + _2 = const Option::::None; StorageLive(_8); - _8 = discriminant(_2); -- switchInt(move _8) -> [0: bb3, 1: bb4, otherwise: bb2]; +- switchInt(move _8) -> [0: bb2, 1: bb3, otherwise: bb1]; + _8 = const 0_isize; -+ switchInt(const 0_isize) -> [0: bb3, 1: bb4, otherwise: bb2]; ++ switchInt(const 0_isize) -> [0: bb2, 1: bb3, otherwise: bb1]; } bb1: { - StorageDead(_6); - _4 = copy _5 as *mut [u8] (Transmute); - StorageDead(_5); - _3 = copy _4 as *mut u8 (PtrToPtr); - StorageDead(_4); - StorageDead(_3); - StorageDead(_1); - return; - } - - bb2: { unreachable; } - bb3: { + bb2: { _9 = option::unwrap_failed() -> unwind continue; } - bb4: { + bb3: { - _1 = move ((_2 as Some).0: std::alloc::Layout); + _1 = const Layout {{ size: Indirect { alloc_id: ALLOC0, offset: Size(4 bytes) }: usize, align: std::mem::Alignment {{ _inner_repr_trick: Scalar(0x00000000): mem::alignment::AlignmentEnum }} }}; StorageDead(_8); @@ -71,18 +68,44 @@ StorageLive(_6); StorageLive(_7); - _7 = copy _1; -- _6 = std::alloc::Global::alloc_impl_runtime(move _7, const false) -> [return: bb5, unwind continue]; +- _6 = std::alloc::Global::alloc_impl_runtime(move _7, const false) -> [return: bb4, unwind continue]; + _7 = const Layout {{ size: Indirect { alloc_id: ALLOC0, offset: Size(4 bytes) }: usize, align: std::mem::Alignment {{ _inner_repr_trick: Scalar(0x00000000): mem::alignment::AlignmentEnum }} }}; -+ _6 = std::alloc::Global::alloc_impl_runtime(const Layout {{ size: Indirect { alloc_id: ALLOC0, offset: Size(4 bytes) }: usize, align: std::mem::Alignment {{ _inner_repr_trick: Scalar(0x00000000): mem::alignment::AlignmentEnum }} }}, const false) -> [return: bb5, unwind continue]; ++ _6 = std::alloc::Global::alloc_impl_runtime(const Layout {{ size: Indirect { alloc_id: ALLOC0, offset: Size(4 bytes) }: usize, align: std::mem::Alignment {{ _inner_repr_trick: Scalar(0x00000000): mem::alignment::AlignmentEnum }} }}, const false) -> [return: bb4, unwind continue]; } - bb5: { + bb4: { StorageDead(_7); - _5 = Result::, std::alloc::AllocError>::unwrap(move _6) -> [return: bb1, unwind continue]; + StorageLive(_10); + _10 = discriminant(_6); + switchInt(move _10) -> [0: bb6, 1: bb5, otherwise: bb1]; } -+ } -+ + + bb5: { + _11 = result::unwrap_failed::(const "called `Result::unwrap()` on an `Err` value", const std::alloc::AllocError) -> unwind continue; + } + + bb6: { + _5 = move ((_6 as Ok).0: std::ptr::NonNull<[u8]>); + StorageDead(_10); + StorageDead(_6); + _4 = copy _5 as *mut [u8] (Transmute); + StorageDead(_5); + _3 = copy _4 as *mut u8 (PtrToPtr); + StorageDead(_4); + StorageDead(_3); + StorageDead(_1); + return; + } + } + +- ALLOC0 (size: 43, align: 1) { + ALLOC0 (size: 8, align: 4) { + 00 00 00 00 __ __ __ __ │ ....░░░░ ++ } ++ ++ ALLOC1 (size: 43, align: 1) { + 0x00 │ 63 61 6c 6c 65 64 20 60 52 65 73 75 6c 74 3a 3a │ called `Result:: + 0x10 │ 75 6e 77 72 61 70 28 29 60 20 6f 6e 20 61 6e 20 │ unwrap()` on an + 0x20 │ 60 45 72 72 60 20 76 61 6c 75 65 │ `Err` value } diff --git a/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.64bit.panic-abort.diff b/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.64bit.panic-abort.diff index 3a6b52b0f2496..a53e3d63d224e 100644 --- a/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.64bit.panic-abort.diff +++ b/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.64bit.panic-abort.diff @@ -21,10 +21,7 @@ } scope 7 (inlined #[track_caller] Result::, std::alloc::AllocError>::unwrap) { let mut _10: isize; - let _11: std::alloc::AllocError; - let mut _12: !; - let mut _13: &dyn std::fmt::Debug; - let _14: &std::alloc::AllocError; + let mut _11: !; scope 8 { } scope 9 { @@ -84,11 +81,7 @@ } bb5: { - StorageLive(_13); - StorageLive(_14); - _14 = &_11; - _13 = copy _14 as &dyn std::fmt::Debug (PointerCoercion(Unsize, Implicit)); - _12 = result::unwrap_failed(const "called `Result::unwrap()` on an `Err` value", move _13) -> unwind unreachable; + _11 = result::unwrap_failed::(const "called `Result::unwrap()` on an `Err` value", const std::alloc::AllocError) -> unwind unreachable; } bb6: { diff --git a/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.64bit.panic-unwind.diff b/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.64bit.panic-unwind.diff index 673c05d673fb2..d0e4c43f6460f 100644 --- a/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.64bit.panic-unwind.diff +++ b/tests/mir-opt/pre-codegen/issue_117368_print_invalid_constant.main.GVN.64bit.panic-unwind.diff @@ -19,7 +19,15 @@ scope 6 (inlined std::alloc::Global::alloc_impl) { } } - scope 7 (inlined NonNull::<[u8]>::as_ptr) { + scope 7 (inlined #[track_caller] Result::, std::alloc::AllocError>::unwrap) { + let mut _10: isize; + let mut _11: !; + scope 8 { + } + scope 9 { + } + } + scope 10 (inlined NonNull::<[u8]>::as_ptr) { } } scope 3 (inlined #[track_caller] Option::::unwrap) { @@ -36,31 +44,20 @@ + _2 = const Option::::None; StorageLive(_8); - _8 = discriminant(_2); -- switchInt(move _8) -> [0: bb3, 1: bb4, otherwise: bb2]; +- switchInt(move _8) -> [0: bb2, 1: bb3, otherwise: bb1]; + _8 = const 0_isize; -+ switchInt(const 0_isize) -> [0: bb3, 1: bb4, otherwise: bb2]; ++ switchInt(const 0_isize) -> [0: bb2, 1: bb3, otherwise: bb1]; } bb1: { - StorageDead(_6); - _4 = copy _5 as *mut [u8] (Transmute); - StorageDead(_5); - _3 = copy _4 as *mut u8 (PtrToPtr); - StorageDead(_4); - StorageDead(_3); - StorageDead(_1); - return; - } - - bb2: { unreachable; } - bb3: { + bb2: { _9 = option::unwrap_failed() -> unwind continue; } - bb4: { + bb3: { - _1 = move ((_2 as Some).0: std::alloc::Layout); + _1 = const Layout {{ size: Indirect { alloc_id: ALLOC0, offset: Size(8 bytes) }: usize, align: std::mem::Alignment {{ _inner_repr_trick: Scalar(0x0000000000000000): mem::alignment::AlignmentEnum }} }}; StorageDead(_8); @@ -71,18 +68,44 @@ StorageLive(_6); StorageLive(_7); - _7 = copy _1; -- _6 = std::alloc::Global::alloc_impl_runtime(move _7, const false) -> [return: bb5, unwind continue]; +- _6 = std::alloc::Global::alloc_impl_runtime(move _7, const false) -> [return: bb4, unwind continue]; + _7 = const Layout {{ size: Indirect { alloc_id: ALLOC0, offset: Size(8 bytes) }: usize, align: std::mem::Alignment {{ _inner_repr_trick: Scalar(0x0000000000000000): mem::alignment::AlignmentEnum }} }}; -+ _6 = std::alloc::Global::alloc_impl_runtime(const Layout {{ size: Indirect { alloc_id: ALLOC0, offset: Size(8 bytes) }: usize, align: std::mem::Alignment {{ _inner_repr_trick: Scalar(0x0000000000000000): mem::alignment::AlignmentEnum }} }}, const false) -> [return: bb5, unwind continue]; ++ _6 = std::alloc::Global::alloc_impl_runtime(const Layout {{ size: Indirect { alloc_id: ALLOC0, offset: Size(8 bytes) }: usize, align: std::mem::Alignment {{ _inner_repr_trick: Scalar(0x0000000000000000): mem::alignment::AlignmentEnum }} }}, const false) -> [return: bb4, unwind continue]; } - bb5: { + bb4: { StorageDead(_7); - _5 = Result::, std::alloc::AllocError>::unwrap(move _6) -> [return: bb1, unwind continue]; + StorageLive(_10); + _10 = discriminant(_6); + switchInt(move _10) -> [0: bb6, 1: bb5, otherwise: bb1]; } -+ } -+ + + bb5: { + _11 = result::unwrap_failed::(const "called `Result::unwrap()` on an `Err` value", const std::alloc::AllocError) -> unwind continue; + } + + bb6: { + _5 = move ((_6 as Ok).0: std::ptr::NonNull<[u8]>); + StorageDead(_10); + StorageDead(_6); + _4 = copy _5 as *mut [u8] (Transmute); + StorageDead(_5); + _3 = copy _4 as *mut u8 (PtrToPtr); + StorageDead(_4); + StorageDead(_3); + StorageDead(_1); + return; + } + } + +- ALLOC0 (size: 43, align: 1) { + ALLOC0 (size: 16, align: 8) { + 00 00 00 00 00 00 00 00 __ __ __ __ __ __ __ __ │ ........░░░░░░░░ ++ } ++ ++ ALLOC1 (size: 43, align: 1) { + 0x00 │ 63 61 6c 6c 65 64 20 60 52 65 73 75 6c 74 3a 3a │ called `Result:: + 0x10 │ 75 6e 77 72 61 70 28 29 60 20 6f 6e 20 61 6e 20 │ unwrap()` on an + 0x20 │ 60 45 72 72 60 20 76 61 6c 75 65 │ `Err` value } diff --git a/tests/mir-opt/pre-codegen/result_unwrap.result_unwrap.runtime-optimized.after.panic-abort.mir b/tests/mir-opt/pre-codegen/result_unwrap.result_unwrap.runtime-optimized.after.panic-abort.mir new file mode 100644 index 0000000000000..8cf10c489da95 --- /dev/null +++ b/tests/mir-opt/pre-codegen/result_unwrap.result_unwrap.runtime-optimized.after.panic-abort.mir @@ -0,0 +1,43 @@ +// MIR for `result_unwrap` after runtime-optimized + +fn result_unwrap(_1: Result) -> i32 { + debug result => _1; + let mut _0: i32; + scope 1 (inlined #[track_caller] Result::::unwrap) { + let mut _2: isize; + let _3: std::string::String; + let mut _4: !; + scope 2 { + } + scope 3 { + } + } + + bb0: { + StorageLive(_2); + _2 = discriminant(_1); + switchInt(move _2) -> [0: bb1, 1: bb2, otherwise: bb3]; + } + + bb1: { + _0 = copy ((_1 as Ok).0: i32); + StorageDead(_2); + return; + } + + bb2: { + StorageLive(_3); + _3 = copy ((_1 as Err).0: std::string::String); + _4 = result::unwrap_failed::(const "called `Result::unwrap()` on an `Err` value", move _3) -> unwind unreachable; + } + + bb3: { + unreachable; + } +} + +ALLOC0 (size: 43, align: 1) { + 0x00 │ 63 61 6c 6c 65 64 20 60 52 65 73 75 6c 74 3a 3a │ called `Result:: + 0x10 │ 75 6e 77 72 61 70 28 29 60 20 6f 6e 20 61 6e 20 │ unwrap()` on an + 0x20 │ 60 45 72 72 60 20 76 61 6c 75 65 │ `Err` value +} diff --git a/tests/mir-opt/pre-codegen/result_unwrap.result_unwrap.runtime-optimized.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/result_unwrap.result_unwrap.runtime-optimized.after.panic-unwind.mir new file mode 100644 index 0000000000000..c4a02dbe9d9c8 --- /dev/null +++ b/tests/mir-opt/pre-codegen/result_unwrap.result_unwrap.runtime-optimized.after.panic-unwind.mir @@ -0,0 +1,43 @@ +// MIR for `result_unwrap` after runtime-optimized + +fn result_unwrap(_1: Result) -> i32 { + debug result => _1; + let mut _0: i32; + scope 1 (inlined #[track_caller] Result::::unwrap) { + let mut _2: isize; + let _3: std::string::String; + let mut _4: !; + scope 2 { + } + scope 3 { + } + } + + bb0: { + StorageLive(_2); + _2 = discriminant(_1); + switchInt(move _2) -> [0: bb1, 1: bb2, otherwise: bb3]; + } + + bb1: { + _0 = copy ((_1 as Ok).0: i32); + StorageDead(_2); + return; + } + + bb2: { + StorageLive(_3); + _3 = copy ((_1 as Err).0: std::string::String); + _4 = result::unwrap_failed::(const "called `Result::unwrap()` on an `Err` value", move _3) -> unwind continue; + } + + bb3: { + unreachable; + } +} + +ALLOC0 (size: 43, align: 1) { + 0x00 │ 63 61 6c 6c 65 64 20 60 52 65 73 75 6c 74 3a 3a │ called `Result:: + 0x10 │ 75 6e 77 72 61 70 28 29 60 20 6f 6e 20 61 6e 20 │ unwrap()` on an + 0x20 │ 60 45 72 72 60 20 76 61 6c 75 65 │ `Err` value +} diff --git a/tests/mir-opt/pre-codegen/result_unwrap.rs b/tests/mir-opt/pre-codegen/result_unwrap.rs new file mode 100644 index 0000000000000..473f579d1d47f --- /dev/null +++ b/tests/mir-opt/pre-codegen/result_unwrap.rs @@ -0,0 +1,15 @@ +//@ compile-flags: -O -Zmir-opt-level=2 +// EMIT_MIR_FOR_EACH_PANIC_STRATEGY + +// Ensure result.unwrap() gets inlined, but does not generate a landing pad to drop the error. + +#![crate_type = "lib"] + +// EMIT_MIR result_unwrap.result_unwrap.runtime-optimized.after.mir +pub fn result_unwrap(result: Result) -> i32 { + // CHECK-LABEL: fn result_unwrap(_1: Result) -> i32 + // CHECK: (inlined #[track_caller] Result::::unwrap) + // CHECK: = result::unwrap_failed::( + // CHECK-NOT: (cleanup): + result.unwrap() +} From afa50b98a41a397a32af1d8efd034ea593a331ac Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 13 Oct 2026 21:40:09 +1100 Subject: [PATCH 17/19] Address review comments --- compiler/rustc_middle/src/traits/query.rs | 4 ++-- .../src/traits/implied_outlives_bounds.rs | 3 ++- .../src/traits/query/type_op/ascribe_user_type.rs | 3 ++- .../src/traits/query/type_op/mod.rs | 10 ++++------ .../src/traits/query/type_op/normalize.rs | 12 +++++------- .../src/traits/query/type_op/outlives.rs | 5 +++-- .../src/traits/query/type_op/prove_predicate.rs | 7 ++++--- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_middle/src/traits/query.rs b/compiler/rustc_middle/src/traits/query.rs index f2a2c6c3f4a63..45fa1ebf16ae9 100644 --- a/compiler/rustc_middle/src/traits/query.rs +++ b/compiler/rustc_middle/src/traits/query.rs @@ -101,7 +101,7 @@ pub struct MirBorrowckImpliedOutlivesBounds<'tcx> { pub normalized_inputs_and_output: Vec>, } -#[derive(Clone, Debug, Default, StableHash, TypeFoldable, TypeVisitable)] +#[derive(Clone, Debug, Default, PartialEq, StableHash, TypeFoldable, TypeVisitable)] pub struct DropckOutlivesResult<'tcx> { pub kinds: Vec>, pub overflows: Vec>, @@ -207,7 +207,7 @@ pub struct NormalizationResult<'tcx> { /// case they are called implied bounds). They are fed to the /// `OutlivesEnv` which in turn is supplied to the region checker and /// other parts of the inference system. -#[derive(Copy, Clone, Debug, TypeFoldable, TypeVisitable, StableHash)] +#[derive(Copy, Clone, Debug, PartialEq, TypeFoldable, TypeVisitable, StableHash)] pub enum OutlivesBound<'tcx> { RegionSubRegion(ty::Region<'tcx>, ty::Region<'tcx>), RegionSubParam(ty::Region<'tcx>, ty::ParamTy), diff --git a/compiler/rustc_trait_selection/src/traits/implied_outlives_bounds.rs b/compiler/rustc_trait_selection/src/traits/implied_outlives_bounds.rs index 286de7bed0db8..d234f95fe7602 100644 --- a/compiler/rustc_trait_selection/src/traits/implied_outlives_bounds.rs +++ b/compiler/rustc_trait_selection/src/traits/implied_outlives_bounds.rs @@ -11,6 +11,7 @@ use rustc_span::def_id::CRATE_DEF_ID; use rustc_span::{DUMMY_SP, Span, sym}; use smallvec::{SmallVec, smallvec}; +use crate::infer::InferCtxt; use crate::traits::query::NoSolution; use crate::traits::query::type_op::QueryTypeOp; use crate::traits::{ObligationCtxt, wf}; @@ -20,7 +21,7 @@ impl<'tcx> QueryTypeOp<'tcx> for ImpliedOutlivesBounds<'tcx> { type QueryResponse = Vec>; fn try_fast_path( - _tcx: TyCtxt<'tcx>, + _infcx: &InferCtxt<'tcx>, key: &ParamEnvAnd<'tcx, Self>, ) -> Option { // Don't go into the query for things that can't possibly have lifetimes. diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/ascribe_user_type.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/ascribe_user_type.rs index 2fcb7f1741f5a..ad2124ab73e9e 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/ascribe_user_type.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/ascribe_user_type.rs @@ -9,6 +9,7 @@ use rustc_middle::ty::{ use rustc_span::{DUMMY_SP, Span}; use tracing::{debug, instrument}; +use crate::infer::InferCtxt; use crate::infer::canonical::{CanonicalQueryInput, CanonicalQueryResponse}; use crate::traits::ObligationCtxt; @@ -16,7 +17,7 @@ impl<'tcx> super::QueryTypeOp<'tcx> for AscribeUserType<'tcx> { type QueryResponse = (); fn try_fast_path( - _tcx: TyCtxt<'tcx>, + _infcx: &InferCtxt<'tcx>, _key: &ParamEnvAnd<'tcx, Self>, ) -> Option { None diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs index 1fbe5e93ae94d..a7f42fc446598 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs @@ -60,13 +60,13 @@ pub struct TypeOpOutput<'tcx, Op: TypeOp<'tcx>> { /// /// [c]: https://rust-lang.github.io/chalk/book/canonical_queries/canonicalization.html pub trait QueryTypeOp<'tcx>: fmt::Debug + Copy + TypeFoldable> + 'tcx { - type QueryResponse: TypeFoldable>; + type QueryResponse: TypeFoldable> + PartialEq; /// Give query the option for a simple fast path that never /// actually hits the tcx cache lookup etc. Return `Some(r)` with /// a final result or `None` to do the full path. fn try_fast_path( - tcx: TyCtxt<'tcx>, + infcx: &InferCtxt<'tcx>, key: &ParamEnvAnd<'tcx, Self>, ) -> Option; @@ -96,11 +96,9 @@ where span: Span, ) -> Result, ErrorGuaranteed> { if !infcx.disable_trait_solver_fast_paths() - && let Some(value) = QueryTypeOp::try_fast_path(infcx.tcx, &self) + && let Some(value) = QueryTypeOp::try_fast_path(infcx, &self) { - // In this case most of the work done by `scrape_region_constraints` becomes a no-op - // and can be skipped. This deep resolve is the only thing that isn't a no-op. - let value = infcx.deeply_resolve_ignoring_regions(value); + debug_assert_eq!(value, infcx.deeply_resolve_ignoring_regions(value.clone())); return Ok(TypeOpOutput { output: value, constraints: None, error_info: None }); } diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/normalize.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/normalize.rs index 66a2d44699634..f41104327895d 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/normalize.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/normalize.rs @@ -4,6 +4,7 @@ use rustc_middle::traits::query::NoSolution; pub use rustc_middle::traits::query::type_op::Normalize; use rustc_middle::ty::{self, Lift, ParamEnvAnd, Ty, TyCtxt, TypeFoldable, TypeVisitableExt}; +use crate::infer::InferCtxt; use crate::infer::canonical::{CanonicalQueryInput, CanonicalQueryResponse}; impl<'tcx, T> super::QueryTypeOp<'tcx> for Normalize<'tcx, T> @@ -12,12 +13,9 @@ where { type QueryResponse = T; - fn try_fast_path(_tcx: TyCtxt<'tcx>, key: &ParamEnvAnd<'tcx, Self>) -> Option { - if !key.value.value.skip_normalization().has_aliases() { - Some(key.value.value.skip_normalization()) - } else { - None - } + fn try_fast_path(infcx: &InferCtxt<'tcx>, key: &ParamEnvAnd<'tcx, Self>) -> Option { + let value = key.value.value.skip_normalization(); + if !value.has_aliases() { Some(infcx.deeply_resolve_ignoring_regions(value)) } else { None } } fn perform_query( @@ -29,7 +27,7 @@ where } pub trait Normalizable<'tcx>: - fmt::Debug + TypeFoldable> + Lift> + Copy + PartialEq + fmt::Debug + TypeFoldable> + Lift> + Copy { fn type_op_method( tcx: TyCtxt<'tcx>, diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/outlives.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/outlives.rs index fd06dd4403c12..c96494a91f59e 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/outlives.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/outlives.rs @@ -1,6 +1,7 @@ use rustc_middle::traits::query::{DropckOutlivesResult, NoSolution}; use rustc_middle::ty::{ParamEnvAnd, TyCtxt}; +use crate::infer::InferCtxt; use crate::infer::canonical::{CanonicalQueryInput, CanonicalQueryResponse}; use crate::traits::query::dropck_outlives::trivial_dropck_outlives; use crate::traits::query::type_op::DropckOutlives; @@ -9,10 +10,10 @@ impl<'tcx> super::QueryTypeOp<'tcx> for DropckOutlives<'tcx> { type QueryResponse = DropckOutlivesResult<'tcx>; fn try_fast_path( - tcx: TyCtxt<'tcx>, + infcx: &InferCtxt<'tcx>, key: &ParamEnvAnd<'tcx, Self>, ) -> Option { - trivial_dropck_outlives(tcx, key.value.dropped_ty).then(DropckOutlivesResult::default) + trivial_dropck_outlives(infcx.tcx, key.value.dropped_ty).then(DropckOutlivesResult::default) } fn perform_query( diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/prove_predicate.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/prove_predicate.rs index 5dedd9af40561..148f75e0a6308 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/prove_predicate.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/prove_predicate.rs @@ -4,6 +4,7 @@ use rustc_middle::traits::query::NoSolution; pub use rustc_middle::traits::query::type_op::ProvePredicate; use rustc_middle::ty::{self, ParamEnvAnd, TyCtxt}; +use crate::infer::InferCtxt; use crate::infer::canonical::{CanonicalQueryInput, CanonicalQueryResponse}; use crate::traits::{ObligationCtxt, sizedness_fast_path}; @@ -11,16 +12,16 @@ impl<'tcx> super::QueryTypeOp<'tcx> for ProvePredicate<'tcx> { type QueryResponse = (); fn try_fast_path( - tcx: TyCtxt<'tcx>, + infcx: &InferCtxt<'tcx>, key: &ParamEnvAnd<'tcx, Self>, ) -> Option { - if sizedness_fast_path(tcx, key.value.predicate, key.param_env) { + if sizedness_fast_path(infcx.tcx, key.value.predicate, key.param_env) { return Some(()); } if let ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(term)) = key.value.predicate.kind().skip_binder() - && term.is_trivially_wf(tcx) + && term.is_trivially_wf(infcx.tcx) { return Some(()); } From df331dc6c480d675064dadb6cfdce2b0ce4d2218 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 13 Oct 2026 11:41:00 +0000 Subject: [PATCH 18/19] Rename allow_const to allow_const_params in FnParseMode --- compiler/rustc_parse/src/parser/attr.rs | 2 +- compiler/rustc_parse/src/parser/function.rs | 4 ++-- compiler/rustc_parse/src/parser/item.rs | 10 +++++----- compiler/rustc_parse/src/parser/path.rs | 2 +- compiler/rustc_parse/src/parser/stmt.rs | 2 +- compiler/rustc_parse/src/parser/ty.rs | 6 +++--- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs index f7829b3fe12c3..1a53dcb3d8550 100644 --- a/compiler/rustc_parse/src/parser/attr.rs +++ b/compiler/rustc_parse/src/parser/attr.rs @@ -215,7 +215,7 @@ impl<'a> Parser<'a> { req_name: |_, _| true, context: FnContext::Free, req_body: true, - allow_const: true, + allow_const_params: true, }, ForceCollect::No, AllowConstBlockItems::Yes, diff --git a/compiler/rustc_parse/src/parser/function.rs b/compiler/rustc_parse/src/parser/function.rs index b76ddf2a07295..a3ad62a9d817f 100644 --- a/compiler/rustc_parse/src/parser/function.rs +++ b/compiler/rustc_parse/src/parser/function.rs @@ -95,7 +95,7 @@ pub(crate) struct FnParseMode { /// Whether function_arg_const_generics (`const N: T`) is allowed /// in the parameter list. Enabled for free, impl, and trait functions. /// Disabled for the rest. - pub(super) allow_const: bool, + pub(super) allow_const_params: bool, } /// The context in which a function is parsed. @@ -771,7 +771,7 @@ impl<'a> Parser<'a> { }; let mut is_const = false; - if fn_parse_mode.allow_const + if fn_parse_mode.allow_const_params && this.token.is_keyword(kw::Const) && this.look_ahead(1, |token| token.is_non_reserved_ident()) && this.look_ahead(2, |token| *token == token::Colon) diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index b8509073a6a79..ee0f317f637d7 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -130,7 +130,7 @@ impl<'a> Parser<'a> { req_name: |_, _| true, context: FnContext::Free, req_body: true, - allow_const: true, + allow_const_params: true, }; self.parse_item_(fn_parse_mode, force_collect, allow_const_block_items) .map(|i| i.map(Box::new)) @@ -1243,7 +1243,7 @@ impl<'a> Parser<'a> { req_name: |_, _| true, context: FnContext::Impl, req_body: true, - allow_const: true, + allow_const_params: true, }; self.parse_assoc_item(fn_parse_mode, force_collect) } @@ -1256,7 +1256,7 @@ impl<'a> Parser<'a> { req_name: |edition, _| edition >= Edition::Edition2018, context: FnContext::Trait, req_body: false, - allow_const: true, + allow_const_params: true, }; self.parse_assoc_item(fn_parse_mode, force_collect) } @@ -1603,7 +1603,7 @@ impl<'a> Parser<'a> { req_name: |_, is_dot_dot_dot| is_dot_dot_dot == IsDotDotDot::No, context: FnContext::Free, req_body: false, - allow_const: false, + allow_const_params: false, }; Ok(self .parse_item_( @@ -2528,7 +2528,7 @@ impl<'a> Parser<'a> { req_name: |_, _| true, context: FnContext::Free, req_body: true, - allow_const: true, + allow_const_params: true, }; match self.parse_fn( &mut AttrVec::new(), diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs index c8aacd7718fcf..59d8bf932d430 100644 --- a/compiler/rustc_parse/src/parser/path.rs +++ b/compiler/rustc_parse/src/parser/path.rs @@ -403,7 +403,7 @@ impl<'a> Parser<'a> { context: FnContext::ParenthesizedArgumentList, req_name: |_, _| false, req_body: false, - allow_const: false, + allow_const_params: false, }; let param = p.parse_param_general(&mode, first_param)?; first_param = false; diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 57141883c4726..4382420a59b7c 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -163,7 +163,7 @@ impl<'a> Parser<'a> { req_name: |_, _| true, context: FnContext::Free, req_body: true, - allow_const: true, + allow_const_params: true, }, force_collect, AllowConstBlockItems::No, diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index 2f68d7723b6a1..8eabae128d779 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -886,7 +886,7 @@ impl<'a> Parser<'a> { req_name: |_, _| false, context: FnContext::FunctionPtrType, req_body: false, - allow_const: false, + allow_const_params: false, }; let decl = self.parse_fn_decl(&mode, AllowPlus::No, recover_return_sign)?; let decl = Box::new(decl); @@ -1455,7 +1455,7 @@ impl<'a> Parser<'a> { req_name: |_, _| false, context: FnContext::Free, req_body: false, - allow_const: false, + allow_const_params: false, }; match self.parse_fn_decl(&mode, AllowPlus::No, RecoverReturnSign::OnlyFatArrow) { Ok(decl) => { @@ -1557,7 +1557,7 @@ impl<'a> Parser<'a> { req_name: |_, _| false, context: FnContext::Free, req_body: false, - allow_const: false, + allow_const_params: false, }; let inputs = match self.parse_fn_params(&mode) { Ok(params) => params, From 81045228f0e2950c16dee8985df12a2b2c271cbe Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 13 Oct 2026 14:31:41 +0000 Subject: [PATCH 19/19] Add a page about function-arg-const-generics in unstable book --- .../function-arg-const-generics.md | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 src/doc/unstable-book/src/language-features/function-arg-const-generics.md diff --git a/src/doc/unstable-book/src/language-features/function-arg-const-generics.md b/src/doc/unstable-book/src/language-features/function-arg-const-generics.md new file mode 100644 index 0000000000000..2259928094a76 --- /dev/null +++ b/src/doc/unstable-book/src/language-features/function-arg-const-generics.md @@ -0,0 +1,142 @@ +# function_arg_const_generics + +The tracking issue for this feature is: [#163261] + +[#163261]: https://github.com/rust-lang/rust/issues/163261 + +------------------------ + +The `#![feature(function_arg_const_generics)]` allows const generic parameters to be declared in a function's value parameter list and supplied as ordinary call arguments, rather than through generic arguments (turbofish syntax). + +Warning: This feature is incomplete; its design and syntax may change. + +This feature requires either `#![feature(gca_min_const_items)]` or `#![feature(gca_adts)]`. + +## Examples + +Normally, const generic parameters are declared in the generic parameter list and supplied using turbofish syntax: + +```rust +fn foo(value: u8) { + let _ = N; +} + +fn main() { + foo::<42>(10); +} +``` + +With `function_arg_const_generics`, the const generic parameter can instead be declared in the function's value parameter list: + +```rust +#![allow(incomplete_features)] +#![feature(function_arg_const_generics, gca_min_const_items)] + +fn foo(const N: usize, value: u8) { + let _ = (N, value); +} + +fn main() { + foo(42, 10); +} +``` + +Here, `N` is still a const generic parameter, but its value is supplied as the first argument to the function call. + +### Methods and trait functions + +Argument position const generic parameters are supported in free functions, inherent methods, trait methods, and trait implementations. + +```rust +#![allow(incomplete_features)] +#![feature(function_arg_const_generics, gca_min_const_items)] + +struct Foo; + +impl Foo { + fn bar(&self, const N: usize) { + let _ = N; + } +} + +trait Trait { + fn baz(const N: usize); +} + +impl Trait for Foo { + fn baz(const N: usize) { + let _ = N; + } +} + +fn main() { + Foo.bar(10); + Foo::baz(20); +} +``` + +### Supported const arguments + +Currently, only the following forms of const arguments are supported: + +- Literals. +- Paths to const generic parameters. +- Paths to const items, provided the const item is marked for use in the type system, for example using `gca!`. + +```rust +#![allow(incomplete_features)] +#![feature(function_arg_const_generics, gca_min_const_items)] + +fn foo(const N: usize) { + let _ = N; +} + +const VALUE: usize = core::gca!(42); + +fn bar() { + foo(K); +} + +fn main() { + foo(10); // OK: integer literal + foo(VALUE); // OK: const item marked with gca! + bar::<20>(); +} +``` + +### Restrictions + +Argument position const generic parameters have the following restrictions: + +- They must be declared using a plain identifier, such as `const N: usize`. +- They cannot be explicitly supplied using turbofish syntax. +- Arguments must use one of the supported const argument forms described above. + +For example, a runtime local variable cannot be used as a const argument: + +```rust,compile_fail +#![allow(incomplete_features)] +#![feature(function_arg_const_generics, gca_min_const_items)] + +fn foo(const N: usize) {} + +fn main() { + let n = 10; + foo(n); // ERROR: runtime local variable +} +``` + +Similarly, argument position const generic parameters cannot be specified using turbofish syntax: + +```rust,compile_fail,E0107 +#![allow(incomplete_features)] +#![feature(function_arg_const_generics, gca_min_const_items)] + +fn foo(const N: usize) {} + +fn main() { + foo::<10>(10); // ERROR: N cannot be specified using turbofish +} +``` + +Argument position const generic parameters are not currently supported in function pointer types, `Fn()` trait syntax, or foreign function declarations.