Repository: dtolnay/syn
Pull request #1332 (2023-01-29 00:39:38)

Support parameter name on function signature variadic


Patch:
diff --git a/src/gen/clone.rs b/src/gen/clone.rs
index d5a3a673..b6d63c84 100644
--- a/src/gen/clone.rs
+++ b/src/gen/clone.rs
@@ -98,6 +98,18 @@ impl Clone for BareFnArg {
 }
 #[cfg(any(feature = "derive", feature = "full"))]
 #[cfg_attr(doc_cfg, doc(cfg(feature = "clone-impls")))]
+impl Clone for BareVariadic {
+    fn clone(&self) -> Self {
+        BareVariadic {
+            attrs: self.attrs.clone(),
+            name: self.name.clone(),
+            dots: self.dots.clone(),
+            comma: self.comma.clone(),
+        }
+    }
+}
+#[cfg(any(feature = "derive", feature = "full"))]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "clone-impls")))]
 impl Copy for BinOp {}
 #[cfg(any(feature = "derive", feature = "full"))]
 #[cfg_attr(doc_cfg, doc(cfg(feature = "clone-impls")))]
@@ -2084,13 +2096,15 @@ impl Clone for UseTree {
         }
     }
 }
-#[cfg(any(feature = "derive", feature = "full"))]
+#[cfg(feature = "full")]
 #[cfg_attr(doc_cfg, doc(cfg(feature = "clone-impls")))]
 impl Clone for Variadic {
     fn clone(&self) -> Self {
         Variadic {
             attrs: self.attrs.clone(),
+            pat: self.pat.clone(),
             dots: self.dots.clone(),
+            comma: self.comma.clone(),
         }
     }
 }
diff --git a/src/gen/debug.rs b/src/gen/debug.rs
index 98c1a4a1..1048dd46 100644
--- a/src/gen/debug.rs
+++ b/src/gen/debug.rs
@@ -102,6 +102,18 @@ impl Debug for BareFnArg {
 }
 #[cfg(any(feature = "derive", feature = "full"))]
 #[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
+impl Debug for BareVariadic {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        let mut formatter = formatter.debug_struct("BareVariadic");
+        formatter.field("attrs", &self.attrs);
+        formatter.field("name", &self.name);
+        formatter.field("dots", &self.dots);
+        formatter.field("comma", &self.comma);
+        formatter.finish()
+    }
+}
+#[cfg(any(feature = "derive", feature = "full"))]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
 impl Debug for BinOp {
     fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
         match self {
@@ -2849,13 +2861,15 @@ impl Debug for UseTree {
         }
     }
 }
-#[cfg(any(feature = "derive", feature = "full"))]
+#[cfg(feature = "full")]
 #[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
 impl Debug for Variadic {
     fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
         let mut formatter = formatter.debug_struct("Variadic");
         formatter.field("attrs", &self.attrs);
+        formatter.field("pat", &self.pat);
         formatter.field("dots", &self.dots);
+        formatter.field("comma", &self.comma);
         formatter.finish()
     }
 }
diff --git a/src/gen/eq.rs b/src/gen/eq.rs
index b849d01e..eba79955 100644
--- a/src/gen/eq.rs
+++ b/src/gen/eq.rs
@@ -93,6 +93,16 @@ impl PartialEq for BareFnArg {
 }
 #[cfg(any(feature = "derive", feature = "full"))]
 #[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
+impl Eq for BareVariadic {}
+#[cfg(any(feature = "derive", feature = "full"))]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
+impl PartialEq for BareVariadic {
+    fn eq(&self, other: &Self) -> bool {
+        self.attrs == other.attrs && self.name == other.name && self.comma == other.comma
+    }
+}
+#[cfg(any(feature = "derive", feature = "full"))]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
 impl Eq for BinOp {}
 #[cfg(any(feature = "derive", feature = "full"))]
 #[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
@@ -2036,14 +2046,14 @@ impl PartialEq for UseTree {
         }
     }
 }
-#[cfg(any(feature = "derive", feature = "full"))]
+#[cfg(feature = "full")]
 #[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
 impl Eq for Variadic {}
-#[cfg(any(feature = "derive", feature = "full"))]
+#[cfg(feature = "full")]
 #[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
 impl PartialEq for Variadic {
     fn eq(&self, other: &Self) -> bool {
-        self.attrs == other.attrs
+        self.attrs == other.attrs && self.pat == other.pat && self.comma == other.comma
     }
 }
 #[cfg(any(feature = "derive", feature = "full"))]
diff --git a/src/gen/fold.rs b/src/gen/fold.rs
index 1fc2ba5d..f06f981c 100644
--- a/src/gen/fold.rs
+++ b/src/gen/fold.rs
@@ -63,6 +63,10 @@ pub trait Fold {
         fold_bare_fn_arg(self, i)
     }
     #[cfg(any(feature = "derive", feature = "full"))]
+    fn fold_bare_variadic(&mut self, i: BareVariadic) -> BareVariadic {
+        fold_bare_variadic(self, i)
+    }
+    #[cfg(any(feature = "derive", feature = "full"))]
     fn fold_bin_op(&mut self, i: BinOp) -> BinOp {
         fold_bin_op(self, i)
     }
