]> git.lizzy.rs Git - rust.git/commitdiff
syntax: recovery for incorrect associated item paths like `[T; N]::clone`
authorVadim Petrochenkov <vadim.petrochenkov@gmail.com>
Sat, 16 Dec 2017 22:53:11 +0000 (01:53 +0300)
committerVadim Petrochenkov <vadim.petrochenkov@gmail.com>
Sun, 17 Dec 2017 16:00:50 +0000 (19:00 +0300)
src/libsyntax/ast.rs
src/libsyntax/lib.rs
src/libsyntax/parse/parser.rs
src/test/ui/did_you_mean/bad-assoc-expr.rs [new file with mode: 0644]
src/test/ui/did_you_mean/bad-assoc-expr.stderr [new file with mode: 0644]
src/test/ui/did_you_mean/bad-assoc-pat.rs [new file with mode: 0644]
src/test/ui/did_you_mean/bad-assoc-pat.stderr [new file with mode: 0644]
src/test/ui/did_you_mean/bad-assoc-ty.rs [new file with mode: 0644]
src/test/ui/did_you_mean/bad-assoc-ty.stderr [new file with mode: 0644]

index 0d289dbd46b5b596efffd533c0562cfce228a796..461cb0480d20bf19560da5bba7cb22a2c34a1c38 100644 (file)
@@ -20,6 +20,7 @@
 use codemap::{respan, Spanned};
 use abi::Abi;
 use ext::hygiene::{Mark, SyntaxContext};
+use parse::parser::{RecoverQPath, PathStyle};
 use print::pprust;
 use ptr::P;
 use rustc_data_structures::indexed_vec;
@@ -519,6 +520,38 @@ pub fn walk<F>(&self, it: &mut F) -> bool
     }
 }
 
+impl RecoverQPath for Pat {
+    fn to_ty(&self) -> Option<P<Ty>> {
+        let node = match &self.node {
+            PatKind::Wild => TyKind::Infer,
+            PatKind::Ident(BindingMode::ByValue(Mutability::Immutable), ident, None) =>
+                TyKind::Path(None, Path::from_ident(ident.span, ident.node)),
+            PatKind::Path(qself, path) => TyKind::Path(qself.clone(), path.clone()),
+            PatKind::Mac(mac) => TyKind::Mac(mac.clone()),
+            PatKind::Ref(pat, mutbl) =>
+                pat.to_ty().map(|ty| TyKind::Rptr(None, MutTy { ty, mutbl: *mutbl }))?,
+            PatKind::Slice(pats, None, _) if pats.len() == 1 =>
+                pats[0].to_ty().map(TyKind::Slice)?,
+            PatKind::Tuple(pats, None) => {
+                let mut tys = Vec::new();
+                for pat in pats {
+                    tys.push(pat.to_ty()?);
+                }
+                TyKind::Tup(tys)
+            }
+            _ => return None,
+        };
+
+        Some(P(Ty { node, id: self.id, span: self.span }))
+    }
+    fn to_recovered(&self, qself: Option<QSelf>, path: Path) -> Self {
+        Self { span: path.span, node: PatKind::Path(qself, path), id: self.id }
+    }
+    fn to_string(&self) -> String {
+        pprust::pat_to_string(self)
+    }
+}
+
 /// A single field in a struct pattern
 ///
 /// Patterns like the fields of Foo `{ x, ref y, ref mut z }`
@@ -877,6 +910,54 @@ pub fn returns(&self) -> bool {
             true
         }
     }
