From e038f58105cc1769a9c7991981822d01ebffe277 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Wed, 5 Apr 2017 01:12:53 +0300 Subject: [PATCH 1/3] syntax: Support parentheses around trait bounds --- src/libsyntax/parse/parser.rs | 29 ++++++++++++++++--- .../parse-fail/trait-object-bad-parens.rs | 20 +++++++++++++ .../trait-object-lifetime-parens.rs | 18 ++++++++++++ .../parse-fail/trait-object-trait-parens.rs | 21 ++++++++++++++ 4 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 src/test/parse-fail/trait-object-bad-parens.rs create mode 100644 src/test/parse-fail/trait-object-lifetime-parens.rs create mode 100644 src/test/parse-fail/trait-object-trait-parens.rs diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs index 0cdb09a842f..1232411d1ec 100644 --- a/src/libsyntax/parse/parser.rs +++ b/src/libsyntax/parse/parser.rs @@ -152,6 +152,7 @@ fn maybe_append(mut lhs: Vec, rhs: Option>) enum PrevTokenKind { DocComment, Comma, + Plus, Interpolated, Eof, Other, @@ -1061,6 +1062,7 @@ impl<'a> Parser<'a> { self.prev_token_kind = match self.token { token::DocComment(..) => PrevTokenKind::DocComment, token::Comma => PrevTokenKind::Comma, + token::BinOp(token::Plus) => PrevTokenKind::Plus, token::Interpolated(..) => PrevTokenKind::Interpolated, token::Eof => PrevTokenKind::Eof, _ => PrevTokenKind::Other, @@ -1354,20 +1356,29 @@ impl<'a> Parser<'a> { break; } } + let trailing_plus = self.prev_token_kind == PrevTokenKind::Plus; self.expect(&token::CloseDelim(token::Paren))?; if ts.len() == 1 && !last_comma { let ty = ts.into_iter().nth(0).unwrap().unwrap(); + let maybe_bounds = allow_plus && self.token == token::BinOp(token::Plus); match ty.node { - // Accept `(Trait1) + Trait2 + 'a` for backward compatibility (#39318). - TyKind::Path(None, ref path) - if allow_plus && self.token == token::BinOp(token::Plus) => { + // `(TY_BOUND_NOPAREN) + BOUND + ...`. + TyKind::Path(None, ref path) if maybe_bounds => { self.bump(); // `+` let pt = PolyTraitRef::new(Vec::new(), path.clone(), lo.to(self.prev_span)); let mut bounds = vec![TraitTyParamBound(pt, TraitBoundModifier::None)]; bounds.append(&mut self.parse_ty_param_bounds()?); TyKind::TraitObject(bounds) } + TyKind::TraitObject(ref bounds) + if maybe_bounds && bounds.len() == 1 && !trailing_plus => { + self.bump(); // `+` + let mut bounds = bounds.clone(); + bounds.append(&mut self.parse_ty_param_bounds()?); + TyKind::TraitObject(bounds) + } + // `(TYPE)` _ => TyKind::Paren(P(ty)) } } else { @@ -4070,10 +4081,12 @@ impl<'a> Parser<'a> { // Parse bounds of a type parameter `BOUND + BOUND + BOUND`, possibly with trailing `+`. // BOUND = TY_BOUND | LT_BOUND // LT_BOUND = LIFETIME (e.g. `'a`) - // TY_BOUND = [?] [for] SIMPLE_PATH (e.g. `?for<'a: 'b> m::Trait<'a>`) + // TY_BOUND = TY_BOUND_NOPAREN | (TY_BOUND_NOPAREN) + // TY_BOUND_NOPAREN = [?] [for] SIMPLE_PATH (e.g. `?for<'a: 'b> m::Trait<'a>`) fn parse_ty_param_bounds_common(&mut self, allow_plus: bool) -> PResult<'a, TyParamBounds> { let mut bounds = Vec::new(); loop { + let has_parens = self.eat(&token::OpenDelim(token::Paren)); let question = if self.eat(&token::Question) { Some(self.prev_span) } else { None }; if self.check_lifetime() { if let Some(question_span) = question { @@ -4081,6 +4094,11 @@ impl<'a> Parser<'a> { "`?` may only modify trait bounds, not lifetime bounds"); } bounds.push(RegionTyParamBound(self.expect_lifetime())); + if has_parens { + self.expect(&token::CloseDelim(token::Paren))?; + self.span_err(self.prev_span, + "parenthesized lifetime bounds are not supported"); + } } else if self.check_keyword(keywords::For) || self.check_path() { let lo = self.span; let lifetime_defs = self.parse_late_bound_lifetime_defs()?; @@ -4092,6 +4110,9 @@ impl<'a> Parser<'a> { TraitBoundModifier::None }; bounds.push(TraitTyParamBound(poly_trait, modifier)); + if has_parens { + self.expect(&token::CloseDelim(token::Paren))?; + } } else { break } diff --git a/src/test/parse-fail/trait-object-bad-parens.rs b/src/test/parse-fail/trait-object-bad-parens.rs new file mode 100644 index 00000000000..a44c0c3f32f --- /dev/null +++ b/src/test/parse-fail/trait-object-bad-parens.rs @@ -0,0 +1,20 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-flags: -Z parse-only -Z continue-parse-after-error + +fn main() { + let _: Box<((Copy)) + Copy>; + //~^ ERROR expected a path on the left-hand side of `+`, not `((Copy))` + let _: Box<(Copy + Copy) + Copy>; + //~^ ERROR expected a path on the left-hand side of `+`, not `( Copy + Copy)` + let _: Box<(Copy +) + Copy>; + //~^ ERROR expected a path on the left-hand side of `+`, not `( Copy)` +} diff --git a/src/test/parse-fail/trait-object-lifetime-parens.rs b/src/test/parse-fail/trait-object-lifetime-parens.rs new file mode 100644 index 00000000000..6be62d966eb --- /dev/null +++ b/src/test/parse-fail/trait-object-lifetime-parens.rs @@ -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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-flags: -Z parse-only -Z continue-parse-after-error + +fn f() {} //~ ERROR parenthesized lifetime bounds are not supported + +fn main() { + let _: Box; //~ ERROR parenthesized lifetime bounds are not supported + let _: Box<('a) + Copy>; //~ ERROR expected type, found `'a` +} diff --git a/src/test/parse-fail/trait-object-trait-parens.rs b/src/test/parse-fail/trait-object-trait-parens.rs new file mode 100644 index 00000000000..dc44f4f3fb1 --- /dev/null +++ b/src/test/parse-fail/trait-object-trait-parens.rs @@ -0,0 +1,21 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-flags: -Z parse-only + +fn f Trait<'a>)>() {} + +fn main() { + let _: Box<(Copy) + (?Sized) + (for<'a> Trait<'a>)>; + let _: Box<(?Sized) + (for<'a> Trait<'a>) + (Copy)>; + let _: Box<(for<'a> Trait<'a>) + (Copy) + (?Sized)>; +} + +FAIL //~ ERROR From 6e75def7dba6ee28660b8479f0ede72986c299f7 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Fri, 21 Apr 2017 00:19:06 +0300 Subject: [PATCH 2/3] Fix issue with single question mark or paren --- src/doc/grammar.md | 5 +- src/libsyntax/parse/parser.rs | 51 ++++++++++--------- .../parse-fail/bound-single-question-mark.rs | 13 +++++ 3 files changed, 44 insertions(+), 25 deletions(-) create mode 100644 src/test/parse-fail/bound-single-question-mark.rs diff --git a/src/doc/grammar.md b/src/doc/grammar.md index 3fbf9f06d99..12daa24e857 100644 --- a/src/doc/grammar.md +++ b/src/doc/grammar.md @@ -781,10 +781,11 @@ never_type : "!" ; ### Type parameter bounds ```antlr +bound-list := bound | bound '+' bound-list '+' ? bound := ty_bound | lt_bound lt_bound := lifetime -ty_bound := [?] [ for ] simple_path -bound-list := bound | bound '+' bound-list '+' ? +ty_bound := ty_bound_noparen | (ty_bound_noparen) +ty_bound_noparen := [?] [ for ] simple_path ``` ### Self types diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs index 1232411d1ec..79ae1d62915 100644 --- a/src/libsyntax/parse/parser.rs +++ b/src/libsyntax/parse/parser.rs @@ -4086,32 +4086,37 @@ impl<'a> Parser<'a> { fn parse_ty_param_bounds_common(&mut self, allow_plus: bool) -> PResult<'a, TyParamBounds> { let mut bounds = Vec::new(); loop { - let has_parens = self.eat(&token::OpenDelim(token::Paren)); - let question = if self.eat(&token::Question) { Some(self.prev_span) } else { None }; - if self.check_lifetime() { - if let Some(question_span) = question { - self.span_err(question_span, - "`?` may only modify trait bounds, not lifetime bounds"); - } - bounds.push(RegionTyParamBound(self.expect_lifetime())); - if has_parens { - self.expect(&token::CloseDelim(token::Paren))?; - self.span_err(self.prev_span, - "parenthesized lifetime bounds are not supported"); - } - } else if self.check_keyword(keywords::For) || self.check_path() { - let lo = self.span; - let lifetime_defs = self.parse_late_bound_lifetime_defs()?; - let path = self.parse_path(PathStyle::Type)?; - let poly_trait = PolyTraitRef::new(lifetime_defs, path, lo.to(self.prev_span)); - let modifier = if question.is_some() { - TraitBoundModifier::Maybe + let is_bound_start = self.check_path() || self.check_lifetime() || + self.check(&token::Question) || + self.check_keyword(keywords::For) || + self.check(&token::OpenDelim(token::Paren)); + if is_bound_start { + let has_parens = self.eat(&token::OpenDelim(token::Paren)); + let question = if self.eat(&token::Question) { Some(self.prev_span) } else { None }; + if self.token.is_lifetime() { + if let Some(question_span) = question { + self.span_err(question_span, + "`?` may only modify trait bounds, not lifetime bounds"); + } + bounds.push(RegionTyParamBound(self.expect_lifetime())); } else { - TraitBoundModifier::None - }; - bounds.push(TraitTyParamBound(poly_trait, modifier)); + let lo = self.span; + let lifetime_defs = self.parse_late_bound_lifetime_defs()?; + let path = self.parse_path(PathStyle::Type)?; + let poly_trait = PolyTraitRef::new(lifetime_defs, path, lo.to(self.prev_span)); + let modifier = if question.is_some() { + TraitBoundModifier::Maybe + } else { + TraitBoundModifier::None + }; + bounds.push(TraitTyParamBound(poly_trait, modifier)); + } if has_parens { self.expect(&token::CloseDelim(token::Paren))?; + if let Some(&RegionTyParamBound(..)) = bounds.last() { + self.span_err(self.prev_span, + "parenthesized lifetime bounds are not supported"); + } } } else { break diff --git a/src/test/parse-fail/bound-single-question-mark.rs b/src/test/parse-fail/bound-single-question-mark.rs new file mode 100644 index 00000000000..9dde5268c08 --- /dev/null +++ b/src/test/parse-fail/bound-single-question-mark.rs @@ -0,0 +1,13 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-flags: -Z parse-only + +fn f() {} //~ ERROR expected identifier, found `>` From 8838cd10f2374ed51c4b219c1f070d0c29af3b86 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Fri, 21 Apr 2017 21:32:11 +0300 Subject: [PATCH 3/3] Move parse_remaining_bounds into a separate function --- src/libsyntax/parse/parser.rs | 41 ++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs index 79ae1d62915..1baf0d1b54c 100644 --- a/src/libsyntax/parse/parser.rs +++ b/src/libsyntax/parse/parser.rs @@ -1365,18 +1365,15 @@ impl<'a> Parser<'a> { match ty.node { // `(TY_BOUND_NOPAREN) + BOUND + ...`. TyKind::Path(None, ref path) if maybe_bounds => { - self.bump(); // `+` - let pt = PolyTraitRef::new(Vec::new(), path.clone(), lo.to(self.prev_span)); - let mut bounds = vec![TraitTyParamBound(pt, TraitBoundModifier::None)]; - bounds.append(&mut self.parse_ty_param_bounds()?); - TyKind::TraitObject(bounds) + self.parse_remaining_bounds(Vec::new(), path.clone(), lo, true)? } TyKind::TraitObject(ref bounds) if maybe_bounds && bounds.len() == 1 && !trailing_plus => { - self.bump(); // `+` - let mut bounds = bounds.clone(); - bounds.append(&mut self.parse_ty_param_bounds()?); - TyKind::TraitObject(bounds) + let path = match bounds[0] { + TraitTyParamBound(ref pt, ..) => pt.trait_ref.path.clone(), + _ => self.bug("unexpected lifetime bound"), + }; + self.parse_remaining_bounds(Vec::new(), path, lo, true)? } // `(TYPE)` _ => TyKind::Paren(P(ty)) @@ -1429,11 +1426,8 @@ impl<'a> Parser<'a> { // Just a type path or bound list (trait object type) starting with a trait. // `Type` // `Trait1 + Trait2 + 'a` - if allow_plus && self.eat(&token::BinOp(token::Plus)) { - let poly_trait = PolyTraitRef::new(Vec::new(), path, lo.to(self.prev_span)); - let mut bounds = vec![TraitTyParamBound(poly_trait, TraitBoundModifier::None)]; - bounds.append(&mut self.parse_ty_param_bounds()?); - TyKind::TraitObject(bounds) + if allow_plus && self.check(&token::BinOp(token::Plus)) { + self.parse_remaining_bounds(Vec::new(), path, lo, true)? } else { TyKind::Path(None, path) } @@ -1451,12 +1445,8 @@ impl<'a> Parser<'a> { self.parse_ty_bare_fn(lifetime_defs)? } else { let path = self.parse_path(PathStyle::Type)?; - let poly_trait = PolyTraitRef::new(lifetime_defs, path, lo.to(self.prev_span)); - let mut bounds = vec![TraitTyParamBound(poly_trait, TraitBoundModifier::None)]; - if allow_plus && self.eat(&token::BinOp(token::Plus)) { - bounds.append(&mut self.parse_ty_param_bounds()?) - } - TyKind::TraitObject(bounds) + let parse_plus = allow_plus && self.check(&token::BinOp(token::Plus)); + self.parse_remaining_bounds(lifetime_defs, path, lo, parse_plus)? } } else if self.eat_keyword(keywords::Impl) { // FIXME: figure out priority of `+` in `impl Trait1 + Trait2` (#34511). @@ -1479,6 +1469,17 @@ impl<'a> Parser<'a> { Ok(P(ty)) } + fn parse_remaining_bounds(&mut self, lifetime_defs: Vec, path: ast::Path, + lo: Span, parse_plus: bool) -> PResult<'a, TyKind> { + let poly_trait_ref = PolyTraitRef::new(lifetime_defs, path, lo.to(self.prev_span)); + let mut bounds = vec![TraitTyParamBound(poly_trait_ref, TraitBoundModifier::None)]; + if parse_plus { + self.bump(); // `+` + bounds.append(&mut self.parse_ty_param_bounds()?); + } + Ok(TyKind::TraitObject(bounds)) + } + fn maybe_recover_from_bad_type_plus(&mut self, allow_plus: bool, ty: &Ty) -> PResult<'a, ()> { // Do not add `+` to expected tokens. if !allow_plus || self.token != token::BinOp(token::Plus) {