From 0107879a703d0767b002764b5262cd2c2f3bfd5b Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sat, 11 Jan 2025 17:24:32 +0100 Subject: [PATCH] Fix TypeExpr::as_path() when used as :ty in decl-macros The resulting token tree contains a top-level Group with None separator. However, venial currently expects the path tokens to be on the top level themselves. --- ...erpret_ty_expr_from_declarative_macro.snap | 25 +++++++++++++++++ src/tests.rs | 27 ++++++++++++++++++- src/types_edition.rs | 18 +++++++++++-- 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 src/snapshots/venial__tests__interpret_ty_expr_from_declarative_macro.snap diff --git a/src/snapshots/venial__tests__interpret_ty_expr_from_declarative_macro.snap b/src/snapshots/venial__tests__interpret_ty_expr_from_declarative_macro.snap new file mode 100644 index 0000000..0fc3862 --- /dev/null +++ b/src/snapshots/venial__tests__interpret_ty_expr_from_declarative_macro.snap @@ -0,0 +1,25 @@ +--- +source: src/tests.rs +expression: path +--- +Path { + segments: [ + PathSegment { + ident: Ident( + path, + ), + }, + PathSegment { + tk_separator_colons: "::", + ident: Ident( + to, + ), + }, + PathSegment { + tk_separator_colons: "::", + ident: Ident( + Type, + ), + }, + ], +} diff --git a/src/tests.rs b/src/tests.rs index fff43f9..fc4a534 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -3,7 +3,7 @@ use crate::{parse_item, GenericParam, Item, Struct, TypeExpr, WhereClausePredica use crate::parse_type::consume_generic_args; use crate::types::GenericArgList; use insta::assert_debug_snapshot; -use proc_macro2::TokenStream; +use proc_macro2::{Delimiter, TokenStream}; use quote::quote; // TODO - check test coverage @@ -1316,6 +1316,31 @@ fn interpret_ty_expr_invalid_as_path() { assert!(invalid.is_none()) } +#[test] +fn interpret_ty_expr_from_declarative_macro() { + // Simulates a declarative macro which takes a `ty` placeholder generates an item with a proc-macro attribute. + // On a token level, this emits a group with `Delimiter::None`. + // + // Example: + // macro_rules! declarative_macro { + // ($Type:ty) => { + // #[proc_macro] + // impl Trait for $Type; + // }; + // } + + let ty = quote!(path::to::Type); + let ty_group = proc_macro2::Group::new(Delimiter::None, ty); + let tokens = quote!(impl Trait for #ty_group {}); + + let Ok(Item::Impl(impl_decl)) = parse_item(tokens) else { + panic!("failed to parse item"); + }; + + let path = impl_decl.self_ty.as_path().expect("as_path()"); + assert_debug_snapshot!(path); +} + // ================ // MOD DECLARATIONS // ================ diff --git a/src/types_edition.rs b/src/types_edition.rs index 2254884..db7a2a2 100644 --- a/src/types_edition.rs +++ b/src/types_edition.rs @@ -6,7 +6,7 @@ use crate::types::{ Module, NamedField, Path, Punctuated, Struct, Trait, TupleField, TypeAlias, TypeExpr, Union, UseDeclaration, VisMarker, WhereClause, WhereClausePredicate, }; -use proc_macro2::{Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; +use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; use quote::spanned::Spanned; impl Item { @@ -800,10 +800,24 @@ impl TypeExpr { /// /// If it does not match a path, `None` is returned. pub fn as_path(&self) -> Option { - let tokens = tokens_from_slice(&self.tokens); + let tokens = if let Some(path) = self.unwrap_invisible_group() { + tokens_from_slice(&path) + } else { + tokens_from_slice(&self.tokens) + }; consume_path(tokens) } + + /// If the type has a top-level `Group` token without separator, extract the contents. Otherwise return `None`. + fn unwrap_invisible_group(&self) -> Option> { + match self.tokens.as_slice() { + [TokenTree::Group(group)] if group.delimiter() == Delimiter::None => { + Some(group.stream().into_iter().collect()) + } + _ => None, + } + } } macro_rules! implement_span {