+
+    fn to_bound(&self) -> Option<TyParamBound> {
+        match &self.node {
+            ExprKind::Path(None, path) =>
+                Some(TraitTyParamBound(PolyTraitRef::new(Vec::new(), path.clone(), self.span),
+                                       TraitBoundModifier::None)),
+            _ => None,
+        }
+    }
+}
+
+impl RecoverQPath for Expr {
+    fn to_ty(&self) -> Option<P<Ty>> {
+        let node = match &self.node {
+            ExprKind::Path(qself, path) => TyKind::Path(qself.clone(), path.clone()),
+            ExprKind::Mac(mac) => TyKind::Mac(mac.clone()),
+            ExprKind::Paren(expr) => expr.to_ty().map(TyKind::Paren)?,
+            ExprKind::AddrOf(mutbl, expr) =>
+                expr.to_ty().map(|ty| TyKind::Rptr(None, MutTy { ty, mutbl: *mutbl }))?,
+            ExprKind::Repeat(expr, expr_len) =>
+                expr.to_ty().map(|ty| TyKind::Array(ty, expr_len.clone()))?,
+            ExprKind::Array(exprs) if exprs.len() == 1 =>
+                exprs[0].to_ty().map(TyKind::Slice)?,
+            ExprKind::Tup(exprs) => {
+                let mut tys = Vec::new();
+                for expr in exprs {
+                    tys.push(expr.to_ty()?);
+                }
+                TyKind::Tup(tys)
+            }
+            ExprKind::Binary(binop, lhs, rhs) if binop.node == BinOpKind::Add =>
+                if let (Some(lhs), Some(rhs)) = (lhs.to_bound(), rhs.to_bound()) {
+                    TyKind::TraitObject(vec![lhs, rhs], TraitObjectSyntax::None)
+                } else {
+                    return None;
+                }
+            _ => return None,
+        };
+
+        Some(P(Ty { node, id: self.id, span: self.span }))
+    }
+    fn to_recovered(&self, qself: Option<QSelf>, path: Path) -> Self {
+        Self { span: path.span, node: ExprKind::Path(qself, path),
+               id: self.id, attrs: self.attrs.clone() }
+    }
+    fn to_string(&self) -> String {
+        pprust::expr_to_string(self)
+    }
 }
 
 impl fmt::Debug for Expr {
@@ -1388,6 +1469,19 @@ pub struct Ty {
     pub span: Span,
 }
 
+impl RecoverQPath for Ty {
+    fn to_ty(&self) -> Option<P<Ty>> {
+        Some(P(self.clone()))
+    }
+    fn to_recovered(&self, qself: Option<QSelf>, path: Path) -> Self {
+        Self { span: path.span, node: TyKind::Path(qself, path), id: self.id }
+    }
+    fn to_string(&self) -> String {
+        pprust::ty_to_string(self)
+    }
+    const PATH_STYLE: PathStyle = PathStyle::Type;
+}
+
 impl fmt::Debug for Ty {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         write!(f, "type({})", pprust::ty_to_string(self))
index 9e4f134e2bd569371dd82c4e4bd8a018a40fd33c..44383233a8af190b505944f6ed57dba8df52ab65 100644 (file)
@@ -22,6 +22,7 @@
 
 #![feature(unicode)]
 #![feature(rustc_diagnostic_macros)]
+#![feature(match_default_bindings)]
 #![feature(i128_type)]
 
 // See librustc_cratesio_shim/Cargo.toml for a comment explaining this.
index 09a65046e20f0897463983859c2929ff3423cbd1..c3dd17e8775668ca8c2526bc481020d392ebb010 100644 (file)
@@ -169,6 +169,13 @@ enum PrevTokenKind {
     Other,
 }
 
+pub(crate) trait RecoverQPath: Sized {
+    fn to_ty(&self) -> Option<P<Ty>>;
+    fn to_recovered(&self, qself: Option<QSelf>, path: ast::Path) -> Self;
+    fn to_string(&self) -> String;
+    const PATH_STYLE: PathStyle = PathStyle::Expr;
+}
+
 /* ident is handled by common.rs */
 
 #[derive(Clone)]
@@ -1567,6 +1574,7 @@ fn parse_ty_common(&mut self, allow_plus: bool) -> PResult<'a, P<Ty>> {
 
         // Try to recover from use of `+` with incorrect priority.
         self.maybe_recover_from_bad_type_plus(allow_plus, &ty)?;
+        let ty = self.maybe_recover_from_bad_qpath(ty)?;
 
         Ok(P(ty))
     }
@@ -1621,6 +1629,32 @@ fn maybe_recover_from_bad_type_plus(&mut self, allow_plus: bool, ty: &Ty) -> PRe
         Ok(())
     }
 
+    // Try to recover from associated item paths like `[T]::AssocItem`/`(T, U)::AssocItem`.
+    fn maybe_recover_from_bad_qpath<T: RecoverQPath>(&mut self, base: T) -> PResult<'a, T> {
+        // Do not add `::` to expected tokens.
+        if self.token != token::ModSep {
+            return Ok(base);
+        }
+        let ty = match base.to_ty() {
+            Some(ty) => ty,
+            None => return Ok(base),
+        };
+
+        self.bump(); // `::`
+        let mut segments = Vec::new();
+        self.parse_path_segments(&mut segments, T::PATH_STYLE, true)?;
+
+        let span = ty.span.to(self.prev_span);
+        let recovered =
+            base.to_recovered(Some(QSelf { ty, position: 0 }), ast::Path { segments, span });
+
+        self.diagnostic()
+            .struct_span_err(span, "missing angle brackets in associated item path")
+            .span_suggestion(span, "try", recovered.to_string()).emit();
+
+        Ok(recovered)
+    }
+
     fn parse_borrowed_pointee(&mut self) -> PResult<'a, TyKind> {
         let opt_lifetime = if self.check_lifetime() { Some(self.expect_lifetime()) } else { None };
         let mutbl = self.parse_mutability();
