Skip to content

Commit c854516

Browse files
committed
add merge_cfg_attributes pass
1 parent 5b259ff commit c854516

File tree

2 files changed

+192
-1
lines changed

2 files changed

+192
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ clang-sys = "1"
3131
clap = "4"
3232
clap_complete = "4"
3333
env_logger = "0.10.0"
34-
itertools = { version = ">=0.10,<0.14", default-features = false, features = ["use_alloc"]}
34+
itertools = { version = ">=0.10,<0.15", default-features = false, features = ["use_alloc"]}
3535
libloading = "0.8"
3636
log = "0.4"
3737
objc = "0.2"
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
use proc_macro2::Span;
2+
use quote::{quote, ToTokens};
3+
use std::collections::HashMap;
4+
use syn::{
5+
parse_quote,
6+
visit_mut::{visit_file_mut, VisitMut},
7+
Attribute, File, ForeignItem, Ident, Item, ItemConst, ItemEnum, ItemFn,
8+
ItemForeignMod, ItemImpl, ItemMod, ItemStatic, ItemStruct, ItemType,
9+
ItemUnion, ItemUse,
10+
};
11+
12+
use crate::HashSet;
13+
14+
pub fn merge_cfg_attributes(file: &mut File) {
15+
let mut visitor = Visitor;
16+
visitor.visit_file_mut(file);
17+
}
18+
19+
struct Visitor;
20+
21+
impl VisitMut for Visitor {
22+
fn visit_file_mut(&mut self, file: &mut File) {
23+
process_items(&mut file.items);
24+
}
25+
}
26+
27+
#[derive(Default)]
28+
struct AttributeSet {
29+
cfg_attrs: HashSet<Attribute>,
30+
cc_attrs: HashSet<Attribute>,
31+
other_attrs: HashSet<Attribute>,
32+
}
33+
use itertools::Itertools;
34+
35+
impl AttributeSet {
36+
fn ident(&self) -> Ident {
37+
assert!(!self.cfg_attrs.is_empty() || !self.cc_attrs.is_empty());
38+
39+
Ident::new(
40+
self.cfg_attrs
41+
.iter()
42+
.chain(self.cc_attrs.iter())
43+
.map(|attr| attr.to_token_stream().to_string())
44+
.sorted()
45+
.map(|s| {
46+
s.replace('=', "_eq_").replace(
47+
|c: char| !c.is_alphanumeric() && c != '_',
48+
"_",
49+
)
50+
})
51+
.join("_")
52+
.chars()
53+
.coalesce(|a, b| {
54+
if a == '_' && b == '_' {
55+
Ok(a)
56+
} else {
57+
Err((a, b))
58+
}
59+
})
60+
.collect::<String>()
61+
.trim_matches('_'),
62+
Span::call_site(),
63+
)
64+
}
65+
}
66+
67+
fn process_items(items: &mut Vec<Item>) {
68+
let mut synthetic_mods: HashMap<Ident, (AttributeSet, Vec<Item>)> =
69+
HashMap::new();
70+
let mut new_items = Vec::new();
71+
72+
for mut item in std::mem::take(items) {
73+
match &mut item {
74+
Item::Const(ItemConst { ref mut attrs, .. }) |
75+
Item::Struct(ItemStruct { ref mut attrs, .. }) |
76+
Item::Enum(ItemEnum { ref mut attrs, .. }) |
77+
Item::Fn(ItemFn { ref mut attrs, .. }) |
78+
Item::Union(ItemUnion { ref mut attrs, .. }) |
79+
Item::Type(ItemType { ref mut attrs, .. }) |
80+
Item::Impl(ItemImpl { ref mut attrs, .. }) |
81+
Item::Mod(ItemMod { ref mut attrs, .. }) |
82+
Item::Use(ItemUse { ref mut attrs, .. }) |
83+
Item::Static(ItemStatic { ref mut attrs, .. }) => {
84+
let attr_set = partition_attributes(attrs);
85+
*attrs = attr_set.other_attrs.iter().cloned().collect();
86+
87+
let items = if !attr_set.cfg_attrs.is_empty() || !attr_set.cc_attrs.is_empty() {
88+
&mut synthetic_mods
89+
.entry(attr_set.ident())
90+
.or_insert_with(|| (attr_set, vec![]))
91+
.1
92+
} else {
93+
&mut new_items
94+
};
95+
items.push(item);
96+
}
97+
98+
Item::ForeignMod(ItemForeignMod {
99+
ref mut attrs,
100+
ref mut items,
101+
..
102+
}) => {
103+
for foreign_item in items.iter_mut() {
104+
let mut attr_set = partition_attributes(&attrs);
105+
let inner_attrs = match foreign_item {
106+
ForeignItem::Fn(ref mut foreign_fn) => {
107+
&mut foreign_fn.attrs
108+
}
109+
ForeignItem::Static(ref mut foreign_static) => {
110+
&mut foreign_static.attrs
111+
}
112+
_ => &mut vec![],
113+
};
114+
115+
let inner_attr_set = partition_attributes(inner_attrs);
116+
attr_set
117+
.other_attrs
118+
.extend(inner_attr_set.other_attrs.clone());
119+
attr_set.cfg_attrs.extend(inner_attr_set.cfg_attrs);
120+
attr_set.cc_attrs.extend(inner_attr_set.cc_attrs);
121+
*inner_attrs =
122+
inner_attr_set.other_attrs.into_iter().collect();
123+
124+
let items = if !attr_set.cfg_attrs.is_empty() || !attr_set.cc_attrs.is_empty() {
125+
&mut synthetic_mods
126+
.entry(attr_set.ident())
127+
.or_insert_with(|| (attr_set, vec![]))
128+
.1
129+
} else {
130+
&mut new_items
131+
};
132+
items.push(Item::Verbatim(quote! {
133+
#foreign_item
134+
}));
135+
}
136+
}
137+
_ => {
138+
new_items.push(item);
139+
}
140+
}
141+
}
142+
143+
for (ident, (attr_set, items)) in synthetic_mods {
144+
let cfg_attrs: Vec<_> = attr_set.cfg_attrs.iter().collect();
145+
let cc_attrs: Vec<_> = attr_set.cc_attrs.iter().collect();
146+
let block = if cc_attrs.is_empty() {
147+
quote! {
148+
#(#items)*
149+
}
150+
} else {
151+
// TODO: include unsafe and abi from original items
152+
quote! {
153+
#(#cc_attrs)*
154+
unsafe extern "C" {
155+
#(#items)*
156+
}
157+
}
158+
};
159+
160+
new_items.push(Item::Verbatim(quote! {
161+
#(#cfg_attrs)*
162+
pub mod #ident {
163+
#block
164+
}
165+
166+
#(#cfg_attrs)*
167+
pub use #ident::*;
168+
}));
169+
}
170+
171+
items.extend(new_items);
172+
}
173+
174+
fn partition_attributes(attrs: &[Attribute]) -> AttributeSet {
175+
let mut attribute_set = AttributeSet::default();
176+
177+
for attr in attrs {
178+
let target_set = if let Some(ident) = attr.path().get_ident() {
179+
match ident.to_string().as_str() {
180+
"cfg" => &mut attribute_set.cfg_attrs,
181+
"link" => &mut attribute_set.cc_attrs,
182+
_ => &mut attribute_set.other_attrs,
183+
}
184+
} else {
185+
&mut attribute_set.other_attrs
186+
};
187+
target_set.insert(attr.clone());
188+
}
189+
190+
attribute_set
191+
}

0 commit comments

Comments
 (0)