Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ impl PathSegment {
pub enum GenericArgs {
/// The `<'a, A, B, C>` in `foo::bar::baz::<'a, A, B, C>`.
AngleBracketed(AngleBracketedArgs),
/// The `(A, B)` and `C` in `Foo(A, B) -> C`.
/// The `(A, B)` and `C` in `Foo(A, B) -> C`, used for the `Fn` trait among others.
Parenthesized(ParenthesizedArgs),
/// `(..)` in return type notation.
ParenthesizedElided(Span),
Expand Down
27 changes: 20 additions & 7 deletions compiler/rustc_ast_passes/src/ast_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,11 @@ impl<'a> AstValidator<'a> {
});
}

fn check_decl_no_pat(decl: &FnDecl, mut report_err: impl FnMut(Span, Option<Ident>, bool)) {
for Param { pat, .. } in &decl.inputs {
fn check_decl_no_pat(
fn_inputs: &[Param],
mut report_err: impl FnMut(Span, Option<Ident>, bool),
) {
for Param { pat, .. } in fn_inputs {
match pat.kind {
PatKind::Missing | PatKind::Ident(BindingMode::NONE, _, None) | PatKind::Wild => {}
PatKind::Ident(BindingMode::MUT, ident, None) => {
Expand Down Expand Up @@ -397,7 +400,7 @@ impl<'a> AstValidator<'a> {
let c_variadic_span = self.check_decl_cvariadic_pos(fn_decl);
self.check_decl_splatting(fn_decl, c_variadic_span, splat_semantic);
self.check_decl_attrs(fn_decl);
self.check_decl_self_param(fn_decl, self_semantic);
self.check_decl_self_param(&fn_decl.inputs, self_semantic);
}

/// Emits fatal error if function declaration has more than `u16::MAX` arguments
Expand Down Expand Up @@ -544,8 +547,8 @@ impl<'a> AstValidator<'a> {
});
}

fn check_decl_self_param(&self, fn_decl: &FnDecl, self_semantic: SelfSemantic) {
if let (SelfSemantic::No, [param, ..]) = (self_semantic, &*fn_decl.inputs) {
fn check_decl_self_param(&self, fn_inputs: &[Param], self_semantic: SelfSemantic) {
if let (SelfSemantic::No, [param, ..]) = (self_semantic, fn_inputs) {
if param.is_self() {
self.dcx().emit_err(diagnostics::FnParamForbiddenSelf { span: param.span });
}
Expand Down Expand Up @@ -1200,7 +1203,7 @@ impl<'a> AstValidator<'a> {
SelfSemantic::No,
SplatSemantic::from_extern(bfty.ext),
);
Self::check_decl_no_pat(&bfty.decl, |span, _, _| {
Self::check_decl_no_pat(&bfty.decl.inputs, |span, _, _| {
self.dcx().emit_err(diagnostics::PatternFnPointer { span });
});
if let Extern::Implicit(extern_span) = bfty.ext {
Expand Down Expand Up @@ -2009,7 +2012,7 @@ impl Visitor<'_> for AstValidator<'_> {

// Functions without bodies cannot have patterns.
if let FnKind::Fn(ctxt, _, Fn { body: None, sig, .. }) = fk {
Self::check_decl_no_pat(&sig.decl, |span, ident, mut_ident| {
Self::check_decl_no_pat(&sig.decl.inputs, |span, ident, mut_ident| {
if mut_ident && matches!(ctxt, FnCtxt::Assoc(_)) {
if let Some(ident) = ident {
let is_foreign = matches!(ctxt, FnCtxt::Foreign);
Expand Down Expand Up @@ -2208,6 +2211,16 @@ impl Visitor<'_> for AstValidator<'_> {
|this| visit::walk_anon_const(this, anon_const),
)
}

fn visit_path_segment(&mut self, seg: &PathSegment) -> Self::Result {
if let Some(Parenthesized(args)) = &seg.args {

@folkertdev folkertdev Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah, I assume this is where the Parenthesized name came from? It is currently documented as

/// The `(A, B)` and `C` in `Foo(A, B) -> C`.

pre-existing but I'd really prefer

/// The `(A, B)` and `C` in `Fn(A, B) -> C`.

View changes since the review

@fmease fmease Sep 13, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The path that has the parenthesized generic arguments isn't forced to be of the form Fn* (as you probably know). This type is part of the AST so it makes sense for the docs to deliberately "generalize" the example to signal to the reader that it's immaterial what the path is.

Even semantically speaking, the path could be std::ops::Fn, FnMut, AsyncFn, core::ops::AsyncFnOnce, X (if the user did use Fn as X;), Y (if the user defined a trait with #[rustc_paren_sugar]), etc.

@fmease fmease Sep 13, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah, I assume this is where the Parenthesized name came from?

It's a well-established convention across fields that ( ) are (round) parentheses or round brackets, [ ] are (square) brackets, { } are (curly) braces or curly brackets, < > are angle brackets. Of course, there are various deviations & competing conventions.

In any case, in rustc if you see parens that's ( ), brackets that's [ ]; braces that's { }.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In the general case that naming makes sense, but the usage earlier appears to be more specific, and e.g. fn(...) could also be said to have a parenthesized argument list.

Also, just as a data point, I spent some time squinting at Foo(A, B) -> C and especially Foo(A, B) in the comment above, unsure about where that could occur. So specifically mentioning the function traits would have helped there.

@fmease fmease Sep 13, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Right, that's fair, I missed that nuance to your statement. In this case, it's parenthesized parameter list as contrasted with angle-bracketed parameter list. Of course, since the (unstable) introduction of RTN (e.g., T::f(..)) it's become more blurry.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

fn(...) could also be said to have a parenthesized argument list.

In the case of fn(…) … / fn …(…) … / |…| … the list is not an argument list but a parameter list actually, so no :P

Funnily, this unstable feature actually blurs the line between arguments ("actual parameters") & parameters ("formal parameters") because it (ab)uses the syntax of parameter lists for argument lists.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Changed to something that should make you both happy

The `(A, B)` and `C` in `Foo(A, B) -> C`, used for the `Fn` trait among others.

self.check_decl_self_param(&args.inputs, SelfSemantic::No);
Self::check_decl_no_pat(&args.inputs, |span, _, _| {
self.dcx().emit_err(diagnostics::PatternParenthesizedArgList { span });
});
}
visit::walk_path_segment(self, seg);
}
}

pub fn check_crate(
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_ast_passes/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,13 @@ pub(crate) struct PatternFnPointer {
pub span: Span,
}

#[derive(Diagnostic)]
#[diag("patterns aren't allowed in parenthesized argument lists", code = E0561)]
pub(crate) struct PatternParenthesizedArgList {
#[primary_span]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag("only a single explicit lifetime bound is permitted", code = E0226)]
pub(crate) struct TraitObjectBound {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,7 @@ declare_features! (
/// Allows using `#[target_feature(enable = "...")]` on `#[naked]` on functions.
(unstable, naked_functions_target_feature, "1.86.0", Some(138568)),
/// Allows providing names to parameters of `impl Fn` etc
(incomplete, named_fn_trait_parameters, "1.99.0", Some(158499)),
(unstable, named_fn_trait_parameters, "1.99.0", Some(158499)),
/// Allows specifying the as-needed link modifier
(unstable, native_link_modifiers_as_needed, "1.53.0", Some(81490)),
/// Allow negative trait implementations.
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_parse/src/parser/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2379,6 +2379,7 @@ impl<'a> Parser<'a> {
target: match context {
FnContext::Trait => "methods without bodies",
FnContext::FunctionPtrType => "function pointer types",
FnContext::ParenthesizedArgumentList => "parenthesized argument list",
FnContext::Free => unreachable!("This method is not called in free functions, as patterns are always allowed there"),
FnContext::Impl => unreachable!("This method is not called in impls, as patterns are always allowed there"),
},
Expand Down
22 changes: 16 additions & 6 deletions compiler/rustc_parse/src/parser/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ pub(crate) enum FnContext {
Free,
/// A Function Pointer Type `fn(..)`.
FunctionPtrType,
/// A Parenthesized Argument List `impl Fn(...)`
ParenthesizedArgumentList,
Comment on lines +106 to +107

@folkertdev folkertdev Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any argument list is parenthesized though, right? idk, would FnTraitArgumentList work?

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Apparently this is sort of an established name. Not the best name, but I'm OK with it with that context.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah this is the established name for this, indeed also not sure I like it...

/// A Trait context.
Trait,
/// An Impl block.
Expand Down Expand Up @@ -691,7 +693,7 @@ impl<'a> Parser<'a> {
let (mut params, _) = self.parse_paren_comma_seq(|p| {
p.recover_vcs_conflict_marker();
let snapshot = p.create_snapshot_for_diagnostic();
let param = p.parse_param_general(fn_parse_mode, first_param, true).or_else(|e| {
let param = p.parse_param_general(fn_parse_mode, first_param).or_else(|e| {
let guar = e.emit();
// When parsing a param failed, we should check to make the span of the param
// not contain '(' before it.
Expand Down Expand Up @@ -724,7 +726,6 @@ impl<'a> Parser<'a> {
&mut self,
fn_parse_mode: &FnParseMode,
first_param: bool,
recover_arg_parse: bool,
) -> PResult<'a, Param> {
let lo = self.token.span;
let attrs = self.parse_outer_attributes()?;
Expand Down Expand Up @@ -812,13 +813,22 @@ impl<'a> Parser<'a> {
// If this is a C-variadic argument and we hit an error, return the error.
Err(err) if this.token == token::DotDotDot => return Err(err),
Err(err) if this.unmatched_angle_bracket_count > 0 => return Err(err),
Err(err) if recover_arg_parse => {
Err(err) => {
// Recover from attempting to parse the argument as a type without pattern.
err.cancel();
this.restore_snapshot(parser_snapshot_before_ty);
this.recover_arg_parse(fn_parse_mode.context)?
match this.recover_arg_parse(fn_parse_mode.context) {
Ok(res) => {
// We managed to parse the argument as a pattern, cancel the original error and emit a better one
err.cancel();
res
}
Err(new_err) => {
// We did not manage to parse the argument as a pattern, avoid suggesting a pattern and emit the original error
new_err.cancel();
return Err(err);
}
}
}
Err(err) => return Err(err),
}
};

Expand Down
6 changes: 4 additions & 2 deletions compiler/rustc_parse/src/parser/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,14 +397,16 @@ impl<'a> Parser<'a> {
}

let dcx = self.dcx();
let mut first_param = true;
let parse_params_result = self.parse_paren_comma_seq(|p| {
// Inside parenthesized type arguments, we want types only, not names.
let mode = FnParseMode {
context: FnContext::Free,
context: FnContext::ParenthesizedArgumentList,
req_name: |_, _| false,
req_body: false,
};
let param = p.parse_param_general(&mode, false, false)?;
let param = p.parse_param_general(&mode, first_param)?;
first_param = false;
if !matches!(param.pat.kind, PatKind::Missing) {
self.psess
.gated_spans
Expand Down
2 changes: 2 additions & 0 deletions tests/ui/fn/fn-ptr-pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ fn semantics<F>(
//~^ ERROR patterns aren't allowed in function pointer types
restricted_pat6: fn(&true: ()),
//~^ ERROR patterns aren't allowed in function pointer types

duplicate_names: fn(x: usize, x: usize),
) { }

// Patterns are also syntactically rejected, but restricted patterns are not
Expand Down
16 changes: 8 additions & 8 deletions tests/ui/fn/fn-ptr-pattern.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ LL | self3: fn(bool, self),
| ^^^^ must be the first parameter of an associated function

error[E0642]: patterns aren't allowed in function pointer types
--> $DIR/fn-ptr-pattern.rs:50:14
--> $DIR/fn-ptr-pattern.rs:52:14
|
LL | pat1: fn(1..3: bool),
| ^^^^
Expand All @@ -83,7 +83,7 @@ LL + pat1: fn(_: bool),
|

error[E0642]: patterns aren't allowed in function pointer types
--> $DIR/fn-ptr-pattern.rs:52:14
--> $DIR/fn-ptr-pattern.rs:54:14
|
LL | pat2: fn((x, y): (bool, bool)),
| ^^^^^^
Expand All @@ -95,7 +95,7 @@ LL + pat2: fn(_: (bool, bool)),
|

error[E0642]: patterns aren't allowed in function pointer types
--> $DIR/fn-ptr-pattern.rs:54:14
--> $DIR/fn-ptr-pattern.rs:56:14
|
LL | pat3: fn(Thing { a, b }: Thing),
| ^^^^^^^^^^^^^^
Expand All @@ -107,7 +107,7 @@ LL + pat3: fn(_: Thing),
|

error[E0642]: patterns aren't allowed in function pointer types
--> $DIR/fn-ptr-pattern.rs:56:14
--> $DIR/fn-ptr-pattern.rs:58:14
|
LL | pat4: fn(NoThing { a, b }: NoThing),
| ^^^^^^^^^^^^^^^^
Expand All @@ -119,7 +119,7 @@ LL + pat4: fn(_: NoThing),
|

error[E0642]: patterns aren't allowed in function pointer types
--> $DIR/fn-ptr-pattern.rs:58:14
--> $DIR/fn-ptr-pattern.rs:60:14
|
LL | pat5: fn((((((x))))): bool),
| ^^^^^^^^^^^
Expand All @@ -131,13 +131,13 @@ LL + pat5: fn(_: bool),
|

error: unexpected `self` parameter in function
--> $DIR/fn-ptr-pattern.rs:62:21
--> $DIR/fn-ptr-pattern.rs:64:21
|
LL | self2: fn(self, self),
| ^^^^ must be the first parameter of an associated function

error: unexpected `self` parameter in function
--> $DIR/fn-ptr-pattern.rs:64:21
--> $DIR/fn-ptr-pattern.rs:66:21
|
LL | self3: fn(bool, self),
| ^^^^ must be the first parameter of an associated function
Expand Down Expand Up @@ -201,7 +201,7 @@ LL | pat4: fn(NoThing { a, b }: NoThing),
| ^^^^^^^ not found in this scope
|
note: similarly named struct `Thing` defined here
--> $DIR/fn-ptr-pattern.rs:75:1
--> $DIR/fn-ptr-pattern.rs:77:1
|
LL | struct Thing { a: bool, b: bool }
| ^^^^^^^^^^^^
Expand Down
38 changes: 22 additions & 16 deletions tests/ui/fn/named-fn-trait-parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,52 +15,58 @@ fn allowed<F>(
// Patterns are semantically rejected
fn semantics<F>(
pat1: impl Fn(1..3: bool),
//~^ ERROR expected type, found `1`
//~^ ERROR patterns aren't allowed in parenthesized argument list
pat2: impl Fn((x, y): (bool, bool)),
//~^ ERROR unexpected token: `:`
//~^ ERROR patterns aren't allowed in parenthesized argument list
pat3: impl Fn(Thing { a, b }: Thing),
//~^ ERROR expected one of `!`, `(`, `+`, `::`, or `<`, found `{`
//~^ ERROR patterns aren't allowed in parenthesized argument list
pat4: impl Fn(NoThing { a, b }: NoThing),
//~^ ERROR expected one of `!`, `(`, `+`, `::`, or `<`, found `{`
//~^ ERROR patterns aren't allowed in parenthesized argument list
//~| ERROR cannot find type `NoThing` in this scope
pat5: impl Fn((((((x))))): bool),
//~^ ERROR unexpected token: `:`
//~^ ERROR patterns aren't allowed in parenthesized argument list

self1: impl Fn(self),
//~^ ERROR unexpected `self` parameter in function
//~^ ERROR `self` parameter is only allowed in associated functions
self2: impl Fn(self, self),
//~^ ERROR unexpected `self` parameter in function
//~^ ERROR `self` parameter is only allowed in associated functions
//~| ERROR unexpected `self` parameter in function
self3: impl Fn(bool, self),
//~^ ERROR unexpected `self` parameter in function

// FIXME should be rejected
restricted_pat1: impl Fn(mut x: ()),
//~^ ERROR patterns aren't allowed in parenthesized argument lists
restricted_pat2: impl Fn(&x: ()),
//~^ ERROR patterns aren't allowed in parenthesized argument lists
restricted_pat3: impl Fn(&&x: ()),
//~^ ERROR patterns aren't allowed in parenthesized argument lists
restricted_pat4: impl Fn(false: ()),
//~^ ERROR patterns aren't allowed in parenthesized argument lists
restricted_pat5: impl Fn(&_: ()),
//~^ ERROR patterns aren't allowed in parenthesized argument lists
restricted_pat6: impl Fn(&true: ()),
//~^ ERROR patterns aren't allowed in parenthesized argument lists

duplicate_names: impl Fn(x: usize, x: usize),
) { }

// Patterns are also syntactically rejected, but restricted patterns are not
#[cfg(false)]
fn syntax<F>(
pat1: impl Fn(1..3: bool),
//~^ ERROR expected type, found `1`
//~^ ERROR patterns aren't allowed in parenthesized argument list
pat2: impl Fn((x, y): (bool, bool)),
//~^ ERROR unexpected token: `:`
//~^ ERROR patterns aren't allowed in parenthesized argument list
pat3: impl Fn(Thing { a, b }: Thing),
//~^ ERROR expected one of `!`, `(`, `+`, `::`, or `<`, found `{`
//~^ ERROR patterns aren't allowed in parenthesized argument list
pat4: impl Fn(NoThing { a, b }: NoThing),
//~^ ERROR expected one of `!`, `(`, `+`, `::`, or `<`, found `{`
//~^ ERROR patterns aren't allowed in parenthesized argument list
pat5: impl Fn((((((x))))): bool),
//~^ ERROR unexpected token: `:`
//~^ ERROR patterns aren't allowed in parenthesized argument list

self1: impl Fn(self), // FIXME should be accepted
//~^ ERROR unexpected `self` parameter in function
self1: impl Fn(self),
self2: impl Fn(self, self),
//~^ ERROR unexpected `self` parameter in function
//~| ERROR unexpected `self` parameter in function
self3: impl Fn(bool, self),
//~^ ERROR unexpected `self` parameter in function

Expand Down
Loading
Loading