blob: 7a193fb55bfcdfe799232d58e079d02b74b952f2 [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 OR MIT
// Based on https://github.com/dtolnay/syn/blob/2.0.37/src/item.rs.
use syn::{punctuated::Punctuated, token, Attribute, Ident, Member, Path, Token, Type};
use super::PatPath;
ast_enum_of_structs! {
/// A pattern in a local binding, function signature, match expression, or
/// various other places.
#[non_exhaustive]
pub enum Pat {
/// A pattern that binds a new variable: `ref mut binding @ SUBPATTERN`.
Ident(PatIdent),
/// A path pattern like `Color::Red`.
Path(PatPath),
/// A reference pattern: `&mut var`.
Reference(PatReference),
/// A struct or struct variant pattern: `Variant { x, y, .. }`.
Struct(PatStruct),
/// A tuple pattern: `(a, b)`.
Tuple(PatTuple),
/// A tuple struct or tuple variant pattern: `Variant(x, y, .., z)`.
TupleStruct(PatTupleStruct),
/// A type ascription pattern: `foo: f64`.
Type(PatType),
/// A pattern that matches any value: `_`.
Wild(PatWild),
}
}
ast_struct! {
/// A pattern that binds a new variable: `ref mut binding @ SUBPATTERN`.
pub struct PatIdent {
pub attrs: Vec<Attribute>,
pub by_ref: Option<Token![ref]>,
pub mutability: Option<Token![mut]>,
pub ident: Ident,
}
}
ast_struct! {
/// A reference pattern: `&mut var`.
pub struct PatReference {
pub attrs: Vec<Attribute>,
pub and_token: Token![&],
pub mutability: Option<Token![mut]>,
pub pat: Box<Pat>,
}
}
ast_struct! {
/// The dots in a tuple pattern: `[0, 1, ..]`.
pub struct PatRest {
pub attrs: Vec<Attribute>,
pub dot2_token: Token![..],
}
}
ast_struct! {
/// A struct or struct variant pattern: `Variant { x, y, .. }`.
pub struct PatStruct {
pub attrs: Vec<Attribute>,
pub path: Path,
pub brace_token: token::Brace,
pub fields: Punctuated<FieldPat, Token![,]>,
pub rest: Option<PatRest>,
}
}
ast_struct! {
/// A tuple pattern: `(a, b)`.
pub struct PatTuple {
pub attrs: Vec<Attribute>,
pub paren_token: token::Paren,
pub elems: Punctuated<Pat, Token![,]>,
}
}
ast_struct! {
/// A tuple struct or tuple variant pattern: `Variant(x, y, .., z)`.
pub struct PatTupleStruct {
pub attrs: Vec<Attribute>,
pub path: Path,
pub paren_token: token::Paren,
pub elems: Punctuated<Pat, Token![,]>,
}
}
ast_struct! {
/// A type ascription pattern: `foo: f64`.
pub struct PatType {
pub attrs: Vec<Attribute>,
pub pat: Box<Pat>,
pub colon_token: Token![:],
pub ty: Box<Type>,
}
}
ast_struct! {
/// A pattern that matches any value: `_`.
pub struct PatWild {
pub attrs: Vec<Attribute>,
pub underscore_token: Token![_],
}
}
ast_struct! {
/// A single field in a struct pattern.
///
/// Patterns like the fields of Foo `{ x, ref y, ref mut z }` are treated
/// the same as `x: x, y: ref y, z: ref mut z` but there is no colon token.
pub struct FieldPat {
pub attrs: Vec<Attribute>,
pub member: Member,
pub colon_token: Option<Token![:]>,
pub pat: Box<Pat>,
}
}
mod parsing {
use syn::{
braced,
ext::IdentExt,
parenthesized,
parse::{ParseStream, Result},
punctuated::Punctuated,
token, Attribute, ExprPath, Ident, Member, Path, Token,
};
use super::{
FieldPat, Pat, PatIdent, PatReference, PatRest, PatStruct, PatTuple, PatTupleStruct,
PatWild,
};
use crate::path;
impl Pat {
/// Parse a pattern that does _not_ involve `|` at the top level.
pub fn parse_single(input: ParseStream<'_>) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(Ident)
&& (input.peek2(Token![::])
|| input.peek2(Token![!])
|| input.peek2(token::Brace)
|| input.peek2(token::Paren)
|| input.peek2(Token![..]))
|| input.peek(Token![self]) && input.peek2(Token![::])
|| lookahead.peek(Token![::])
|| lookahead.peek(Token![<])
|| input.peek(Token![Self])
|| input.peek(Token![super])
|| input.peek(Token![crate])
{
pat_path_or_struct(input)
} else if lookahead.peek(Token![_]) {
input.call(pat_wild).map(Pat::Wild)
} else if lookahead.peek(Token![ref])
|| lookahead.peek(Token![mut])
|| input.peek(Token![self])
|| input.peek(Ident)
{
input.call(pat_ident).map(Pat::Ident)
} else if lookahead.peek(Token![&]) {
input.call(pat_reference).map(Pat::Reference)
} else if lookahead.peek(token::Paren) {
input.call(pat_paren_or_tuple)
} else {
Err(lookahead.error())
}
}
}
fn pat_path_or_struct(input: ParseStream<'_>) -> Result<Pat> {
let path = path::parse_path(input)?;
if input.peek(token::Brace) {
pat_struct(input, path).map(Pat::Struct)
} else if input.peek(token::Paren) {
pat_tuple_struct(input, path).map(Pat::TupleStruct)
} else {
Ok(Pat::Path(ExprPath { attrs: Vec::new(), qself: None, path }))
}
}
fn pat_wild(input: ParseStream<'_>) -> Result<PatWild> {
Ok(PatWild { attrs: Vec::new(), underscore_token: input.parse()? })
}
fn pat_ident(input: ParseStream<'_>) -> Result<PatIdent> {
Ok(PatIdent {
attrs: Vec::new(),
by_ref: input.parse()?,
mutability: input.parse()?,
ident: input.call(Ident::parse_any)?,
})
}
fn pat_tuple_struct(input: ParseStream<'_>, path: Path) -> Result<PatTupleStruct> {
let content;
let paren_token = parenthesized!(content in input);
let mut elems = Punctuated::new();
while !content.is_empty() {
let value = Pat::parse_single(&content)?;
elems.push_value(value);
if content.is_empty() {
break;
}
let punct = content.parse()?;
elems.push_punct(punct);
}
Ok(PatTupleStruct { attrs: Vec::new(), path, paren_token, elems })
}
fn pat_struct(input: ParseStream<'_>, path: Path) -> Result<PatStruct> {
let content;
let brace_token = braced!(content in input);
let mut fields = Punctuated::new();
let mut rest = None;
while !content.is_empty() {
let attrs = content.call(Attribute::parse_outer)?;
if content.peek(Token![..]) {
rest = Some(PatRest { attrs, dot2_token: content.parse()? });
break;
}
let mut value = content.call(field_pat)?;
value.attrs = attrs;
fields.push_value(value);
if content.is_empty() {
break;
}
let punct: Token![,] = content.parse()?;
fields.push_punct(punct);
}
Ok(PatStruct { attrs: Vec::new(), path, brace_token, fields, rest })
}
fn field_pat(input: ParseStream<'_>) -> Result<FieldPat> {
let boxed: Option<Token![box]> = input.parse()?;
let by_ref: Option<Token![ref]> = input.parse()?;
let mutability: Option<Token![mut]> = input.parse()?;
let member = if boxed.is_some() || by_ref.is_some() || mutability.is_some() {
input.parse().map(Member::Named)
} else {
input.parse()
}?;
if boxed.is_none() && by_ref.is_none() && mutability.is_none() && input.peek(Token![:])
|| is_unnamed(&member)
{
return Ok(FieldPat {
attrs: Vec::new(),
member,
colon_token: Some(input.parse()?),
pat: Box::new(Pat::parse_single(input)?),
});
}
let ident = match member {
Member::Named(ident) => ident,
Member::Unnamed(_) => unreachable!(),
};
let pat =
Pat::Ident(PatIdent { attrs: Vec::new(), by_ref, mutability, ident: ident.clone() });
Ok(FieldPat {
attrs: Vec::new(),
member: Member::Named(ident),
colon_token: None,
pat: Box::new(pat),
})
}
fn pat_paren_or_tuple(input: ParseStream<'_>) -> Result<Pat> {
let content;
let paren_token = parenthesized!(content in input);
let mut elems = Punctuated::new();
while !content.is_empty() {
let value = Pat::parse_single(&content)?;
if content.is_empty() {
elems.push_value(value);
break;
}
elems.push_value(value);
let punct = content.parse()?;
elems.push_punct(punct);
}
Ok(Pat::Tuple(PatTuple { attrs: Vec::new(), paren_token, elems }))
}
fn pat_reference(input: ParseStream<'_>) -> Result<PatReference> {
Ok(PatReference {
attrs: Vec::new(),
and_token: input.parse()?,
mutability: input.parse()?,
pat: Box::new(Pat::parse_single(input)?),
})
}
fn is_unnamed(member: &Member) -> bool {
match member {
Member::Named(_) => false,
Member::Unnamed(_) => true,
}
}
}
mod printing {
use proc_macro2::TokenStream;
use quote::{ToTokens, TokenStreamExt};
use syn::Token;
use super::{
FieldPat, PatIdent, PatReference, PatRest, PatStruct, PatTuple, PatTupleStruct, PatType,
PatWild,
};
impl ToTokens for PatIdent {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.append_all(&self.attrs);
self.by_ref.to_tokens(tokens);
self.mutability.to_tokens(tokens);
self.ident.to_tokens(tokens);
}
}
impl ToTokens for PatReference {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.append_all(&self.attrs);
self.and_token.to_tokens(tokens);
self.mutability.to_tokens(tokens);
self.pat.to_tokens(tokens);
}
}
impl ToTokens for PatRest {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.append_all(&self.attrs);
self.dot2_token.to_tokens(tokens);
}
}
impl ToTokens for PatStruct {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.append_all(&self.attrs);
self.path.to_tokens(tokens);
self.brace_token.surround(tokens, |tokens| {
self.fields.to_tokens(tokens);
// Note: We need a comma before the dot2 token if it is present.
if !self.fields.empty_or_trailing() && self.rest.is_some() {
<Token![,]>::default().to_tokens(tokens);
}
self.rest.to_tokens(tokens);
});
}
}
impl ToTokens for PatTuple {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.append_all(&self.attrs);
self.paren_token.surround(tokens, |tokens| {
self.elems.to_tokens(tokens);
});
}
}
impl ToTokens for PatTupleStruct {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.append_all(&self.attrs);
self.path.to_tokens(tokens);
self.paren_token.surround(tokens, |tokens| {
self.elems.to_tokens(tokens);
});
}
}
impl ToTokens for PatType {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.append_all(&self.attrs);
self.pat.to_tokens(tokens);
self.colon_token.to_tokens(tokens);
self.ty.to_tokens(tokens);
}
}
impl ToTokens for PatWild {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.append_all(&self.attrs);
self.underscore_token.to_tokens(tokens);
}
}
impl ToTokens for FieldPat {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.append_all(&self.attrs);
if let Some(colon_token) = &self.colon_token {
self.member.to_tokens(tokens);
colon_token.to_tokens(tokens);
}
self.pat.to_tokens(tokens);
}
}
}