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:
commit
9833e21c5d
@ -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
|
||||||
|
@ -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) => {
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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).
|
||||||
|
@ -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,
|
||||||
|
@ -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),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
15
tests/ui/parser/cfg-keyword-lifetime.rs
Normal file
15
tests/ui/parser/cfg-keyword-lifetime.rs
Normal 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() {}
|
14
tests/ui/parser/cfg-keyword-lifetime.stderr
Normal file
14
tests/ui/parser/cfg-keyword-lifetime.stderr
Normal 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
|
||||||
|
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
|
|
||||||
|
Loading…
Reference in New Issue
Block a user