• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zbraniecki / icu4x / 6815798908

09 Nov 2023 05:17PM UTC coverage: 72.607% (-2.4%) from 75.01%
6815798908

push

github

web-flow
Implement `Any/BufferProvider` for some smart pointers (#4255)

Allows storing them as a `Box<dyn Any/BufferProvider>` without using a
wrapper type that implements the trait.

44281 of 60987 relevant lines covered (72.61%)

201375.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.47
/tools/ffi_coverage/src/main.rs
1
// This file is part of ICU4X. For terms of use, please see the file
1✔
2
// called LICENSE at the top level of the ICU4X source tree
3
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4

5
use diplomat_core::*;
6
use rustdoc_types::{Crate, Item, ItemEnum};
7
use std::collections::{BTreeSet, HashSet};
8
use std::fmt;
9
use std::fs::{self, File};
10
use std::path::PathBuf;
11
mod allowlist;
12
use allowlist::{IGNORED_ASSOCIATED_ITEMS, IGNORED_PATHS, IGNORED_SUBSTRINGS, IGNORED_TRAITS};
13

14
static FILE_HEADER: &str = r##"# This file contains all APIs that are exposed in Rust but not over FFI, and not allowlisted in IGNORED_PATHS in tools/ffi_coverage/allowlist.rs.
15
# It is generated by `cargo make diplomat-coverage`.
16
#
17
# If adding an API that expands this file, please consider:
18
#
19
#  - Whether you can expose that API over FFI in the same PR (and if you do so, be sure to add a `rust_link` annotation)
20
#  - Whether the API already has its functionality exposed over FFI (if so, add a potentially-`hidden` `rust_link` annotation to the corresponding FFI API)
21
#  - Whether that API is rust-specific functionality that need not be exposed (if so, add it to the allowlist with a note, or add a `hidden` `rust_link` annotation to a related API if possible)
22
#  - Whether that API should be punted for later in FFI (if so, please check in with @Manishearth, @robertbastian, or @sffc)
23
#  - Whether the API is experimental (if so, add it to the allowlist as experimental)
24
#
25
# It is acceptable to temporarily have APIs in this file that you plan to add in a soon-upcoming PR.
26
#
27
# Please check in with @Manishearth, @robertbastian, or @sffc if you have questions
28

29
"##;
30
/// RustLink but without display information
31
#[derive(PartialEq, Eq, Debug, Clone, PartialOrd, Ord, Hash)]
×
32
struct RustLinkInfo {
33
    path: ast::Path,
×
34
    typ: ast::DocType,
×
35
}
36

37
impl fmt::Display for RustLinkInfo {
38
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
×
39
        write!(f, "{}#{:?}", self.path, self.typ)
×
40
    }
×
41
}
42

43
fn main() {
×
44
    let out_path = std::env::args().nth(1);
×
45
    let doc_types = ["icu", "fixed_decimal", "icu_provider_adapters"]
×
46
        .into_iter()
47
        .flat_map(collect_public_types)
48
        .map(|(path_vec, typ)| {
×
49
            let mut path = ast::Path::empty();
×
50
            path.elements = path_vec
×
51
                .into_iter()
52
                .map(|s| ast::Ident::try_from(s).expect("item path is valid"))
×
53
                .collect();
54
            RustLinkInfo { path, typ }
×
55
        })
×
56
        .filter(|rl| {
×
57
            ![
×
58
                ast::DocType::EnumVariant,
59
                ast::DocType::Mod,
60
                ast::DocType::Trait,
61
            ]
62
            .contains(&rl.typ)
×
63
        })
×
64
        .collect::<BTreeSet<_>>();
65

66
    let capi_crate = PathBuf::from(concat!(
×
67
        std::env!("CARGO_MANIFEST_DIR"),
68
        "/../../ffi/capi/src/lib.rs"
69
    ));
70
    eprintln!("Loading icu_capi crate from {capi_crate:?}");
×
71
    let capi_types = ast::File::from(&syn_inline_mod::parse_and_inline_modules(&capi_crate))
×
72
        .all_rust_links()
73
        .into_iter()
74
        .cloned()
75
        .map(|rl| RustLinkInfo {
×
76
            path: rl.path,
×
77
            typ: rl.typ,
×
78
        })
×
79
        .collect::<BTreeSet<_>>();
×
80

81
    let mut file_anchor = None;
×
82
    let mut stdout_anchor = None;
×
83
    let out_stream = if let Some(out_path) = out_path {
×
84
        let stream = file_anchor.insert(File::create(out_path).expect("opening output file"));
×
85
        stream as &mut dyn std::io::Write
×
86
    } else {
87
        let stream = stdout_anchor.insert(std::io::stdout());
×
88
        stream as &mut dyn std::io::Write
×
89
    };
90
    writeln!(out_stream, "{FILE_HEADER}").unwrap();
×
91
    doc_types
×
92
        .difference(&capi_types)
93
        .for_each(|item| writeln!(out_stream, "{item}").unwrap());
×
94
}
×
95