@@ -717,7 +721,7 @@ pub trait Fold {
     fn fold_use_tree(&mut self, i: UseTree) -> UseTree {
         fold_use_tree(self, i)
     }
-    #[cfg(any(feature = "derive", feature = "full"))]
+    #[cfg(feature = "full")]
     fn fold_variadic(&mut self, i: Variadic) -> Variadic {
         fold_variadic(self, i)
     }
@@ -858,6 +862,22 @@ where
     }
 }
 #[cfg(any(feature = "derive", feature = "full"))]
+pub fn fold_bare_variadic<F>(f: &mut F, node: BareVariadic) -> BareVariadic
+where
+    F: Fold + ?Sized,
+{
+    BareVariadic {
+        attrs: FoldHelper::lift(node.attrs, |it| f.fold_attribute(it)),
+        name: (node.name)
+            .map(|it| (
+                f.fold_ident((it).0),
+                Token![:](tokens_helper(f, &(it).1.spans)),
+            )),
+        dots: Token![...](tokens_helper(f, &node.dots.spans)),
+        comma: (node.comma).map(|it| Token![,](tokens_helper(f, &it.spans))),
+    }
+}
+#[cfg(any(feature = "derive", feature = "full"))]
 pub fn fold_bin_op<F>(f: &mut F, node: BinOp) -> BinOp
 where
     F: Fold + ?Sized,
@@ -2899,7 +2919,7 @@ where
         fn_token: Token![fn](tokens_helper(f, &node.fn_token.span)),
         paren_token: Paren(tokens_helper(f, &node.paren_token.span)),
         inputs: FoldHelper::lift(node.inputs, |it| f.fold_bare_fn_arg(it)),
-        variadic: (node.variadic).map(|it| f.fold_variadic(it)),
+        variadic: (node.variadic).map(|it| f.fold_bare_variadic(it)),
         output: f.fold_return_type(node.output),
     }
 }
@@ -3134,14 +3154,20 @@ where
         UseTree::Group(_binding_0) => UseTree::Group(f.fold_use_group(_binding_0)),
     }
 }
-#[cfg(any(feature = "derive", feature = "full"))]
+#[cfg(feature = "full")]
 pub fn fold_variadic<F>(f: &mut F, node: Variadic) -> Variadic
 where
     F: Fold + ?Sized,
 {
     Variadic {
         attrs: FoldHelper::lift(node.attrs, |it| f.fold_attribute(it)),
+        pat: (node.pat)
+            .map(|it| (
+                Box::new(f.fold_pat(*(it).0)),
+                Token![:](tokens_helper(f, &(it).1.spans)),
+            )),
         dots: Token![...](tokens_helper(f, &node.dots.spans)),
+        comma: (node.comma).map(|it| Token![,](tokens_helper(f, &it.spans))),
     }
 }
 #[cfg(any(feature = "derive", feature = "full"))]
diff --git a/src/gen/hash.rs b/src/gen/hash.rs
index 8082073b..bfa66cbc 100644
--- a/src/gen/hash.rs
+++ b/src/gen/hash.rs
@@ -106,6 +106,18 @@ impl Hash for BareFnArg {
 }
 #[cfg(any(feature = "derive", feature = "full"))]
 #[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
+impl Hash for BareVariadic {
+    fn hash<H>(&self, state: &mut H)
+    where
+        H: Hasher,
+    {
+        self.attrs.hash(state);
+        self.name.hash(state);
+        self.comma.hash(state);
+    }
+}
+#[cfg(any(feature = "derive", feature = "full"))]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
 impl Hash for BinOp {
     fn hash<H>(&self, state: &mut H)
     where
@@ -2675,7 +2687,7 @@ impl Hash for UseTree {
         }
     }
 }
-#[cfg(any(feature = "derive", feature = "full"))]
+#[cfg(feature = "full")]
 #[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
 impl Hash for Variadic {
     fn hash<H>(&self, state: &mut H)
@@ -2683,6 +2695,8 @@ impl Hash for Variadic {
         H: Hasher,
     {
         self.attrs.hash(state);
+        self.pat.hash(state);
+        self.comma.hash(state);
     }
 }
 #[cfg(any(feature = "derive", feature = "full"))]
diff --git a/src/gen/visit.rs b/src/gen/visit.rs
index 6e6f9dbf..390d903e 100644
--- a/src/gen/visit.rs
+++ b/src/gen/visit.rs
@@ -65,6 +65,10 @@ pub trait Visit<'ast> {
         visit_bare_fn_arg(self, i);
     }
     #[cfg(any(feature = "derive", feature = "full"))]
+    fn visit_bare_variadic(&mut self, i: &'ast BareVariadic) {
+        visit_bare_variadic(self, i);
+    }
+    #[cfg(any(feature = "derive", feature = "full"))]
     fn visit_bin_op(&mut self, i: &'ast BinOp) {
         visit_bin_op(self, i);
     }
@@ -716,7 +720,7 @@ pub trait Visit<'ast> {
     fn visit_use_tree(&mut self, i: &'ast UseTree) {
         visit_use_tree(self, i);
     }
