Skip to content

Commit 482836b

Browse files
committed
feat(ast_tools): allow defining foreign types with #[ast(foreign = ...)]
1 parent 3fd392f commit 482836b

2 files changed

Lines changed: 70 additions & 21 deletions

File tree

tasks/ast_tools/src/parse/load.rs

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ use syn::{
66
parse::{Parse, ParseBuffer},
77
parse_file,
88
punctuated::Punctuated,
9-
Attribute, Generics, Ident, Item, ItemEnum, ItemMacro, ItemStruct, Token, Variant, Visibility,
10-
WhereClause,
9+
Attribute, Generics, Ident, Item, ItemEnum, ItemMacro, ItemStruct, Meta, Token, Variant,
10+
Visibility, WhereClause,
1111
};
1212

1313
use crate::schema::FileId;
1414

1515
use super::{
1616
ident_name,
17+
parse::convert_expr_to_string,
1718
skeleton::{EnumSkeleton, Skeleton, StructSkeleton},
1819
FxIndexMap,
1920
};
@@ -59,20 +60,12 @@ pub fn load_file(file_id: FileId, file_path: &str, skeletons: &mut FxIndexMap<St
5960
}
6061

6162
fn parse_struct(item: ItemStruct, file_id: FileId) -> Option<StructSkeleton> {
62-
if !has_ast_attr(&item.attrs) {
63-
return None;
64-
}
65-
66-
let name = ident_name(&item.ident);
63+
let name = get_type_name(&item.attrs, &item.ident)?;
6764
Some(StructSkeleton { name, file_id, item })
6865
}
6966

7067
fn parse_enum(item: ItemEnum, file_id: FileId) -> Option<EnumSkeleton> {
71-
if !has_ast_attr(&item.attrs) {
72-
return None;
73-
}
74-
75-
let name = ident_name(&item.ident);
68+
let name = get_type_name(&item.attrs, &item.ident)?;
7669
Some(EnumSkeleton { name, file_id, item, inherits: vec![] })
7770
}
7871

@@ -96,15 +89,13 @@ fn parse_macro(item: &ItemMacro, file_id: FileId) -> Option<EnumSkeleton> {
9689
let ident = input.parse::<Ident>()?;
9790
let generics = input.parse::<Generics>()?;
9891

99-
let name = ident_name(&ident);
100-
10192
let where_clause = input.parse::<Option<WhereClause>>()?;
10293
assert!(where_clause.is_none(), "Types with `where` clauses are not supported");
10394

104-
assert!(
105-
has_ast_attr(&attrs),
106-
"Enum in `inherit_variants!` macro must have `#[ast]` attr: {name}",
107-
);
95+
let name = get_type_name(&attrs, &ident);
96+
let Some(name) = name else {
97+
panic!("Enum in `inherit_variants!` macro must have `#[ast]` attr: {ident}");
98+
};
10899

109100
let content;
110101
let brace_token = braced!(content in input);
@@ -133,7 +124,61 @@ fn parse_macro(item: &ItemMacro, file_id: FileId) -> Option<EnumSkeleton> {
133124
Some(skeleton)
134125
}
135126

136-
/// Returns `true` if type has an `#[ast]` attribute on it.
137-
fn has_ast_attr(attrs: &[Attribute]) -> bool {
138-
attrs.iter().any(|attr| attr.path().is_ident("ast"))
127+
/// Get name of type.
128+
///
129+
/// Parse attributes and find `#[ast]` attr, or `#[ast(foreign = ForeignType)]`.
130+
///
131+
/// If no `#[ast]` attr is present, returns `None`.
132+
///
133+
/// Otherwise, returns foreign name if provided with `#[ast(foreign = ForeignType)]`,
134+
/// or otherwise name of the `ident`.
135+
///
136+
/// # Panics
137+
/// Panics if cannot parse `#[ast]` attribute.
138+
fn get_type_name(attrs: &[Attribute], ident: &Ident) -> Option<String> {
139+
let mut has_ast_attr = false;
140+
let mut foreign_name = None;
141+
for attr in attrs {
142+
if attr.path().is_ident("ast") {
143+
has_ast_attr = true;
144+
if let Some(this_foreign_name) = parse_ast_attr_foreign_name(attr, ident) {
145+
assert!(
146+
foreign_name.is_none(),
147+
"Multiple `#[ast(foreign)]` attributes on type: `{ident}`"
148+
);
149+
foreign_name = Some(this_foreign_name);
150+
}
151+
}
152+
}
153+
154+
if has_ast_attr {
155+
Some(foreign_name.unwrap_or_else(|| ident_name(ident)))
156+
} else {
157+
None
158+
}
159+
}
160+
161+
fn parse_ast_attr_foreign_name(attr: &Attribute, ident: &Ident) -> Option<String> {
162+
let meta_list = match &attr.meta {
163+
Meta::Path(_) => return None,
164+
Meta::List(meta_list) => meta_list,
165+
Meta::NameValue(_) => panic!("Failed to parse `#[ast]` attribute"),
166+
};
167+
let metas = meta_list
168+
.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
169+
.expect("Unable to parse `#[ast]` attribute");
170+
171+
let mut foreign_name = None;
172+
for meta in &metas {
173+
if let Meta::NameValue(name_value) = meta {
174+
if name_value.path.is_ident("foreign") {
175+
assert!(
176+
foreign_name.is_none(),
177+
"Multiple `#[ast(foreign)]` attributes on type: `{ident}`"
178+
);
179+
foreign_name = Some(convert_expr_to_string(&name_value.value));
180+
}
181+
}
182+
}
183+
foreign_name
139184
}

tasks/ast_tools/src/parse/parse.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,10 @@ impl<'c> Parser<'c> {
601601

602602
for meta in &parts {
603603
let attr_name = meta.path().get_ident().unwrap().to_string();
604+
if attr_name == "foreign" {
605+
continue;
606+
}
607+
604608
if let Some((processor, positions)) = self.codegen.attr_processor(&attr_name) {
605609
// Check attribute is legal in this position
606610
// and has the relevant trait `#[generate_derive]`-ed on it

0 commit comments

Comments
 (0)