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
9 changes: 8 additions & 1 deletion compiler/rustc_parse/src/parser/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ impl<'a> Parser<'a> {
}) == Some(true) ||
// This branch is only for better diagnostics; `pub`, `unsafe`, etc. are not
// allowed here.
// This branch also follows `$qual fn` or `$qual $qual` rule
// above since a valid `fn` can be after `extern`.
(self.may_recover()
&& self.tree_look_ahead(2, |tt| {
match tt {
Expand All @@ -374,7 +376,12 @@ impl<'a> Parser<'a> {
}) == Some(true)
&& self.tree_look_ahead(3, |tt| {
match tt {
TokenTree::Token(t, _) => t.is_keyword_case(kw::Fn, case),
TokenTree::Token(t, _) => {
t.is_keyword_case(kw::Fn, case) ||
ALL_QUALS.iter().any(|exp| {

@chenyukang chenyukang Sep 9, 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.

help me understand the fix, per my try current nightly could give correct diagnostic for:

extern "C" const fn b() {}

so the pr is fixing the issue that caused by add some more keyword before fn?

extern "C" const async unsafe fn b() {}

is not giving the correct suggest because of the extra async unsafe?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi @chenyukang , thanks for the review

So in check_fn_front_matter, there is this set of conditions above my changes on how to identify whether it's an fn or not:

self.check_keyword_case(exp!(Fn), case) // Definitely an `fn`.
            // `$qual fn` or `$qual $qual`:
            || quals.iter().any(|&exp| self.check_keyword_case(exp, case))
                && self.look_ahead(1, |t| {
                    // `$qual fn`, e.g. `const fn` or `async fn`.
                    t.is_keyword_case(kw::Fn, case)
                    // Two qualifiers `$qual $qual` is enough, e.g. `async unsafe`.
                    || (
                        (
                            t.is_non_raw_ident_where(|i|
                                quals.iter().any(|exp| exp.kw == i.name)
                                    // Rule out 2015 `const async: T = val`.
                                    && i.is_reserved()
                            )
                            || case == Case::Insensitive
                                && t.is_non_raw_ident_where(|i| quals.iter().any(|exp| {
                                    exp.kw.as_str() == i.name.as_str().to_lowercase()
                                }))
                        )
                        // Rule out `unsafe extern {`.
                        && !self.is_unsafe_foreign_mod()
                        // Rule out `async gen {` and `async gen move {`
                        && !self.is_async_gen_block()
                        // Rule out `const unsafe auto` and `const unsafe trait` and `const unsafe impl`
                        && !self.is_keyword_ahead(2, &[kw::Auto, kw::Trait, kw::Impl])
                    )
                })

Here the code claims that we can confidently know it's an fn if:

  • Current token is fn
  • Current token is $qual and next token is fn
  • Current token is $qual, next token is another $qual so someone is definitely trying to write a fn because I suppose only fn can have two quals

You can also write fn after extern ABI but the current code doesn't follow the same fn checks as the code above it - I simply add that in for this issue

is not giving the correct suggest because of the extra async unsafe?

yes, exactly, extern ABI fn have not check for two quals when identifying fn.

I agree that check_fn_front_matter is currently very hard to understand due to all the || I can try to make it easier to read with if-else if you want

t.is_keyword(exp.kw)
})
},
TokenTree::Delimited(..) => false,
}
}) == Some(true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Test for issue https://github.com/rust-lang/rust/issues/140171
// There is an order to respect for keywords before a function:
// `<visibility>, const, async, unsafe, extern, "<ABI>"`
//
// This test ensures the compiler is helpful about them being misplaced.
//@ edition:2018

extern "C" const async unsafe fn b() {}
//~^ ERROR expected `fn`, found keyword `const`
//~| NOTE expected `fn`
//~| HELP `const` must come before `extern "C"`
//~| SUGGESTION const extern "C"
//~| NOTE keyword order for functions declaration is `pub`, `default`, `const`, `async`, `unsafe`, `extern`

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error: expected `fn`, found keyword `const`
--> $DIR/wrong-const-async-unsafe-abi.rs:8:12
|
LL | extern "C" const async unsafe fn b() {}
| ^^^^^ expected `fn`
|
= note: keyword order for functions declaration is `pub`, `default`, `const`, `async`, `unsafe`, `extern`
help: `const` must come before `extern "C"`
|
LL - extern "C" const async unsafe fn b() {}
LL + const extern "C" async unsafe fn b() {}
|

error: aborting due to 1 previous error

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Test for issue https://github.com/rust-lang/rust/issues/140171
// There is an order to respect for keywords before a function:
// `<visibility>, const, async, unsafe, extern, "<ABI>"`
//
// This test ensures the compiler is helpful about them being misplaced.
//@ edition:2018

extern "C" const unsafe fn a() {}
//~^ ERROR expected `fn`, found keyword `const`
//~| NOTE expected `fn`
//~| HELP `const` must come before `extern "C"`
//~| SUGGESTION const extern "C"
//~| NOTE keyword order for functions declaration is `pub`, `default`, `const`, `async`, `unsafe`, `extern`

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error: expected `fn`, found keyword `const`
--> $DIR/wrong-const-unsafe-abi.rs:8:12
|
LL | extern "C" const unsafe fn a() {}
| ^^^^^ expected `fn`
|
= note: keyword order for functions declaration is `pub`, `default`, `const`, `async`, `unsafe`, `extern`
help: `const` must come before `extern "C"`
|
LL - extern "C" const unsafe fn a() {}
LL + const extern "C" unsafe fn a() {}
|

error: aborting due to 1 previous error

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Test for issue https://github.com/rust-lang/rust/issues/140171
// There is an order to respect for keywords before a function:
// `<visibility>, const, async, unsafe, extern, "<ABI>"`
//
// This test ensures the compiler is helpful about them being misplaced.
//@ edition:2018

unsafe extern "C" { static errno: i32; }

extern "C" pub const unsafe fn c() {}
//~^ ERROR expected `fn`, found keyword `pub`
//~| NOTE expected `fn`
//~| HELP visibility `pub` must come before `extern "C"`
//~| SUGGESTION pub extern "C"

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: expected `fn`, found keyword `pub`
--> $DIR/wrong-pub-const-unsafe-abi.rs:10:12
|
LL | extern "C" pub const unsafe fn c() {}
| ^^^ expected `fn`
|
help: visibility `pub` must come before `extern "C"`
|
LL - extern "C" pub const unsafe fn c() {}
LL + pub extern "C" const unsafe fn c() {}
|

error: aborting due to 1 previous error

Loading