-    #[cfg(any(feature = "derive", feature = "full"))]
+    #[cfg(feature = "full")]
     fn visit_variadic(&mut self, i: &'ast Variadic) {
         visit_variadic(self, i);
     }
@@ -860,6 +864,23 @@ where
     v.visit_type(&node.ty);
 }
 #[cfg(any(feature = "derive", feature = "full"))]
+pub fn visit_bare_variadic<'ast, V>(v: &mut V, node: &'ast BareVariadic)
+where
+    V: Visit<'ast> + ?Sized,
+{
+    for it in &node.attrs {
+        v.visit_attribute(it);
+    }
+    if let Some(it) = &node.name {
+        v.visit_ident(&(it).0);
+        tokens_helper(v, &(it).1.spans);
+    }
+    tokens_helper(v, &node.dots.spans);
+    if let Some(it) = &node.comma {
+        tokens_helper(v, &it.spans);
+    }
+}
+#[cfg(any(feature = "derive", feature = "full"))]
 pub fn visit_bin_op<'ast, V>(v: &mut V, node: &'ast BinOp)
 where
     V: Visit<'ast> + ?Sized,
@@ -3324,7 +3345,7 @@ where
         }
     }
     if let Some(it) = &node.variadic {
-        v.visit_variadic(it);
+        v.visit_bare_variadic(it);
     }
     v.visit_return_type(&node.output);
 }
@@ -3584,7 +3605,7 @@ where
         }
     }
 }
-#[cfg(any(feature = "derive", feature = "full"))]
+#[cfg(feature = "full")]
 pub fn visit_variadic<'ast, V>(v: &mut V, node: &'ast Variadic)
 where
     V: Visit<'ast> + ?Sized,
@@ -3592,7 +3613,14 @@ where
     for it in &node.attrs {
         v.visit_attribute(it);
     }
+    if let Some(it) = &node.pat {
+        v.visit_pat(&*(it).0);
+        tokens_helper(v, &(it).1.spans);
+    }
     tokens_helper(v, &node.dots.spans);
+    if let Some(it) = &node.comma {
+        tokens_helper(v, &it.spans);
+    }
 }
 #[cfg(any(feature = "derive", feature = "full"))]
 pub fn visit_variant<'ast, V>(v: &mut V, node: &'ast Variant)
diff --git a/src/gen/visit_mut.rs b/src/gen/visit_mut.rs
index 82f27827..3a8a425f 100644
--- a/src/gen/visit_mut.rs
+++ b/src/gen/visit_mut.rs
@@ -66,6 +66,10 @@ pub trait VisitMut {
         visit_bare_fn_arg_mut(self, i);
     }
     #[cfg(any(feature = "derive", feature = "full"))]
+    fn visit_bare_variadic_mut(&mut self, i: &mut BareVariadic) {
+        visit_bare_variadic_mut(self, i);
+    }
+    #[cfg(any(feature = "derive", feature = "full"))]
     fn visit_bin_op_mut(&mut self, i: &mut BinOp) {
         visit_bin_op_mut(self, i);
     }
@@ -717,7 +721,7 @@ pub trait VisitMut {
     fn visit_use_tree_mut(&mut self, i: &mut UseTree) {
         visit_use_tree_mut(self, i);
     }
-    #[cfg(any(feature = "derive", feature = "full"))]
+    #[cfg(feature = "full")]
     fn visit_variadic_mut(&mut self, i: &mut Variadic) {
         visit_variadic_mut(self, i);
     }
@@ -861,6 +865,23 @@ where
     v.visit_type_mut(&mut node.ty);
 }
 #[cfg(any(feature = "derive", feature = "full"))]
+pub fn visit_bare_variadic_mut<V>(v: &mut V, node: &mut BareVariadic)
+where
+    V: VisitMut + ?Sized,
+{
+    for it in &mut node.attrs {
+        v.visit_attribute_mut(it);
+    }
+    if let Some(it) = &mut node.name {
+        v.visit_ident_mut(&mut (it).0);
+        tokens_helper(v, &mut (it).1.spans);
+    }
+    tokens_helper(v, &mut node.dots.spans);
+    if let Some(it) = &mut node.comma {
+        tokens_helper(v, &mut it.spans);
+    }
+}
+#[cfg(any(feature = "derive", feature = "full"))]
 pub fn visit_bin_op_mut<V>(v: &mut V, node: &mut BinOp)
 where
     V: VisitMut + ?Sized,
@@ -3327,7 +3348,7 @@ where
         }
     }
     if let Some(it) = &mut node.variadic {
-        v.visit_variadic_mut(it);
+        v.visit_bare_variadic_mut(it);
     }
     v.visit_return_type_mut(&mut node.output);
 }
@@ -3587,7 +3608,7 @@ where
         }
     }
 }
-#[cfg(any(feature = "derive", feature = "full"))]
+#[cfg(feature = "full")]
 pub fn visit_variadic_mut<V>(v: &mut V, node: &mut Variadic)
 where
     V: VisitMut + ?Sized,
@@ -3595,7 +3616,14 @@ where
     for it in &mut node.attrs {
         v.visit_attribute_mut(it);
     }
+    if let Some(it) = &mut node.pat {
+        v.visit_pat_mut(&mut *(it).0);
+        tokens_helper(v, &mut (it).1.spans);
+    }
     tokens_helper(v, &mut node.dots.spans);