@@ -2012,12 +2046,7 @@ pub fn parse_field(&mut self) -> PResult<'a, Field> {
     }
 
     pub fn mk_expr(&mut self, span: Span, node: ExprKind, attrs: ThinVec<Attribute>) -> P<Expr> {
-        P(Expr {
-            id: ast::DUMMY_NODE_ID,
-            node,
-            span,
-            attrs: attrs.into(),
-        })
+        P(Expr { node, span, attrs, id: ast::DUMMY_NODE_ID })
     }
 
     pub fn mk_unary(&mut self, unop: ast::UnOp, expr: P<Expr>) -> ast::ExprKind {
@@ -2139,12 +2168,11 @@ fn parse_bottom_expr(&mut self) -> PResult<'a, P<Expr>> {
                 self.bump();
 
                 hi = self.prev_span;
-                let span = lo.to(hi);
-                return if es.len() == 1 && !trailing_comma {
-                    Ok(self.mk_expr(span, ExprKind::Paren(es.into_iter().nth(0).unwrap()), attrs))
+                ex = if es.len() == 1 && !trailing_comma {
+                    ExprKind::Paren(es.into_iter().nth(0).unwrap())
                 } else {
-                    Ok(self.mk_expr(span, ExprKind::Tup(es), attrs))
-                }
+                    ExprKind::Tup(es)
+                };
             }
             token::OpenDelim(token::Brace) => {
                 return self.parse_block_expr(lo, BlockCheckMode::Default, attrs);
@@ -2344,7 +2372,10 @@ fn parse_bottom_expr(&mut self) -> PResult<'a, P<Expr>> {
             }
         }
 
-        return Ok(self.mk_expr(lo.to(hi), ex, attrs));
+        let expr = Expr { node: ex, span: lo.to(hi), id: ast::DUMMY_NODE_ID, attrs };
+        let expr = self.maybe_recover_from_bad_qpath(expr)?;
+
+        return Ok(P(expr));
     }
 
     fn parse_struct_expr(&mut self, lo: Span, pth: ast::Path, mut attrs: ThinVec<Attribute>)
