Auto merge of #42578 - estebank:recover-binop, r=nikomatsakis
Learn to parse `a as usize < b` Parsing `a as usize > b` always works, but `a as usize < b` was a parsing error because the parser would think the `<` started a generic type argument for `usize`. The parser now attempts to parse as before, and if a DiagnosticError is returned, try to parse again as a type with no generic arguments. If this fails, return the original `DiagnosticError`. Fix #22644.
This commit is contained in:
commit
44eeb2109b
5 changed files with 140 additions and 9 deletions
|
@ -248,6 +248,10 @@ impl Diagnostic {
|
||||||
self.message.iter().map(|i| i.0.to_owned()).collect::<String>()
|
self.message.iter().map(|i| i.0.to_owned()).collect::<String>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_message(&mut self, message: &str) {
|
||||||
|
self.message = vec![(message.to_owned(), Style::NoStyle)];
|
||||||
|
}
|
||||||
|
|
||||||
pub fn styled_message(&self) -> &Vec<(String, Style)> {
|
pub fn styled_message(&self) -> &Vec<(String, Style)> {
|
||||||
&self.message
|
&self.message
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,7 +150,7 @@ fn maybe_append(mut lhs: Vec<Attribute>, rhs: Option<Vec<Attribute>>)
|
||||||
lhs
|
lhs
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
enum PrevTokenKind {
|
enum PrevTokenKind {
|
||||||
DocComment,
|
DocComment,
|
||||||
Comma,
|
Comma,
|
||||||
|
@ -162,6 +162,7 @@ enum PrevTokenKind {
|
||||||
|
|
||||||
/* ident is handled by common.rs */
|
/* ident is handled by common.rs */
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Parser<'a> {
|
pub struct Parser<'a> {
|
||||||
pub sess: &'a ParseSess,
|
pub sess: &'a ParseSess,
|
||||||
/// the current token:
|
/// the current token:
|
||||||
|
@ -193,11 +194,13 @@ pub struct Parser<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
struct TokenCursor {
|
struct TokenCursor {
|
||||||
frame: TokenCursorFrame,
|
frame: TokenCursorFrame,
|
||||||
stack: Vec<TokenCursorFrame>,
|
stack: Vec<TokenCursorFrame>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
struct TokenCursorFrame {
|
struct TokenCursorFrame {
|
||||||
delim: token::DelimToken,
|
delim: token::DelimToken,
|
||||||
span: Span,
|
span: Span,
|
||||||
|
@ -397,6 +400,7 @@ impl Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum LhsExpr {
|
pub enum LhsExpr {
|
||||||
NotYetParsed,
|
NotYetParsed,
|
||||||
AttributesParsed(ThinVec<Attribute>),
|
AttributesParsed(ThinVec<Attribute>),
|
||||||
|
@ -1721,7 +1725,7 @@ impl<'a> Parser<'a> {
|
||||||
|
|
||||||
let segments = match mode {
|
let segments = match mode {
|
||||||
PathStyle::Type => {
|
PathStyle::Type => {
|
||||||
self.parse_path_segments_without_colons()?
|
self.parse_path_segments_without_colons(true)?
|
||||||
}
|
}
|
||||||
PathStyle::Expr => {
|
PathStyle::Expr => {
|
||||||
self.parse_path_segments_with_colons()?
|
self.parse_path_segments_with_colons()?
|
||||||
|
@ -1742,6 +1746,16 @@ impl<'a> Parser<'a> {
|
||||||
/// bounds are permitted and whether `::` must precede type parameter
|
/// bounds are permitted and whether `::` must precede type parameter
|
||||||
/// groups.
|
/// groups.
|
||||||
pub fn parse_path(&mut self, mode: PathStyle) -> PResult<'a, ast::Path> {
|
pub fn parse_path(&mut self, mode: PathStyle) -> PResult<'a, ast::Path> {
|
||||||
|
self.parse_path_common(mode, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_path_without_generics(&mut self, mode: PathStyle) -> PResult<'a, ast::Path> {
|
||||||
|
self.parse_path_common(mode, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_path_common(&mut self, mode: PathStyle, parse_generics: bool)
|
||||||
|
-> PResult<'a, ast::Path>
|
||||||
|
{
|
||||||
maybe_whole!(self, NtPath, |x| x);
|
maybe_whole!(self, NtPath, |x| x);
|
||||||
|
|
||||||
let lo = self.meta_var_span.unwrap_or(self.span);
|
let lo = self.meta_var_span.unwrap_or(self.span);
|
||||||
|
@ -1752,7 +1766,7 @@ impl<'a> Parser<'a> {
|
||||||
// A bound set is a set of type parameter bounds.
|
// A bound set is a set of type parameter bounds.
|
||||||
let mut segments = match mode {
|
let mut segments = match mode {
|
||||||
PathStyle::Type => {
|
PathStyle::Type => {
|
||||||
self.parse_path_segments_without_colons()?
|
self.parse_path_segments_without_colons(parse_generics)?
|
||||||
}
|
}
|
||||||
PathStyle::Expr => {
|
PathStyle::Expr => {
|
||||||
self.parse_path_segments_with_colons()?
|
self.parse_path_segments_with_colons()?
|
||||||
|
@ -1797,7 +1811,9 @@ impl<'a> Parser<'a> {
|
||||||
/// - `a::b<T,U>::c<V,W>`
|
/// - `a::b<T,U>::c<V,W>`
|
||||||
/// - `a::b<T,U>::c(V) -> W`
|
/// - `a::b<T,U>::c(V) -> W`
|
||||||
/// - `a::b<T,U>::c(V)`
|
/// - `a::b<T,U>::c(V)`
|
||||||
pub fn parse_path_segments_without_colons(&mut self) -> PResult<'a, Vec<PathSegment>> {
|
pub fn parse_path_segments_without_colons(&mut self, parse_generics: bool)
|
||||||
|
-> PResult<'a, Vec<PathSegment>>
|
||||||
|
{
|
||||||
let mut segments = Vec::new();
|
let mut segments = Vec::new();
|
||||||
loop {
|
loop {
|
||||||
// First, parse an identifier.
|
// First, parse an identifier.
|
||||||
|
@ -1816,7 +1832,7 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse types, optionally.
|
// Parse types, optionally.
|
||||||
let parameters = if self.eat_lt() {
|
let parameters = if parse_generics && self.eat_lt() {
|
||||||
let (lifetimes, types, bindings) = self.parse_generic_args()?;
|
let (lifetimes, types, bindings) = self.parse_generic_args()?;
|
||||||
self.expect_gt()?;
|
self.expect_gt()?;
|
||||||
ast::AngleBracketedParameterData {
|
ast::AngleBracketedParameterData {
|
||||||
|
@ -2795,8 +2811,9 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
// Special cases:
|
// Special cases:
|
||||||
if op == AssocOp::As {
|
if op == AssocOp::As {
|
||||||
let rhs = self.parse_ty_no_plus()?;
|
// Save the state of the parser before parsing type normally, in case there is a
|
||||||
lhs = self.mk_expr(lhs_span.to(rhs.span), ExprKind::Cast(lhs, rhs), ThinVec::new());
|
// LessThan comparison after this cast.
|
||||||
|
lhs = self.parse_assoc_op_as(lhs, lhs_span)?;
|
||||||
continue
|
continue
|
||||||
} else if op == AssocOp::Colon {
|
} else if op == AssocOp::Colon {
|
||||||
let rhs = self.parse_ty_no_plus()?;
|
let rhs = self.parse_ty_no_plus()?;
|
||||||
|
@ -2894,11 +2911,74 @@ impl<'a> Parser<'a> {
|
||||||
Ok(lhs)
|
Ok(lhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_assoc_op_as(&mut self, lhs: P<Expr>, lhs_span: Span) -> PResult<'a, P<Expr>> {
|
||||||
|
let rp = self.clone();
|
||||||
|
match self.parse_ty_no_plus() {
|
||||||
|
Ok(rhs) => {
|
||||||
|
Ok(self.mk_expr(lhs_span.to(rhs.span),
|
||||||
|
ExprKind::Cast(lhs, rhs),
|
||||||
|
ThinVec::new()))
|
||||||
|
}
|
||||||
|
Err(mut err) => {
|
||||||
|
let rp_err = self.clone();
|
||||||
|
let sp = rp_err.span.clone();
|
||||||
|
|
||||||
|
// Rewind to before attempting to parse the type with generics, to get
|
||||||
|
// arround #22644.
|
||||||
|
mem::replace(self, rp);
|
||||||
|
let lo = self.span;
|
||||||
|
match self.parse_path_without_generics(PathStyle::Type) {
|
||||||
|
Ok(path) => {
|
||||||
|
// Successfully parsed the type leaving a `<` yet to parse
|
||||||
|
err.cancel();
|
||||||
|
let codemap = self.sess.codemap();
|
||||||
|
let suggestion_span = lhs_span.to(self.prev_span);
|
||||||
|
let suggestion = match codemap.span_to_snippet(suggestion_span) {
|
||||||
|
Ok(lstring) => format!("({})", lstring),
|
||||||
|
_ => format!("(<expression> as <type>)")
|
||||||
|
};
|
||||||
|
let warn_message = match codemap.span_to_snippet(self.prev_span) {
|
||||||
|
Ok(lstring) => format!("`{}`", lstring),
|
||||||
|
_ => "a type".to_string(),
|
||||||
|
};
|
||||||
|
let msg = format!("`<` is interpreted as a start of generic \
|
||||||
|
arguments for {}, not a comparison",
|
||||||
|
warn_message);
|
||||||
|
let mut err = self.sess.span_diagnostic.struct_span_err(sp, &msg);
|
||||||
|
err.span_label(sp, "interpreted as generic argument");
|
||||||
|
err.span_label(self.span, "not interpreted as comparison");
|
||||||
|
err.span_suggestion(suggestion_span,
|
||||||
|
"if you want to compare the casted value then write:",
|
||||||
|
suggestion);
|
||||||
|
err.emit();
|
||||||
|
|
||||||
|
let path = TyKind::Path(None, path);
|
||||||
|
let span = lo.to(self.prev_span);
|
||||||
|
let rhs = P(Ty { node: path, span: span, id: ast::DUMMY_NODE_ID });
|
||||||
|
// Letting the parser accept the recovered type to avoid further errors,
|
||||||
|
// but the code will still not compile due to the error emitted above.
|
||||||
|
Ok(self.mk_expr(lhs_span.to(rhs.span),
|
||||||
|
ExprKind::Cast(lhs, rhs),
|
||||||
|
ThinVec::new()))
|
||||||
|
}
|
||||||
|
Err(mut path_err) => {
|
||||||
|
// Still couldn't parse, return original error and parser state
|
||||||
|
path_err.cancel();
|
||||||
|
mem::replace(self, rp_err);
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Produce an error if comparison operators are chained (RFC #558).
|
/// Produce an error if comparison operators are chained (RFC #558).
|
||||||
/// We only need to check lhs, not rhs, because all comparison ops
|
/// We only need to check lhs, not rhs, because all comparison ops
|
||||||
/// have same precedence and are left-associative
|
/// have same precedence and are left-associative
|
||||||
fn check_no_chained_comparison(&mut self, lhs: &Expr, outer_op: &AssocOp) {
|
fn check_no_chained_comparison(&mut self, lhs: &Expr, outer_op: &AssocOp) {
|
||||||
debug_assert!(outer_op.is_comparison());
|
debug_assert!(outer_op.is_comparison(),
|
||||||
|
"check_no_chained_comparison: {:?} is not comparison",
|
||||||
|
outer_op);
|
||||||
match lhs.node {
|
match lhs.node {
|
||||||
ExprKind::Binary(op, _, _) if op.node.is_comparison() => {
|
ExprKind::Binary(op, _, _) if op.node.is_comparison() => {
|
||||||
// respan to include both operators
|
// respan to include both operators
|
||||||
|
@ -2922,7 +3002,9 @@ impl<'a> Parser<'a> {
|
||||||
fn parse_prefix_range_expr(&mut self,
|
fn parse_prefix_range_expr(&mut self,
|
||||||
already_parsed_attrs: Option<ThinVec<Attribute>>)
|
already_parsed_attrs: Option<ThinVec<Attribute>>)
|
||||||
-> PResult<'a, P<Expr>> {
|
-> PResult<'a, P<Expr>> {
|
||||||
debug_assert!(self.token == token::DotDot || self.token == token::DotDotDot);
|
debug_assert!(self.token == token::DotDot || self.token == token::DotDotDot,
|
||||||
|
"parse_prefix_range_expr: token {:?} is not DotDot or DotDotDot",
|
||||||
|
self.token);
|
||||||
let tok = self.token.clone();
|
let tok = self.token.clone();
|
||||||
let attrs = self.parse_or_use_outer_attributes(already_parsed_attrs)?;
|
let attrs = self.parse_or_use_outer_attributes(already_parsed_attrs)?;
|
||||||
let lo = self.span;
|
let lo = self.span;
|
||||||
|
|
|
@ -227,14 +227,17 @@ impl TokenStream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Cursor(CursorKind);
|
pub struct Cursor(CursorKind);
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
enum CursorKind {
|
enum CursorKind {
|
||||||
Empty,
|
Empty,
|
||||||
Tree(TokenTree, bool /* consumed? */),
|
Tree(TokenTree, bool /* consumed? */),
|
||||||
Stream(StreamCursor),
|
Stream(StreamCursor),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
struct StreamCursor {
|
struct StreamCursor {
|
||||||
stream: RcSlice<TokenStream>,
|
stream: RcSlice<TokenStream>,
|
||||||
index: usize,
|
index: usize,
|
||||||
|
|
18
src/test/ui/issue-22644.rs
Normal file
18
src/test/ui/issue-22644.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let a : u32 = 0;
|
||||||
|
let b : usize = 0;
|
||||||
|
|
||||||
|
println!("{}", a as usize > b);
|
||||||
|
println!("{}", a as usize < b);
|
||||||
|
println!("{}", a as usize < 4);
|
||||||
|
}
|
24
src/test/ui/issue-22644.stderr
Normal file
24
src/test/ui/issue-22644.stderr
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
error: `<` is interpreted as a start of generic arguments for `usize`, not a comparison
|
||||||
|
--> $DIR/issue-22644.rs:16:33
|
||||||
|
|
|
||||||
|
16 | println!("{}", a as usize < b);
|
||||||
|
| - ^ interpreted as generic argument
|
||||||
|
| |
|
||||||
|
| not interpreted as comparison
|
||||||
|
|
|
||||||
|
help: if you want to compare the casted value then write:
|
||||||
|
| println!("{}", (a as usize) < b);
|
||||||
|
|
||||||
|
error: `<` is interpreted as a start of generic arguments for `usize`, not a comparison
|
||||||
|
--> $DIR/issue-22644.rs:17:33
|
||||||
|
|
|
||||||
|
17 | println!("{}", a as usize < 4);
|
||||||
|
| - ^ interpreted as generic argument
|
||||||
|
| |
|
||||||
|
| not interpreted as comparison
|
||||||
|
|
|
||||||
|
help: if you want to compare the casted value then write:
|
||||||
|
| println!("{}", (a as usize) < 4);
|
||||||
|
|
||||||
|
error: aborting due to previous error(s)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue