diff --git a/src/lib/parser/expr.rs b/src/lib/parser/expr.rs index 8b99e95..7efb799 100644 --- a/src/lib/parser/expr.rs +++ b/src/lib/parser/expr.rs @@ -720,8 +720,6 @@ impl<'src> super::Parser<'_, '_, 'src> { self.validate_numeric_ident(binder, ExpInNumIdentPolicy::Reject); } - // NB: Shorthand numeric fields are not permitted - // in struct exprs contrary to struct pats. let body = if self.consume_or_parse(TokenKind::SingleColon, !numeric)? { Some(self.parse_expr()?) } else { diff --git a/src/lib/parser/pat.rs b/src/lib/parser/pat.rs index 6785c4c..4839c8a 100644 --- a/src/lib/parser/pat.rs +++ b/src/lib/parser/pat.rs @@ -293,26 +293,32 @@ impl<'src> super::Parser<'_, '_, 'src> { let attrs = self.parse_attrs(ast::AttrStyle::Outer)?; - let box_ = if let span = self.token.span + let boxed = if let span = self.token.span && self.consume(TokenKind::Box) { self.feature(Feature::box_patterns, span); - true + Boxed::Yes } else { - false + Boxed::No }; let (mut_, by_ref) = self.parse_mut_by_ref(); - // NB: Shorthand numeric fields are permitted - // in struct pats contrary to struct exprs. - let (binder, numeric) = self.parse_common_ident_or(TokenKind::NumLit)?; + let unmarked = matches!( + (boxed, mut_, by_ref), + (Boxed::No, ast::Mut::No, ast::ByRef::No) + ); + + let (binder, numeric) = if unmarked { + self.parse_common_ident_or(TokenKind::NumLit)? + } else { + (self.parse_common_ident()?, false) + }; if numeric { self.validate_numeric_ident(binder, ExpInNumIdentPolicy::Reject); } - let (binder, body) = if let (false, ast::Mut::No, ast::ByRef::No) = - (box_, mut_, by_ref) - && self.consume(TokenKind::SingleColon) + let (binder, body) = if unmarked + && self.consume_or_parse(TokenKind::SingleColon, !numeric)? { let body = self.parse_pat_where( OrPolicy::Parse, @@ -327,7 +333,10 @@ impl<'src> super::Parser<'_, '_, 'src> { binder, pat: None, })); - let body = if box_ { ast::Pat::Box(Box::new(body)) } else { body }; + let body = match boxed { + Boxed::Yes => ast::Pat::Box(Box::new(body)), + Boxed::No => body, + }; (None, body) }; @@ -337,6 +346,12 @@ impl<'src> super::Parser<'_, '_, 'src> { if self.token.kind != DELIMITER { self.parse(SEPARATOR)?; } + + #[derive(Clone, Copy)] + enum Boxed { + Yes, + No, + } } return Ok(ast::Pat::Struct(Box::new(ast::StructPat { path, fields, rest }))); diff --git a/src/lib/parser/test.rs b/src/lib/parser/test.rs index e0decbc..72cea8b 100644 --- a/src/lib/parser/test.rs +++ b/src/lib/parser/test.rs @@ -153,9 +153,9 @@ fn parse_via<'src, T>( Ok(node.unwrap()) } -macro t($parse:ident, $edition:ident, $source:literal, $ast:pat $(,)?) { +macro t($parse:ident, $edition:ident, $source:expr, $ast:pat $(if $guard:expr)? $(,)?) { match $parse(normalize($source).as_ref(), $edition) { - $ast => {} + $ast $(if $guard)? => {} ast => panic!("{:?}: {} != {:#?}", $source, stringify!($ast), ast), } } diff --git a/src/lib/parser/test/item.rs b/src/lib/parser/test/item.rs index a1bea5d..d750b4b 100644 --- a/src/lib/parser/test/item.rs +++ b/src/lib/parser/test/item.rs @@ -363,77 +363,31 @@ use {self::*, self::{}}; ); // Make sure that we don't consider these weak / context-dependent keywords as item modifiers: - t!( - parse_stmt, - Rust2015, - "auto as _", - Ok(ast::Stmt::Expr( - ast::Expr { - kind: ast::ExprKind::Cast( - r!(ast::Expr { - kind: ast::ExprKind::Path(r!(ast::ExtPath { - ext: None, - path: ast::Path { - segs: [ast::PathSeg { ident: ast::Ident!("auto"), .. }] - } - })), - .. - }), - _ - ), - .. - }, - _ - )) - ); - - t!( - parse_stmt, - Rust2015, - "default as _", - Ok(ast::Stmt::Expr( - ast::Expr { - kind: ast::ExprKind::Cast( - r!(ast::Expr { - kind: ast::ExprKind::Path(r!(ast::ExtPath { - ext: None, - path: ast::Path { - segs: [ast::PathSeg { ident: ast::Ident!("default"), .. }] - } - })), - .. - }), - _ - ), - .. - }, - _ - )) - ); - - t!( - parse_stmt, - Rust2015, - "safe as _", - Ok(ast::Stmt::Expr( - ast::Expr { - kind: ast::ExprKind::Cast( - r!(ast::Expr { - kind: ast::ExprKind::Path(r!(ast::ExtPath { - ext: None, - path: ast::Path { - segs: [ast::PathSeg { ident: ast::Ident!("safe"), .. }] - } - })), - .. - }), - _ - ), - .. - }, - _ - )) - ); + for weak in ["auto", "default", "safe"] { + t!( + parse_stmt, + Rust2015, + &format!("{weak} as _"), + Ok(ast::Stmt::Expr( + ast::Expr { + kind: ast::ExprKind::Cast( + r!(ast::Expr { + kind: ast::ExprKind::Path(r!(ast::ExtPath { + ext: None, + path: ast::Path { + segs: [ast::PathSeg { ident: ast::Ident!(name), .. }] + } + })), + .. + }), + _ + ), + .. + }, + _ + )) if name == weak + ); + } } #[test] diff --git a/src/lib/parser/test/num_lit.rs b/src/lib/parser/test/num_lit.rs index 708bb22..a3091cf 100644 --- a/src/lib/parser/test/num_lit.rs +++ b/src/lib/parser/test/num_lit.rs @@ -56,7 +56,7 @@ fn suffixes_invalid_places() { "Compound { 0suffix: 0 }", Err(r!([Error::UnexpectedToken( Token { kind: TokenKind::LitSuffix, .. }, - r!([Fragment::Token(TokenKind::Comma)]), + r!([Fragment::Token(TokenKind::SingleColon)]), )])) ); @@ -66,7 +66,7 @@ fn suffixes_invalid_places() { "Compound { 0suffix }", Err(r!([Error::UnexpectedToken( Token { kind: TokenKind::LitSuffix, .. }, - r!([Fragment::Token(TokenKind::Comma)]), + r!([Fragment::Token(TokenKind::SingleColon)]), )])) ); } @@ -168,9 +168,42 @@ fn exponents_invalid_places() { t!(parse_expr, Rust2015, "Compound { 0e-1: 0 }", Err(r!([Error::InvalidNumericIdent(_)]))); t!(parse_pat, Rust2015, "Compound { 0e1: 0 }", Err(r!([Error::InvalidNumericIdent(_)]))); t!(parse_pat, Rust2015, "Compound { 0e+1: 0 }", Err(r!([Error::InvalidNumericIdent(_)]))); - t!(parse_pat, Rust2015, "Compound { 0e1 }", Err(r!([Error::InvalidNumericIdent(_)]))); - t!(parse_pat, Rust2015, "Compound { 0e+1 }", Err(r!([Error::InvalidNumericIdent(_)]))); - t!(parse_pat, Rust2015, "Compound { 0e-1 }", Err(r!([Error::InvalidNumericIdent(_)]))); + t!( + parse_pat, + Rust2015, + "Compound { 0e1 }", + Err(r!([ + Error::InvalidNumericIdent(_), + Error::UnexpectedToken( + Token { kind: TokenKind::CloseCurlyBracket, .. }, + [Fragment::Token(TokenKind::SingleColon)] + ) + ])) + ); + t!( + parse_pat, + Rust2015, + "Compound { 0e+1 }", + Err(r!([ + Error::InvalidNumericIdent(_), + Error::UnexpectedToken( + Token { kind: TokenKind::CloseCurlyBracket, .. }, + [Fragment::Token(TokenKind::SingleColon)] + ) + ])) + ); + t!( + parse_pat, + Rust2015, + "Compound { 0e-1 }", + Err(r!([ + Error::InvalidNumericIdent(_), + Error::UnexpectedToken( + Token { kind: TokenKind::CloseCurlyBracket, .. }, + [Fragment::Token(TokenKind::SingleColon)] + ) + ])) + ); } #[test] @@ -183,6 +216,28 @@ fn fractional_part_invalid_places() { t!(parse_expr, Rust2015, "Compound { 0.: 0 }", Err(r!([Error::InvalidNumericIdent(_)]))); t!(parse_pat, Rust2015, "Compound { 0.0: 0 }", Err(r!([Error::InvalidNumericIdent(_)]))); t!(parse_pat, Rust2015, "Compound { 0.: 0 }", Err(r!([Error::InvalidNumericIdent(_)]))); - t!(parse_pat, Rust2015, "Compound { 0.0 }", Err(r!([Error::InvalidNumericIdent(_)]))); - t!(parse_pat, Rust2015, "Compound { 0. }", Err(r!([Error::InvalidNumericIdent(_)]))); + t!( + parse_pat, + Rust2015, + "Compound { 0.0 }", + Err(r!([ + Error::InvalidNumericIdent(_), + Error::UnexpectedToken( + Token { kind: TokenKind::CloseCurlyBracket, .. }, + [Fragment::Token(TokenKind::SingleColon)] + ) + ])) + ); + t!( + parse_pat, + Rust2015, + "Compound { 0. }", + Err(r!([ + Error::InvalidNumericIdent(_), + Error::UnexpectedToken( + Token { kind: TokenKind::CloseCurlyBracket, .. }, + [Fragment::Token(TokenKind::SingleColon)] + ) + ])) + ); } diff --git a/src/lib/parser/test/pat.rs b/src/lib/parser/test/pat.rs index 4868a67..d059f47 100644 --- a/src/lib/parser/test/pat.rs +++ b/src/lib/parser/test/pat.rs @@ -74,6 +74,156 @@ fn pseudo_field_binding_mode_box() { ); } +#[test] +fn structs() { + t!( + parse_pat, + Rust2015, + "S {}", + Ok(ast::Pat::Struct(r!(ast::StructPat { + path: ast::ExtPath { + ext: None, + path: ast::Path { + segs: r!([ast::PathSeg { ident: ast::Ident!("S"), args: None }]) + } + }, + fields: r!([]), + rest: false + }))) + ); + + t!( + parse_pat, + Rust2015, + "S { .. }", + Ok(ast::Pat::Struct(r!(ast::StructPat { fields: r!([]), rest: true, .. }))) + ); + + t!( + parse_pat, + Rust2015, + "S { f: x @ Some(_) }", + Ok(ast::Pat::Struct(r!(ast::StructPat { + fields: r!([ast::StructPatField { + attrs: r!([]), + binder: Some(ast::Ident!("f")), + body: ast::Pat::Binding(_), + }]), + .. + }))) + ); + + t!( + parse_pat, + Rust2015, + "S { f: .. }", + Ok(ast::Pat::Struct(r!(ast::StructPat { + fields: r!([ast::StructPatField { + binder: Some(ast::Ident!("f")), + body: ast::Pat::Rest, + .. + }]), + rest: false, + .. + }))) + ); + + t!( + parse_pat, + Rust2015, + "S { f }", + Ok(ast::Pat::Struct(r!(ast::StructPat { + fields: r!([ast::StructPatField { + binder: None, + body: ast::Pat::Binding(r!(ast::BindingPat { + mut_: ast::Mut::No, + by_ref: ast::ByRef::No, + binder: ast::Ident!("f"), + pat: None, + })), + .. + }]), + rest: false, + .. + }))) + ); + + t!( + parse_pat, + Rust2015, + "S { _ }", + Err(r!([Error::UnexpectedToken( + Token { kind: TokenKind::Underscore, .. }, + r!([Fragment::Token(TokenKind::CommonIdent), Fragment::Token(TokenKind::NumLit)]) + )])) + ); + + t!( + parse_pat, + Rust2015, + "S { #[a] f: _ }", + Ok(ast::Pat::Struct(r!(ast::StructPat { + fields: r!([ast::StructPatField { + attrs: r!([ast::Attr { + style: ast::AttrStyle::Outer, + kind: ast::AttrKind::Regular(_), + .. + }]), + binder: Some(ast::Ident!("f")), + body: ast::Pat::Wildcard(ast::WildcardKind::Normal), + }]), + .. + }))) + ); + + // Context: rustc once used to accept this accidentally: + t!( + parse_pat, + Rust2015, + "S { #[a] .. }", + Err(r!([Error::UnexpectedToken( + Token { kind: TokenKind::DoubleDot, .. }, + r!([Fragment::Token(TokenKind::CommonIdent), Fragment::Token(TokenKind::NumLit)]) + )])) + ); + + // No numeric identifier shorthands: + t!( + parse_pat, + Rust2015, + "S { 0 }", + Err(r!([Error::UnexpectedToken( + Token { kind: TokenKind::CloseCurlyBracket, .. }, + r!([Fragment::Token(TokenKind::SingleColon)]) + )])) + ); + + // If a "modifier" is present, the explicit form is forbidden: + for modifier in ["box", "mut", "ref"] { + t!( + parse_pat, + Rust2015, + &format!("S {{ {modifier} f: _ }}"), + Err(r!([Error::UnexpectedToken( + Token { kind: TokenKind::SingleColon, .. }, + r!([Fragment::Token(TokenKind::Comma)]) + )])) + ); + } + + // There are no numeric identifier shorthands and the presence of modifiers requires shorthand. + // Consequently, modifiers are incompatible with numeric identifiers: + t!( + parse_pat, + Rust2015, + "S { ref 0 }", + Err(r!([Error::UnexpectedToken( + Token { kind: TokenKind::NumLit, .. }, + r!([Fragment::Token(TokenKind::CommonIdent)]) + )])) + ); +} + #[test] fn ranges_and_rest() { // Not a range but a rest pattern.