Skip to content

Commit 5b82ea7

Browse files
committed
Auto merge of rust-lang#99918 - WaffleLapkin:fnFnfun, r=estebank
Recover wrong-cased keywords that start items (_this pr was inspired by [this tweet](https://twitter.com/Azumanga/status/1552982326409367561)_) r? `@estebank` We've talked a bit about this recovery, but I just wanted to make sure that this is the right approach :) For now I've only added the case insensitive recovery to `use`s, since most other items like `impl` blocks, modules, functions can start with multiple keywords which complicates the matter.
2 parents c1a859b + d86f9cd commit 5b82ea7

File tree

10 files changed

+286
-42
lines changed

10 files changed

+286
-42
lines changed

compiler/rustc_ast/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ extern crate rustc_macros;
2929
extern crate tracing;
3030

3131
pub mod util {
32+
pub mod case;
3233
pub mod classify;
3334
pub mod comments;
3435
pub mod literal;

compiler/rustc_ast/src/token.rs

+10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub use TokenKind::*;
55

66
use crate::ast;
77
use crate::ptr::P;
8+
use crate::util::case::Case;
89

910
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
1011
use rustc_data_structures::sync::Lrc;
@@ -615,6 +616,15 @@ impl Token {
615616
self.is_non_raw_ident_where(|id| id.name == kw)
616617
}
617618

619+
/// Returns `true` if the token is a given keyword, `kw` or if `case` is `Insensitive` and this token is an identifier equal to `kw` ignoring the case.
620+
pub fn is_keyword_case(&self, kw: Symbol, case: Case) -> bool {
621+
self.is_keyword(kw)
622+
|| (case == Case::Insensitive
623+
&& self.is_non_raw_ident_where(|id| {
624+
id.name.as_str().to_lowercase() == kw.as_str().to_lowercase()
625+
}))
626+
}
627+
618628
pub fn is_path_segment_keyword(&self) -> bool {
619629
self.is_non_raw_ident_where(Ident::is_path_segment_keyword)
620630
}

compiler/rustc_ast/src/util/case.rs

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// Whatever to ignore case (`fn` vs `Fn` vs `FN`) or not. Used for recovering.
2+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
3+
pub enum Case {
4+
Sensitive,
5+
Insensitive,
6+
}

compiler/rustc_parse/src/parser/expr.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use core::mem;
3333
use rustc_ast::ptr::P;
3434
use rustc_ast::token::{self, Delimiter, Token, TokenKind};
3535
use rustc_ast::tokenstream::Spacing;
36+
use rustc_ast::util::case::Case;
3637
use rustc_ast::util::classify;
3738
use rustc_ast::util::literal::LitError;
3839
use rustc_ast::util::parser::{prec_let_scrutinee_needs_par, AssocOp, Fixity};
@@ -2090,7 +2091,7 @@ impl<'a> Parser<'a> {
20902091
if self.eat_keyword(kw::Static) { Movability::Static } else { Movability::Movable };
20912092

20922093
let asyncness = if self.token.uninterpolated_span().rust_2018() {
2093-
self.parse_asyncness()
2094+
self.parse_asyncness(Case::Sensitive)
20942095
} else {
20952096
Async::No
20962097
};

compiler/rustc_parse/src/parser/item.rs

+60-31
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use rustc_ast::ast::*;
88
use rustc_ast::ptr::P;
99
use rustc_ast::token::{self, Delimiter, TokenKind};
1010
use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree};
11+
use rustc_ast::util::case::Case;
1112
use rustc_ast::{self as ast, AttrVec, Attribute, DUMMY_NODE_ID};
1213
use rustc_ast::{Async, Const, Defaultness, IsAuto, Mutability, Unsafe, UseTree, UseTreeKind};
1314
use rustc_ast::{BindingAnnotation, Block, FnDecl, FnSig, Param, SelfKind};
@@ -34,7 +35,7 @@ impl<'a> Parser<'a> {
3435

3536
/// Parses a `mod <foo> { ... }` or `mod <foo>;` item.
3637
fn parse_item_mod(&mut self, attrs: &mut AttrVec) -> PResult<'a, ItemInfo> {
37-
let unsafety = self.parse_unsafety();
38+
let unsafety = self.parse_unsafety(Case::Sensitive);
3839
self.expect_keyword(kw::Mod)?;
3940
let id = self.parse_ident()?;
4041
let mod_kind = if self.eat(&token::Semi) {
@@ -143,8 +144,15 @@ impl<'a> Parser<'a> {
143144
let lo = self.token.span;
144145
let vis = self.parse_visibility(FollowedByType::No)?;
145146
let mut def = self.parse_defaultness();
146-
let kind =
147-
self.parse_item_kind(&mut attrs, mac_allowed, lo, &vis, &mut def, fn_parse_mode)?;
147+
let kind = self.parse_item_kind(
148+
&mut attrs,
149+
mac_allowed,
150+
lo,
151+
&vis,
152+
&mut def,
153+
fn_parse_mode,
154+
Case::Sensitive,
155+
)?;
148156
if let Some((ident, kind)) = kind {
149157
self.error_on_unconsumed_default(def, &kind);
150158
let span = lo.to(self.prev_token.span);
@@ -205,16 +213,17 @@ impl<'a> Parser<'a> {
205213
vis: &Visibility,
206214
def: &mut Defaultness,
207215
fn_parse_mode: FnParseMode,
216+
case: Case,
208217
) -> PResult<'a, Option<ItemInfo>> {
209218
let def_final = def == &Defaultness::Final;
210-
let mut def = || mem::replace(def, Defaultness::Final);
219+
let mut def_ = || mem::replace(def, Defaultness::Final);
211220

212-
let info = if self.eat_keyword(kw::Use) {
221+
let info = if self.eat_keyword_case(kw::Use, case) {
213222
self.parse_use_item()?
214-
} else if self.check_fn_front_matter(def_final) {
223+
} else if self.check_fn_front_matter(def_final, case) {
215224
// FUNCTION ITEM
216225
let (ident, sig, generics, body) = self.parse_fn(attrs, fn_parse_mode, lo, vis)?;
217-
(ident, ItemKind::Fn(Box::new(Fn { defaultness: def(), sig, generics, body })))
226+
(ident, ItemKind::Fn(Box::new(Fn { defaultness: def_(), sig, generics, body })))
218227
} else if self.eat_keyword(kw::Extern) {
219228
if self.eat_keyword(kw::Crate) {
220229
// EXTERN CRATE
@@ -225,7 +234,7 @@ impl<'a> Parser<'a> {
225234
}
226235
} else if self.is_unsafe_foreign_mod() {
227236
// EXTERN BLOCK
228-
let unsafety = self.parse_unsafety();
237+
let unsafety = self.parse_unsafety(Case::Sensitive);
229238
self.expect_keyword(kw::Extern)?;
230239
self.parse_item_foreign_mod(attrs, unsafety)?
231240
} else if self.is_static_global() {
@@ -234,15 +243,15 @@ impl<'a> Parser<'a> {
234243
let m = self.parse_mutability();
235244
let (ident, ty, expr) = self.parse_item_global(Some(m))?;
236245
(ident, ItemKind::Static(ty, m, expr))
237-
} else if let Const::Yes(const_span) = self.parse_constness() {
246+
} else if let Const::Yes(const_span) = self.parse_constness(Case::Sensitive) {
238247
// CONST ITEM
239248
if self.token.is_keyword(kw::Impl) {
240249
// recover from `const impl`, suggest `impl const`
241-
self.recover_const_impl(const_span, attrs, def())?
250+
self.recover_const_impl(const_span, attrs, def_())?
242251
} else {
243252
self.recover_const_mut(const_span);
244253
let (ident, ty, expr) = self.parse_item_global(None)?;
245-
(ident, ItemKind::Const(def(), ty, expr))
254+
(ident, ItemKind::Const(def_(), ty, expr))
246255
}
247256
} else if self.check_keyword(kw::Trait) || self.check_auto_or_unsafe_trait_item() {
248257
// TRAIT ITEM
@@ -251,15 +260,15 @@ impl<'a> Parser<'a> {
251260
|| self.check_keyword(kw::Unsafe) && self.is_keyword_ahead(1, &[kw::Impl])
252261
{
253262
// IMPL ITEM
254-
self.parse_item_impl(attrs, def())?
263+
self.parse_item_impl(attrs, def_())?
255264
} else if self.check_keyword(kw::Mod)
256265
|| self.check_keyword(kw::Unsafe) && self.is_keyword_ahead(1, &[kw::Mod])
257266
{
258267
// MODULE ITEM
259268
self.parse_item_mod(attrs)?
260269
} else if self.eat_keyword(kw::Type) {
261270
// TYPE ITEM
262-
self.parse_type_alias(def())?
271+
self.parse_type_alias(def_())?
263272
} else if self.eat_keyword(kw::Enum) {
264273
// ENUM ITEM
265274
self.parse_item_enum()?
@@ -286,6 +295,19 @@ impl<'a> Parser<'a> {
286295
} else if self.isnt_macro_invocation() && vis.kind.is_pub() {
287296
self.recover_missing_kw_before_item()?;
288297
return Ok(None);
298+
} else if self.isnt_macro_invocation() && case == Case::Sensitive {
299+
_ = def_;
300+
301+
// Recover wrong cased keywords
302+
return self.parse_item_kind(
303+
attrs,
304+
macros_allowed,
305+
lo,
306+
vis,
307+
def,
308+
fn_parse_mode,
309+
Case::Insensitive,
310+
);
289311
} else if macros_allowed && self.check_path() {
290312
// MACRO INVOCATION ITEM
291313
(Ident::empty(), ItemKind::MacCall(P(self.parse_item_macro(vis)?)))
@@ -538,7 +560,7 @@ impl<'a> Parser<'a> {
538560
attrs: &mut AttrVec,
539561
defaultness: Defaultness,
540562
) -> PResult<'a, ItemInfo> {
541-
let unsafety = self.parse_unsafety();
563+
let unsafety = self.parse_unsafety(Case::Sensitive);
542564
self.expect_keyword(kw::Impl)?;
543565

544566
// First, parse generic parameters if necessary.
@@ -552,7 +574,7 @@ impl<'a> Parser<'a> {
552574
generics
553575
};
554576

555-
let constness = self.parse_constness();
577+
let constness = self.parse_constness(Case::Sensitive);
556578
if let Const::Yes(span) = constness {
557579
self.sess.gated_spans.gate(sym::const_trait_impl, span);
558580
}
@@ -796,7 +818,7 @@ impl<'a> Parser<'a> {
796818

797819
/// Parses `unsafe? auto? trait Foo { ... }` or `trait Foo = Bar;`.
798820
fn parse_item_trait(&mut self, attrs: &mut AttrVec, lo: Span) -> PResult<'a, ItemInfo> {
799-
let unsafety = self.parse_unsafety();
821+
let unsafety = self.parse_unsafety(Case::Sensitive);
800822
// Parse optional `auto` prefix.
801823
let is_auto = if self.eat_keyword(kw::Auto) { IsAuto::Yes } else { IsAuto::No };
802824

@@ -1762,7 +1784,7 @@ impl<'a> Parser<'a> {
17621784
let (ident, is_raw) = self.ident_or_err()?;
17631785
if !is_raw && ident.is_reserved() {
17641786
let snapshot = self.create_snapshot_for_diagnostic();
1765-
let err = if self.check_fn_front_matter(false) {
1787+
let err = if self.check_fn_front_matter(false, Case::Sensitive) {
17661788
let inherited_vis = Visibility {
17671789
span: rustc_span::DUMMY_SP,
17681790
kind: VisibilityKind::Inherited,
@@ -2172,7 +2194,7 @@ impl<'a> Parser<'a> {
21722194
///
21732195
/// `check_pub` adds additional `pub` to the checks in case users place it
21742196
/// wrongly, can be used to ensure `pub` never comes after `default`.
2175-
pub(super) fn check_fn_front_matter(&mut self, check_pub: bool) -> bool {
2197+
pub(super) fn check_fn_front_matter(&mut self, check_pub: bool, case: Case) -> bool {
21762198
// We use an over-approximation here.
21772199
// `const const`, `fn const` won't parse, but we're not stepping over other syntax either.
21782200
// `pub` is added in case users got confused with the ordering like `async pub fn`,
@@ -2182,23 +2204,30 @@ impl<'a> Parser<'a> {
21822204
} else {
21832205
&[kw::Const, kw::Async, kw::Unsafe, kw::Extern]
21842206
};
2185-
self.check_keyword(kw::Fn) // Definitely an `fn`.
2207+
self.check_keyword_case(kw::Fn, case) // Definitely an `fn`.
21862208
// `$qual fn` or `$qual $qual`:
2187-
|| quals.iter().any(|&kw| self.check_keyword(kw))
2209+
|| quals.iter().any(|&kw| self.check_keyword_case(kw, case))
21882210
&& self.look_ahead(1, |t| {
21892211
// `$qual fn`, e.g. `const fn` or `async fn`.
2190-
t.is_keyword(kw::Fn)
2212+
t.is_keyword_case(kw::Fn, case)
21912213
// Two qualifiers `$qual $qual` is enough, e.g. `async unsafe`.
2192-
|| t.is_non_raw_ident_where(|i| quals.contains(&i.name)
2193-
// Rule out 2015 `const async: T = val`.
2194-
&& i.is_reserved()
2214+
|| (
2215+
(
2216+
t.is_non_raw_ident_where(|i|
2217+
quals.contains(&i.name)
2218+
// Rule out 2015 `const async: T = val`.
2219+
&& i.is_reserved()
2220+
)
2221+
|| case == Case::Insensitive
2222+
&& t.is_non_raw_ident_where(|i| quals.iter().any(|qual| qual.as_str() == i.name.as_str().to_lowercase()))
2223+
)
21952224
// Rule out unsafe extern block.
21962225
&& !self.is_unsafe_foreign_mod())
21972226
})
21982227
// `extern ABI fn`
2199-
|| self.check_keyword(kw::Extern)
2228+
|| self.check_keyword_case(kw::Extern, case)
22002229
&& self.look_ahead(1, |t| t.can_begin_literal_maybe_minus())
2201-
&& self.look_ahead(2, |t| t.is_keyword(kw::Fn))
2230+
&& self.look_ahead(2, |t| t.is_keyword_case(kw::Fn, case))
22022231
}
22032232

22042233
/// Parses all the "front matter" (or "qualifiers") for a `fn` declaration,
@@ -2214,22 +2243,22 @@ impl<'a> Parser<'a> {
22142243
/// `Visibility::Inherited` when no visibility is known.
22152244
pub(super) fn parse_fn_front_matter(&mut self, orig_vis: &Visibility) -> PResult<'a, FnHeader> {
22162245
let sp_start = self.token.span;
2217-
let constness = self.parse_constness();
2246+
let constness = self.parse_constness(Case::Insensitive);
22182247

22192248
let async_start_sp = self.token.span;
2220-
let asyncness = self.parse_asyncness();
2249+
let asyncness = self.parse_asyncness(Case::Insensitive);
22212250

22222251
let unsafe_start_sp = self.token.span;
2223-
let unsafety = self.parse_unsafety();
2252+
let unsafety = self.parse_unsafety(Case::Insensitive);
22242253

22252254
let ext_start_sp = self.token.span;
2226-
let ext = self.parse_extern();
2255+
let ext = self.parse_extern(Case::Insensitive);
22272256

22282257
if let Async::Yes { span, .. } = asyncness {
22292258
self.ban_async_in_2015(span);
22302259
}
22312260

2232-
if !self.eat_keyword(kw::Fn) {
2261+
if !self.eat_keyword_case(kw::Fn, Case::Insensitive) {
22332262
// It is possible for `expect_one_of` to recover given the contents of
22342263
// `self.expected_tokens`, therefore, do not use `self.unexpected()` which doesn't
22352264
// account for this.

compiler/rustc_parse/src/parser/mod.rs

+50-8
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use rustc_ast::token::{self, Delimiter, Nonterminal, Token, TokenKind};
2222
use rustc_ast::tokenstream::AttributesData;
2323
use rustc_ast::tokenstream::{self, DelimSpan, Spacing};
2424
use rustc_ast::tokenstream::{TokenStream, TokenTree};
25+
use rustc_ast::util::case::Case;
2526
use rustc_ast::AttrId;
2627
use rustc_ast::DUMMY_NODE_ID;
2728
use rustc_ast::{self as ast, AnonConst, AttrStyle, AttrVec, Const, Extern};
@@ -636,6 +637,20 @@ impl<'a> Parser<'a> {
636637
self.token.is_keyword(kw)
637638
}
638639

640+
fn check_keyword_case(&mut self, kw: Symbol, case: Case) -> bool {
641+
if self.check_keyword(kw) {
642+
return true;
643+
}
644+
645+
if case == Case::Insensitive
646+
&& let Some((ident, /* is_raw */ false)) = self.token.ident()
647+
&& ident.as_str().to_lowercase() == kw.as_str().to_lowercase() {
648+
true
649+
} else {
650+
false
651+
}
652+
}
653+
639654
/// If the next token is the given keyword, eats it and returns `true`.
640655
/// Otherwise, returns `false`. An expectation is also added for diagnostics purposes.
641656
// Public for rustfmt usage.
@@ -648,6 +663,33 @@ impl<'a> Parser<'a> {
648663
}
649664
}
650665

666+
/// Eats a keyword, optionally ignoring the case.
667+
/// If the case differs (and is ignored) an error is issued.
668+
/// This is useful for recovery.
669+
fn eat_keyword_case(&mut self, kw: Symbol, case: Case) -> bool {
670+
if self.eat_keyword(kw) {
671+
return true;
672+
}
673+
674+
if case == Case::Insensitive
675+
&& let Some((ident, /* is_raw */ false)) = self.token.ident()
676+
&& ident.as_str().to_lowercase() == kw.as_str().to_lowercase() {
677+
self
678+
.struct_span_err(ident.span, format!("keyword `{kw}` is written in a wrong case"))
679+
.span_suggestion(
680+
ident.span,
681+
"write it in the correct case",
682+
kw,
683+
Applicability::MachineApplicable
684+
).emit();
685+
686+
self.bump();
687+
return true;
688+
}
689+
690+
false
691+
}
692+
651693
fn eat_keyword_noexpect(&mut self, kw: Symbol) -> bool {
652694
if self.token.is_keyword(kw) {
653695
self.bump();
@@ -1127,8 +1169,8 @@ impl<'a> Parser<'a> {
11271169
}
11281170

11291171
/// Parses asyncness: `async` or nothing.
1130-
fn parse_asyncness(&mut self) -> Async {
1131-
if self.eat_keyword(kw::Async) {
1172+
fn parse_asyncness(&mut self, case: Case) -> Async {
1173+
if self.eat_keyword_case(kw::Async, case) {
11321174
let span = self.prev_token.uninterpolated_span();
11331175
Async::Yes { span, closure_id: DUMMY_NODE_ID, return_impl_trait_id: DUMMY_NODE_ID }
11341176
} else {
@@ -1137,19 +1179,19 @@ impl<'a> Parser<'a> {
11371179
}
11381180

11391181
/// Parses unsafety: `unsafe` or nothing.
1140-
fn parse_unsafety(&mut self) -> Unsafe {
1141-
if self.eat_keyword(kw::Unsafe) {
1182+
fn parse_unsafety(&mut self, case: Case) -> Unsafe {
1183+
if self.eat_keyword_case(kw::Unsafe, case) {
11421184
Unsafe::Yes(self.prev_token.uninterpolated_span())
11431185
} else {
11441186
Unsafe::No
11451187
}
11461188
}
11471189

11481190
/// Parses constness: `const` or nothing.
1149-
fn parse_constness(&mut self) -> Const {
1191+
fn parse_constness(&mut self, case: Case) -> Const {
11501192
// Avoid const blocks to be parsed as const items
11511193
if self.look_ahead(1, |t| t != &token::OpenDelim(Delimiter::Brace))
1152-
&& self.eat_keyword(kw::Const)
1194+
&& self.eat_keyword_case(kw::Const, case)
11531195
{
11541196
Const::Yes(self.prev_token.uninterpolated_span())
11551197
} else {
@@ -1404,8 +1446,8 @@ impl<'a> Parser<'a> {
14041446
}
14051447

14061448
/// Parses `extern string_literal?`.
1407-
fn parse_extern(&mut self) -> Extern {
1408-
if self.eat_keyword(kw::Extern) {
1449+
fn parse_extern(&mut self, case: Case) -> Extern {
1450+
if self.eat_keyword_case(kw::Extern, case) {
14091451
let mut extern_span = self.prev_token.span;
14101452
let abi = self.parse_abi();
14111453
if let Some(abi) = abi {

0 commit comments

Comments
 (0)