Rollup merge of #126762 - compiler-errors:kw-lt, r=michaelwoerister

Deny keyword lifetimes pre-expansion

https://github.com/rust-lang/rust/pull/126452#issuecomment-2179464266

> Secondly, we confirmed that we're OK with moving the validation of keywords in lifetimes to pre-expansion from post-expansion. We similarly consider this a bug fix. While the breakage of the convenience feature of the with_locals crate that relies on this is unfortunate, and we wish we had not overlooked this earlier for that reason, we're fortunate that the breakage is contained to only one crate, and we're going to accept this breakage as the extra complexity we'd need to carry in the compiler to work around this isn't deemed worth it.

T-lang considers it to be a bugfix to deny `'keyword` lifetimes in the parser, rather than during AST validation that only happens post-expansion. This has one breakage: https://github.com/rust-lang/rust/pull/126452#issuecomment-2171654756

This probably should get lang FCP'd just for consistency.
This commit is contained in:
Trevor Gross 2024-07-16 16:15:15 -05:00 committed by GitHub
commit 9833e21c5d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 99 additions and 70 deletions

View File

@ -159,9 +159,6 @@ ast_passes_inherent_cannot_be = inherent impls cannot be {$annotation}
.type = inherent impl for this type .type = inherent impl for this type
.only_trait = only trait implementations may be annotated with {$annotation} .only_trait = only trait implementations may be annotated with {$annotation}
ast_passes_invalid_label =
invalid label name `{$name}`
ast_passes_invalid_unnamed_field = ast_passes_invalid_unnamed_field =
unnamed fields are not allowed outside of structs or unions unnamed fields are not allowed outside of structs or unions
.label = unnamed field declared here .label = unnamed field declared here
@ -176,9 +173,6 @@ ast_passes_item_invalid_safety = items outside of `unsafe extern {"{ }"}` cannot
ast_passes_item_underscore = `{$kind}` items in this context need a name ast_passes_item_underscore = `{$kind}` items in this context need a name
.label = `_` is not a valid name for this `{$kind}` item .label = `_` is not a valid name for this `{$kind}` item
ast_passes_keyword_lifetime =
lifetimes cannot use keyword names
ast_passes_match_arm_with_no_body = ast_passes_match_arm_with_no_body =
`match` arm with no body `match` arm with no body
.suggestion = add a body after the pattern .suggestion = add a body after the pattern

View File