96
fn collect_public_types(krate: &str) -> impl Iterator<Item = (Vec<String>, ast::DocType)> {
×
97
    fn parse_doc(krate: &str) -> &Crate {
×
98
        lazy_static::lazy_static! {
99
            static ref CRATES: elsa::sync::FrozenMap<String, Box<Crate>> = elsa::sync::FrozenMap::new();
100
        }
101

102
        if CRATES.get(krate).is_none() {
×
103
            eprintln!("Parsing crate {krate}");
×
104
            let output = std::process::Command::new("rustup")
×
105
                .args([
×
106
                    "run",
107
                    "nightly-2022-12-26",
108
                    "cargo",
109
                    "rustdoc",
110
                    "-p",
111
                    krate,
×
112
                    "--all-features",
113
                    "--",
114
                    "-Zunstable-options",
115
                    "--output-format",
116
                    "json",
117
                ])
118
                .output()
119
                .expect("failed to execute rustdoc");
×
120
            if !output.status.success() {
×
121
                panic!("Rustdoc build failed with {output:?}");
×
122
            }
123
            let path = PathBuf::from(std::env!("CARGO_MANIFEST_DIR"))
×
124
                .join("../../target/doc")
125
                .join(krate)
×
126
                .with_extension("json");
×
127
            eprintln!("Attempting to load {path:?}");
×
128
            CRATES.insert(
×
129
                krate.to_owned(),
×
130
                serde_json::from_slice(&fs::read(&path).unwrap()).unwrap(),
×
131
            );
×
132
        }
×
133
        CRATES.get(krate).unwrap()
×
134
    }
×
135

136
    #[derive(Debug)]
×
137
    enum In<'a> {
138
        Trait,
139
        // The Option<String> is for the trait name of an impl
140
        Enum(Option<&'a str>),
×
141
        Struct(Option<&'a str>),
×
142
    }
143

144
    fn recurse(
×
145
        item: &Item,
146
        krate: &Crate,
147
        types: &mut HashSet<(Vec<String>, ast::DocType)>,
148
        mut path: Vec<String>,
149
        path_already_extended: bool,
150
        inside: Option<In>,
151
    ) {
152
        fn ignored(path: &Vec<String>) -> bool {
×
153
            IGNORED_PATHS.contains(path)
×
154
                || path
×
155
                    .last()
156
                    .map(|l| IGNORED_SUBSTRINGS.iter().any(|i| l.contains(i)))
×
157
                    .unwrap_or(false)
158
        }
×
159
        /// Helper function that ensures that ignored()
160
        /// is respected for every type inserted
161
        ///
162
        /// (We have a check at the beginning of recurse() but that won't catch leaf nodes)
163
        fn insert_ty(
×
164
            types: &mut HashSet<(Vec<String>, ast::DocType)>,
165
            path: Vec<String>,
166
            ty: ast::DocType,
167
        ) {
168
            if !ignored(&path) {
×
169
                types.insert((path, ty));
×
170
            }
171
        }
×
172

173
        fn check_ignored_assoc_item(name: &str, trait_path: Option<&str>) -> bool {
×
174
            if let Some(tr) = trait_path {
×
175
                if let Some(ignored) = IGNORED_ASSOCIATED_ITEMS.get(tr) {
×
176
                    if ignored.contains(&name) {
×
177
                        return true;
×
178
                    }
179
                }
180
            }
181
            false
×
182
        }
×
183

184
        if ignored(&path) {
×
185
            return;
186
        }
187
        match &item.inner {
×
188
            ItemEnum::Import(import) => {
×
189
                if !import.glob {
×
190
                    path.push(import.name.clone());
×
191
                }
192

193
                if let Some(item) = &krate.index.get(import.id.as_ref().unwrap()) {
×
194
                    recurse(item, krate, types, path, true, None);
×
195
                } else if let Some(item) = &krate.paths.get(import.id.as_ref().unwrap()) {
×
196
                    // This is a reexport of a hidden module, which works fine in HTML doc but
197
                    // doesn't seem to be reachable anywhere in JSON.
198
                    if matches!(import.source.as_str(), "icu_provider::fallback") {
×
199
                        insert_ty(
×
200
                            types,
201
                            vec![
×
202
                                "icu".to_string(),
×
203
                                "locid_transform".to_string(),
×
204
                                "fallback".to_string(),
×
205
                                "LocaleFallbackConfig".to_string(),
×
206
                            ],
207
                            ast::DocType::Struct,
×
208
                        );
209
                        insert_ty(
×
210
                            types,
211
                            vec![
×
212
                                "icu".to_string(),
×
213
                                "locid_transform".to_string(),
×
214
                                "fallback".to_string(),
×
215
                                "LocaleFallbackPriority".to_string(),
×
216
                            ],
217
                            ast::DocType::Enum,
×
218
                        );
219
                        insert_ty(
×
220
                            types,
221
                            vec![
×
222
                                "icu".to_string(),
×
223
                                "locid_transform".to_string(),
×
224
                                "fallback".to_string(),
×
225
                                "LocaleFallbackSupplement".to_string(),
×
226
                            ],
227
                            ast::DocType::Enum,
×
228
                        );
229
                        return;
230
                    }
231

232
                    // External crate. This is quite complicated and while it works, I'm not sure
233
                    // it's correct. This basically handles the case `pub use other_crate::module::Struct`,
234
                    // which means we have to parse `other_crate`, then look for `module`, then look
235
                    // for `Struct`. Now, `module` could actually be a reexport, which is why we have to
236
                    // check the `Import` case when traversing the path.
237
                    let external_crate = parse_doc(&krate.external_crates[&item.crate_id].name);
×
238
                    let mut item = &external_crate.index[&external_crate.root];
×
239
                    for segment in import.source.split("::").skip(1) {
×
240
                        match &item.inner {
×
241
                            ItemEnum::Module(inner) => {
×
242
                                item = inner
×
243
                                    .items
244
                                    .iter()
245
                                    .map(|id| &external_crate.index[id])
×
246
                                    .find(|item| match &item.inner {
×
247
                                        ItemEnum::Import(import) => {
×
248
                                            if import.name.as_str() == segment {
×
249
                                                path.pop();
×
250
                                                true
×
251
                                            } else {
252
                                                false
×
253
                                            }
254
                                        }
255
                                        _ => item.name.as_deref() == Some(segment),
×
256
                                    })
×
257
                                    .expect(&import.source);
×
258
                            }
259
                            _ => unreachable!(),
×
260
                        }
261
                    }
262
                    recurse(item, external_crate, types, path, true, None);
×
263
                } else {
264
                    eprintln!("{path:?} should be in either index or paths");
×
265
                }
266
            }
267
            _ => {
268
                let item_name = item.name.as_ref().unwrap();
×
269
                if !path_already_extended {
×
270
                    path.push(item_name.clone());
×
271
                }
272
                match &item.inner {
×
273
                    ItemEnum::Module(module) => {
×
274
                        for id in &module.items {
×
275
                            recurse(&krate.index[id], krate, types, path.clone(), false, None);
×
276
                        }
277
                        insert_ty(types, path, ast::DocType::Mod);
×
278
                    }
279
                    ItemEnum::Struct(structt) => {
×
280
                        for id in &structt.impls {
×
281
                            if let ItemEnum::Impl(inner) = &krate.index[id].inner {
×
282
                                let mut trait_name = None;
×
283
                                if let Some(path) = &inner.trait_ {
×
284
                                    let name = &path.name;
×
285
                                    if IGNORED_TRAITS.contains(name.as_str()) {
×
286
                                        continue;
287
                                    }
288
                                    trait_name = Some(&*path.name);
×
289
                                }
290
                                for id in &inner.items {
×
291
                                    recurse(
×
292
                                        &krate.index[id],
×
293
                                        krate,
294
                                        types,
295
                                        path.clone(),
×
296
                                        false,
297
                                        Some(In::Struct(trait_name)),
×
298
                                    );
299
                                }
300
                            }
301
                        }
302

303
                        insert_ty(types, path, ast::DocType::Struct);
×
304
                    }
305
                    ItemEnum::Enum(enumm) => {
×
306
                        for id in &enumm.variants {
×
307
                            recurse(&krate.index[id], krate, types, path.clone(), false, None);
×
308
                        }
309

310
                        for id in &enumm.impls {
×
311
                            if let ItemEnum::Impl(inner) = &krate.index[id].inner {
×
312
                                let mut trait_name = None;
×
313
                                if let Some(path) = &inner.trait_ {
×
314
                                    let name = &path.name;
×
315
                                    if IGNORED_TRAITS.contains(name.as_str()) {
×
316
                                        continue;
317
                                    }
318
                                    trait_name = Some(&*path.name);
×
319
                                }
320
                                for id in &inner.items {
×
321
                                    recurse(
×
322
                                        &krate.index[id],
×
323
                                        krate,
324
                                        types,
325
                                        path.clone(),
×
326
                                        false,
327
                                        Some(In::Enum(trait_name)),
×
328
                                    );
329
                                }
330
                            }
331
                        }
332

333
                        insert_ty(types, path, ast::DocType::Enum);
×
334
                    }
335
                    ItemEnum::Trait(inner) => {
×
336
                        for id in &inner.items {
×
337
                            recurse(
×
338
                                &krate.index[id],
×
339
                                krate,
340
                                types,
341
                                path.clone(),
×
342
                                false,
343
                                Some(In::Trait),
×
344
                            );
345
                        }
346
                        insert_ty(types, path, ast::DocType::Trait);
×
347
                    }
348
                    ItemEnum::Constant(_) => {
349
                        insert_ty(types, path, ast::DocType::Constant);
×
350
                    }
351
                    ItemEnum::Function(_) => {
352
                        let doc_type = match inside {
×
353
                            Some(In::Enum(tr)) | Some(In::Struct(tr))
×
354
                                if check_ignored_assoc_item(item_name, tr) =>
×
355
                            {
356
                                return
357
                            }
358
                            Some(In::Enum(_)) => ast::DocType::FnInEnum,
×
359
                            Some(In::Trait) => ast::DocType::FnInTrait,
×
360
                            Some(In::Struct(_)) => ast::DocType::FnInStruct,
×
361
                            _ => ast::DocType::Fn,
×
362
                        };
363
                        insert_ty(types, path, doc_type);
×
364
                    }
365
                    ItemEnum::Macro(_) => {
366
                        insert_ty(types, path, ast::DocType::Macro);
×
367
                    }
368
                    ItemEnum::Typedef(_) => {
369
                        insert_ty(types, path, ast::DocType::Typedef);
×
370
                    }
371
                    ItemEnum::Variant(_) => {
372
                        insert_ty(types, path, ast::DocType::EnumVariant);
×
373
                    }
374
                    ItemEnum::AssocConst { .. } => {
375
                        let doc_type = match inside {
×
376
                            Some(In::Enum(tr)) | Some(In::Struct(tr))
×
377
                                if check_ignored_assoc_item(item_name, tr) =>
×
378
                            {
379
                                return
380
                            }
381
                            Some(In::Enum(_)) => ast::DocType::AssociatedConstantInEnum,
×
382
                            Some(In::Trait) => ast::DocType::AssociatedConstantInTrait,
×
383
                            Some(In::Struct(_)) => ast::DocType::AssociatedConstantInStruct,
×
384
                            _ => panic!("AssocConst needs In"),
×
385
                        };
386
                        insert_ty(types, path, doc_type);
×
387
                    }
388
                    ItemEnum::AssocType { .. } => {
389
                        let doc_type = match inside {
×
390
                            Some(In::Enum(tr)) | Some(In::Struct(tr))
×
391
                                if check_ignored_assoc_item(item_name, tr) =>
×
392
                            {
393
                                return
394
                            }
395
                            Some(In::Enum(_)) => ast::DocType::AssociatedTypeInEnum,
×
396
                            Some(In::Trait) => ast::DocType::AssociatedTypeInTrait,
×
397
                            Some(In::Struct(_)) => ast::DocType::AssociatedTypeInStruct,
×
398
                            _ => panic!("AssocType needs In"),
×
399
                        };
400
                        insert_ty(types, path, doc_type);
×
401
                    }
402
                    ItemEnum::ProcMacro(..) => {}
403
                    _ => todo!("{:?}", item),
×
404
                }
405
            }
406
        }
407
    }
×
408

409
    let mut types = HashSet::new();
×
410
    let krate = parse_doc(krate);
×
411

412
    recurse(
×
413
        &krate.index[&krate.root],
×
414
        krate,
415
        &mut types,
416
        Vec::new(),
×
417
        false,
418
        None,
×
419
    );
420

421
    types.into_iter()
×
422
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc