Skip to content

Commit

Permalink
Fix TypeExpr::as_path() when used as :ty in decl-macros
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Bromeon committed Jan 11, 2025
1 parent 304b72a commit 0107879
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -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,
),
},
],
}
27 changes: 26 additions & 1 deletion src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
// ================
Expand Down
18 changes: 16 additions & 2 deletions src/types_edition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -800,10 +800,24 @@ impl TypeExpr {
///
/// If it does not match a path, `None` is returned.
pub fn as_path(&self) -> Option<Path> {
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<Vec<TokenTree>> {
match self.tokens.as_slice() {
[TokenTree::Group(group)] if group.delimiter() == Delimiter::None => {
Some(group.stream().into_iter().collect())
}
_ => None,
}
}
}

macro_rules! implement_span {
Expand Down

0 comments on commit 0107879

Please sign in to comment.