@ -269,19 +269,6 @@ fn dcx(&self) -> DiagCtxtHandle<'a> {
self.session.dcx() self.session.dcx()
} }
fn check_lifetime(&self, ident: Ident) {
let valid_names = [kw::UnderscoreLifetime, kw::StaticLifetime, kw::Empty];
if !valid_names.contains(&ident.name) && ident.without_first_quote().is_reserved() {
self.dcx().emit_err(errors::KeywordLifetime { span: ident.span });
}
}
fn check_label(&self, ident: Ident) {
if ident.without_first_quote().is_reserved() {
self.dcx().emit_err(errors::InvalidLabel { span: ident.span, name: ident.name });
}
}
fn visibility_not_permitted(&self, vis: &Visibility, note: errors::VisibilityNotPermittedNote) { fn visibility_not_permitted(&self, vis: &Visibility, note: errors::VisibilityNotPermittedNote) {
if let VisibilityKind::Inherited = vis.kind { if let VisibilityKind::Inherited = vis.kind {
return; return;
@ -908,16 +895,6 @@ fn visit_ty(&mut self, ty: &'a Ty) {
self.walk_ty(ty) self.walk_ty(ty)
} }
fn visit_label(&mut self, label: &'a Label) {
self.check_label(label.ident);
visit::walk_label(self, label);
}
fn visit_lifetime(&mut self, lifetime: &'a Lifetime, _: visit::LifetimeCtxt) {
self.check_lifetime(lifetime.ident);
visit::walk_lifetime(self, lifetime);
}
fn visit_field_def(&mut self, field: &'a FieldDef) { fn visit_field_def(&mut self, field: &'a FieldDef) {
self.deny_unnamed_field(field); self.deny_unnamed_field(field);
visit::walk_field_def(self, field) visit::walk_field_def(self, field)
@ -1356,13 +1333,6 @@ fn visit_generics(&mut self, generics: &'a Generics) {
} }
} }
fn visit_generic_param(&mut self, param: &'a GenericParam) {
if let GenericParamKind::Lifetime { .. } = param.kind {
self.check_lifetime(param.ident);
}
visit::walk_generic_param(self, param);
}
fn visit_param_bound(&mut self, bound: &'a GenericBound, ctxt: BoundKind) { fn visit_param_bound(&mut self, bound: &'a GenericBound, ctxt: BoundKind) {
match bound { match bound {
GenericBound::Trait(trait_ref, modifiers) => { GenericBound::Trait(trait_ref, modifiers) => {

View File

@ -9,21 +9,6 @@
use crate::fluent_generated as fluent; use crate::fluent_generated as fluent;
#[derive(Diagnostic)]
#[diag(ast_passes_keyword_lifetime)]
pub struct KeywordLifetime {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(ast_passes_invalid_label)]
pub struct InvalidLabel {
#[primary_span]
pub span: Span,
pub name: Symbol,
}
#[derive(Diagnostic)] #[derive(Diagnostic)]
#[diag(ast_passes_visibility_not_permitted, code = E0449)] #[diag(ast_passes_visibility_not_permitted, code = E0449)]
pub struct VisibilityNotPermitted { pub struct VisibilityNotPermitted {

View File

@ -388,6 +388,9 @@ parse_invalid_dyn_keyword = invalid `dyn` keyword
parse_invalid_expression_in_let_else = a `{$operator}` expression cannot be directly assigned in `let...else` parse_invalid_expression_in_let_else = a `{$operator}` expression cannot be directly assigned in `let...else`
parse_invalid_identifier_with_leading_number = identifiers cannot start with a number parse_invalid_identifier_with_leading_number = identifiers cannot start with a number
parse_invalid_label =
invalid label name `{$name}`
parse_invalid_literal_suffix_on_tuple_index = suffixes on a tuple index are invalid parse_invalid_literal_suffix_on_tuple_index = suffixes on a tuple index are invalid
.label = invalid suffix `{$suffix}` .label = invalid suffix `{$suffix}`
.tuple_exception_line_1 = `{$suffix}` is *temporarily* accepted on tuple index fields as it was incorrectly accepted on stable for a few releases .tuple_exception_line_1 = `{$suffix}` is *temporarily* accepted on tuple index fields as it was incorrectly accepted on stable for a few releases
@ -414,6 +417,9 @@ parse_invalid_unicode_escape = invalid unicode character escape
parse_invalid_variable_declaration = parse_invalid_variable_declaration =
invalid variable declaration invalid variable declaration
parse_keyword_lifetime =
lifetimes cannot use keyword names
parse_kw_bad_case = keyword `{$kw}` is written in the wrong case parse_kw_bad_case = keyword `{$kw}` is written in the wrong case
.suggestion = write it in the correct case .suggestion = write it in the correct case

View File

@ -2009,6 +2009,21 @@ pub struct CannotBeRawIdent {
pub ident: Symbol, pub ident: Symbol,
} }
#[derive(Diagnostic)]
#[diag(parse_keyword_lifetime)]
pub struct KeywordLifetime {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(parse_invalid_label)]
pub struct InvalidLabel {
#[primary_span]
pub span: Span,
pub name: Symbol,
}
#[derive(Diagnostic)] #[derive(Diagnostic)]
#[diag(parse_cr_doc_comment)] #[diag(parse_cr_doc_comment)]
pub struct CrDocComment { pub struct CrDocComment {

View File

@ -2932,10 +2932,17 @@ fn parse_expr_loop(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a,
} }
pub(crate) fn eat_label(&mut self) -> Option<Label> { pub(crate) fn eat_label(&mut self) -> Option<Label> {
self.token.lifetime().map(|ident| { if let Some(ident) = self.token.lifetime() {
// Disallow `'fn`, but with a better error message than `expect_lifetime`.
if ident.without_first_quote().is_reserved() {
self.dcx().emit_err(errors::InvalidLabel { span: ident.span, name: ident.name });
}
self.bump(); self.bump();
Label { ident } Some(Label { ident })
}) } else {
None
}
} }
/// Parses a `match ... { ... }` expression (`match` token already eaten). /// Parses a `match ... { ... }` expression (`match` token already eaten).

View File

@ -177,8 +177,11 @@ pub fn parse_nonterminal(&mut self, kind: NonterminalKind) -> PResult<'a, ParseN
.collect_tokens_no_attrs(|this| this.parse_visibility(FollowedByType::Yes))?)) .collect_tokens_no_attrs(|this| this.parse_visibility(FollowedByType::Yes))?))
} }
NonterminalKind::Lifetime => { NonterminalKind::Lifetime => {
return if self.check_lifetime() { // We want to keep `'keyword` parsing, just like `keyword` is still
Ok(ParseNtResult::Lifetime(self.expect_lifetime().ident)) // an ident for nonterminal purposes.
return if let Some(ident) = self.token.lifetime() {
self.bump();
Ok(ParseNtResult::Lifetime(ident))
} else { } else {
Err(self.dcx().create_err(UnexpectedNonterminal::Lifetime { Err(self.dcx().create_err(UnexpectedNonterminal::Lifetime {
span: self.token.span, span: self.token.span,

View File

@ -542,12 +542,12 @@ fn parse_pat_with_range_pat(
None => PatKind::Path(qself, path), None => PatKind::Path(qself, path),
} }
} }
} else if let token::Lifetime(lt) = self.token.kind } else if let Some(lt) = self.token.lifetime()
// In pattern position, we're totally fine with using "next token isn't colon" // In pattern position, we're totally fine with using "next token isn't colon"
// as a heuristic. We could probably just always try to recover if it's a lifetime, // as a heuristic. We could probably just always try to recover if it's a lifetime,
// because we never have `'a: label {}` in a pattern position anyways, but it does // because we never have `'a: label {}` in a pattern position anyways, but it does
// keep us from suggesting something like `let 'a: Ty = ..` => `let 'a': Ty = ..` // keep us from suggesting something like `let 'a: Ty = ..` => `let 'a': Ty = ..`
&& could_be_unclosed_char_literal(Ident::with_dummy_span(lt)) && could_be_unclosed_char_literal(lt)
&& !self.look_ahead(1, |token| matches!(token.kind, token::Colon)) && !self.look_ahead(1, |token| matches!(token.kind, token::Colon))
{ {
// Recover a `'a` as a `'a'` literal // Recover a `'a` as a `'a'` literal
@ -683,12 +683,12 @@ fn ban_pat_range_if_ambiguous(&self, pat: &Pat) {
/// Parse `&pat` / `&mut pat`. /// Parse `&pat` / `&mut pat`.
fn parse_pat_deref(&mut self, expected: Option<Expected>) -> PResult<'a, PatKind> { fn parse_pat_deref(&mut self, expected: Option<Expected>) -> PResult<'a, PatKind> {
self.expect_and()?; self.expect_and()?;
if let token::Lifetime(name) = self.token.kind { if let Some(lifetime) = self.token.lifetime() {
self.bump(); // `'a` self.bump(); // `'a`
self.dcx().emit_err(UnexpectedLifetimeInPattern { self.dcx().emit_err(UnexpectedLifetimeInPattern {
span: self.prev_token.span, span: self.prev_token.span,
symbol: name, symbol: lifetime.name,
suggestion: self.prev_token.span.until(self.token.span), suggestion: self.prev_token.span.until(self.token.span),
}); });
} }

View File

@ -1230,6 +1230,12 @@ pub(super) fn check_lifetime(&mut self) -> bool {
/// Parses a single lifetime `'a` or panics. /// Parses a single lifetime `'a` or panics.
pub(super) fn expect_lifetime(&mut self) -> Lifetime { pub(super) fn expect_lifetime(&mut self) -> Lifetime {
if let Some(ident) = self.token.lifetime() { if let Some(ident) = self.token.lifetime() {
if ident.without_first_quote().is_reserved()
&& ![kw::UnderscoreLifetime, kw::StaticLifetime].contains(&ident.name)
{
self.dcx().emit_err(errors::KeywordLifetime { span: ident.span });
}
self.bump(); self.bump();
Lifetime { ident, id: ast::DUMMY_NODE_ID } Lifetime { ident, id: ast::DUMMY_NODE_ID }
} else { } else {

View File

@ -0,0 +1,15 @@
// Disallow `'keyword` even in cfg'd code.
#[cfg(any())]
fn hello() -> &'ref () {}
//~^ ERROR lifetimes cannot use keyword names
macro_rules! macro_invocation {
($i:item) => {}
}
macro_invocation! {
fn hello() -> &'ref () {}
//~^ ERROR lifetimes cannot use keyword names
}
fn main() {}

View File

@ -0,0 +1,14 @@
error: lifetimes cannot use keyword names
--> $DIR/cfg-keyword-lifetime.rs:4:16
|
LL | fn hello() -> &'ref () {}
| ^^^^
error: lifetimes cannot use keyword names
--> $DIR/cfg-keyword-lifetime.rs:11:20
|
LL | fn hello() -> &'ref () {}
| ^^^^
error: aborting due to 2 previous errors

View File

@ -24,12 +24,14 @@ fn main() {
//~| HELP use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments //~| HELP use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments
//~| ERROR expected //~| ERROR expected
//~| HELP add `'` to close the char literal //~| HELP add `'` to close the char literal
//~| ERROR invalid label name
f<'_>(); f<'_>();
//~^ comparison operators cannot be chained //~^ comparison operators cannot be chained
//~| HELP use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments //~| HELP use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments
//~| ERROR expected //~| ERROR expected
//~| HELP add `'` to close the char literal //~| HELP add `'` to close the char literal
//~| ERROR invalid label name
let _ = f<u8>; let _ = f<u8>;
//~^ ERROR comparison operators cannot be chained //~^ ERROR comparison operators cannot be chained

View File

@ -53,6 +53,12 @@ help: use `::<...>` instead of `<...>` to specify lifetime, type, or const argum
LL | let _ = f::<u8, i8>(); LL | let _ = f::<u8, i8>();
| ++ | ++
error: invalid label name `'_`
--> $DIR/require-parens-for-chained-comparison.rs:22:15
|
LL | let _ = f<'_, i8>();
| ^^
error: expected `while`, `for`, `loop` or `{` after a label error: expected `while`, `for`, `loop` or `{` after a label
--> $DIR/require-parens-for-chained-comparison.rs:22:17 --> $DIR/require-parens-for-chained-comparison.rs:22:17
| |
@ -75,8 +81,14 @@ help: use `::<...>` instead of `<...>` to specify lifetime, type, or const argum
LL | let _ = f::<'_, i8>(); LL | let _ = f::<'_, i8>();
| ++ | ++
error: invalid label name `'_`
--> $DIR/require-parens-for-chained-comparison.rs:29:7
|
LL | f<'_>();
| ^^
error: expected `while`, `for`, `loop` or `{` after a label error: expected `while`, `for`, `loop` or `{` after a label
--> $DIR/require-parens-for-chained-comparison.rs:28:9 --> $DIR/require-parens-for-chained-comparison.rs:29:9
| |
LL | f<'_>(); LL | f<'_>();
| ^ expected `while`, `for`, `loop` or `{` after a label | ^ expected `while`, `for`, `loop` or `{` after a label
@ -87,7 +99,7 @@ LL | f<'_'>();
| + | +
error: comparison operators cannot be chained error: comparison operators cannot be chained
--> $DIR/require-parens-for-chained-comparison.rs:28:6 --> $DIR/require-parens-for-chained-comparison.rs:29:6
| |
LL | f<'_>(); LL | f<'_>();
| ^ ^ | ^ ^
@ -98,7 +110,7 @@ LL | f::<'_>();
| ++ | ++
error: comparison operators cannot be chained error: comparison operators cannot be chained
--> $DIR/require-parens-for-chained-comparison.rs:34:14 --> $DIR/require-parens-for-chained-comparison.rs:36:14
| |
LL | let _ = f<u8>; LL | let _ = f<u8>;
| ^ ^ | ^ ^
@ -106,5 +118,5 @@ LL | let _ = f<u8>;
= help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments = help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments
= help: or use `(...)` if you meant to specify fn arguments = help: or use `(...)` if you meant to specify fn arguments
error: aborting due to 10 previous errors error: aborting due to 12 previous errors

View File

@ -4,6 +4,12 @@ error: expected identifier, found keyword `Self`
LL | struct Self; LL | struct Self;
| ^^^^ expected identifier, found keyword | ^^^^ expected identifier, found keyword
error: lifetimes cannot use keyword names
--> $DIR/self_type_keyword.rs:6:12
|
LL | struct Bar<'Self>;
| ^^^^^
error: expected identifier, found keyword `Self` error: expected identifier, found keyword `Self`
--> $DIR/self_type_keyword.rs:14:13 --> $DIR/self_type_keyword.rs:14:13
| |
@ -53,12 +59,6 @@ error: expected identifier, found keyword `Self`
LL | trait Self {} LL | trait Self {}
| ^^^^ expected identifier, found keyword | ^^^^ expected identifier, found keyword
error: lifetimes cannot use keyword names
--> $DIR/self_type_keyword.rs:6:12
|
LL | struct Bar<'Self>;
| ^^^^^
error: cannot find macro `Self` in this scope error: cannot find macro `Self` in this scope
--> $DIR/self_type_keyword.rs:21:9 --> $DIR/self_type_keyword.rs:21:9
| |