Rollup merge of #106620 - estebank:issue-82051, r=davidtwco

Detect struct literal needing parentheses

Fix #82051.
This commit is contained in:
Matthias Krüger 2023-01-11 21:08:07 +01:00 committed by GitHub
commit 7ee3fd2edc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 86 additions and 15 deletions

View File

@ -2,6 +2,10 @@ parse_struct_literal_body_without_path =
struct literal body without path struct literal body without path
.suggestion = you might have forgotten to add the struct literal inside the block .suggestion = you might have forgotten to add the struct literal inside the block
parse_struct_literal_needing_parens =
invalid struct literal
.suggestion = you might need to surround the struct literal in parentheses
parse_maybe_report_ambiguous_plus = parse_maybe_report_ambiguous_plus =
ambiguous `+` in a type ambiguous `+` in a type
.suggestion = use parentheses to disambiguate .suggestion = use parentheses to disambiguate

View File

@ -970,6 +970,24 @@ pub(crate) struct StructLiteralBodyWithoutPathSugg {
pub after: Span, pub after: Span,
} }
#[derive(Diagnostic)]
#[diag(parse_struct_literal_needing_parens)]
pub(crate) struct StructLiteralNeedingParens {
#[primary_span]
pub span: Span,
#[subdiagnostic]
pub sugg: StructLiteralNeedingParensSugg,
}
#[derive(Subdiagnostic)]
#[multipart_suggestion(suggestion, applicability = "machine-applicable")]
pub(crate) struct StructLiteralNeedingParensSugg {
#[suggestion_part(code = "(")]
pub before: Span,
#[suggestion_part(code = ")")]
pub after: Span,
}
#[derive(Diagnostic)] #[derive(Diagnostic)]
#[diag(parse_unmatched_angle_brackets)] #[diag(parse_unmatched_angle_brackets)]
pub(crate) struct UnmatchedAngleBrackets { pub(crate) struct UnmatchedAngleBrackets {

View File

@ -12,9 +12,10 @@ use crate::errors::{
IncorrectAwait, IncorrectSemicolon, IncorrectUseOfAwait, ParenthesesInForHead, IncorrectAwait, IncorrectSemicolon, IncorrectUseOfAwait, ParenthesesInForHead,
ParenthesesInForHeadSugg, PatternMethodParamWithoutBody, QuestionMarkInType, ParenthesesInForHeadSugg, PatternMethodParamWithoutBody, QuestionMarkInType,
QuestionMarkInTypeSugg, SelfParamNotFirst, StructLiteralBodyWithoutPath, QuestionMarkInTypeSugg, SelfParamNotFirst, StructLiteralBodyWithoutPath,
StructLiteralBodyWithoutPathSugg, SuggEscapeToUseAsIdentifier, SuggRemoveComma, StructLiteralBodyWithoutPathSugg, StructLiteralNeedingParens, StructLiteralNeedingParensSugg,
UnexpectedConstInGenericParam, UnexpectedConstParamDeclaration, SuggEscapeToUseAsIdentifier, SuggRemoveComma, UnexpectedConstInGenericParam,
UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets, UseEqInstead, UnexpectedConstParamDeclaration, UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets,
UseEqInstead,
}; };
use crate::lexer::UnmatchedBrace; use crate::lexer::UnmatchedBrace;
@ -623,12 +624,15 @@ impl<'a> Parser<'a> {
&mut self, &mut self,
lo: Span, lo: Span,
s: BlockCheckMode, s: BlockCheckMode,
maybe_struct_name: token::Token,
can_be_struct_literal: bool,
) -> Option<PResult<'a, P<Block>>> { ) -> Option<PResult<'a, P<Block>>> {
if self.token.is_ident() && self.look_ahead(1, |t| t == &token::Colon) { if self.token.is_ident() && self.look_ahead(1, |t| t == &token::Colon) {
// We might be having a struct literal where people forgot to include the path: // We might be having a struct literal where people forgot to include the path:
// fn foo() -> Foo { // fn foo() -> Foo {
// field: value, // field: value,
// } // }
info!(?maybe_struct_name, ?self.token);
let mut snapshot = self.create_snapshot_for_diagnostic(); let mut snapshot = self.create_snapshot_for_diagnostic();
let path = Path { let path = Path {
segments: ThinVec::new(), segments: ThinVec::new(),
@ -648,13 +652,6 @@ impl<'a> Parser<'a> {
// field: value, // field: value,
// } } // } }
err.delay_as_bug(); err.delay_as_bug();
self.sess.emit_err(StructLiteralBodyWithoutPath {
span: expr.span,
sugg: StructLiteralBodyWithoutPathSugg {
before: expr.span.shrink_to_lo(),
after: expr.span.shrink_to_hi(),
},
});
self.restore_snapshot(snapshot); self.restore_snapshot(snapshot);
let mut tail = self.mk_block( let mut tail = self.mk_block(
vec![self.mk_stmt_err(expr.span)], vec![self.mk_stmt_err(expr.span)],
@ -662,7 +659,25 @@ impl<'a> Parser<'a> {
lo.to(self.prev_token.span), lo.to(self.prev_token.span),
); );
tail.could_be_bare_literal = true; tail.could_be_bare_literal = true;
Ok(tail) if maybe_struct_name.is_ident() && can_be_struct_literal {
// Account for `if Example { a: one(), }.is_pos() {}`.
Err(self.sess.create_err(StructLiteralNeedingParens {
span: maybe_struct_name.span.to(expr.span),
sugg: StructLiteralNeedingParensSugg {
before: maybe_struct_name.span.shrink_to_lo(),
after: expr.span.shrink_to_hi(),
},
}))
} else {
self.sess.emit_err(StructLiteralBodyWithoutPath {
span: expr.span,
sugg: StructLiteralBodyWithoutPathSugg {
before: expr.span.shrink_to_lo(),
after: expr.span.shrink_to_hi(),
},
});
Ok(tail)
}
} }
(Err(err), Ok(tail)) => { (Err(err), Ok(tail)) => {
// We have a block tail that contains a somehow valid type ascription expr. // We have a block tail that contains a somehow valid type ascription expr.

View File

@ -2039,7 +2039,7 @@ impl<'a> Parser<'a> {
}); });
} }
let (attrs, blk) = self.parse_block_common(lo, blk_mode)?; let (attrs, blk) = self.parse_block_common(lo, blk_mode, true)?;
Ok(self.mk_expr_with_attrs(blk.span, ExprKind::Block(blk, opt_label), attrs)) Ok(self.mk_expr_with_attrs(blk.span, ExprKind::Block(blk, opt_label), attrs))
} }

View File

@ -2214,7 +2214,8 @@ impl<'a> Parser<'a> {
*sig_hi = self.prev_token.span; *sig_hi = self.prev_token.span;
(AttrVec::new(), None) (AttrVec::new(), None)
} else if self.check(&token::OpenDelim(Delimiter::Brace)) || self.token.is_whole_block() { } else if self.check(&token::OpenDelim(Delimiter::Brace)) || self.token.is_whole_block() {
self.parse_inner_attrs_and_block().map(|(attrs, body)| (attrs, Some(body)))? self.parse_block_common(self.token.span, BlockCheckMode::Default, false)
.map(|(attrs, body)| (attrs, Some(body)))?
} else if self.token.kind == token::Eq { } else if self.token.kind == token::Eq {
// Recover `fn foo() = $expr;`. // Recover `fn foo() = $expr;`.
self.bump(); // `=` self.bump(); // `=`

View File

@ -498,7 +498,7 @@ impl<'a> Parser<'a> {
/// Parses a block. Inner attributes are allowed. /// Parses a block. Inner attributes are allowed.
pub(super) fn parse_inner_attrs_and_block(&mut self) -> PResult<'a, (AttrVec, P<Block>)> { pub(super) fn parse_inner_attrs_and_block(&mut self) -> PResult<'a, (AttrVec, P<Block>)> {
self.parse_block_common(self.token.span, BlockCheckMode::Default) self.parse_block_common(self.token.span, BlockCheckMode::Default, true)
} }
/// Parses a block. Inner attributes are allowed. /// Parses a block. Inner attributes are allowed.
@ -506,16 +506,23 @@ impl<'a> Parser<'a> {
&mut self, &mut self,
lo: Span, lo: Span,
blk_mode: BlockCheckMode, blk_mode: BlockCheckMode,
can_be_struct_literal: bool,
) -> PResult<'a, (AttrVec, P<Block>)> { ) -> PResult<'a, (AttrVec, P<Block>)> {
maybe_whole!(self, NtBlock, |x| (AttrVec::new(), x)); maybe_whole!(self, NtBlock, |x| (AttrVec::new(), x));
let maybe_ident = self.prev_token.clone();
self.maybe_recover_unexpected_block_label(); self.maybe_recover_unexpected_block_label();
if !self.eat(&token::OpenDelim(Delimiter::Brace)) { if !self.eat(&token::OpenDelim(Delimiter::Brace)) {
return self.error_block_no_opening_brace(); return self.error_block_no_opening_brace();
} }
let attrs = self.parse_inner_attributes()?; let attrs = self.parse_inner_attributes()?;
let tail = match self.maybe_suggest_struct_literal(lo, blk_mode) { let tail = match self.maybe_suggest_struct_literal(
lo,
blk_mode,
maybe_ident,
can_be_struct_literal,
) {
Some(tail) => tail?, Some(tail) => tail?,
None => self.parse_block_tail(lo, blk_mode, AttemptLocalParseRecovery::Yes)?, None => self.parse_block_tail(lo, blk_mode, AttemptLocalParseRecovery::Yes)?,
}; };

View File

@ -0,0 +1,13 @@
pub struct Example { a: i32 }
impl Example {
fn is_pos(&self) -> bool { self.a > 0 }
}
fn one() -> i32 { 1 }
fn main() {
if Example { a: one(), }.is_pos() { //~ ERROR invalid struct literal
println!("Positive!");
}
}

View File

@ -0,0 +1,13 @@
error: invalid struct literal
--> $DIR/method-call-on-struct-literal-in-if-condition.rs:10:8
|
LL | if Example { a: one(), }.is_pos() {
| ^^^^^^^^^^^^^^^^^^^^^
|
help: you might need to surround the struct literal in parentheses
|
LL | if (Example { a: one(), }).is_pos() {
| + +
error: aborting due to previous error