diff --git a/src/doc/unstable-book/src/language-features/macro-at-most-once-rep.md b/src/doc/unstable-book/src/language-features/macro-at-most-once-rep.md new file mode 100644 index 0000000000000..dbaf91b6e78b2 --- /dev/null +++ b/src/doc/unstable-book/src/language-features/macro-at-most-once-rep.md @@ -0,0 +1,17 @@ +# `macro_at_most_once_rep` + +The tracking issue for this feature is: TODO(mark-i-m) + +With this feature gate enabled, one can use `?` as a Kleene operator meaning "0 +or 1 repetitions" in a macro definition. Previously only `+` and `*` were allowed. + +For example: +```rust +macro_rules! foo { + (something $(,)?) // `?` indicates `,` is "optional"... + => {} +} +``` + +------------------------ + diff --git a/src/libsyntax/ext/tt/macro_parser.rs b/src/libsyntax/ext/tt/macro_parser.rs index 1a9849ca5307d..0621f728e2a9d 100644 --- a/src/libsyntax/ext/tt/macro_parser.rs +++ b/src/libsyntax/ext/tt/macro_parser.rs @@ -181,6 +181,8 @@ struct MatcherPos { match_hi: usize, // Specifically used if we are matching a repetition. If we aren't both should be `None`. + /// The KleeneOp of this sequence if we are in a repetition. + seq_op: Option, /// The separator if we are in a repetition sep: Option, /// The "parent" matcher position if we are in a repetition. That is, the matcher position just @@ -263,6 +265,7 @@ fn initial_matcher_pos(ms: Vec, lo: BytePos) -> Box { stack: vec![], // Haven't descended into any sequences, so both of these are `None`. + seq_op: None, sep: None, up: None, }) @@ -466,8 +469,8 @@ fn inner_parse_loop( } } // We don't need a separator. Move the "dot" back to the beginning of the matcher - // and try to match again. - else { + // and try to match again UNLESS we are only allowed to have _one_ repetition. + else if item.seq_op != Some(quoted::KleeneOp::ZeroOrOne) { item.match_cur = item.match_lo; item.idx = 0; cur_items.push(item); @@ -486,8 +489,10 @@ fn inner_parse_loop( match item.top_elts.get_tt(idx) { // Need to descend into a sequence TokenTree::Sequence(sp, seq) => { - if seq.op == quoted::KleeneOp::ZeroOrMore { - // Examine the case where there are 0 matches of this sequence + // Examine the case where there are 0 matches of this sequence + if seq.op == quoted::KleeneOp::ZeroOrMore + || seq.op == quoted::KleeneOp::ZeroOrOne + { let mut new_item = item.clone(); new_item.match_cur += seq.num_captures; new_item.idx += 1; @@ -497,11 +502,11 @@ fn inner_parse_loop( cur_items.push(new_item); } - // Examine the case where there is at least one match of this sequence let matches = create_matches(item.matches.len()); cur_items.push(Box::new(MatcherPos { stack: vec![], sep: seq.separator.clone(), + seq_op: Some(seq.op), idx: 0, matches, match_lo: item.match_cur, diff --git a/src/libsyntax/ext/tt/macro_rules.rs b/src/libsyntax/ext/tt/macro_rules.rs index 9efb4faa63535..5254c751e6b62 100644 --- a/src/libsyntax/ext/tt/macro_rules.rs +++ b/src/libsyntax/ext/tt/macro_rules.rs @@ -237,7 +237,8 @@ pub fn compile(sess: &ParseSess, features: &RefCell, def: &ast::Item) s.iter().map(|m| { if let MatchedNonterminal(ref nt) = *m { if let NtTT(ref tt) = **nt { - let tt = quoted::parse(tt.clone().into(), true, sess).pop().unwrap(); + let tt = quoted::parse(tt.clone().into(), true, sess, features, &def.attrs) + .pop().unwrap(); valid &= check_lhs_nt_follows(sess, features, &def.attrs, &tt); return tt; } @@ -253,7 +254,8 @@ pub fn compile(sess: &ParseSess, features: &RefCell, def: &ast::Item) s.iter().map(|m| { if let MatchedNonterminal(ref nt) = *m { if let NtTT(ref tt) = **nt { - return quoted::parse(tt.clone().into(), false, sess).pop().unwrap(); + return quoted::parse(tt.clone().into(), false, sess, features, &def.attrs) + .pop().unwrap(); } } sess.span_diagnostic.span_bug(def.span, "wrong-structured lhs") diff --git a/src/libsyntax/ext/tt/quoted.rs b/src/libsyntax/ext/tt/quoted.rs index c55dfaba8f6b2..982b60b81e47e 100644 --- a/src/libsyntax/ext/tt/quoted.rs +++ b/src/libsyntax/ext/tt/quoted.rs @@ -8,14 +8,17 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use ast; +use {ast, attr}; use ext::tt::macro_parser; +use feature_gate::{self, emit_feature_err, Features, GateIssue}; use parse::{token, ParseSess}; use print::pprust; use symbol::keywords; use syntax_pos::{BytePos, Span, DUMMY_SP}; use tokenstream; +use std::cell::RefCell; +use std::iter::Peekable; use std::rc::Rc; /// Contains the sub-token-trees of a "delimited" token tree, such as the contents of `(`. Note @@ -78,6 +81,7 @@ pub enum KleeneOp { ZeroOrMore, /// Kleene plus (`+`) for one or more repetitions OneOrMore, + ZeroOrOne, } /// Similar to `tokenstream::TokenTree`, except that `$i`, `$i:ident`, and `$(...)` @@ -169,6 +173,8 @@ impl TokenTree { /// `ident` are "matchers". They are not present in the body of a macro rule -- just in the /// pattern, so we pass a parameter to indicate whether to expect them or not. /// - `sess`: the parsing session. Any errors will be emitted to this session. +/// - `features`, `attrs`: language feature flags and attributes so that we know whether to use +/// unstable features or not. /// /// # Returns /// @@ -177,18 +183,19 @@ pub fn parse( input: tokenstream::TokenStream, expect_matchers: bool, sess: &ParseSess, + features: &RefCell, + attrs: &[ast::Attribute], ) -> Vec { // Will contain the final collection of `self::TokenTree` let mut result = Vec::new(); // For each token tree in `input`, parse the token into a `self::TokenTree`, consuming // additional trees if need be. - let mut trees = input.trees(); + let mut trees = input.trees().peekable(); while let Some(tree) = trees.next() { - let tree = parse_tree(tree, &mut trees, expect_matchers, sess); - // Given the parsed tree, if there is a metavar and we are expecting matchers, actually // parse out the matcher (i.e. in `$id:ident` this would parse the `:` and `ident`). + let tree = parse_tree(tree, &mut trees, expect_matchers, sess, features, attrs); match tree { TokenTree::MetaVar(start_sp, ident) if expect_matchers => { let span = match trees.next() { @@ -237,11 +244,15 @@ pub fn parse( /// converting `tree` /// - `expect_matchers`: same as for `parse` (see above). /// - `sess`: the parsing session. Any errors will be emitted to this session. +/// - `features`, `attrs`: language feature flags and attributes so that we know whether to use +/// unstable features or not. fn parse_tree( tree: tokenstream::TokenTree, - trees: &mut I, + trees: &mut Peekable, expect_matchers: bool, sess: &ParseSess, + features: &RefCell, + attrs: &[ast::Attribute], ) -> TokenTree where I: Iterator, @@ -260,9 +271,9 @@ where sess.span_diagnostic.span_err(span, &msg); } // Parse the contents of the sequence itself - let sequence = parse(delimited.tts.into(), expect_matchers, sess); + let sequence = parse(delimited.tts.into(), expect_matchers, sess, features, attrs); // Get the Kleene operator and optional separator - let (separator, op) = parse_sep_and_kleene_op(trees, span, sess); + let (separator, op) = parse_sep_and_kleene_op(trees, span, sess, features, attrs); // Count the number of captured "names" (i.e. named metavars) let name_captures = macro_parser::count_names(&sequence); TokenTree::Sequence( @@ -315,12 +326,46 @@ where span, Rc::new(Delimited { delim: delimited.delim, - tts: parse(delimited.tts.into(), expect_matchers, sess), + tts: parse(delimited.tts.into(), expect_matchers, sess, features, attrs), }), ), } } +/// Takes a token and returns `Some(KleeneOp)` if the token is `+` `*` or `?`. Otherwise, return +/// `None`. +fn kleene_op(token: &token::Token) -> Option { + match *token { + token::BinOp(token::Star) => Some(KleeneOp::ZeroOrMore), + token::BinOp(token::Plus) => Some(KleeneOp::OneOrMore), + token::Question => Some(KleeneOp::ZeroOrOne), + _ => None, + } +} + +/// Parse the next token tree of the input looking for a KleeneOp. Returns +/// +/// - Ok(Ok(op)) if the next token tree is a KleeneOp +/// - Ok(Err(tok, span)) if the next token tree is a token but not a KleeneOp +/// - Err(span) if the next token tree is not a token +fn parse_kleene_op( + input: &mut I, + span: Span, +) -> Result, Span> +where + I: Iterator, +{ + match input.next() { + Some(tokenstream::TokenTree::Token(span, tok)) => match kleene_op(&tok) { + Some(op) => Ok(Ok(op)), + None => Ok(Err((tok, span))), + }, + tree => Err(tree.as_ref() + .map(tokenstream::TokenTree::span) + .unwrap_or(span)), + } +} + /// Attempt to parse a single Kleene star, possibly with a separator. /// /// For example, in a pattern such as `$(a),*`, `a` is the pattern to be repeated, `,` is the @@ -334,55 +379,121 @@ where /// operator and separator, then a tuple with `(separator, KleeneOp)` is returned. Otherwise, an /// error with the appropriate span is emitted to `sess` and a dummy value is returned. fn parse_sep_and_kleene_op( - input: &mut I, + input: &mut Peekable, span: Span, sess: &ParseSess, + features: &RefCell, + attrs: &[ast::Attribute], ) -> (Option, KleeneOp) where I: Iterator, { - fn kleene_op(token: &token::Token) -> Option { - match *token { - token::BinOp(token::Star) => Some(KleeneOp::ZeroOrMore), - token::BinOp(token::Plus) => Some(KleeneOp::OneOrMore), - _ => None, + // We basically look at two token trees here, denoted as #1 and #2 below + let span = match parse_kleene_op(input, span) { + // #1 is a `+` or `*` KleeneOp + // + // `?` is ambiguous: it could be a separator or a Kleene::ZeroOrOne, so we need to look + // ahead one more token to be sure. + Ok(Ok(op)) if op != KleeneOp::ZeroOrOne => return (None, op), + + // #1 is `?` token, but it could be a Kleene::ZeroOrOne without a separator or it could + // be a `?` separator followed by any Kleene operator. We need to look ahead 1 token to + // find out which. + Ok(Ok(op)) => { + assert_eq!(op, KleeneOp::ZeroOrOne); + + // Lookahead at #2. If it is a KleenOp, then #1 is a separator. + let is_1_sep = if let Some(&tokenstream::TokenTree::Token(_, ref tok2)) = input.peek() { + kleene_op(tok2).is_some() + } else { + false + }; + + if is_1_sep { + // #1 is a separator and #2 should be a KleepeOp::* + // (N.B. We need to advance the input iterator.) + match parse_kleene_op(input, span) { + // #2 is a KleeneOp (this is the only valid option) :) + Ok(Ok(op)) if op == KleeneOp::ZeroOrOne => { + if !features.borrow().macro_at_most_once_rep + && !attr::contains_name(attrs, "allow_internal_unstable") + { + let explain = feature_gate::EXPLAIN_MACRO_AT_MOST_ONCE_REP; + emit_feature_err( + sess, + "macro_at_most_once_rep", + span, + GateIssue::Language, + explain, + ); + } + return (Some(token::Question), op); + } + Ok(Ok(op)) => return (Some(token::Question), op), + + // #2 is a random token (this is an error) :( + Ok(Err((_, span))) => span, + + // #2 is not even a token at all :( + Err(span) => span, + } + } else { + if !features.borrow().macro_at_most_once_rep + && !attr::contains_name(attrs, "allow_internal_unstable") + { + let explain = feature_gate::EXPLAIN_MACRO_AT_MOST_ONCE_REP; + emit_feature_err( + sess, + "macro_at_most_once_rep", + span, + GateIssue::Language, + explain, + ); + } + + // #2 is a random tree and #1 is KleeneOp::ZeroOrOne + return (None, op); + } } - } - // We attempt to look at the next two token trees in `input`. I will call the first #1 and the - // second #2. If #1 and #2 don't match a valid KleeneOp with/without separator, that is an - // error, and we should emit an error on the most specific span possible. - let span = match input.next() { - // #1 is a token - Some(tokenstream::TokenTree::Token(span, tok)) => match kleene_op(&tok) { - // #1 is a KleeneOp with no separator - Some(op) => return (None, op), - - // #1 is not a KleeneOp, but may be a separator... need to look at #2 - None => match input.next() { - // #2 is a token - Some(tokenstream::TokenTree::Token(span, tok2)) => match kleene_op(&tok2) { - // #2 is a KleeneOp, so #1 must be a separator - Some(op) => return (Some(tok), op), - - // #2 is not a KleeneOp... error - None => span, - }, - - // #2 is not a token at all... error - tree => tree.as_ref() - .map(tokenstream::TokenTree::span) - .unwrap_or(span), - }, + // #1 is a separator followed by #2, a KleeneOp + Ok(Err((tok, span))) => match parse_kleene_op(input, span) { + // #2 is a KleeneOp :D + Ok(Ok(op)) if op == KleeneOp::ZeroOrOne => { + if !features.borrow().macro_at_most_once_rep + && !attr::contains_name(attrs, "allow_internal_unstable") + { + let explain = feature_gate::EXPLAIN_MACRO_AT_MOST_ONCE_REP; + emit_feature_err( + sess, + "macro_at_most_once_rep", + span, + GateIssue::Language, + explain, + ); + } + return (Some(tok), op); + } + Ok(Ok(op)) => return (Some(tok), op), + + // #2 is a random token :( + Ok(Err((_, span))) => span, + + // #2 is not a token at all :( + Err(span) => span, }, - // #1 is not a token at all... error - tree => tree.as_ref() - .map(tokenstream::TokenTree::span) - .unwrap_or(span), + // #1 is not a token + Err(span) => span, }; - // Error... - sess.span_diagnostic.span_err(span, "expected `*` or `+`"); + if !features.borrow().macro_at_most_once_rep + && !attr::contains_name(attrs, "allow_internal_unstable") + { + sess.span_diagnostic + .span_err(span, "expected one of: `*`, `+`, or `?`"); + } else { + sess.span_diagnostic.span_err(span, "expected `*` or `+`"); + } (None, KleeneOp::ZeroOrMore) } diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 9a2560b04583d..382ee3525ec2c 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -452,6 +452,9 @@ declare_features! ( // Allows `#[repr(transparent)]` attribute on newtype structs (active, repr_transparent, "1.25.0", Some(43036)), + + // Use `?` as the Kleene "at most one" operator + (active, macro_at_most_once_rep, "1.25.0", Some(48075)), ); declare_features! ( @@ -1250,6 +1253,9 @@ pub const EXPLAIN_PLACEMENT_IN: &'static str = pub const EXPLAIN_UNSIZED_TUPLE_COERCION: &'static str = "Unsized tuple coercion is not stable enough for use and is subject to change"; +pub const EXPLAIN_MACRO_AT_MOST_ONCE_REP: &'static str = + "Using the `?` macro Kleene operator for \"at most one\" repetition is unstable"; + struct PostExpansionVisitor<'a> { context: &'a Context<'a>, } diff --git a/src/test/compile-fail/issue-39388.rs b/src/test/compile-fail/issue-39388.rs index 15eef429eab97..6da049374086a 100644 --- a/src/test/compile-fail/issue-39388.rs +++ b/src/test/compile-fail/issue-39388.rs @@ -11,7 +11,7 @@ #![allow(unused_macros)] macro_rules! assign { - (($($a:tt)*) = ($($b:tt))*) => { //~ ERROR expected `*` or `+` + (($($a:tt)*) = ($($b:tt))*) => { //~ ERROR expected one of: `*`, `+`, or `?` $($a)* = $($b)* } } diff --git a/src/test/compile-fail/macro-at-most-once-rep-ambig.rs b/src/test/compile-fail/macro-at-most-once-rep-ambig.rs new file mode 100644 index 0000000000000..a5660f8b41f8d --- /dev/null +++ b/src/test/compile-fail/macro-at-most-once-rep-ambig.rs @@ -0,0 +1,53 @@ +// Copyright 2012 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. + +// The logic for parsing Kleene operators in macros has a special case to disambiguate `?`. +// Specifically, `$(pat)?` is the ZeroOrOne operator whereas `$(pat)?+` or `$(pat)?*` are the +// ZeroOrMore and OneOrMore operators using `?` as a separator. These tests are intended to +// exercise that logic in the macro parser. +// +// Moreover, we also throw in some tests for using a separator with `?`, which is meaningless but +// included for consistency with `+` and `*`. +// +// This test focuses on error cases. + +#![feature(macro_at_most_once_rep)] + +macro_rules! foo { + ($(a)?) => {} +} + +macro_rules! baz { + ($(a),?) => {} // comma separator is meaningless for `?` +} + +macro_rules! barplus { + ($(a)?+) => {} +} + +macro_rules! barstar { + ($(a)?*) => {} +} + +pub fn main() { + foo!(a?a?a); //~ ERROR no rules expected the token `?` + foo!(a?a); //~ ERROR no rules expected the token `?` + foo!(a?); //~ ERROR no rules expected the token `?` + baz!(a?a?a); //~ ERROR no rules expected the token `?` + baz!(a?a); //~ ERROR no rules expected the token `?` + baz!(a?); //~ ERROR no rules expected the token `?` + baz!(a,); //~ ERROR unexpected end of macro invocation + baz!(a?a?a,); //~ ERROR no rules expected the token `?` + baz!(a?a,); //~ ERROR no rules expected the token `?` + baz!(a?,); //~ ERROR no rules expected the token `?` + barplus!(); //~ ERROR unexpected end of macro invocation + barplus!(a?); //~ ERROR unexpected end of macro invocation + barstar!(a?); //~ ERROR unexpected end of macro invocation +} diff --git a/src/test/parse-fail/issue-33569.rs b/src/test/parse-fail/issue-33569.rs index 15d491719a6d5..af90d0a83c926 100644 --- a/src/test/parse-fail/issue-33569.rs +++ b/src/test/parse-fail/issue-33569.rs @@ -13,7 +13,7 @@ macro_rules! foo { { $+ } => { //~ ERROR expected identifier, found `+` //~^ ERROR missing fragment specifier - $(x)(y) //~ ERROR expected `*` or `+` + $(x)(y) //~ ERROR expected one of: `*`, `+`, or `?` } } diff --git a/src/test/run-pass-fulldeps/auxiliary/procedural_mbe_matching.rs b/src/test/run-pass-fulldeps/auxiliary/procedural_mbe_matching.rs index b5d6ff595afd5..9ebc438ad5a00 100644 --- a/src/test/run-pass-fulldeps/auxiliary/procedural_mbe_matching.rs +++ b/src/test/run-pass-fulldeps/auxiliary/procedural_mbe_matching.rs @@ -18,6 +18,7 @@ extern crate syntax_pos; extern crate rustc; extern crate rustc_plugin; +use syntax::feature_gate::Features; use syntax::parse::token::{NtExpr, NtPat}; use syntax::ast::{Ident, Pat}; use syntax::tokenstream::{TokenTree}; @@ -31,11 +32,17 @@ use syntax::ptr::P; use syntax_pos::Span; use rustc_plugin::Registry; +use std::cell::RefCell; + fn expand_mbe_matches(cx: &mut ExtCtxt, _: Span, args: &[TokenTree]) -> Box { let mbe_matcher = quote_tokens!(cx, $$matched:expr, $$($$pat:pat)|+); - let mbe_matcher = quoted::parse(mbe_matcher.into_iter().collect(), true, cx.parse_sess); + let mbe_matcher = quoted::parse(mbe_matcher.into_iter().collect(), + true, + cx.parse_sess, + &RefCell::new(Features::new()), + &[]); let map = match TokenTree::parse(cx, &mbe_matcher, args.iter().cloned().collect()) { Success(map) => map, Failure(_, tok) => { diff --git a/src/test/run-pass/macro-at-most-once-rep.rs b/src/test/run-pass/macro-at-most-once-rep.rs new file mode 100644 index 0000000000000..b7e942f938321 --- /dev/null +++ b/src/test/run-pass/macro-at-most-once-rep.rs @@ -0,0 +1,88 @@ +// Copyright 2012 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. + +// The logic for parsing Kleene operators in macros has a special case to disambiguate `?`. +// Specifically, `$(pat)?` is the ZeroOrOne operator whereas `$(pat)?+` or `$(pat)?*` are the +// ZeroOrMore and OneOrMore operators using `?` as a separator. These tests are intended to +// exercise that logic in the macro parser. +// +// Moreover, we also throw in some tests for using a separator with `?`, which is meaningless but +// included for consistency with `+` and `*`. +// +// This test focuses on non-error cases and making sure the correct number of repetitions happen. + +#![feature(macro_at_most_once_rep)] + +macro_rules! foo { + ($($a:ident)? ; $num:expr) => { { + let mut x = 0; + + $( + x += $a; + )? + + assert_eq!(x, $num); + } } +} + +macro_rules! baz { + ($($a:ident),? ; $num:expr) => { { // comma separator is meaningless for `?` + let mut x = 0; + + $( + x += $a; + )? + + assert_eq!(x, $num); + } } +} + +macro_rules! barplus { + ($($a:ident)?+ ; $num:expr) => { { + let mut x = 0; + + $( + x += $a; + )+ + + assert_eq!(x, $num); + } } +} + +macro_rules! barstar { + ($($a:ident)?* ; $num:expr) => { { + let mut x = 0; + + $( + x += $a; + )* + + assert_eq!(x, $num); + } } +} + +pub fn main() { + let a = 1; + + // accept 0 or 1 repetitions + foo!( ; 0); + foo!(a ; 1); + baz!( ; 0); + baz!(a ; 1); + + // Make sure using ? as a separator works as before + barplus!(a ; 1); + barplus!(a?a ; 2); + barplus!(a?a?a ; 3); + barstar!( ; 0); + barstar!(a ; 1); + barstar!(a?a ; 2); + barstar!(a?a?a ; 3); +} diff --git a/src/test/ui/feature-gate-macro_at_most_once_rep.rs b/src/test/ui/feature-gate-macro_at_most_once_rep.rs new file mode 100644 index 0000000000000..19f5aca5730e1 --- /dev/null +++ b/src/test/ui/feature-gate-macro_at_most_once_rep.rs @@ -0,0 +1,19 @@ +// 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. + +// Test that `?` macro Kleene operator can not be used when the `macro_at_most_once_rep` feature +// gate is not used. + +macro_rules! m { ($(a)?) => {} } +//~^ ERROR Using the `?` macro Kleene operator for "at most one" repetition is unstable + +fn main() { + m!(); +} diff --git a/src/test/ui/feature-gate-macro_at_most_once_rep.stderr b/src/test/ui/feature-gate-macro_at_most_once_rep.stderr new file mode 100644 index 0000000000000..02dbab07bdecc --- /dev/null +++ b/src/test/ui/feature-gate-macro_at_most_once_rep.stderr @@ -0,0 +1,10 @@ +error[E0658]: Using the `?` macro Kleene operator for "at most one" repetition is unstable (see issue #48075) + --> $DIR/feature-gate-macro_at_most_once_rep.rs:14:20 + | +14 | macro_rules! m { ($(a)?) => {} } + | ^^^ + | + = help: add #![feature(macro_at_most_once_rep)] to the crate attributes to enable + +error: aborting due to previous error +