From 1ae5627967cdc89ed7ee2006d97a80a7c10a3918 Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Mon, 19 Feb 2024 23:43:52 +0100 Subject: [PATCH] Add Lifetime type and support it in function parameters and generics --- src/parse.rs | 3 +- src/parse_fn.rs | 5 +- src/parse_impl.rs | 7 +- src/parse_type.rs | 35 +++++---- src/parse_utils.rs | 3 +- ...ts__interpret_ty_expr_generic_as_path.snap | 14 ++-- ...tests__parse_fn_mut_receiver_lifetime.snap | 76 +++++++++++++++++++ ...al__tests__parse_fn_receiver_lifetime.snap | 64 ++++++++++++++++ .../venial__tests__parse_fn_self_param-2.snap | 1 + .../venial__tests__parse_fn_self_param-3.snap | 1 + .../venial__tests__parse_fn_self_param-4.snap | 1 + .../venial__tests__parse_fn_self_param.snap | 1 + .../venial__tests__parse_generic_args.snap | 14 ++-- .../venial__tests__parse_impl_inherent.snap | 1 + .../venial__tests__parse_impl_trait.snap | 1 + ...l__tests__parse_inline_generic_args-2.snap | 14 ++-- .../venial__tests__parse_trait_simple.snap | 1 + src/tests.rs | 20 +++++ src/types.rs | 32 ++++++-- src/types_edition.rs | 18 +++-- 20 files changed, 257 insertions(+), 55 deletions(-) create mode 100644 src/snapshots/venial__tests__parse_fn_mut_receiver_lifetime.snap create mode 100644 src/snapshots/venial__tests__parse_fn_receiver_lifetime.snap diff --git a/src/parse.rs b/src/parse.rs index ba76717..b3677aa 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -9,8 +9,7 @@ use crate::parse_type::{ parse_named_fields, parse_tuple_fields, }; use crate::parse_utils::{consume_outer_attributes, consume_punct, consume_vis_marker}; -use crate::types::{Enum, Fields, Item, Struct, Union}; -use crate::types_edition::GroupSpan; +use crate::types::{Enum, Fields, GroupSpan, Item, Struct, Union}; use proc_macro2::token_stream::IntoIter; use proc_macro2::{Delimiter, TokenStream, TokenTree}; use std::iter::Peekable; diff --git a/src/parse_fn.rs b/src/parse_fn.rs index a6bd5c3..83a94b7 100644 --- a/src/parse_fn.rs +++ b/src/parse_fn.rs @@ -1,5 +1,6 @@ use crate::parse_type::{ - consume_field_type, consume_generic_params, consume_item_name, consume_where_clause, + consume_field_type, consume_generic_params, consume_item_name, consume_lifetime, + consume_where_clause, }; use crate::parse_utils::{ consume_any_ident, consume_comma, consume_ident, consume_outer_attributes, consume_punct, @@ -79,6 +80,7 @@ fn parse_fn_params(tokens: TokenStream) -> Punctuated { let attributes = consume_outer_attributes(&mut tokens); let tk_ref = consume_punct(&mut tokens, '&'); + let lifetime = consume_lifetime(&mut tokens, false); let tk_mut = consume_ident(&mut tokens, "mut"); let tk_self = consume_ident(&mut tokens, "self"); @@ -86,6 +88,7 @@ fn parse_fn_params(tokens: TokenStream) -> Punctuated { FnParam::Receiver(FnReceiverParam { attributes, tk_ref, + lifetime, tk_mut, tk_self, }) diff --git a/src/parse_impl.rs b/src/parse_impl.rs index d4db482..45af19e 100644 --- a/src/parse_impl.rs +++ b/src/parse_impl.rs @@ -6,9 +6,10 @@ use crate::parse_utils::{ consume_ident, consume_inner_attributes, consume_outer_attributes, consume_punct, consume_stuff_until, consume_vis_marker, parse_any_ident, parse_ident, parse_punct, }; -use crate::types::{Constant, ImplMember, TypeAlias, ValueExpr}; -use crate::types_edition::GroupSpan; -use crate::{Attribute, Impl, Item, Trait, TraitMember, TypeExpr, VisMarker}; +use crate::types::{ + Attribute, Constant, GroupSpan, Impl, ImplMember, Item, Trait, TraitMember, TypeAlias, + TypeExpr, ValueExpr, VisMarker, +}; use proc_macro2::{Delimiter, Group, TokenTree}; use quote::ToTokens; use std::iter::Peekable; diff --git a/src/parse_type.rs b/src/parse_type.rs index 818a62c..21045e2 100644 --- a/src/parse_type.rs +++ b/src/parse_type.rs @@ -3,16 +3,13 @@ use crate::parse_utils::{ consume_colon2, consume_comma, consume_ident, consume_outer_attributes, consume_punct, consume_stuff_until, consume_vis_marker, parse_any_ident, parse_punct, }; -use crate::punctuated::Punctuated; use crate::types::{ EnumVariant, EnumVariantValue, Fields, GenericArg, GenericArgList, GenericBound, GenericParam, - GenericParamList, NamedField, NamedFields, TupleField, TupleFields, TypeExpr, WhereClause, - WhereClausePredicate, + GenericParamList, GroupSpan, Lifetime, NamedField, NamedFields, Punctuated, TupleField, + TupleFields, TypeExpr, WhereClause, WhereClausePredicate, }; -use crate::types_edition::GroupSpan; use proc_macro2::{Delimiter, Group, Ident, Punct, TokenStream, TokenTree}; use std::iter::Peekable; -use std::vec::IntoIter; type TokenIter = Peekable; @@ -99,8 +96,8 @@ fn parse_generic_arg(tokens: Vec) -> GenericArg { // Note: method not called if tokens is empty let mut tokens = tokens.into_iter().peekable(); - if let Some((tk_lifetime, ident)) = consume_lifetime(&mut tokens) { - return GenericArg::Lifetime { tk_lifetime, ident }; + if let Some(lifetime) = consume_lifetime(&mut tokens, true) { + return GenericArg::Lifetime { lifetime }; } // Then, try parsing Item = ... @@ -129,9 +126,12 @@ fn parse_generic_arg(tokens: Vec) -> GenericArg { } } -fn consume_lifetime(tokens: &mut Peekable>) -> Option<(Punct, Ident)> { +pub(crate) fn consume_lifetime( + tokens: &mut Peekable>, + expect_end: bool, +) -> Option { // Try parsing 'lifetime - let tk_lifetime = match tokens.peek() { + let tk_apostrophe = match tokens.peek() { Some(TokenTree::Punct(punct)) if punct.as_char() == '\'' => { let apostrophe = punct.clone(); tokens.next(); // consume ' @@ -142,13 +142,18 @@ fn consume_lifetime(tokens: &mut Peekable>) -> Option<(Punct // after the ', there must be a single identifier match tokens.next() { - Some(TokenTree::Ident(ident)) => { - assert!( - tokens.next().is_none(), - "cannot parse lifetime generic argument" - ); + Some(TokenTree::Ident(name)) => { + if expect_end { + assert!( + tokens.next().is_none(), + "cannot parse lifetime generic argument" + ); + } - return Some((tk_lifetime, ident)); + Some(Lifetime { + tk_apostrophe, + name, + }) } Some(other) => { panic!( diff --git a/src/parse_utils.rs b/src/parse_utils.rs index 3fa871c..26388df 100644 --- a/src/parse_utils.rs +++ b/src/parse_utils.rs @@ -1,6 +1,5 @@ use crate::parse_type::consume_generic_args; -use crate::types::{Attribute, AttributeValue, Path, PathSegment, VisMarker}; -use crate::types_edition::GroupSpan; +use crate::types::{Attribute, AttributeValue, GroupSpan, Path, PathSegment, VisMarker}; use proc_macro2::{Delimiter, Ident, Punct, Spacing, TokenStream, TokenTree}; use std::iter::Peekable; diff --git a/src/snapshots/venial__tests__interpret_ty_expr_generic_as_path.snap b/src/snapshots/venial__tests__interpret_ty_expr_generic_as_path.snap index 3d685b9..b022e65 100644 --- a/src/snapshots/venial__tests__interpret_ty_expr_generic_as_path.snap +++ b/src/snapshots/venial__tests__interpret_ty_expr_generic_as_path.snap @@ -23,13 +23,15 @@ Path { generic_args: GenericArgList { args: [ Lifetime { - tk_lifetime: Punct { - char: '\'', - spacing: Joint, + lifetime: Lifetime { + tk_apostrophe: Punct { + char: '\'', + spacing: Joint, + }, + name: Ident( + a, + ), }, - ident: Ident( - a, - ), }, TypeOrConst { expr: [ diff --git a/src/snapshots/venial__tests__parse_fn_mut_receiver_lifetime.snap b/src/snapshots/venial__tests__parse_fn_mut_receiver_lifetime.snap new file mode 100644 index 0000000..bcea60f --- /dev/null +++ b/src/snapshots/venial__tests__parse_fn_mut_receiver_lifetime.snap @@ -0,0 +1,76 @@ +--- +source: src/tests.rs +expression: func +--- +Function( + Function { + attributes: [], + vis_marker: None, + qualifiers: FnQualifiers { + tk_default: None, + tk_const: None, + tk_async: None, + tk_unsafe: None, + tk_extern: None, + extern_abi: None, + }, + tk_fn_keyword: Ident( + fn, + ), + name: Ident( + prototype, + ), + generic_params: Some( + [ + GenericParam { + tk_prefix: "'", + name: "a", + bound: None, + }, + ], + ), + tk_params_parens: (), + params: [ + Receiver( + FnReceiverParam { + attributes: [], + tk_ref: Some( + Punct { + char: '&', + spacing: Alone, + }, + ), + lifetime: Some( + Lifetime { + tk_apostrophe: Punct { + char: '\'', + spacing: Joint, + }, + name: Ident( + a, + ), + }, + ), + tk_mut: Some( + Ident( + mut, + ), + ), + tk_self: Ident( + self, + ), + }, + ), + ], + where_clause: None, + tk_return_arrow: None, + return_ty: None, + tk_semicolon: Some( + Punct { + char: ';', + spacing: Alone, + }, + ), + body: None, + }, +) diff --git a/src/snapshots/venial__tests__parse_fn_receiver_lifetime.snap b/src/snapshots/venial__tests__parse_fn_receiver_lifetime.snap new file mode 100644 index 0000000..f6ae0e3 --- /dev/null +++ b/src/snapshots/venial__tests__parse_fn_receiver_lifetime.snap @@ -0,0 +1,64 @@ +--- +source: src/tests.rs +expression: func +--- +Function( + Function { + attributes: [], + vis_marker: None, + qualifiers: FnQualifiers { + tk_default: None, + tk_const: None, + tk_async: None, + tk_unsafe: None, + tk_extern: None, + extern_abi: None, + }, + tk_fn_keyword: Ident( + fn, + ), + name: Ident( + prototype, + ), + generic_params: None, + tk_params_parens: (), + params: [ + Receiver( + FnReceiverParam { + attributes: [], + tk_ref: Some( + Punct { + char: '&', + spacing: Alone, + }, + ), + lifetime: Some( + Lifetime { + tk_apostrophe: Punct { + char: '\'', + spacing: Joint, + }, + name: Ident( + lifetime, + ), + }, + ), + tk_mut: None, + tk_self: Ident( + self, + ), + }, + ), + ], + where_clause: None, + tk_return_arrow: None, + return_ty: None, + tk_semicolon: Some( + Punct { + char: ';', + spacing: Alone, + }, + ), + body: None, + }, +) diff --git a/src/snapshots/venial__tests__parse_fn_self_param-2.snap b/src/snapshots/venial__tests__parse_fn_self_param-2.snap index 67ffddb..ba85b1d 100644 --- a/src/snapshots/venial__tests__parse_fn_self_param-2.snap +++ b/src/snapshots/venial__tests__parse_fn_self_param-2.snap @@ -33,6 +33,7 @@ Ok( spacing: Alone, }, ), + lifetime: None, tk_mut: None, tk_self: Ident( self, diff --git a/src/snapshots/venial__tests__parse_fn_self_param-3.snap b/src/snapshots/venial__tests__parse_fn_self_param-3.snap index a7f3ea6..5dbca1f 100644 --- a/src/snapshots/venial__tests__parse_fn_self_param-3.snap +++ b/src/snapshots/venial__tests__parse_fn_self_param-3.snap @@ -28,6 +28,7 @@ Ok( FnReceiverParam { attributes: [], tk_ref: None, + lifetime: None, tk_mut: Some( Ident( mut, diff --git a/src/snapshots/venial__tests__parse_fn_self_param-4.snap b/src/snapshots/venial__tests__parse_fn_self_param-4.snap index 49c1d1d..9bf6b13 100644 --- a/src/snapshots/venial__tests__parse_fn_self_param-4.snap +++ b/src/snapshots/venial__tests__parse_fn_self_param-4.snap @@ -33,6 +33,7 @@ Ok( spacing: Alone, }, ), + lifetime: None, tk_mut: Some( Ident( mut, diff --git a/src/snapshots/venial__tests__parse_fn_self_param.snap b/src/snapshots/venial__tests__parse_fn_self_param.snap index 91111ff..78ed165 100644 --- a/src/snapshots/venial__tests__parse_fn_self_param.snap +++ b/src/snapshots/venial__tests__parse_fn_self_param.snap @@ -28,6 +28,7 @@ Ok( FnReceiverParam { attributes: [], tk_ref: None, + lifetime: None, tk_mut: None, tk_self: Ident( self, diff --git a/src/snapshots/venial__tests__parse_generic_args.snap b/src/snapshots/venial__tests__parse_generic_args.snap index f54d8b7..8743f07 100644 --- a/src/snapshots/venial__tests__parse_generic_args.snap +++ b/src/snapshots/venial__tests__parse_generic_args.snap @@ -5,13 +5,15 @@ expression: generic_args GenericArgList { args: [ Lifetime { - tk_lifetime: Punct { - char: '\'', - spacing: Joint, + lifetime: Lifetime { + tk_apostrophe: Punct { + char: '\'', + spacing: Joint, + }, + name: Ident( + a, + ), }, - ident: Ident( - a, - ), }, TypeOrConst { expr: [ diff --git a/src/snapshots/venial__tests__parse_impl_inherent.snap b/src/snapshots/venial__tests__parse_impl_inherent.snap index 11214f5..3941db0 100644 --- a/src/snapshots/venial__tests__parse_impl_inherent.snap +++ b/src/snapshots/venial__tests__parse_impl_inherent.snap @@ -182,6 +182,7 @@ Impl( spacing: Alone, }, ), + lifetime: None, tk_mut: Some( Ident( mut, diff --git a/src/snapshots/venial__tests__parse_impl_trait.snap b/src/snapshots/venial__tests__parse_impl_trait.snap index ea224a1..43a7a7e 100644 --- a/src/snapshots/venial__tests__parse_impl_trait.snap +++ b/src/snapshots/venial__tests__parse_impl_trait.snap @@ -264,6 +264,7 @@ Impl( spacing: Alone, }, ), + lifetime: None, tk_mut: Some( Ident( mut, diff --git a/src/snapshots/venial__tests__parse_inline_generic_args-2.snap b/src/snapshots/venial__tests__parse_inline_generic_args-2.snap index 11de882..e4e44c8 100644 --- a/src/snapshots/venial__tests__parse_inline_generic_args-2.snap +++ b/src/snapshots/venial__tests__parse_inline_generic_args-2.snap @@ -5,13 +5,15 @@ expression: owned_args GenericArgList { args: [ Lifetime { - tk_lifetime: Punct { - char: '\'', - spacing: Joint, + lifetime: Lifetime { + tk_apostrophe: Punct { + char: '\'', + spacing: Joint, + }, + name: Ident( + a, + ), }, - ident: Ident( - a, - ), }, TypeOrConst { expr: [ diff --git a/src/snapshots/venial__tests__parse_trait_simple.snap b/src/snapshots/venial__tests__parse_trait_simple.snap index 5231ed4..6acece4 100644 --- a/src/snapshots/venial__tests__parse_trait_simple.snap +++ b/src/snapshots/venial__tests__parse_trait_simple.snap @@ -68,6 +68,7 @@ Trait( spacing: Alone, }, ), + lifetime: None, tk_mut: None, tk_self: Ident( self, diff --git a/src/tests.rs b/src/tests.rs index 9af2027..c08f3ef 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -874,6 +874,26 @@ fn parse_fn_lifetimes() { assert_debug_snapshot!(func); } +#[test] +fn parse_fn_receiver_lifetime() { + let func = parse_item(quote!( + fn prototype(&'lifetime self); + )) + .unwrap(); + + assert_debug_snapshot!(func); +} + +#[test] +fn parse_fn_mut_receiver_lifetime() { + let func = parse_item(quote!( + fn prototype<'a>(&'a mut self); + )) + .unwrap(); + + assert_debug_snapshot!(func); +} + // FIXME #[test] #[should_panic] diff --git a/src/types.rs b/src/types.rs index 6cb1d29..85fd00f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -3,7 +3,7 @@ use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; use quote::{ToTokens, TokenStreamExt as _}; -use crate::Punctuated; +pub use crate::Punctuated; /// The declaration of a Rust item. /// @@ -425,9 +425,13 @@ pub enum FnParam { #[derive(Clone, Debug)] pub struct FnReceiverParam { pub attributes: Vec, + /// `&` token. pub tk_ref: Option, - // TODO ref lifetime (update doc) + /// `'lifetime` tokens. + pub lifetime: Option, + /// `mut` keyword. pub tk_mut: Option, + /// `self` keyword; required. pub tk_self: Ident, } @@ -595,7 +599,7 @@ pub struct GenericArgList { #[derive(Clone, Debug)] pub enum GenericArg { /// E.g. `Ref<'a>`. - Lifetime { tk_lifetime: Punct, ident: Ident }, + Lifetime { lifetime: Lifetime }, /// E.g. `Iterator`. Binding { /// For the above example, this would be `Item`. @@ -620,6 +624,15 @@ pub enum GenericArg { /// differently when passed to quote macros. pub struct InlineGenericArgs<'a>(pub(crate) &'a GenericParamList); +/// Lifetime declaration. +/// +/// Handles the tokens `'lifetime` in various contexts. +#[derive(Clone, Debug)] +pub struct Lifetime { + pub tk_apostrophe: Punct, + pub name: Ident, +} + /// All the stuff that comes after the `where` keyword. #[derive(Clone)] pub struct WhereClause { @@ -1363,6 +1376,7 @@ impl ToTokens for FnReceiverParam { attribute.to_tokens(tokens); } self.tk_ref.to_tokens(tokens); + self.lifetime.to_tokens(tokens); self.tk_mut.to_tokens(tokens); self.tk_self.to_tokens(tokens); } @@ -1507,9 +1521,8 @@ impl ToTokens for GenericArgList { impl ToTokens for GenericArg { fn to_tokens(&self, tokens: &mut TokenStream) { match self { - GenericArg::Lifetime { tk_lifetime, ident } => { - tk_lifetime.to_tokens(tokens); - ident.to_tokens(tokens); + GenericArg::Lifetime { lifetime } => { + lifetime.to_tokens(tokens); } GenericArg::Binding { ident, @@ -1543,6 +1556,13 @@ impl ToTokens for InlineGenericArgs<'_> { } } +impl ToTokens for Lifetime { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append(self.tk_apostrophe.clone()); + tokens.append(self.name.clone()); + } +} + impl ToTokens for WhereClause { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.append(self.tk_where.clone()); diff --git a/src/types_edition.rs b/src/types_edition.rs index d15f126..c5a4465 100644 --- a/src/types_edition.rs +++ b/src/types_edition.rs @@ -1,11 +1,11 @@ use crate::parse_utils::{consume_path, tokens_from_slice}; -pub use crate::types::{ - Attribute, AttributeValue, Enum, EnumVariant, EnumVariantValue, Fields, Function, GenericBound, - GenericParam, GenericParamList, GroupSpan, InlineGenericArgs, Item, NamedField, Struct, - TupleField, TypeExpr, Union, VisMarker, WhereClause, WhereClausePredicate, +use crate::types::{ + Attribute, AttributeValue, Constant, Enum, EnumVariant, EnumVariantValue, Fields, FnQualifiers, + Function, GenericArg, GenericArgList, GenericBound, GenericParam, GenericParamList, GroupSpan, + Impl, InlineGenericArgs, Item, Lifetime, Module, NamedField, Path, Struct, Trait, TupleField, + TypeAlias, TypeExpr, Union, VisMarker, WhereClause, WhereClausePredicate, }; -use crate::types::{FnQualifiers, GenericArg, GenericArgList, Impl, Module, Path}; -use crate::{Constant, Punctuated, Trait, TypeAlias}; +use crate::Punctuated; use proc_macro2::{Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; use quote::spanned::Spanned; @@ -678,8 +678,10 @@ impl<'a> InlineGenericArgs<'a> { let arg = match ¶m.tk_prefix { Some(TokenTree::Punct(punct)) if punct.as_char() == '\'' => { GenericArg::Lifetime { - tk_lifetime: punct.clone(), - ident: name, + lifetime: Lifetime { + tk_apostrophe: punct.clone(), + name, + }, } } Some(TokenTree::Ident(ident)) if ident == "const" => {