@@ -3405,7 +3436,7 @@ fn parse_pat_vec_elements(
 
                     if self.check(&token::Comma) ||
                             self.check(&token::CloseDelim(token::Bracket)) {
-                        slice = Some(P(ast::Pat {
+                        slice = Some(P(Pat {
                             id: ast::DUMMY_NODE_ID,
                             node: PatKind::Wild,
                             span: self.span,
@@ -3492,14 +3523,14 @@ fn parse_pat_fields(&mut self) -> PResult<'a, (Vec<codemap::Spanned<ast::FieldPa
                     (false, false) => BindingMode::ByValue(Mutability::Immutable),
                 };
                 let fieldpath = codemap::Spanned{span:self.prev_span, node:fieldname};
-                let fieldpat = P(ast::Pat{
+                let fieldpat = P(Pat {
                     id: ast::DUMMY_NODE_ID,
                     node: PatKind::Ident(bind_type, fieldpath, None),
                     span: boxed_span.to(hi),
                 });
 
                 let subpat = if is_box {
-                    P(ast::Pat{
+                    P(Pat {
                         id: ast::DUMMY_NODE_ID,
                         node: PatKind::Box(fieldpat),
                         span: lo.to(hi),
@@ -3708,11 +3739,10 @@ pub fn parse_pat(&mut self) -> PResult<'a, P<Pat>> {
             }
         }
 
-        Ok(P(ast::Pat {
-            id: ast::DUMMY_NODE_ID,
-            node: pat,
-            span: lo.to(self.prev_span),
-        }))
+        let pat = Pat { node: pat, span: lo.to(self.prev_span), id: ast::DUMMY_NODE_ID };
+        let pat = self.maybe_recover_from_bad_qpath(pat)?;
+
+        Ok(P(pat))
     }
 
     /// Parse ident or ident @ pat
diff --git a/src/test/ui/did_you_mean/bad-assoc-expr.rs b/src/test/ui/did_you_mean/bad-assoc-expr.rs
new file mode 100644 (file)
index 0000000..72b616d
--- /dev/null
@@ -0,0 +1,24 @@
+// 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 = [1, 2, 3, 4];
+    [i32; 4]::clone(&a);
+    //~^ ERROR missing angle brackets in associated item path
+
+    [i32]::as_ref(&a);
+    //~^ ERROR missing angle brackets in associated item path
+
+    (u8)::clone(&0);
+    //~^ ERROR missing angle brackets in associated item path
+
+    (u8, u8)::clone(&(0, 0));
+    //~^ ERROR missing angle brackets in associated item path
+}
diff --git a/src/test/ui/did_you_mean/bad-assoc-expr.stderr b/src/test/ui/did_you_mean/bad-assoc-expr.stderr
new file mode 100644 (file)
index 0000000..1f8fc11
--- /dev/null
@@ -0,0 +1,26 @@
+error: missing angle brackets in associated item path
+  --> $DIR/bad-assoc-expr.rs:13:5
+   |
+13 |     [i32; 4]::clone(&a);
+   |     ^^^^^^^^^^^^^^^ help: try: `<[i32; 4]>::clone`
+
+error: missing angle brackets in associated item path
+  --> $DIR/bad-assoc-expr.rs:16:5
+   |
+16 |     [i32]::as_ref(&a);
+   |     ^^^^^^^^^^^^^ help: try: `<[i32]>::as_ref`
+
+error: missing angle brackets in associated item path
+  --> $DIR/bad-assoc-expr.rs:19:5
+   |
+19 |     (u8)::clone(&0);
+   |     ^^^^^^^^^^^ help: try: `<(u8)>::clone`
+
+error: missing angle brackets in associated item path
+  --> $DIR/bad-assoc-expr.rs:22:5
+   |
+22 |     (u8, u8)::clone(&(0, 0));
+   |     ^^^^^^^^^^^^^^^ help: try: `<(u8, u8)>::clone`
+
+error: aborting due to 4 previous errors
+
diff --git a/src/test/ui/did_you_mean/bad-assoc-pat.rs b/src/test/ui/did_you_mean/bad-assoc-pat.rs
new file mode 100644 (file)
index 0000000..e6b7127
--- /dev/null
@@ -0,0 +1,23 @@
+// 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() {
+    match 0u8 {
+        [u8]::AssocItem => {}
+        //~^ ERROR missing angle brackets in associated item path
+        //~| ERROR no associated item named `AssocItem` found for type `[u8]` in the current scope
+        (u8, u8)::AssocItem => {}
+        //~^ ERROR missing angle brackets in associated item path
+        //~| ERROR no associated item named `AssocItem` found for type `(u8, u8)` in the current sco
+        _::AssocItem => {}
+        //~^ ERROR missing angle brackets in associated item path
+        //~| ERROR no associated item named `AssocItem` found for type `_` in the current scope
+    }
+}
diff --git a/src/test/ui/did_you_mean/bad-assoc-pat.stderr b/src/test/ui/did_you_mean/bad-assoc-pat.stderr
new file mode 100644 (file)
index 0000000..20f9b96
--- /dev/null
@@ -0,0 +1,38 @@
+error: missing angle brackets in associated item path
+  --> $DIR/bad-assoc-pat.rs:13:9
+   |
+13 |         [u8]::AssocItem => {}
+   |         ^^^^^^^^^^^^^^^ help: try: `<[u8]>::AssocItem`
+
+error: missing angle brackets in associated item path
+  --> $DIR/bad-assoc-pat.rs:16:9
+   |
+16 |         (u8, u8)::AssocItem => {}
+   |         ^^^^^^^^^^^^^^^^^^^ help: try: `<(u8, u8)>::AssocItem`
+
+error: missing angle brackets in associated item path
+  --> $DIR/bad-assoc-pat.rs:19:9
+   |
+19 |         _::AssocItem => {}
+   |         ^^^^^^^^^^^^ help: try: `<_>::AssocItem`
+
+error[E0599]: no associated item named `AssocItem` found for type `[u8]` in the current scope
+  --> $DIR/bad-assoc-pat.rs:13:9
+   |
+13 |         [u8]::AssocItem => {}
+   |         ^^^^^^^^^^^^^^^ associated item not found in `[u8]`
+
+error[E0599]: no associated item named `AssocItem` found for type `(u8, u8)` in the current scope
+  --> $DIR/bad-assoc-pat.rs:16:9
+   |
+16 |         (u8, u8)::AssocItem => {}
+   |         ^^^^^^^^^^^^^^^^^^^ associated item not found in `(u8, u8)`
+
+error[E0599]: no associated item named `AssocItem` found for type `_` in the current scope
+  --> $DIR/bad-assoc-pat.rs:19:9
+   |
+19 |         _::AssocItem => {}
+   |         ^^^^^^^^^^^^ associated item not found in `_`
+
+error: aborting due to 6 previous errors
+
diff --git a/src/test/ui/did_you_mean/bad-assoc-ty.rs b/src/test/ui/did_you_mean/bad-assoc-ty.rs
new file mode 100644 (file)
index 0000000..45a5293
--- /dev/null
@@ -0,0 +1,31 @@
+// 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.
+
+type A = [u8; 4]::AssocTy;
+//~^ ERROR missing angle brackets in associated item path
+//~| ERROR ambiguous associated type
+
+type B = [u8]::AssocTy;
+//~^ ERROR missing angle brackets in associated item path
+//~| ERROR ambiguous associated type
+
+type C = (u8)::AssocTy;
+//~^ ERROR missing angle brackets in associated item path
+//~| ERROR ambiguous associated type
+
+type D = (u8, u8)::AssocTy;
+//~^ ERROR missing angle brackets in associated item path
+//~| ERROR ambiguous associated type
+
+type E = _::AssocTy;
+//~^ ERROR missing angle brackets in associated item path
+//~| ERROR the type placeholder `_` is not allowed within types on item signatures
+
+fn main() {}
diff --git a/src/test/ui/did_you_mean/bad-assoc-ty.stderr b/src/test/ui/did_you_mean/bad-assoc-ty.stderr
new file mode 100644 (file)
index 0000000..617339a
--- /dev/null
@@ -0,0 +1,70 @@
+error: missing angle brackets in associated item path
+  --> $DIR/bad-assoc-ty.rs:11:10
+   |
+11 | type A = [u8; 4]::AssocTy;
+   |          ^^^^^^^^^^^^^^^^ help: try: `<[u8; 4]>::AssocTy`
+
+error: missing angle brackets in associated item path
+  --> $DIR/bad-assoc-ty.rs:15:10
+   |
+15 | type B = [u8]::AssocTy;
+   |          ^^^^^^^^^^^^^ help: try: `<[u8]>::AssocTy`
+
+error: missing angle brackets in associated item path
+  --> $DIR/bad-assoc-ty.rs:19:10
+   |
+19 | type C = (u8)::AssocTy;
+   |          ^^^^^^^^^^^^^ help: try: `<(u8)>::AssocTy`
+
+error: missing angle brackets in associated item path
+  --> $DIR/bad-assoc-ty.rs:23:10
+   |
+23 | type D = (u8, u8)::AssocTy;
+   |          ^^^^^^^^^^^^^^^^^ help: try: `<(u8, u8)>::AssocTy`
+
+error: missing angle brackets in associated item path
+  --> $DIR/bad-assoc-ty.rs:27:10
+   |
+27 | type E = _::AssocTy;
+   |          ^^^^^^^^^^ help: try: `<_>::AssocTy`
+
+error[E0223]: ambiguous associated type
+  --> $DIR/bad-assoc-ty.rs:11:10
+   |
+11 | type A = [u8; 4]::AssocTy;
+   |          ^^^^^^^^^^^^^^^^ ambiguous associated type
+   |
+   = note: specify the type using the syntax `<[u8; <unevaluated[]>] as Trait>::AssocTy`
+
+error[E0223]: ambiguous associated type
+  --> $DIR/bad-assoc-ty.rs:15:10
+   |
+15 | type B = [u8]::AssocTy;
+   |          ^^^^^^^^^^^^^ ambiguous associated type
+   |
+   = note: specify the type using the syntax `<[u8] as Trait>::AssocTy`
+
+error[E0223]: ambiguous associated type
+  --> $DIR/bad-assoc-ty.rs:19:10
+   |
+19 | type C = (u8)::AssocTy;
+   |          ^^^^^^^^^^^^^ ambiguous associated type
+   |
+   = note: specify the type using the syntax `<u8 as Trait>::AssocTy`
+
+error[E0223]: ambiguous associated type
+  --> $DIR/bad-assoc-ty.rs:23:10
+   |
+23 | type D = (u8, u8)::AssocTy;
+   |          ^^^^^^^^^^^^^^^^^ ambiguous associated type
+   |
+   = note: specify the type using the syntax `<(u8, u8) as Trait>::AssocTy`
+
+error[E0121]: the type placeholder `_` is not allowed within types on item signatures
+  --> $DIR/bad-assoc-ty.rs:27:10
+   |
+27 | type E = _::AssocTy;
+   |          ^ not allowed in type signatures
+
+error: aborting due to 10 previous errors
+