+    if let Some(it) = &mut node.comma {
+        tokens_helper(v, &mut it.spans);
+    }
 }
 #[cfg(any(feature = "derive", feature = "full"))]
 pub fn visit_variant_mut<V>(v: &mut V, node: &mut Variant)
diff --git a/src/item.rs b/src/item.rs
index 8429786a..5d7546ba 100644
--- a/src/item.rs
+++ b/src/item.rs
@@ -842,6 +842,16 @@ impl Receiver {
     }
 }
 
+ast_struct! {
+    #[cfg_attr(doc_cfg, doc(cfg(feature = "full")))]
+    pub struct Variadic {
+        pub attrs: Vec<Attribute>,
+        pub pat: Option<(Box<Pat>, Token![:])>,
+        pub dots: Token![...],
+        pub comma: Option<Token![,]>,
+    }
+}
+
 #[cfg(feature = "parsing")]
 pub mod parsing {
     use super::*;
@@ -1356,54 +1366,6 @@ pub mod parsing {
         }
     }
 
-    fn pop_variadic(args: &mut Punctuated<FnArg, Token![,]>) -> Option<Variadic> {
-        let trailing_punct = args.trailing_punct();
-
-        let last = match args.last_mut()? {
-            FnArg::Typed(last) => last,
-            _ => return None,
-        };
-
-        let ty = match last.ty.as_ref() {
-            Type::Verbatim(ty) => ty,
-            _ => return None,
-        };
-
-        let mut variadic = Variadic {
-            attrs: Vec::new(),
-            dots: parse2(ty.clone()).ok()?,
-        };
-
-        if let Pat::Verbatim(pat) = last.pat.as_ref() {
-            if pat.to_string() == "..." && !trailing_punct {
-                variadic.attrs = mem::take(&mut last.attrs);
-                args.pop();
-            }
-        }
-
-        Some(variadic)
-    }
-
-    fn variadic_to_tokens(dots: &Token![...]) -> TokenStream {
-        TokenStream::from_iter(vec![
-            TokenTree::Punct({
-                let mut dot = Punct::new('.', Spacing::Joint);
-                dot.set_span(dots.spans[0]);
-                dot
-            }),
-            TokenTree::Punct({
-                let mut dot = Punct::new('.', Spacing::Joint);
-                dot.set_span(dots.spans[1]);
-                dot
-            }),
-            TokenTree::Punct({
-                let mut dot = Punct::new('.', Spacing::Alone);
-                dot.set_span(dots.spans[2]);
-                dot
-            }),
-        ])
-    }
-
     fn peek_signature(input: ParseStream) -> bool {
         let fork = input.fork();
         fork.parse::<Option<Token![const]>>().is_ok()
@@ -1426,8 +1388,7 @@ pub mod parsing {
 
             let content;
             let paren_token = parenthesized!(content in input);
-            let mut inputs = parse_fn_args(&content)?;
-            let variadic = pop_variadic(&mut inputs);
+            let (inputs, variadic) = parse_fn_args(&content)?;
 
             let output: ReturnType = input.parse()?;
             generics.where_clause = input.parse()?;
@@ -1480,45 +1441,70 @@ pub mod parsing {
     #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
     impl Parse for FnArg {
         fn parse(input: ParseStream) -> Result<Self> {
+            let allow_variadic = false;
             let attrs = input.call(Attribute::parse_outer)?;
+            match parse_fn_arg_or_variadic(input, attrs, allow_variadic)? {
+                FnArgOrVariadic::FnArg(arg) => Ok(arg),
+                FnArgOrVariadic::Variadic(_) => unreachable!(),
+            }
+        }
+    }
 
-            let ahead = input.fork();
-            if let Ok(mut receiver) = ahead.parse::<Receiver>() {
-                if !ahead.peek(Token![:]) {
-                    input.advance_to(&ahead);
-                    receiver.attrs = attrs;
-                    return Ok(FnArg::Receiver(receiver));
-                }
+    enum FnArgOrVariadic {
+        FnArg(FnArg),
+        Variadic(Variadic),
+    }
+
+    fn parse_fn_arg_or_variadic(
+        input: ParseStream,
+        attrs: Vec<Attribute>,
+        allow_variadic: bool,
+    ) -> Result<FnArgOrVariadic> {
+        let ahead = input.fork();
+        if let Ok(mut receiver) = ahead.parse::<Receiver>() {
+            if !ahead.peek(Token![:]) {
+                input.advance_to(&ahead);
+                receiver.attrs = attrs;
+                return Ok(FnArgOrVariadic::FnArg(FnArg::Receiver(receiver)));
             }
+        }
 
-            Ok(FnArg::Typed(
-                if input.peek(Ident) && input.peek2(Token![<]) {
-                    // Hack to parse pre-2018 syntax in
-                    // test/ui/rfc-2565-param-attrs/param-attrs-pretty.rs
-                    // because the rest of the test case is valuable.
-                    let span = input.fork().parse::<Ident>()?.span();
-                    PatType {
-                        attrs,
-                        pat: Box::new(Pat::Wild(PatWild {
-                            attrs: Vec::new(),
-                            underscore_token: Token![_](span),
-                        })),
-                        colon_token: Token![:](span),
-                        ty: input.parse()?,
-                    }
-                } else {
-                    PatType {
-                        attrs,
-                        pat: Box::new(Pat::parse_single(input)?),
-                        colon_token: input.parse()?,
-                        ty: Box::new(match input.parse::<Option<Token![...]>>()? {
-                            Some(dot3) => Type::Verbatim(variadic_to_tokens(&dot3)),
-                            None => input.parse()?,
-                        }),
-                    }
-                },
-            ))
+        // Hack to parse pre-2018 syntax in
+        // test/ui/rfc-2565-param-attrs/param-attrs-pretty.rs
+        // because the rest of the test case is valuable.
+        if input.peek(Ident) && input.peek2(Token![<]) {
+            let span = input.fork().parse::<Ident>()?.span();
+            return Ok(FnArgOrVariadic::FnArg(FnArg::Typed(PatType {
+                attrs,
+                pat: Box::new(Pat::Wild(PatWild {
+                    attrs: Vec::new(),
+                    underscore_token: Token![_](span),
+                })),
+                colon_token: Token![:](span),
+                ty: input.parse()?,
+            })));
+        }
+
+        let pat = Box::new(Pat::parse_single(input)?);
+        let colon_token: Token![:] = input.parse()?;
+
+        if allow_variadic {
+            if let Some(dots) = input.parse::<Option<Token![...]>>()? {
+                return Ok(FnArgOrVariadic::Variadic(Variadic {
+                    attrs,
+                    pat: Some((pat, colon_token)),
+                    dots,
+                    comma: None,
+                }));
+            }
         }
+
+        Ok(FnArgOrVariadic::FnArg(FnArg::Typed(PatType {
+            attrs,
+            pat,
+            colon_token,
+            ty: input.parse()?,
+        })))
     }
 
     #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
@@ -1539,43 +1525,62 @@ pub mod parsing {
         }
     }
 
-    fn parse_fn_args(input: ParseStream) -> Result<Punctuated<FnArg, Token![,]>> {
+    fn parse_fn_args(
+        input: ParseStream,
+    ) -> Result<(Punctuated<FnArg, Token![,]>, Option<Variadic>)> {
         let mut args = Punctuated::new();
+        let mut variadic = None;
         let mut has_receiver = false;
 
         while !input.is_empty() {
             let attrs = input.call(Attribute::parse_outer)?;
 
-            let arg = if let Some(dots) = input.parse::<Option<Token![...]>>()? {
-                FnArg::Typed(PatType {
+            if let Some(dots) = input.parse::<Option<Token![...]>>()? {
+                variadic = Some(Variadic {
                     attrs,
-                    pat: Box::new(Pat::Verbatim(variadic_to_tokens(&dots))),
-                    colon_token: Token![:](dots.spans[0]),
-                    ty: Box::new(Type::Verbatim(variadic_to_tokens(&dots))),
-                })
-            } else {
-                let mut arg: FnArg = input.parse()?;
-                match &mut arg {
-                    FnArg::Receiver(receiver) if has_receiver => {
-                        return Err(Error::new(
-                            receiver.self_token.span,
-                            "unexpected second method receiver",
-                        ));
-                    }
-                    FnArg::Receiver(receiver) if !args.is_empty() => {
-                        return Err(Error::new(
-                            receiver.self_token.span,
-                            "unexpected method receiver",
-                        ));
-                    }
-                    FnArg::Receiver(receiver) => {
-                        has_receiver = true;
-                        receiver.attrs = attrs;
-                    }
-                    FnArg::Typed(arg) => arg.attrs = attrs,
+                    pat: None,
+                    dots,
+                    comma: if input.is_empty() {
+                        None
+                    } else {
+                        Some(input.parse()?)
+                    },
+                });
+                break;
+            }
+
+            let allow_variadic = true;
+            let arg = match parse_fn_arg_or_variadic(input, attrs, allow_variadic)? {
+                FnArgOrVariadic::FnArg(arg) => arg,
+                FnArgOrVariadic::Variadic(arg) => {
+                    variadic = Some(Variadic {
+                        comma: if input.is_empty() {
+                            None
+                        } else {
+                            Some(input.parse()?)
+                        },
+                        ..arg
+                    });
+                    break;
                 }
-                arg
             };
+
+            match &arg {
+                FnArg::Receiver(receiver) if has_receiver => {
+                    return Err(Error::new(
+                        receiver.self_token.span,
+                        "unexpected second method receiver",
+                    ));
+                }
+                FnArg::Receiver(receiver) if !args.is_empty() => {
+                    return Err(Error::new(
+                        receiver.self_token.span,
+                        "unexpected method receiver",
+                    ));
+                }
+                FnArg::Receiver(_) => has_receiver = true,
+                FnArg::Typed(_) => {}
+            }
             args.push_value(arg);
 
             if input.is_empty() {
@@ -1586,7 +1591,7 @@ pub mod parsing {
             args.push_punct(comma);
         }
 
-        Ok(args)
+        Ok((args, variadic))
     }
 
     #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
@@ -3138,33 +3143,6 @@ mod printing {
         }
     }
 
-    fn maybe_variadic_to_tokens(arg: &FnArg, tokens: &mut TokenStream) -> bool {
-        let arg = match arg {
-            FnArg::Typed(arg) => arg,
-            FnArg::Receiver(receiver) => {
-                receiver.to_tokens(tokens);
-                return false;
-            }
-        };
-
-        match arg.ty.as_ref() {
-            Type::Verbatim(ty) if ty.to_string() == "..." => {
-                match arg.pat.as_ref() {
-                    Pat::Verbatim(pat) if pat.to_string() == "..." => {
-                        tokens.append_all(arg.attrs.outer());
-                        pat.to_tokens(tokens);
-                    }
-                    _ => arg.to_tokens(tokens),
-                }
-                true
-            }
-            _ => {
-                arg.to_tokens(tokens);
-                false
-            }
-        }
-    }
-
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
     impl ToTokens for Signature {
         fn to_tokens(&self, tokens: &mut TokenStream) {
@@ -3176,16 +3154,12 @@ mod printing {
             self.ident.to_tokens(tokens);
             self.generics.to_tokens(tokens);
             self.paren_token.surround(tokens, |tokens| {
-                let mut last_is_variadic = false;
-                for pair in self.inputs.pairs() {
-                    last_is_variadic = maybe_variadic_to_tokens(pair.value(), tokens);
-                    pair.punct().to_tokens(tokens);
-                }
-                if self.variadic.is_some() && !last_is_variadic {
+                self.inputs.to_tokens(tokens);
+                if let Some(variadic) = &self.variadic {
                     if !self.inputs.empty_or_trailing() {
                         <Token![,]>::default().to_tokens(tokens);
                     }
-                    self.variadic.to_tokens(tokens);
+                    variadic.to_tokens(tokens);
                 }
             });
             self.output.to_tokens(tokens);
@@ -3205,4 +3179,17 @@ mod printing {
             self.self_token.to_tokens(tokens);
         }
     }
+
+    #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
+    impl ToTokens for Variadic {
+        fn to_tokens(&self, tokens: &mut TokenStream) {
+            tokens.append_all(self.attrs.outer());
+            if let Some((pat, colon)) = &self.pat {
+                pat.to_tokens(tokens);
+                colon.to_tokens(tokens);
+            }
+            self.dots.to_tokens(tokens);
+            self.comma.to_tokens(tokens);
+        }
+    }
 }
diff --git a/src/lib.rs b/src/lib.rs
index ffa8d972..373bad62 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -388,7 +388,7 @@ pub use crate::item::{
     ItemExternCrate, ItemFn, ItemForeignMod, ItemImpl, ItemMacro, ItemMod, ItemStatic, ItemStruct,
     ItemTrait, ItemTraitAlias, ItemType, ItemUnion, ItemUse, Receiver, Signature, TraitItem,
     TraitItemConst, TraitItemFn, TraitItemMacro, TraitItemType, UseGlob, UseGroup, UseName,
-    UsePath, UseRename, UseTree,
+    UsePath, UseRename, UseTree, Variadic,
 };
 
 mod lifetime;
@@ -488,9 +488,9 @@ mod tt;
 mod ty;
 #[cfg(any(feature = "full", feature = "derive"))]
 pub use crate::ty::{
-    Abi, BareFnArg, ReturnType, Type, TypeArray, TypeBareFn, TypeGroup, TypeImplTrait, TypeInfer,
-    TypeMacro, TypeNever, TypeParen, TypePath, TypePtr, TypeReference, TypeSlice, TypeTraitObject,
-    TypeTuple, Variadic,
+    Abi, BareFnArg, BareVariadic, ReturnType, Type, TypeArray, TypeBareFn, TypeGroup,
+    TypeImplTrait, TypeInfer, TypeMacro, TypeNever, TypeParen, TypePath, TypePtr, TypeReference,
+    TypeSlice, TypeTraitObject, TypeTuple,
 };
 
 #[cfg(all(any(feature = "full", feature = "derive"), feature = "parsing"))]
diff --git a/src/ty.rs b/src/ty.rs
index 10e45d2a..1e9fdbb8 100644
--- a/src/ty.rs
+++ b/src/ty.rs
@@ -101,7 +101,7 @@ ast_struct! {
         pub fn_token: Token![fn],
         pub paren_token: token::Paren,
         pub inputs: Punctuated<BareFnArg, Token![,]>,
-        pub variadic: Option<Variadic>,
+        pub variadic: Option<BareVariadic>,
         pub output: ReturnType,
     }
 }
@@ -250,9 +250,11 @@ ast_struct! {
     /// }
     /// ```
     #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))]
-    pub struct Variadic {
+    pub struct BareVariadic {
         pub attrs: Vec<Attribute>,
+        pub name: Option<(Ident, Token![:])>,
         pub dots: Token![...],
+        pub comma: Option<Token![,]>,
     }
 }
 
@@ -275,7 +277,7 @@ pub mod parsing {
     use crate::ext::IdentExt;
     use crate::parse::{Parse, ParseStream, Result};
     use crate::path;
-    use proc_macro2::{Punct, Spacing, Span, TokenTree};
+    use proc_macro2::Span;
 
     #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
     impl Parse for Type {
@@ -664,11 +666,13 @@ pub mod parsing {
                     while !args.is_empty() {
                         let attrs = args.call(Attribute::parse_outer)?;
 
-                        if inputs.empty_or_trailing() && args.peek(Token![...]) {
-                            variadic = Some(Variadic {
-                                attrs,
-                                dots: args.parse()?,
-                            });
+                        if inputs.empty_or_trailing()
+                            && (args.peek(Token![...])
+                                || args.peek(Ident)
+                                    && args.peek2(Token![:])
+                                    && args.peek3(Token![...]))
+                        {
+                            variadic = Some(parse_bare_variadic(&args, attrs)?);
                             break;
                         }
 
@@ -971,23 +975,8 @@ pub mod parsing {
             None
         };
 
-        let ty = if !has_self && input.peek(Token![...]) {
-            let dot3 = input.parse::<Token![...]>()?;
-            let args = vec![
-                TokenTree::Punct(Punct::new('.', Spacing::Joint)),
-                TokenTree::Punct(Punct::new('.', Spacing::Joint)),
-                TokenTree::Punct(Punct::new('.', Spacing::Alone)),
-            ];
-            let tokens: TokenStream = args
-                .into_iter()
-                .zip(&dot3.spans)
-                .map(|(mut arg, span)| {
-                    arg.set_span(*span);
-                    arg
-                })
-                .collect();
-            Some(Type::Verbatim(tokens))
-        } else if allow_self && !has_self && input.peek(Token![mut]) && input.peek2(Token![self]) {
+        let ty = if allow_self && !has_self && input.peek(Token![mut]) && input.peek2(Token![self])
+        {
             input.parse::<Token![mut]>()?;
             input.parse::<Token![self]>()?;
             None
@@ -1006,6 +995,21 @@ pub mod parsing {
         Ok(BareFnArg { attrs, name, ty })
     }
 
+    fn parse_bare_variadic(input: ParseStream, attrs: Vec<Attribute>) -> Result<BareVariadic> {
+        Ok(BareVariadic {
+            attrs,
+            name: if input.peek(Ident) || input.peek(Token![_]) {
+                let name = input.call(Ident::parse_any)?;
+                let colon: Token![:] = input.parse()?;
+                Some((name, colon))
+            } else {
+                None
+            },
+            dots: input.parse()?,
+            comma: input.parse()?,
+        })
+    }
+
     #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
     impl Parse for Abi {
         fn parse(input: ParseStream) -> Result<Self> {
@@ -1198,10 +1202,15 @@ mod printing {
     }
 
     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
-    impl ToTokens for Variadic {
+    impl ToTokens for BareVariadic {
         fn to_tokens(&self, tokens: &mut TokenStream) {
             tokens.append_all(self.attrs.outer());
+            if let Some((name, colon)) = &self.name {
+                name.to_tokens(tokens);
+                colon.to_tokens(tokens);
+            }
             self.dots.to_tokens(tokens);
+            self.comma.to_tokens(tokens);
         }
     }
 
diff --git a/syn.json b/syn.json
index f3abb9ae..d47ecf02 100644
--- a/syn.json
+++ b/syn.json
@@ -216,6 +216,42 @@
         }
       }
     },
+    {
+      "ident": "BareVariadic",
+      "features": {
+        "any": [
+          "derive",
+          "full"
+        ]
+      },
+      "fields": {
+        "attrs": {
+          "vec": {
+            "syn": "Attribute"
+          }
+        },
+        "name": {
+          "option": {
+            "tuple": [
+              {
+                "proc_macro2": "Ident"
+              },
+              {
+                "token": "Colon"
+              }
+            ]
+          }
+        },
+        "dots": {
+          "token": "Dot3"
+        },
+        "comma": {
+          "option": {
+            "token": "Comma"
+          }
+        }
+      }
+    },
     {
       "ident": "BinOp",
       "features": {
@@ -4732,7 +4768,7 @@
         },
         "variadic": {
           "option": {
-            "syn": "Variadic"
+            "syn": "BareVariadic"
           }
         },
         "output": {
@@ -5205,7 +5241,6 @@
       "ident": "Variadic",
       "features": {
         "any": [
-          "derive",
           "full"
         ]
       },
@@ -5215,8 +5250,27 @@
             "syn": "Attribute"
           }
         },
+        "pat": {
+          "option": {
+            "tuple": [
+              {
+                "box": {
+                  "syn": "Pat"
+                }
+              },
+              {
+                "token": "Colon"
+              }
+            ]
+          }
+        },
         "dots": {
           "token": "Dot3"
+        },
+        "comma": {
+          "option": {
+            "token": "Comma"
+          }
         }
       }
     },


Test patch:
diff --git a/tests/debug/gen.rs b/tests/debug/gen.rs
index 43f3e97f..cd8c7110 100644
--- a/tests/debug/gen.rs
+++ b/tests/debug/gen.rs
@@ -186,6 +186,44 @@ impl Debug for Lite<syn::BareFnArg> {
         formatter.finish()
     }
 }
+impl Debug for Lite<syn::BareVariadic> {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        let _val = &self.value;
+        let mut formatter = formatter.debug_struct("BareVariadic");
+        if !_val.attrs.is_empty() {
+            formatter.field("attrs", Lite(&_val.attrs));
+        }
+        if let Some(val) = &_val.name {
+            #[derive(RefCast)]
+            #[repr(transparent)]
+            struct Print((proc_macro2::Ident, syn::token::Colon));
+            impl Debug for Print {
+                fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                    formatter.write_str("Some")?;
+                    let _val = &self.0;
+                    formatter.write_str("(")?;
+                    Debug::fmt(Lite(&_val.0), formatter)?;
+                    formatter.write_str(")")?;
+                    Ok(())
+                }
+            }
+            formatter.field("name", Print::ref_cast(val));
+        }
+        if let Some(val) = &_val.comma {
+            #[derive(RefCast)]
+            #[repr(transparent)]
+            struct Print(syn::token::Comma);
+            impl Debug for Print {
+                fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                    formatter.write_str("Some")?;
+                    Ok(())
+                }
+            }
+            formatter.field("comma", Print::ref_cast(val));
+        }
+        formatter.finish()
+    }
+}
 impl Debug for Lite<syn::BinOp> {
     fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
         let _val = &self.value;
@@ -5018,7 +5056,7 @@ impl Debug for Lite<syn::Type> {
                 if let Some(val) = &_val.variadic {
                     #[derive(RefCast)]
                     #[repr(transparent)]
-                    struct Print(syn::Variadic);
+                    struct Print(syn::BareVariadic);
                     impl Debug for Print {
                         fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                             formatter.write_str("Some")?;
@@ -5252,7 +5290,7 @@ impl Debug for Lite<syn::TypeBareFn> {
         if let Some(val) = &_val.variadic {
             #[derive(RefCast)]
             #[repr(transparent)]
-            struct Print(syn::Variadic);
+            struct Print(syn::BareVariadic);
             impl Debug for Print {
                 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                     formatter.write_str("Some")?;
@@ -5643,6 +5681,34 @@ impl Debug for Lite<syn::Variadic> {
         if !_val.attrs.is_empty() {
             formatter.field("attrs", Lite(&_val.attrs));
         }
+        if let Some(val) = &_val.pat {
+            #[derive(RefCast)]
+            #[repr(transparent)]
+            struct Print((Box<syn::Pat>, syn::token::Colon));
+            impl Debug for Print {
+                fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                    formatter.write_str("Some")?;
+                    let _val = &self.0;
+                    formatter.write_str("(")?;
+                    Debug::fmt(Lite(&_val.0), formatter)?;
+                    formatter.write_str(")")?;
+                    Ok(())
+                }
+            }
+            formatter.field("pat", Print::ref_cast(val));
+        }
+        if let Some(val) = &_val.comma {
+            #[derive(RefCast)]
+            #[repr(transparent)]
+            struct Print(syn::token::Comma);
+            impl Debug for Print {
+                fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                    formatter.write_str("Some")?;
+                    Ok(())
+                }
+            }
+            formatter.field("comma", Print::ref_cast(val));
+        }
         formatter.finish()
     }
 }
diff --git a/tests/repo/mod.rs b/tests/repo/mod.rs
index d15a1a0b..b06fab76 100644
--- a/tests/repo/mod.rs
+++ b/tests/repo/mod.rs
@@ -25,6 +25,9 @@ static EXCLUDE_FILES: &[&str] = &[
     "tests/ui/const-generics/early/closing-args-token.rs",
     "tests/ui/const-generics/early/const-expression-parameter.rs",
 
+    // Compile-fail variadics in not the last position of a function parameter list
+    "tests/ui/parser/variadic-ffi-syntactic-pass.rs",
+
     // Need at least one trait in impl Trait, no such type as impl 'static
     "tests/ui/type-alias-impl-trait/generic_type_does_not_live_long_enough.rs",
 
@@ -43,7 +46,9 @@ static EXCLUDE_FILES: &[&str] = &[
     // Various extensions to Rust syntax made up by rust-analyzer
     "src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0012_type_item_where_clause.rs",
     "src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0040_crate_keyword_vis.rs",
+    "src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0123_param_list_vararg.rs",
     "src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0131_existential_type.rs",
+    "src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0156_fn_def_param.rs",
     "src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0179_use_tree_abs_star.rs",
     "src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0188_const_param_default_path.rs",
     "src/tools/rust-analyzer/crates/parser/test_data/parser/ok/0015_use_tree.rs",
diff --git a/tests/test_size.rs b/tests/test_size.rs
index de779825..2bde9459 100644
--- a/tests/test_size.rs
+++ b/tests/test_size.rs
@@ -12,13 +12,13 @@ fn test_expr_size() {
 #[rustversion::attr(before(2022-09-09), ignore)]
 #[test]
 fn test_item_size() {
-    assert_eq!(mem::size_of::<Item>(), 320);
+    assert_eq!(mem::size_of::<Item>(), 344);
 }
 
 #[rustversion::attr(before(2022-11-24), ignore)]
 #[test]
 fn test_type_size() {
-    assert_eq!(mem::size_of::<Type>(), 192);
+    assert_eq!(mem::size_of::<Type>(), 224);
 }
 
 #[rustversion::attr(before(2021-10-11), ignore)]