From b3a540a0d6741d798f749e1b9096881037f2e3e9 Mon Sep 17 00:00:00 2001 From: Stuart Harris Date: Sat, 26 Oct 2024 12:49:28 +0100 Subject: [PATCH] build graph wip --- Cargo.lock | 63 +- crux_cli/Cargo.toml | 14 +- crux_cli/codegen.fish | 2 +- crux_cli/src/codegen/graph.rs | 8 - crux_cli/src/codegen/mod.rs | 46 +- crux_cli/src/codegen/parser.rs | 238 ++- .../src/codegen/public_api/crate_wrapper.rs | 36 - .../public_api/intermediate_public_item.rs | 54 - .../src/codegen/public_api/item_processor.rs | 441 ------ crux_cli/src/codegen/public_api/mod.rs | 91 -- .../src/codegen/public_api/nameable_item.rs | 92 -- .../src/codegen/public_api/path_component.rs | 23 - .../src/codegen/public_api/public_item.rs | 97 -- crux_cli/src/codegen/public_api/render.rs | 1352 ----------------- crux_cli/src/codegen/public_api/tokens.rs | 111 -- crux_cli/src/command_runner.rs | 68 - crux_cli/src/main.rs | 1 - 17 files changed, 199 insertions(+), 2538 deletions(-) delete mode 100644 crux_cli/src/codegen/graph.rs delete mode 100644 crux_cli/src/codegen/public_api/crate_wrapper.rs delete mode 100644 crux_cli/src/codegen/public_api/intermediate_public_item.rs delete mode 100644 crux_cli/src/codegen/public_api/item_processor.rs delete mode 100644 crux_cli/src/codegen/public_api/mod.rs delete mode 100644 crux_cli/src/codegen/public_api/nameable_item.rs delete mode 100644 crux_cli/src/codegen/public_api/path_component.rs delete mode 100644 crux_cli/src/codegen/public_api/public_item.rs delete mode 100644 crux_cli/src/codegen/public_api/render.rs delete mode 100644 crux_cli/src/codegen/public_api/tokens.rs delete mode 100644 crux_cli/src/command_runner.rs diff --git a/Cargo.lock b/Cargo.lock index 7b50d230d..695b8aabf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -255,6 +255,17 @@ dependencies = [ "serde", ] +[[package]] +name = "cargo-manifest" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03fa8484a7f2eef80e6dd2e2be90b322b9c29aeb1bbc206013d6eb2104db7241" +dependencies = [ + "serde", + "thiserror", + "toml", +] + [[package]] name = "cargo-platform" version = "0.1.8" @@ -436,7 +447,9 @@ dependencies = [ "guppy", "ignore", "libc", + "petgraph", "ramhorns", + "rustdoc-json", "rustdoc-types", "serde", "serde_json", @@ -1651,11 +1664,25 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustdoc-json" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "182c2e4c76cc5e19dd718721036d82a59d396e35c6882a3f2b54e8b928e1b36a" +dependencies = [ + "cargo-manifest", + "cargo_metadata", + "serde", + "thiserror", + "toml", + "tracing", +] + [[package]] name = "rustdoc-types" -version = "0.26.0" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9be1bc4a0ec3445cfa2e4ba112827544890d43d68b7d1eda5359a9c09d2cd8" +checksum = "7fd5cb7a0c0a5a4f6bc429274fc1073d395237c83ef13d0ac728e0f95f5499ca" dependencies = [ "serde", ] @@ -2020,6 +2047,7 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -2048,6 +2076,37 @@ dependencies = [ "winnow", ] +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.82", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + [[package]] name = "typeid" version = "1.0.2" diff --git a/crux_cli/Cargo.toml b/crux_cli/Cargo.toml index 809974e6b..c7f289ae0 100644 --- a/crux_cli/Cargo.toml +++ b/crux_cli/Cargo.toml @@ -17,14 +17,16 @@ path = "src/main.rs" anyhow.workspace = true clap = { version = "4.4.18", features = ["derive"] } console = "0.15.8" -guppy = "0.17.5" +guppy = "0.17.4" ignore = "0.4.23" -libc = "0.2.155" -ramhorns = "1.0.0" -rustdoc-types = "0.26.0" +libc = "0.2.161" +petgraph = "0.6.5" +ramhorns = "1.0.1" +rustdoc-json = "0.9.2" +rustdoc-types = "0.32.2" serde = { workspace = true, features = ["derive"] } -serde_json = "1.0.117" +serde_json = "1.0.132" similar = { version = "2.6.0", features = ["inline"] } -tokio = { version = "1.38.0", features = ["full"] } +tokio = { version = "1.41.0", features = ["full"] } tokio-fd = "0.3.0" toml = "0.8.19" diff --git a/crux_cli/codegen.fish b/crux_cli/codegen.fish index a51f53253..4064d165f 100755 --- a/crux_cli/codegen.fish +++ b/crux_cli/codegen.fish @@ -2,7 +2,7 @@ cargo build -for d in ../examples/* +for d in ../examples/hello_world echo "" echo "---------------" echo "Public API for $d" diff --git a/crux_cli/src/codegen/graph.rs b/crux_cli/src/codegen/graph.rs deleted file mode 100644 index 4cd1efa08..000000000 --- a/crux_cli/src/codegen/graph.rs +++ /dev/null @@ -1,8 +0,0 @@ -use anyhow::Result; -use guppy::{graph::PackageGraph, MetadataCommand}; - -pub(crate) fn compute_package_graph() -> Result { - let mut cmd = MetadataCommand::new(); - let package_graph = PackageGraph::from_command(&mut cmd)?; - Ok(package_graph) -} diff --git a/crux_cli/src/codegen/mod.rs b/crux_cli/src/codegen/mod.rs index dbdfdc553..da7bd9c09 100644 --- a/crux_cli/src/codegen/mod.rs +++ b/crux_cli/src/codegen/mod.rs @@ -1,46 +1,26 @@ -mod graph; mod parser; -mod public_api; -use std::{ - fs::File, - io::{stdout, IsTerminal}, -}; +use std::fs::File; use anyhow::{bail, Result}; +use guppy::{graph::PackageGraph, MetadataCommand}; use rustdoc_types::Crate; -use tokio::{process::Command, task::spawn_blocking}; +use tokio::task::spawn_blocking; -use crate::{args::CodegenArgs, command_runner}; +use crate::args::CodegenArgs; pub async fn codegen(args: &CodegenArgs) -> Result<()> { - let graph = graph::compute_package_graph()?; + let mut cmd = MetadataCommand::new(); + let package_graph = PackageGraph::from_command(&mut cmd)?; - let Ok(lib) = graph.workspace().member_by_path(&args.lib) else { + let Ok(lib) = package_graph.workspace().member_by_path(&args.lib) else { bail!("Could not find workspace package with path {}", args.lib) }; - let mut cmd = Command::new("cargo"); - cmd.env("RUSTC_BOOTSTRAP", "1") - .env( - "RUSTDOCFLAGS", - "-Z unstable-options --output-format=json --cap-lints=allow", - ) - .arg("doc") - .arg("--no-deps") - .arg("--manifest-path") - .arg(lib.manifest_path()) - .arg("--lib"); - if stdout().is_terminal() { - cmd.arg("--color=always"); - } - - command_runner::run(&mut cmd).await?; - - let target_directory = graph.workspace().target_directory().as_std_path(); - let json_path = target_directory - .join("doc") - .join(format!("{}.json", lib.name().replace('-', "_"))); + let json_path = rustdoc_json::Builder::default() + .toolchain("nightly") + .manifest_path(lib.manifest_path()) + .build()?; let crate_: Crate = spawn_blocking(move || -> Result { let file = File::open(json_path)?; @@ -49,7 +29,9 @@ pub async fn codegen(args: &CodegenArgs) -> Result<()> { }) .await??; - parser::parse(&crate_)?; + println!("Parsing rustdoc JSON, version {}", crate_.format_version); + let out = parser::parse(&crate_)?; + println!("{}", out); Ok(()) } diff --git a/crux_cli/src/codegen/parser.rs b/crux_cli/src/codegen/parser.rs index 89df6f9f8..aac68ecea 100644 --- a/crux_cli/src/codegen/parser.rs +++ b/crux_cli/src/codegen/parser.rs @@ -1,136 +1,128 @@ -use anyhow::Result; -use rustdoc_types::{Crate, Id, Impl, ItemEnum, Path, Type}; +use std::collections::HashMap; -use super::public_api::{ - item_processor::{sorting_prefix, ItemProcessor}, - nameable_item::NameableItem, - path_component::PathComponent, - public_item::PublicItem, - PublicApi, -}; -use crate::codegen::public_api::render::RenderingContext; +use anyhow::{anyhow, Result}; +use petgraph::{dot::Dot, graph::NodeIndex, Graph}; +use rustdoc_types::{Crate, Enum, Id, Impl, Item, ItemEnum, Type, VariantKind}; -pub fn parse(crate_: &Crate) -> Result<()> { - let mut item_processor = ItemProcessor::new(crate_); - add_items(crate_, "Effect", &["Ffi"], &mut item_processor); - add_items(crate_, "App", &["Event", "ViewModel"], &mut item_processor); - item_processor.run(); +pub fn parse(crate_: &Crate) -> Result { + let mut graph = Graph::new(); + let mut nodes = HashMap::new(); - let context = RenderingContext { - crate_, - id_to_items: item_processor.id_to_items(), - }; - - let items: Vec<_> = item_processor - .output - .iter() - .filter_map(|item| { - matches!( - &item.item().inner, - ItemEnum::Union(_) - | ItemEnum::Struct(_) - | ItemEnum::StructField(_) - | ItemEnum::Enum(_) - | ItemEnum::Variant(_) - | ItemEnum::Primitive(_) - | ItemEnum::TypeAlias(_) - | ItemEnum::ForeignType - ) - .then_some(PublicItem::from_intermediate_public_item(&context, item)) - }) - .collect(); + // nodes + for (id, item) in crate_.index.clone() { + nodes.insert(id, graph.add_node(Node { id, item })); + } - let mut public_api = PublicApi { - items, - missing_item_ids: item_processor.crate_.missing_item_ids(), + let node = |id| -> Result<&NodeIndex> { + nodes + .get(id) + .ok_or_else(|| anyhow!("Could not find node with id {:?}", id)) }; - public_api.items.sort_by(PublicItem::grouping_cmp); - - println!(); - - for item in public_api.items { - println!("{:?}", item.sortable_path); - println!("{}\n", item); - } - Ok(()) -} - -fn add_items<'c: 'p, 'p>( - crate_: &'c Crate, - trait_name: &'c str, - filter: &'c [&'c str], - item_processor: &'p mut ItemProcessor<'c>, -) { - for root in find_roots(crate_, trait_name, filter) { - let item = &crate_.index[root.parent]; - for id in root.assoc_types { - let parent = PathComponent { - item: NameableItem { - item, - overridden_name: None, - sorting_prefix: sorting_prefix(item), - }, - type_: None, - hide: false, - }; - item_processor.add_to_work_queue(vec![parent], id); + // edges + for (id, item) in &crate_.index { + let source = node(id)?; + match &item.inner { + ItemEnum::Module(_module) => (), + ItemEnum::ExternCrate { name: _, rename: _ } => (), + ItemEnum::Use(_) => (), + ItemEnum::Union(_union) => (), + ItemEnum::Struct(s) => { + match &s.kind { + rustdoc_types::StructKind::Unit => (), + rustdoc_types::StructKind::Tuple(fields) => { + for field in fields { + if let Some(id) = field { + graph.add_edge(*source, *node(&id)?, Edge::HasField); + } + } + } + rustdoc_types::StructKind::Plain { + fields, + has_stripped_fields: _, + } => { + for id in fields { + graph.add_edge(*source, *node(&id)?, Edge::HasField); + } + } + }; + for id in &s.impls { + graph.add_edge(*source, *node(&id)?, Edge::Implements); + } + } + ItemEnum::StructField(_) => (), + ItemEnum::Enum(Enum { variants, .. }) => { + for id in variants { + graph.add_edge(*source, *node(&id)?, Edge::HasVariant); + } + } + ItemEnum::Variant(v) => { + match &v.kind { + VariantKind::Plain => (), + VariantKind::Tuple(_vec) => (), + VariantKind::Struct { + fields, + has_stripped_fields: _, + } => { + for field in fields { + graph.add_edge(*source, *node(&field)?, Edge::HasField); + } + } + }; + } + ItemEnum::Function(_function) => (), + ItemEnum::Trait(_) => (), + ItemEnum::TraitAlias(_trait_alias) => (), + ItemEnum::Impl(Impl { + for_: Type::ResolvedPath(target), + items, + .. + }) => { + graph.add_edge(*source, *node(&target.id)?, Edge::ImplFor); + for id in items { + graph.add_edge(*source, *node(&id)?, Edge::AssociatedItem); + } + } + ItemEnum::Impl(_) => (), + ItemEnum::TypeAlias(_type_alias) => (), + ItemEnum::Constant { + type_: _, + const_: _, + } => (), + ItemEnum::Static(_) => (), + ItemEnum::ExternType => (), + ItemEnum::Macro(_) => (), + ItemEnum::ProcMacro(_proc_macro) => (), + ItemEnum::Primitive(_primitive) => (), + ItemEnum::AssocConst { type_: _, value: _ } => (), + ItemEnum::AssocType { + generics: _, + bounds: _, + type_: Some(Type::ResolvedPath(target)), + } => { + if let Ok(dest) = node(&target.id) { + graph.add_edge(*source, *dest, Edge::AssociatedType); + } + } + ItemEnum::AssocType { .. } => (), } } + let out = Dot::new(&graph); + Ok(format!("{:?}", out)) } -struct Root<'a> { - parent: &'a Id, - assoc_types: Vec<&'a Id>, +#[derive(Debug)] +struct Node { + id: Id, + item: Item, } -fn find_roots<'a>( - crate_: &'a Crate, - trait_name: &'a str, - filter: &'a [&'a str], -) -> impl Iterator> { - crate_ - .index - .iter() - .filter_map(move |(parent, parent_item)| { - if let ItemEnum::Impl(Impl { - trait_: Some(Path { name, .. }), - // for_: Type::ResolvedPath(_), - items, - .. - }) = &parent_item.inner - { - if name.as_str() == trait_name { - let assoc_types = items - .iter() - .filter_map(|id| { - let item = &crate_.index[id]; - item.name.as_deref().and_then(|name| { - if filter.contains(&name) { - if let ItemEnum::AssocType { - default: Some(Type::ResolvedPath(Path { id, .. })), - .. - } = &item.inner - { - Some(id) - } else { - None - } - } else { - None - } - }) - }) - .collect(); - Some(Root { - parent, - assoc_types, - }) - } else { - None - } - } else { - None - } - }) +#[derive(Debug)] +enum Edge { + ImplFor, + HasField, + Implements, + AssociatedItem, + AssociatedType, + HasVariant, } diff --git a/crux_cli/src/codegen/public_api/crate_wrapper.rs b/crux_cli/src/codegen/public_api/crate_wrapper.rs deleted file mode 100644 index 7d698573c..000000000 --- a/crux_cli/src/codegen/public_api/crate_wrapper.rs +++ /dev/null @@ -1,36 +0,0 @@ -use rustdoc_types::{Crate, Id, Item}; - -/// The [`Crate`] type represents the deserialized form of the rustdoc JSON -/// input. This wrapper adds some helpers and state on top. -pub struct CrateWrapper<'c> { - crate_: &'c Crate, - - /// Normally, an item referenced by [`Id`] is present in the rustdoc JSON. - /// If [`Self::crate_.index`] is missing an [`Id`], then we add it here, to - /// aid with debugging. It will typically be missing because of bugs (or - /// borderline bug such as re-exports of foreign items like discussed in - /// ) - /// We do not report it to users by default, because they can't do anything - /// about it. Missing IDs will be printed with `--verbose` however. - missing_ids: Vec<&'c Id>, -} - -impl<'c> CrateWrapper<'c> { - pub fn new(crate_: &'c Crate) -> Self { - Self { - crate_, - missing_ids: vec![], - } - } - - pub fn get_item(&mut self, id: &'c Id) -> Option<&'c Item> { - self.crate_.index.get(id).or_else(|| { - self.missing_ids.push(id); - None - }) - } - - pub fn missing_item_ids(&self) -> Vec { - self.missing_ids.iter().map(|m| m.0.clone()).collect() - } -} diff --git a/crux_cli/src/codegen/public_api/intermediate_public_item.rs b/crux_cli/src/codegen/public_api/intermediate_public_item.rs deleted file mode 100644 index 2e6dfd3f0..000000000 --- a/crux_cli/src/codegen/public_api/intermediate_public_item.rs +++ /dev/null @@ -1,54 +0,0 @@ -use rustdoc_types::Item; - -use super::nameable_item::NameableItem; -use super::path_component::PathComponent; -use super::public_item::PublicItemPath; -use super::render::RenderingContext; -use super::tokens::Token; - -/// This struct represents one public item of a crate, but in intermediate form. -/// Conceptually it wraps a single [`Item`] even though the path to the item -/// consists of many [`Item`]s. Later, one [`Self`] will be converted to exactly -/// one [`crate::PublicItem`]. -#[derive(Clone, Debug)] -pub struct IntermediatePublicItem<'c> { - path: Vec>, -} - -impl<'c> IntermediatePublicItem<'c> { - pub fn new(path: Vec>) -> Self { - Self { path } - } - - #[must_use] - pub fn item(&self) -> &'c Item { - self.path() - .last() - .expect("path must not be empty") - .item - .item - } - - #[must_use] - pub fn path(&self) -> &[PathComponent<'c>] { - &self.path - } - - /// See [`crate::item_processor::sorting_prefix()`] docs for an explanation why we have this. - #[must_use] - pub fn sortable_path(&self, context: &RenderingContext) -> PublicItemPath { - self.path() - .iter() - .map(|p| NameableItem::sortable_name(&p.item, context)) - .collect() - } - - #[must_use] - pub fn path_contains_renamed_item(&self) -> bool { - self.path().iter().any(|m| m.item.overridden_name.is_some()) - } - - pub fn render_token_stream(&self, context: &RenderingContext) -> Vec { - context.token_stream(self) - } -} diff --git a/crux_cli/src/codegen/public_api/item_processor.rs b/crux_cli/src/codegen/public_api/item_processor.rs deleted file mode 100644 index 70a155a99..000000000 --- a/crux_cli/src/codegen/public_api/item_processor.rs +++ /dev/null @@ -1,441 +0,0 @@ -use super::nameable_item::NameableItem; -use super::{ - crate_wrapper::CrateWrapper, intermediate_public_item::IntermediatePublicItem, - path_component::PathComponent, -}; -use rustdoc_types::{ - Crate, Id, Impl, Import, Item, ItemEnum, Module, Struct, StructKind, Type, VariantKind, -}; -use std::{ - collections::{HashMap, VecDeque}, - vec, -}; - -/// Items in rustdoc JSON reference each other by Id. The [`ItemProcessor`] -/// essentially takes one Id at a time and figure out what to do with it. Once -/// complete, the item is ready to be listed as part of the public API, and -/// optionally can also be used as part of a path to another (child) item. -/// -/// This struct contains a (processed) path to an item that is about to be -/// processed further, and the Id of that item. -#[derive(Debug)] -struct UnprocessedItem<'c> { - /// The path to the item to process. - parent_path: Vec>, - - /// The Id of the item to process. - id: &'c Id, -} - -/// Processes items to find more items and to figure out the path to each item. -/// Some non-obvious cases to take into consideration are: -/// 1. A single item is imported several times. -/// 2. An item is (publicly) imported from another crate -/// -/// Note that this implementation iterates over everything, so if the rustdoc -/// JSON is generated with `--document-private-items`, then private items will -/// also be included in the output. Use with `--document-private-items` is not -/// supported. -pub struct ItemProcessor<'c> { - /// The original and unmodified rustdoc JSON, in deserialized form. - pub crate_: CrateWrapper<'c>, - - /// A queue of unprocessed items to process. - work_queue: VecDeque>, - - /// The output. A list of processed items. Note that the order is - /// intentionally "logical", so that e.g. struct fields items follows from - /// struct items. - pub output: Vec>, -} - -impl<'c> ItemProcessor<'c> { - pub(crate) fn new(crate_: &'c Crate) -> Self { - ItemProcessor { - crate_: CrateWrapper::new(crate_), - work_queue: VecDeque::new(), - output: vec![], - } - } - - /// Adds an item to the front of the work queue. We want to add items to the - /// front of the work queue since we process items from the top, and since - /// we want to "fully" process an item before we move on to the next one. So - /// when we encounter a struct, we also encounter its struct fields, and we - /// want to insert the struct fields BEFORE everything else, so that these - /// items remain grouped together. And the same applies for many kinds of - /// groupings (enums, impls, etc). - pub fn add_to_work_queue(&mut self, parent_path: Vec>, id: &'c Id) { - self.work_queue - .push_front(UnprocessedItem { parent_path, id }); - } - - /// Processes the entire work queue. Adds more items based on items it - /// processes. When this returns, all items and their children and impls - /// have been recursively processed. - pub fn run(&mut self) { - while let Some(unprocessed_item) = self.work_queue.pop_front() { - if let Some(item) = self.crate_.get_item(unprocessed_item.id) { - self.process_any_item(item, unprocessed_item); - } - } - } - - /// Process any item. In particular, does the right thing if the item is an - /// impl or an import. - fn process_any_item(&mut self, item: &'c Item, unprocessed_item: UnprocessedItem<'c>) { - match &item.inner { - ItemEnum::Import(import) => { - if import.glob { - self.process_import_glob_item(import, unprocessed_item, item); - } else { - self.process_import_item(item, import, unprocessed_item); - } - } - ItemEnum::Impl(impl_) => { - self.process_impl_item(unprocessed_item, item, impl_); - } - _ => { - self.process_item_unless_recursive(unprocessed_item, item, None); - } - } - } - - /// We need to handle `pub use foo::*` specially. In case of such wildcard - /// imports, `glob` will be `true` and `id` will be the module we should - /// import all items from, but we should NOT add the module itself. Before - /// we inline this wildcard import, make sure that the module is not - /// indirectly trying to import itself. If we allow that, we'll get a stack - /// overflow. - fn process_import_glob_item( - &mut self, - import: &'c Import, - unprocessed_item: UnprocessedItem<'c>, - item: &'c Item, - ) { - if let Some(Item { - inner: ItemEnum::Module(Module { items, .. }), - .. - }) = import - .id - .as_ref() - .and_then(|id| self.get_item_if_not_in_path(&unprocessed_item.parent_path, id)) - { - for item_id in items { - self.add_to_work_queue(unprocessed_item.parent_path.clone(), item_id); - } - } else { - self.process_item( - unprocessed_item, - item, - Some(format!("<<{}::*>>", import.source)), - ); - } - } - - /// Since public imports are part of the public API, we inline them, i.e. - /// replace the item corresponding to an import with the item that is - /// imported. If we didn't do this, publicly imported items would show up as - /// just e.g. `pub use some::function`, which is not sufficient for the use - /// cases of this tool. We want to show the actual API, and thus also show - /// type information! There is one exception; for re-exports of primitive - /// types, there is no item Id to inline with, so they remain as e.g. `pub - /// use my_i32` in the output. - fn process_import_item( - &mut self, - item: &'c Item, - import: &'c Import, - unprocessed_item: UnprocessedItem<'c>, - ) { - let mut actual_item = item; - - if let Some(imported_item) = import - .id - .as_ref() - .and_then(|id| self.get_item_if_not_in_path(&unprocessed_item.parent_path, id)) - { - actual_item = imported_item; - } - - self.process_item(unprocessed_item, actual_item, Some(import.name.clone())); - } - - /// Processes impls. Impls are special because we support filtering out e.g. - /// blanket implementations to reduce output noise. - fn process_impl_item( - &mut self, - unprocessed_item: UnprocessedItem<'c>, - item: &'c Item, - impl_: &'c Impl, - ) { - if !ImplKind::from(item, impl_).is_active() { - return; - } - - self.process_item_for_type(unprocessed_item, item, None, Some(&impl_.for_)); - } - - /// Make sure the item we are about to process is not already part of the - /// item path. If it is, we have encountered recursion. Stop processing in - /// that case. - fn process_item_unless_recursive( - &mut self, - unprocessed_item: UnprocessedItem<'c>, - item: &'c Item, - overridden_name: Option, - ) { - if unprocessed_item - .parent_path - .iter() - .any(|m| m.item.item.id == item.id) - { - let recursion_breaker = unprocessed_item.finish( - item, - Some(format!("<<{}>>", item.name.as_deref().unwrap_or(""))), - None, - ); - self.output.push(recursion_breaker); - } else { - self.process_item(unprocessed_item, item, overridden_name); - } - } - - /// Process an item. Setup jobs for its children and impls and and then put - /// it in the output. - fn process_item( - &mut self, - unprocessed_item: UnprocessedItem<'c>, - item: &'c Item, - overridden_name: Option, - ) { - if !item.attrs.contains(&"#[serde(skip)]".to_string()) { - self.process_item_for_type(unprocessed_item, item, overridden_name, None); - } - } - - /// Process an item. Setup jobs for its children and impls and and then put - /// it in the output. - fn process_item_for_type( - &mut self, - unprocessed_item: UnprocessedItem<'c>, - item: &'c Item, - overridden_name: Option, - type_: Option<&'c Type>, - ) { - let finished_item = unprocessed_item.finish(item, overridden_name, type_); - - let children = children_for_item(item).into_iter().flatten(); - let impls = impls_for_item(item).into_iter().flatten(); - - for id in children { - self.add_to_work_queue(finished_item.path().into(), id); - } - - // As usual, impls are special. We want impl items to appear grouped - // with the trait or type it involves. But when _rendering_ we want to - // use the type that we implement for, so that e.g. generic arguments - // can be shown. So hide the "sorting path" of the impl. We'll instead - // render the path to the type the impl is for. - for id in impls { - let mut path = finished_item.path().to_vec(); - for a in &mut path { - a.hide = true; - } - self.add_to_work_queue(path, id); - } - - self.output.push(finished_item); - } - - /// Get the rustdoc JSON item with `id`, but only if it is not already part - /// of the path. This can happen in the case of recursive re-exports, in - /// which case we need to break the recursion. - fn get_item_if_not_in_path( - &mut self, - parent_path: &[PathComponent<'c>], - id: &'c Id, - ) -> Option<&'c Item> { - if parent_path.iter().any(|m| m.item.item.id == *id) { - // The item is already in the path! Break import recursion... - return None; - } - - self.crate_.get_item(id) - } - - // Returns a HashMap where a rustdoc JSON Id is mapped to what public items - // that have this ID. The reason this is a one-to-many mapping is because of - // re-exports. If an API re-exports a public item in a different place, the - // same item will be reachable by different paths, and thus the Vec will - // contain many [`IntermediatePublicItem`]s for that ID. - // - // You might think this is rare, but it is actually a common thing in - // real-world code. - pub fn id_to_items(&self) -> HashMap<&Id, Vec<&IntermediatePublicItem>> { - let mut id_to_items: HashMap<&Id, Vec<&IntermediatePublicItem>> = HashMap::new(); - for finished_item in &self.output { - id_to_items - .entry(&finished_item.item().id) - .or_default() - .push(finished_item); - } - id_to_items - } -} - -impl<'c> UnprocessedItem<'c> { - /// Turns an [`UnprocessedItem`] into a finished [`IntermediatePublicItem`]. - fn finish( - self, - item: &'c Item, - overridden_name: Option, - type_: Option<&'c Type>, - ) -> IntermediatePublicItem<'c> { - // Transfer path ownership to output item - let mut path = self.parent_path; - - // Complete the path with the last item - path.push(PathComponent { - item: NameableItem { - item, - overridden_name, - sorting_prefix: sorting_prefix(item), - }, - type_, - hide: false, - }); - - // Done - IntermediatePublicItem::new(path) - } -} - -/// In order for items in the output to be nicely grouped, we add a prefix to -/// each item in the path to an item. That way, sorting on the name (with this -/// prefix) will group items. But we don't want this prefix to be be visible to -/// users of course, so we do this "behind the scenes". -pub(crate) fn sorting_prefix(item: &Item) -> u8 { - match &item.inner { - ItemEnum::ExternCrate { .. } => 1, - ItemEnum::Import(_) => 2, - - ItemEnum::Primitive(_) => 3, - - ItemEnum::Module(_) => 4, - - ItemEnum::Macro(_) => 5, - ItemEnum::ProcMacro(_) => 6, - - ItemEnum::Enum(_) => 7, - ItemEnum::Union(_) => 8, - ItemEnum::Struct(_) => 9, - ItemEnum::StructField(_) => 10, - ItemEnum::Variant(_) => 11, - - ItemEnum::Constant { .. } => 12, - - ItemEnum::Static(_) => 13, - - ItemEnum::Trait(_) => 14, - - ItemEnum::AssocType { .. } => 15, - ItemEnum::AssocConst { .. } => 16, - - ItemEnum::Function(_) => 17, - - ItemEnum::TypeAlias(_) => 19, - - ItemEnum::Impl(impl_) => match ImplKind::from(item, impl_) { - // To not cause a diff when changing a manual impl to an - // auto-derived impl (or vice versa), we put them in the same group. - ImplKind::Inherent => 20, - ImplKind::Trait | ImplKind::AutoDerived => 21, - ImplKind::AutoTrait => 23, - ImplKind::Blanket => 24, - }, - - ItemEnum::ForeignType => 25, - - ItemEnum::OpaqueTy(_) => 26, - - ItemEnum::TraitAlias(_) => 27, - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) enum ImplKind { - /// E.g. `impl Foo` or `impl<'a> Foo<'a>` - Inherent, - - /// E.g. `impl Bar for Foo {}` - Trait, - - /// Auto-generated by `#[derive(...)]` - AutoDerived, - - /// Auto-trait impl such as `impl Sync for Foo` - AutoTrait, - - /// Blanket impls such as `impl Any for T` - Blanket, -} - -impl ImplKind { - fn from(impl_item: &Item, impl_: &Impl) -> Self { - let has_blanket_impl = impl_.blanket_impl.is_some(); - let is_automatically_derived = impl_item - .attrs - .iter() - .any(|a| a == "#[automatically_derived]"); - - // See https://github.com/rust-lang/rust/blob/54f20bbb8a7aeab93da17c0019c1aaa10329245a/src/librustdoc/json/conversions.rs#L589-L590 - match (impl_.synthetic, has_blanket_impl) { - (true, false) => ImplKind::AutoTrait, - (false, true) => ImplKind::Blanket, - _ if is_automatically_derived => ImplKind::AutoDerived, - _ if impl_.trait_.is_none() => ImplKind::Inherent, - _ => ImplKind::Trait, - } - } -} - -impl ImplKind { - fn is_active(&self) -> bool { - match self { - ImplKind::Blanket | ImplKind::AutoTrait | ImplKind::AutoDerived => false, - ImplKind::Inherent | ImplKind::Trait => true, - } - } -} - -/// Some items contain other items, which is relevant for analysis. Keep track -/// of such relationships. -const fn children_for_item(item: &Item) -> Option<&Vec> { - match &item.inner { - ItemEnum::Module(m) => Some(&m.items), - ItemEnum::Union(u) => Some(&u.fields), - ItemEnum::Struct(Struct { - kind: StructKind::Plain { fields, .. }, - .. - }) - | ItemEnum::Variant(rustdoc_types::Variant { - kind: VariantKind::Struct { fields, .. }, - .. - }) => Some(fields), - ItemEnum::Enum(e) => Some(&e.variants), - ItemEnum::Trait(t) => Some(&t.items), - ItemEnum::Impl(i) => Some(&i.items), - _ => None, - } -} - -pub fn impls_for_item(item: &Item) -> Option<&[Id]> { - match &item.inner { - ItemEnum::Union(u) => Some(&u.impls), - ItemEnum::Struct(s) => Some(&s.impls), - ItemEnum::Enum(e) => Some(&e.impls), - ItemEnum::Primitive(p) => Some(&p.impls), - ItemEnum::Trait(t) => Some(&t.implementations), - _ => None, - } -} diff --git a/crux_cli/src/codegen/public_api/mod.rs b/crux_cli/src/codegen/public_api/mod.rs deleted file mode 100644 index 4b0c40a0e..000000000 --- a/crux_cli/src/codegen/public_api/mod.rs +++ /dev/null @@ -1,91 +0,0 @@ -mod crate_wrapper; -mod intermediate_public_item; -pub mod item_processor; -pub mod nameable_item; -pub mod path_component; -pub mod public_item; -pub mod render; -mod tokens; - -use public_item::PublicItem; - -/// The public API of a crate -/// -/// Create an instance with [`Builder`]. -/// -/// ## Rendering the items -/// -/// To render the items in the public API you can iterate over the [items](PublicItem). -/// -/// You get the `rustdoc_json_str` in the example below as explained in the [crate] documentation, either via -/// [`rustdoc_json`](https://crates.io/crates/rustdoc_json) or by calling `cargo rustdoc` yourself. -/// -/// ```no_run -/// use public_api::PublicApi; -/// use std::path::PathBuf; -/// -/// # let rustdoc_json: PathBuf = todo!(); -/// // Gather the rustdoc content as described in this crates top-level documentation. -/// let public_api = public_api::Builder::from_rustdoc_json(&rustdoc_json).build()?; -/// -/// for public_item in public_api.items() { -/// // here we print the items to stdout, we could also write to a string or a file. -/// println!("{}", public_item); -/// } -/// -/// // If you want all items of the public API in a single big multi-line String then -/// // you can do like this: -/// let public_api_string = public_api.to_string(); -/// # Ok::<(), Box>(()) -/// ``` -#[derive(Debug)] -#[non_exhaustive] // More fields might be added in the future -pub struct PublicApi { - /// The items that constitutes the public API. An "item" is for example a - /// function, a struct, a struct field, an enum, an enum variant, a module, - /// etc... - pub(crate) items: Vec, - - /// See [`Self::missing_item_ids()`] - #[allow(dead_code)] - pub(crate) missing_item_ids: Vec, -} - -impl PublicApi { - /// Returns an iterator over all public items in the public API - pub fn items(&self) -> impl Iterator { - self.items.iter() - } - - /// Like [`Self::items()`], but ownership of all `PublicItem`s are - /// transferred to the caller. - #[allow(dead_code)] - pub fn into_items(self) -> impl Iterator { - self.items.into_iter() - } - - /// The rustdoc JSON IDs of missing but referenced items. Intended for use - /// with `--verbose` flags or similar. - /// - /// In some cases, a public item might be referenced from another public - /// item (e.g. a `mod`), but is missing from the rustdoc JSON file. This - /// occurs for example in the case of re-exports of external modules (see - /// ). The entries - /// in this Vec are what IDs that could not be found. - /// - /// The exact format of IDs are to be considered an implementation detail - /// and must not be be relied on. - #[allow(dead_code)] - pub fn missing_item_ids(&self) -> impl Iterator { - self.missing_item_ids.iter() - } -} - -impl std::fmt::Display for PublicApi { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for item in self.items() { - writeln!(f, "{item}")?; - } - Ok(()) - } -} diff --git a/crux_cli/src/codegen/public_api/nameable_item.rs b/crux_cli/src/codegen/public_api/nameable_item.rs deleted file mode 100644 index d67fda469..000000000 --- a/crux_cli/src/codegen/public_api/nameable_item.rs +++ /dev/null @@ -1,92 +0,0 @@ -use rustdoc_types::{Item, ItemEnum}; - -use super::render::RenderingContext; - -/// Wraps an [`Item`] and allows us to override its name. -#[derive(Clone, Debug)] -pub struct NameableItem<'c> { - /// The item we are effectively wrapping. - pub item: &'c Item, - - /// If `Some`, this overrides [Item::name], which happens in the case of - /// renamed imports (`pub use other::Item as Foo;`). - /// - /// We can't calculate this on-demand, because we can't know the final name - /// until we have checked if we need to break import recursion. - pub overridden_name: Option, - - /// See [`crate::item_processor::sorting_prefix()`] docs for an explanation why we have this. - pub sorting_prefix: u8, -} - -impl<'c> NameableItem<'c> { - /// The regular name of the item. Shown to users. - pub fn name(&self) -> Option<&str> { - self.overridden_name - .as_deref() - .or(self.item.name.as_deref()) - } - - /// The name that, when sorted on, will group items nicely. Is never shown - /// to a user. - pub fn sortable_name(&self, context: &RenderingContext) -> String { - // Note that in order for the prefix to sort properly lexicographically, - // we need to pad it with leading zeroes. - let mut sortable_name = format!("{:0>3}-", self.sorting_prefix); - - if let Some(name) = self.name() { - sortable_name.push_str(name); - } else if let ItemEnum::Impl(impl_) = &self.item.inner { - // In order for items of impls to be grouped together with its impl, - // add the "name" of the impl to the sorting prefix. Ignore `!` when - // sorting however, because that just messes the expected order up. - sortable_name.push_str(&super::tokens::tokens_to_string(&context.render_impl( - impl_, - &[], - true, /* disregard_negativity */ - ))); - - // If this is an inherent impl, additionally add the concatenated - // names of all associated items to the "name" of the impl. This makes - // multiple inherent impls group together, even if they have the - // same "name". - // - // For example, consider this code: - // - // pub struct MultipleInherentImpls; - // - // impl MultipleInherentImpls { - // pub fn impl_one() {} - // } - // - // impl MultipleInherentImpls { - // pub fn impl_two() {} - // } - // - // In this case, we want to group the two impls together. So - // the name of the first impl should be - // - // impl MultipleInherentImpls-impl_one - // - // and the second one - // - // impl MultipleInherentImpls-impl_two - // - if impl_.trait_.is_none() { - let mut assoc_item_names: Vec<&str> = impl_ - .items - .iter() - .filter_map(|id| context.crate_.index.get(id)) - .filter_map(|item| item.name.as_ref()) - .map(String::as_str) - .collect(); - assoc_item_names.sort_unstable(); - - sortable_name.push('-'); - sortable_name.push_str(&assoc_item_names.join("-")); - } - } - - sortable_name - } -} diff --git a/crux_cli/src/codegen/public_api/path_component.rs b/crux_cli/src/codegen/public_api/path_component.rs deleted file mode 100644 index ab508f093..000000000 --- a/crux_cli/src/codegen/public_api/path_component.rs +++ /dev/null @@ -1,23 +0,0 @@ -use rustdoc_types::Type; - -use super::nameable_item::NameableItem; - -/// A public item in a public API can only be referenced via a path. For example -/// `mod_a::mod_b::StructC`. A `PathComponent` represents one component of such -/// a path. A path component can either be a Rust item, or a Rust type. Normally -/// it is an item. The typical case when it is a type is when there are generic -/// arguments involved. For example, `Option` is a type. The -/// corresponding item is `Option` (no specific generic args has been -/// specified for the generic parameter T). -#[derive(Clone, Debug)] -pub struct PathComponent<'c> { - /// The item for this path component. - pub item: NameableItem<'c>, - - /// The type, if applicable. If we have a type, we'll want to use that - /// instead of `item`, since the type might have specific generic args. - pub type_: Option<&'c Type>, - - /// If `true`, do not render this path component to users. - pub hide: bool, -} diff --git a/crux_cli/src/codegen/public_api/public_item.rs b/crux_cli/src/codegen/public_api/public_item.rs deleted file mode 100644 index 9c0e820e8..000000000 --- a/crux_cli/src/codegen/public_api/public_item.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::cmp::Ordering; -use std::fmt::Display; -use std::hash::Hash; - -use super::intermediate_public_item::IntermediatePublicItem; -use super::render::RenderingContext; -use super::tokens::tokens_to_string; -use super::tokens::Token; - -/// Each public item (except `impl`s) have a path that is displayed like -/// `first::second::third`. Internally we represent that with a `vec!["first", -/// "second", "third"]`. This is a type alias for that internal representation -/// to make the code easier to read. -pub(crate) type PublicItemPath = Vec; - -/// Represent a public item of an analyzed crate, i.e. an item that forms part -/// of the public API of a crate. Implements [`Display`] so it can be printed. It -/// also implements [`Ord`], but how items are ordered are not stable yet, and -/// will change in later versions. -#[derive(Clone)] -pub struct PublicItem { - /// Read [`crate::item_processor::sorting_prefix()`] docs for more info - pub(crate) sortable_path: PublicItemPath, - - /// The rendered item as a stream of [`Token`]s - pub(crate) tokens: Vec, -} - -impl PublicItem { - pub(crate) fn from_intermediate_public_item( - context: &RenderingContext, - public_item: &IntermediatePublicItem<'_>, - ) -> PublicItem { - PublicItem { - sortable_path: public_item.sortable_path(context), - tokens: public_item.render_token_stream(context), - } - } - - /// The rendered item as a stream of [`Token`]s - #[allow(dead_code)] - pub fn tokens(&self) -> impl Iterator { - self.tokens.iter() - } - - /// Special version of [`cmp`](Ord::cmp) that is used to sort public items in a way that - /// makes them grouped logically. For example, struct fields will be put - /// right after the struct they are part of. - #[must_use] - pub fn grouping_cmp(&self, other: &Self) -> std::cmp::Ordering { - // This will make e.g. struct and struct fields be grouped together. - if let Some(ordering) = different_or_none(&self.sortable_path, &other.sortable_path) { - return ordering; - } - - // Fall back to lexical sorting if the above is not sufficient - self.to_string().cmp(&other.to_string()) - } -} - -impl PartialEq for PublicItem { - fn eq(&self, other: &Self) -> bool { - self.tokens == other.tokens - } -} - -impl Eq for PublicItem {} - -impl Hash for PublicItem { - fn hash(&self, state: &mut H) { - self.tokens.hash(state); - } -} - -/// We want pretty-printing (`"{:#?}"`) of [`crate::diff::PublicApiDiff`] to print -/// each public item as `Display`, so implement `Debug` with `Display`. -impl std::fmt::Debug for PublicItem { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(self, f) - } -} - -/// One of the basic uses cases is printing a sorted `Vec` of `PublicItem`s. So -/// we implement `Display` for it. -impl Display for PublicItem { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", tokens_to_string(&self.tokens)) - } -} - -/// Returns `None` if two items are equal. Otherwise their ordering is returned. -fn different_or_none(a: &T, b: &T) -> Option { - match a.cmp(b) { - Ordering::Equal => None, - c => Some(c), - } -} diff --git a/crux_cli/src/codegen/public_api/render.rs b/crux_cli/src/codegen/public_api/render.rs deleted file mode 100644 index 5ceba5d6f..000000000 --- a/crux_cli/src/codegen/public_api/render.rs +++ /dev/null @@ -1,1352 +0,0 @@ -#![allow(clippy::unused_self)] - -use super::intermediate_public_item::IntermediatePublicItem; -use super::nameable_item::NameableItem; -use super::path_component::PathComponent; -use super::tokens::Token; -use std::ops::Deref; -use std::{cmp::Ordering, collections::HashMap, vec}; - -use rustdoc_types::{ - Abi, Constant, Crate, FnDecl, FunctionPointer, GenericArg, GenericArgs, GenericBound, - GenericParamDef, GenericParamDefKind, Generics, Header, Id, Impl, Item, ItemEnum, MacroKind, - Path, PolyTrait, StructKind, Term, Trait, Type, TypeBinding, TypeBindingKind, VariantKind, - WherePredicate, -}; - -/// A simple macro to write `Token::Whitespace` in less characters. -macro_rules! ws { - () => { - Token::Whitespace - }; -} - -/// When we render an item, it might contain references to other parts of the -/// public API. For such cases, the rendering code can use the fields in this -/// struct. -pub struct RenderingContext<'c> { - /// The original and unmodified rustdoc JSON, in deserialized form. - pub crate_: &'c Crate, - - /// Given a rustdoc JSON ID, keeps track of what public items that have this Id. - pub id_to_items: HashMap<&'c Id, Vec<&'c IntermediatePublicItem<'c>>>, -} - -impl<'c> RenderingContext<'c> { - pub fn token_stream(&self, public_item: &IntermediatePublicItem<'c>) -> Vec { - let item = public_item.item(); - let item_path = public_item.path(); - - let mut tokens = vec![]; - - for attr in &item.attrs { - if attr_relevant_for_public_apis(attr) { - tokens.push(Token::Annotation(attr.clone())); - tokens.push(ws!()); - } - } - - let inner_tokens = match &item.inner { - ItemEnum::Module(_) => self.render_simple(&["mod"], item_path), - ItemEnum::ExternCrate { .. } => self.render_simple(&["extern", "crate"], item_path), - ItemEnum::Import(_) => self.render_simple(&["use"], item_path), - ItemEnum::Union(_) => self.render_simple(&["union"], item_path), - ItemEnum::Struct(s) => { - let mut output = self.render_simple(&["struct"], item_path); - output.extend(self.render_generics(&s.generics)); - if let StructKind::Tuple(fields) = &s.kind { - output.extend( - self.render_option_tuple(&self.resolve_tuple_fields(fields), Some(&pub_())), - ); - } - output - } - ItemEnum::StructField(inner) => { - let mut output = self.render_simple(&[], item_path); - output.extend(colon()); - output.extend(self.render_type(inner)); - output - } - ItemEnum::Enum(e) => { - let mut output = self.render_simple(&["enum"], item_path); - output.extend(self.render_generics(&e.generics)); - output - } - ItemEnum::Variant(inner) => { - let mut output = self.render_simple(&[], item_path); - match &inner.kind { - VariantKind::Struct { .. } => {} // Each struct field is printed individually - VariantKind::Plain => { - if let Some(discriminant) = &inner.discriminant { - output.extend(equals()); - output.push(Token::identifier(&discriminant.value)); - } - } - VariantKind::Tuple(fields) => { - output.extend( - self.render_option_tuple(&self.resolve_tuple_fields(fields), None), - ); - } - } - output - } - ItemEnum::Function(inner) => self.render_function( - self.render_path(item_path), - &inner.decl, - &inner.generics, - &inner.header, - ), - ItemEnum::Trait(trait_) => self.render_trait(trait_, item_path), - ItemEnum::TraitAlias(_) => self.render_simple(&["trait", "alias"], item_path), - ItemEnum::Impl(impl_) => { - self.render_impl(impl_, item_path, false /* disregard_negativity */) - } - ItemEnum::TypeAlias(inner) => { - let mut output = self.render_simple(&["type"], item_path); - output.extend(self.render_generics(&inner.generics)); - output.extend(equals()); - output.extend(self.render_type(&inner.type_)); - output - } - ItemEnum::AssocType { - generics, - bounds, - default, - } => { - let mut output = self.render_simple(&["type"], item_path); - output.extend(self.render_generics(generics)); - output.extend(self.render_generic_bounds_with_colon(bounds)); - if let Some(ty) = default { - output.extend(equals()); - output.extend(self.render_type(ty)); - } - output - } - ItemEnum::OpaqueTy(_) => self.render_simple(&["opaque", "type"], item_path), - ItemEnum::Constant { const_, type_ } => { - let mut output = self.render_simple(&["const"], item_path); - output.extend(colon()); - output.extend(self.render_constant(const_, Some(type_))); - output - } - ItemEnum::AssocConst { type_, .. } => { - let mut output = self.render_simple(&["const"], item_path); - output.extend(colon()); - output.extend(self.render_type(type_)); - output - } - ItemEnum::Static(inner) => { - let tags = if inner.mutable { - vec!["mut", "static"] - } else { - vec!["static"] - }; - let mut output = self.render_simple(&tags, item_path); - output.extend(colon()); - output.extend(self.render_type(&inner.type_)); - output - } - ItemEnum::ForeignType => self.render_simple(&["type"], item_path), - ItemEnum::Macro(_definition) => { - // TODO: _definition contains the whole definition, it would be really neat to get out all possible ways to invoke it - let mut output = self.render_simple(&["macro"], item_path); - output.push(Token::symbol("!")); - output - } - ItemEnum::ProcMacro(inner) => { - let mut output = self.render_simple(&["proc", "macro"], item_path); - output.pop(); // Remove name of macro to possibly wrap it in `#[]` - let name = Token::identifier(item.name.as_deref().unwrap_or("")); - match inner.kind { - MacroKind::Bang => output.extend(vec![name, Token::symbol("!()")]), - MacroKind::Attr => { - output.extend(vec![Token::symbol("#["), name, Token::symbol("]")]); - } - MacroKind::Derive => { - output.extend(vec![Token::symbol("#[derive("), name, Token::symbol(")]")]); - } - } - output - } - ItemEnum::Primitive(primitive) => { - // This is hard to write tests for since only Rust `core` is - // allowed to define primitives. So you have to test this code - // using the pre-built rustdoc JSON for core: - // - // rustup component add rust-docs-json --toolchain nightly - // cargo run -- --rustdoc-json ~/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/share/doc/rust/json/core.json - let mut output = pub_(); - output.extend([ - Token::kind("type"), - ws!(), - Token::primitive(&primitive.name), - ]); - output - } - }; - - tokens.extend(inner_tokens); - - tokens - } - - /// Tuple fields are referenced by ID in JSON, but we need to look up the - /// actual types that the IDs correspond to, in order to render the fields. - /// This helper does that for a slice of fields. - fn resolve_tuple_fields(&self, fields: &[Option]) -> Vec> { - let mut resolved_fields: Vec> = vec![]; - - for id in fields { - resolved_fields.push( - if let Some(Item { - inner: ItemEnum::StructField(type_), - .. - }) = id.as_ref().and_then(|id| self.crate_.index.get(id)) - { - Some(type_) - } else { - None - }, - ); - } - - resolved_fields - } - - fn render_simple(&self, tags: &[&str], path: &[PathComponent]) -> Vec { - let mut output = pub_(); - output.extend( - tags.iter() - .flat_map(|t| [Token::kind(*t), ws!()]) - .collect::>(), - ); - output.extend(self.render_path(path)); - output - } - - fn render_path(&self, path: &[PathComponent]) -> Vec { - let mut output = vec![]; - for component in path { - if component.hide { - continue; - } - - let (tokens, push_a_separator) = component.type_.map_or_else( - || self.render_nameable_item(&component.item), - |ty| self.render_type_and_separator(ty), - ); - - output.extend(tokens); - - if push_a_separator { - output.push(Token::symbol("::")); - } - } - if !path.is_empty() { - output.pop(); // Remove last "::" so "a::b::c::" becomes "a::b::c" - } - output - } - - fn render_nameable_item(&self, item: &NameableItem) -> (Vec, bool) { - let mut push_a_separator = false; - let mut output = vec![]; - let token_fn = if matches!(item.item.inner, ItemEnum::Function(_)) { - Token::function - } else if matches!( - item.item.inner, - ItemEnum::Trait(_) - | ItemEnum::Struct(_) - | ItemEnum::Union(_) - | ItemEnum::Enum(_) - | ItemEnum::TypeAlias(_) - ) { - Token::type_ - } else { - Token::identifier - }; - - if let Some(name) = item.name() { - // If we are not debugging, some items (read: impls) do not have - // a name, so only push a name if it exists - output.push(token_fn(name.to_string())); - push_a_separator = true; - } - (output, push_a_separator) - } - - fn render_sequence( - &self, - start: Vec, - end: Vec, - between: Vec, - sequence: &[T], - render: impl Fn(&T) -> Vec, - ) -> Vec { - self.render_sequence_impl(start, end, between, false, sequence, render) - } - - fn render_sequence_if_not_empty( - &self, - start: Vec, - end: Vec, - between: Vec, - sequence: &[T], - render: impl Fn(&T) -> Vec, - ) -> Vec { - self.render_sequence_impl(start, end, between, true, sequence, render) - } - - fn render_sequence_impl( - &self, - start: Vec, - end: Vec, - between: Vec, - return_nothing_if_empty: bool, - sequence: &[T], - render: impl Fn(&T) -> Vec, - ) -> Vec { - if return_nothing_if_empty && sequence.is_empty() { - return vec![]; - } - let mut output = start; - for (index, seq) in sequence.iter().enumerate() { - output.extend(render(seq)); - if index < sequence.len() - 1 { - output.extend(between.clone()); - } - } - output.extend(end); - output - } - - fn render_type(&self, ty: &Type) -> Vec { - self.render_option_type(&Some(ty)) - } - - fn render_type_and_separator(&self, ty: &Type) -> (Vec, bool) { - (self.render_type(ty), true) - } - - fn render_option_type(&self, ty: &Option<&Type>) -> Vec { - let Some(ty) = ty else { - return vec![Token::symbol("_")]; - }; // The `_` in `EnumWithStrippedTupleVariants::DoubleFirstHidden(_, bool)` - match ty { - Type::ResolvedPath(path) => self.render_resolved_path(path), - Type::DynTrait(dyn_trait) => self.render_dyn_trait(dyn_trait), - Type::Generic(name) => vec![Token::generic(name)], - Type::Primitive(name) => vec![Token::primitive(name)], - Type::FunctionPointer(ptr) => self.render_function_pointer(ptr), - Type::Tuple(types) => self.render_tuple(types), - Type::Slice(ty) => self.render_slice(ty), - Type::Array { type_, len } => self.render_array(type_, len), - Type::ImplTrait(bounds) => self.render_impl_trait(bounds), - Type::Infer => vec![Token::symbol("_")], - Type::RawPointer { mutable, type_ } => self.render_raw_pointer(*mutable, type_), - Type::BorrowedRef { - lifetime, - mutable, - type_, - } => self.render_borrowed_ref(lifetime.as_deref(), *mutable, type_), - Type::QualifiedPath { - name, - args: _, - self_type, - trait_, - } => self.render_qualified_path(self_type, trait_.as_ref(), name), - Type::Pat { .. } => vec![Token::symbol( - "https://github.com/rust-lang/rust/issues/123646 is unstable and not supported", - )], - } - } - - fn render_trait(&self, trait_: &Trait, path: &[PathComponent]) -> Vec { - let mut output = pub_(); - if trait_.is_unsafe { - output.extend(vec![Token::qualifier("unsafe"), ws!()]); - }; - output.extend([Token::kind("trait"), ws!()]); - output.extend(self.render_path(path)); - output.extend(self.render_generics(&trait_.generics)); - output.extend(self.render_generic_bounds_with_colon(&trait_.bounds)); - output - } - - fn render_dyn_trait(&self, dyn_trait: &rustdoc_types::DynTrait) -> Vec { - let mut output = vec![]; - - let more_than_one = dyn_trait.traits.len() > 1 || dyn_trait.lifetime.is_some(); - if more_than_one { - output.push(Token::symbol("(")); - } - - output.extend(self.render_sequence_if_not_empty( - vec![Token::keyword("dyn"), ws!()], - vec![], - plus(), - &dyn_trait.traits, - |p| self.render_poly_trait(p), - )); - - if let Some(lt) = &dyn_trait.lifetime { - output.extend(plus()); - output.extend(vec![Token::lifetime(lt)]); - } - - if more_than_one { - output.push(Token::symbol(")")); - } - - output - } - - fn render_function( - &self, - name: Vec, - decl: &FnDecl, - generics: &Generics, - header: &Header, - ) -> Vec { - let mut output = pub_(); - if header.unsafe_ { - output.extend(vec![Token::qualifier("unsafe"), ws!()]); - }; - if header.const_ { - output.extend(vec![Token::qualifier("const"), ws!()]); - }; - if header.async_ { - output.extend(vec![Token::qualifier("async"), ws!()]); - }; - if header.abi != Abi::Rust { - output.push(match &header.abi { - Abi::C { .. } => Token::qualifier("c"), - Abi::Cdecl { .. } => Token::qualifier("cdecl"), - Abi::Stdcall { .. } => Token::qualifier("stdcall"), - Abi::Fastcall { .. } => Token::qualifier("fastcall"), - Abi::Aapcs { .. } => Token::qualifier("aapcs"), - Abi::Win64 { .. } => Token::qualifier("win64"), - Abi::SysV64 { .. } => Token::qualifier("sysV64"), - Abi::System { .. } => Token::qualifier("system"), - Abi::Other(text) => Token::qualifier(text), - Abi::Rust => unreachable!(), - }); - output.push(ws!()); - } - - output.extend(vec![Token::kind("fn"), ws!()]); - output.extend(name); - - // Generic parameters - output.extend(self.render_generic_param_defs(&generics.params)); - - // Regular parameters and return type - output.extend(self.render_fn_decl(decl)); - - // Where predicates - output.extend(self.render_where_predicates(&generics.where_predicates)); - - output - } - - fn render_fn_decl(&self, decl: &FnDecl) -> Vec { - let mut output = vec![]; - // Main arguments - output.extend(self.render_sequence( - vec![Token::symbol("(")], - vec![Token::symbol(")")], - comma(), - &decl.inputs, - |(name, ty)| { - self.simplified_self(name, ty).unwrap_or_else(|| { - let mut output = vec![]; - if name != "_" { - output.extend(vec![Token::identifier(name), Token::symbol(":"), ws!()]); - } - output.extend(self.render_type(ty)); - output - }) - }, - )); - // Return type - if let Some(ty) = &decl.output { - output.extend(arrow()); - output.extend(self.render_type(ty)); - } - output - } - - fn simplified_self(&self, name: &str, ty: &Type) -> Option> { - if name == "self" { - match ty { - Type::Generic(name) if name == "Self" => Some(vec![Token::self_("self")]), - Type::BorrowedRef { - lifetime, - mutable, - type_, - } => match type_.as_ref() { - Type::Generic(name) if name == "Self" => { - let mut output = vec![Token::symbol("&")]; - if let Some(lt) = lifetime { - output.extend(vec![Token::lifetime(lt), ws!()]); - } - if *mutable { - output.extend(vec![Token::keyword("mut"), ws!()]); - } - output.push(Token::self_("self")); - Some(output) - } - _ => None, - }, - _ => None, - } - } else { - None - } - } - - fn render_resolved_path(&self, path: &Path) -> Vec { - let mut output = vec![]; - if let Some(item) = self.best_item_for_id(&path.id) { - output.extend(self.render_path(item.path())); - } else if let Some(item) = self.crate_.paths.get(&path.id) { - output.extend(self.render_path_components(item.path.iter().map(Deref::deref))); - } else if !path.name.is_empty() { - // If we get here it means there was no item for this Path in the - // rustdoc JSON. Examples of when this happens: - // - // * The resolved path is for a public item inside a private mod - // (and thus effectively the item is not public) - // - // In these cases we simply use the `name` verbatim, which typically - // is equal to how it appears in the source text. It might not be - // ideal and end up identical to the corresponding rustdoc HTML, but - // it is good enough given the edge-case nature of this code path. - output.extend(self.render_path_name(&path.name)); - } - if let Some(args) = &path.args { - output.extend(self.render_generic_args(args)); - } - output - } - - fn render_path_name(&self, name: &str) -> Vec { - self.render_path_components(name.split("::")) - } - - fn render_path_components( - &self, - path_iter: impl Iterator>, - ) -> Vec { - let mut output = vec![]; - let path: Vec<_> = path_iter.collect(); - let len = path.len(); - for (index, part) in path.into_iter().enumerate() { - if index == len - 1 { - output.push(Token::type_(part.as_ref())); - } else { - output.push(Token::identifier(part.as_ref())); - } - output.push(Token::symbol("::")); - } - if len > 0 { - output.pop(); - } - output - } - - fn render_function_pointer(&self, ptr: &FunctionPointer) -> Vec { - let mut output = self.render_higher_rank_trait_bounds(&ptr.generic_params); - output.push(Token::kind("fn")); - output.extend(self.render_fn_decl(&ptr.decl)); - output - } - - fn render_tuple(&self, types: &[Type]) -> Vec { - let option_tuple: Vec> = types.iter().map(Some).collect(); - self.render_option_tuple(&option_tuple, None) - } - - /// `prefix` is to handle the difference between tuple structs and enum variant - /// tuple structs. The former marks public fields as `pub ` whereas all fields - /// of enum tuple structs are always implicitly `pub`. - fn render_option_tuple(&self, types: &[Option<&Type>], prefix: Option<&[Token]>) -> Vec { - self.render_sequence( - vec![Token::symbol("(")], - vec![Token::symbol(")")], - comma(), - types, - |type_| { - let mut output: Vec = vec![]; - if let (Some(prefix), Some(_)) = (prefix, type_) { - output.extend(prefix.to_owned()); - } - output.extend(self.render_option_type(type_)); - output - }, - ) - } - - fn render_slice(&self, ty: &Type) -> Vec { - let mut output = vec![Token::symbol("[")]; - output.extend(self.render_type(ty)); - output.push(Token::symbol("]")); - output - } - - fn render_array(&self, type_: &Type, len: &str) -> Vec { - let mut output = vec![Token::symbol("[")]; - output.extend(self.render_type(type_)); - output.extend(vec![ - Token::symbol(";"), - ws!(), - Token::primitive(len), - Token::symbol("]"), - ]); - output - } - - pub(crate) fn render_impl( - &self, - impl_: &Impl, - _path: &[PathComponent], - disregard_negativity: bool, - ) -> Vec { - let mut output = vec![]; - - if impl_.is_unsafe { - output.extend(vec![Token::keyword("unsafe"), ws!()]); - } - - output.push(Token::keyword("impl")); - - output.extend(self.render_generic_param_defs(&impl_.generics.params)); - - output.push(ws!()); - - if let Some(trait_) = &impl_.trait_ { - if !disregard_negativity && impl_.negative { - output.push(Token::symbol("!")); - } - output.extend(self.render_resolved_path(trait_)); - output.extend(vec![ws!(), Token::keyword("for"), ws!()]); - output.extend(self.render_type(&impl_.for_)); - } else { - output.extend(self.render_type(&impl_.for_)); - } - - output.extend(self.render_where_predicates(&impl_.generics.where_predicates)); - - output - } - - fn render_impl_trait(&self, bounds: &[GenericBound]) -> Vec { - let mut output = vec![Token::keyword("impl")]; - output.push(ws!()); - output.extend(self.render_generic_bounds(bounds)); - output - } - - fn render_raw_pointer(&self, mutable: bool, type_: &Type) -> Vec { - let mut output = vec![Token::symbol("*")]; - output.push(Token::keyword(if mutable { "mut" } else { "const" })); - output.push(ws!()); - output.extend(self.render_type(type_)); - output - } - - fn render_borrowed_ref( - &self, - lifetime: Option<&str>, - mutable: bool, - type_: &Type, - ) -> Vec { - let mut output = vec![Token::symbol("&")]; - if let Some(lt) = lifetime { - output.extend(vec![Token::lifetime(lt), ws!()]); - } - if mutable { - output.extend(vec![Token::keyword("mut"), ws!()]); - } - output.extend(self.render_type(type_)); - output - } - - fn render_qualified_path(&self, type_: &Type, trait_: Option<&Path>, name: &str) -> Vec { - let mut output = vec![]; - match (type_, trait_) { - (Type::Generic(name), Some(trait_)) if name == "Self" && trait_.name.is_empty() => { - output.push(Token::keyword("Self")); - } - (_, trait_) => { - if trait_.is_some() { - output.push(Token::symbol("<")); - } - output.extend(self.render_type(type_)); - if let Some(trait_) = trait_ { - output.extend(vec![ws!(), Token::keyword("as"), ws!()]); - output.extend(self.render_resolved_path(trait_)); - output.push(Token::symbol(">")); - } - } - } - output.push(Token::symbol("::")); - output.push(Token::identifier(name)); - output - } - - fn render_generic_args(&self, args: &GenericArgs) -> Vec { - match args { - GenericArgs::AngleBracketed { args, bindings } => { - self.render_angle_bracketed(args, bindings) - } - GenericArgs::Parenthesized { inputs, output } => { - self.render_parenthesized(inputs, output) - } - } - } - - fn render_parenthesized(&self, inputs: &[Type], return_ty: &Option) -> Vec { - let mut output = self.render_sequence( - vec![Token::symbol("(")], - vec![Token::symbol(")")], - comma(), - inputs, - |type_| self.render_type(type_), - ); - if let Some(return_ty) = return_ty { - output.extend(arrow()); - output.extend(self.render_type(return_ty)); - } - output - } - - fn render_angle_bracketed(&self, args: &[GenericArg], bindings: &[TypeBinding]) -> Vec { - enum Arg<'c> { - GenericArg(&'c GenericArg), - TypeBinding(&'c TypeBinding), - } - self.render_sequence_if_not_empty( - vec![Token::symbol("<")], - vec![Token::symbol(">")], - comma(), - &args - .iter() - .map(Arg::GenericArg) - .chain(bindings.iter().map(Arg::TypeBinding)) - .collect::>(), - |arg| match arg { - Arg::GenericArg(arg) => self.render_generic_arg(arg), - Arg::TypeBinding(binding) => self.render_type_binding(binding), - }, - ) - } - - fn render_term(&self, term: &Term) -> Vec { - match term { - Term::Type(ty) => self.render_type(ty), - Term::Constant(c) => self.render_constant(c, None), - } - } - - fn render_poly_trait(&self, poly_trait: &PolyTrait) -> Vec { - let mut output = self.render_higher_rank_trait_bounds(&poly_trait.generic_params); - output.extend(self.render_resolved_path(&poly_trait.trait_)); - output - } - - fn render_generic_arg(&self, arg: &GenericArg) -> Vec { - match arg { - GenericArg::Lifetime(name) => vec![Token::lifetime(name)], - GenericArg::Type(ty) => self.render_type(ty), - GenericArg::Const(c) => self.render_constant(c, None), - GenericArg::Infer => vec![Token::symbol("_")], - } - } - - fn render_type_binding(&self, binding: &TypeBinding) -> Vec { - let mut output = vec![Token::identifier(&binding.name)]; - output.extend(self.render_generic_args(&binding.args)); - match &binding.binding { - TypeBindingKind::Equality(term) => { - output.extend(equals()); - output.extend(self.render_term(term)); - } - TypeBindingKind::Constraint(bounds) => { - output.extend(self.render_generic_bounds(bounds)); - } - } - output - } - - fn render_constant(&self, constant: &Constant, type_: Option<&Type>) -> Vec { - let mut output = vec![]; - if constant.is_literal { - output.extend(self.render_type(type_.expect("constant literals have a type"))); - if let Some(value) = &constant.value { - output.extend(equals()); - if constant.is_literal { - output.push(Token::primitive(value)); - } else { - output.push(Token::identifier(value)); - } - } - } else { - output.push(Token::identifier(&constant.expr)); - } - output - } - - fn render_generics(&self, generics: &Generics) -> Vec { - let mut output = vec![]; - output.extend(self.render_generic_param_defs(&generics.params)); - output.extend(self.render_where_predicates(&generics.where_predicates)); - output - } - - fn render_generic_param_defs(&self, params: &[GenericParamDef]) -> Vec { - let params_without_synthetics: Vec<_> = params - .iter() - .filter(|p| { - if let GenericParamDefKind::Type { synthetic, .. } = p.kind { - !synthetic - } else { - true - } - }) - .collect(); - - self.render_sequence_if_not_empty( - vec![Token::symbol("<")], - vec![Token::symbol(">")], - comma(), - ¶ms_without_synthetics, - |param| self.render_generic_param_def(param), - ) - } - - fn render_generic_param_def(&self, generic_param_def: &GenericParamDef) -> Vec { - let mut output = vec![]; - match &generic_param_def.kind { - GenericParamDefKind::Lifetime { outlives } => { - output.push(Token::lifetime(&generic_param_def.name)); - if !outlives.is_empty() { - output.extend(colon()); - output.extend(self.render_sequence(vec![], vec![], plus(), outlives, |s| { - vec![Token::lifetime(s)] - })); - } - } - GenericParamDefKind::Type { bounds, .. } => { - output.push(Token::generic(&generic_param_def.name)); - output.extend(self.render_generic_bounds_with_colon(bounds)); - } - GenericParamDefKind::Const { type_, .. } => { - output.push(Token::qualifier("const")); - output.push(ws!()); - output.push(Token::identifier(&generic_param_def.name)); - output.extend(colon()); - output.extend(self.render_type(type_)); - } - } - output - } - - fn render_where_predicates(&self, where_predicates: &[WherePredicate]) -> Vec { - let mut output = vec![]; - if !where_predicates.is_empty() { - output.push(ws!()); - output.push(Token::Keyword("where".to_owned())); - output.push(ws!()); - output.extend( - self.render_sequence(vec![], vec![], comma(), where_predicates, |p| { - self.render_where_predicate(p) - }), - ); - } - output - } - - fn render_where_predicate(&self, where_predicate: &WherePredicate) -> Vec { - let mut output = vec![]; - match where_predicate { - WherePredicate::BoundPredicate { - type_, - bounds, - generic_params, - } => { - output.extend(self.render_higher_rank_trait_bounds(generic_params)); - output.extend(self.render_type(type_)); - output.extend(self.render_generic_bounds_with_colon(bounds)); - } - WherePredicate::RegionPredicate { - lifetime, - bounds: _, - } => output.push(Token::Lifetime(lifetime.clone())), - WherePredicate::EqPredicate { lhs, rhs } => { - output.extend(self.render_type(lhs)); - output.extend(equals()); - output.extend(self.render_term(rhs)); - } - } - output - } - - fn render_generic_bounds_with_colon(&self, bounds: &[GenericBound]) -> Vec { - let mut output = vec![]; - if !bounds.is_empty() { - output.extend(colon()); - output.extend(self.render_generic_bounds(bounds)); - } - output - } - - fn render_generic_bounds(&self, bounds: &[GenericBound]) -> Vec { - self.render_sequence_if_not_empty(vec![], vec![], plus(), bounds, |bound| match bound { - GenericBound::TraitBound { - trait_, - generic_params, - .. - } => { - let mut output = vec![]; - output.extend(self.render_higher_rank_trait_bounds(generic_params)); - output.extend(self.render_resolved_path(trait_)); - output - } - GenericBound::Outlives(id) => vec![Token::lifetime(id)], - }) - } - - fn render_higher_rank_trait_bounds(&self, generic_params: &[GenericParamDef]) -> Vec { - let mut output = vec![]; - if !generic_params.is_empty() { - output.push(Token::keyword("for")); - output.extend(self.render_generic_param_defs(generic_params)); - output.push(ws!()); - } - output - } - - fn best_item_for_id(&self, id: &'c Id) -> Option<&'c IntermediatePublicItem<'c>> { - match self.id_to_items.get(&id) { - None => None, - Some(items) => { - items - .iter() - .max_by(|a, b| { - // If there is any item in the path that has been - // renamed/re-exported, i.e. that is not the original - // path, prefer that less than an item with a path where - // all items are original. - let mut ordering = match ( - a.path_contains_renamed_item(), - b.path_contains_renamed_item(), - ) { - (true, false) => Ordering::Less, - (false, true) => Ordering::Greater, - _ => Ordering::Equal, - }; - - // If we still can't make up our mind, go with the shortest path - if ordering == Ordering::Equal { - ordering = b.path().len().cmp(&a.path().len()); - } - - ordering - }) - .copied() - } - } - } -} - -/// Our list of allowed attributes comes from -/// -fn attr_relevant_for_public_apis>(attr: S) -> bool { - let prefixes = [ - "#[export_name", - "#[link_section", - "#[no_mangle", - "#[non_exhaustive", - "#[repr", - ]; - - for prefix in prefixes { - if attr.as_ref().starts_with(prefix) { - return true; - } - } - - false -} - -fn pub_() -> Vec { - vec![Token::qualifier("pub"), ws!()] -} - -fn plus() -> Vec { - vec![ws!(), Token::symbol("+"), ws!()] -} - -fn colon() -> Vec { - vec![Token::symbol(":"), ws!()] -} - -fn comma() -> Vec { - vec![Token::symbol(","), ws!()] -} - -fn equals() -> Vec { - vec![ws!(), Token::symbol("="), ws!()] -} - -fn arrow() -> Vec { - vec![ws!(), Token::symbol("->"), ws!()] -} - -#[cfg(test)] -mod test { - macro_rules! s { - ($value:literal) => { - $value.to_string() - }; - } - - use crate::codegen::public_api::tokens; - - use super::*; - - #[test] - fn test_type_infer() { - assert_render( - |context| context.render_type(&Type::Infer), - vec![Token::symbol("_")], - "_", - ); - } - - #[test] - fn test_type_generic() { - assert_render( - |context| context.render_type(&Type::Generic(s!("name"))), - vec![Token::generic("name")], - "name", - ); - } - - #[test] - fn test_type_primitive() { - assert_render( - |context| context.render_type(&Type::Primitive(s!("name"))), - vec![Token::primitive("name")], - "name", - ); - } - - #[test] - fn test_type_resolved_simple() { - assert_render( - |context| { - context.render_type(&Type::ResolvedPath(Path { - name: s!("name"), - args: None, - id: Id(s!("id")), - })) - }, - vec![Token::type_("name")], - "name", - ); - } - - #[test] - fn test_type_resolved_long_name() { - assert_render( - |context| { - context.render_type(&Type::ResolvedPath(Path { - name: s!("name::with::parts"), - args: None, - id: Id(s!("id")), - })) - }, - vec![ - Token::identifier("name"), - Token::symbol("::"), - Token::identifier("with"), - Token::symbol("::"), - Token::type_("parts"), - ], - "name::with::parts", - ); - } - - #[test] - fn test_type_resolved_crate_name() { - assert_render( - |context| { - context.render_type(&Type::ResolvedPath(Path { - name: s!("$crate::name"), - args: None, - id: Id(s!("id")), - })) - }, - vec![ - Token::identifier("$crate"), - Token::symbol("::"), - Token::type_("name"), - ], - "$crate::name", - ); - } - - #[test] - fn test_type_resolved_name_crate() { - assert_render( - |context| { - context.render_type(&Type::ResolvedPath(Path { - name: s!("name::$crate"), - args: None, - id: Id(s!("id")), - })) - }, - vec![ - Token::identifier("name"), - Token::symbol("::"), - Token::type_("$crate"), - ], - "name::$crate", - ); - } - - #[test] - fn test_type_tuple_empty() { - assert_render( - |context| context.render_type(&Type::Tuple(vec![])), - vec![Token::symbol("("), Token::symbol(")")], - "()", - ); - } - - #[test] - fn test_type_tuple() { - assert_render( - |context| { - context.render_type(&Type::Tuple(vec![Type::Infer, Type::Generic(s!("gen"))])) - }, - vec![ - Token::symbol("("), - Token::symbol("_"), - Token::symbol(","), - ws!(), - Token::generic("gen"), - Token::symbol(")"), - ], - "(_, gen)", - ); - } - - #[test] - fn test_type_slice() { - assert_render( - |context| context.render_type(&Type::Slice(Box::new(Type::Infer))), - vec![Token::symbol("["), Token::symbol("_"), Token::symbol("]")], - "[_]", - ); - } - - #[test] - fn test_type_array() { - assert_render( - |context| { - context.render_type(&Type::Array { - type_: Box::new(Type::Infer), - len: s!("20"), - }) - }, - vec![ - Token::symbol("["), - Token::symbol("_"), - Token::symbol(";"), - ws!(), - Token::primitive("20"), - Token::symbol("]"), - ], - "[_; 20]", - ); - } - - #[test] - fn test_type_pointer() { - assert_render( - |context| { - context.render_type(&Type::RawPointer { - mutable: false, - type_: Box::new(Type::Infer), - }) - }, - vec![ - Token::symbol("*"), - Token::keyword("const"), - ws!(), - Token::symbol("_"), - ], - "*const _", - ); - } - - #[test] - fn test_type_pointer_mut() { - assert_render( - |context| { - context.render_type(&Type::RawPointer { - mutable: true, - type_: Box::new(Type::Infer), - }) - }, - vec![ - Token::symbol("*"), - Token::keyword("mut"), - ws!(), - Token::symbol("_"), - ], - "*mut _", - ); - } - - #[test] - fn test_type_ref() { - assert_render( - |context| { - context.render_type(&Type::BorrowedRef { - lifetime: None, - mutable: false, - type_: Box::new(Type::Infer), - }) - }, - vec![Token::symbol("&"), Token::symbol("_")], - "&_", - ); - } - - #[test] - fn test_type_ref_mut() { - assert_render( - |context| { - context.render_type(&Type::BorrowedRef { - lifetime: None, - mutable: true, - type_: Box::new(Type::Infer), - }) - }, - vec![ - Token::symbol("&"), - Token::keyword("mut"), - ws!(), - Token::symbol("_"), - ], - "&mut _", - ); - } - - #[test] - fn test_type_ref_lt() { - assert_render( - |context| { - context.render_type(&Type::BorrowedRef { - lifetime: Some(s!("'a")), - mutable: false, - type_: Box::new(Type::Infer), - }) - }, - vec![ - Token::symbol("&"), - Token::lifetime("'a"), - ws!(), - Token::symbol("_"), - ], - "&'a _", - ); - } - - #[test] - fn test_type_ref_lt_mut() { - assert_render( - |context| { - context.render_type(&Type::BorrowedRef { - lifetime: Some(s!("'a")), - mutable: true, - type_: Box::new(Type::Infer), - }) - }, - vec![ - Token::symbol("&"), - Token::lifetime("'a"), - ws!(), - Token::keyword("mut"), - ws!(), - Token::symbol("_"), - ], - "&'a mut _", - ); - } - - #[test] - fn test_type_path() { - assert_render( - |context| { - context.render_type(&Type::QualifiedPath { - name: s!("name"), - args: Box::new(GenericArgs::AngleBracketed { - args: vec![], - bindings: vec![], - }), - self_type: Box::new(Type::Generic(s!("type"))), - trait_: Some(Path { - name: String::from("trait"), - args: None, - id: Id(s!("id")), - }), - }) - }, - vec![ - Token::symbol("<"), - Token::generic("type"), - ws!(), - Token::keyword("as"), - ws!(), - Token::type_("trait"), - Token::symbol(">"), - Token::symbol("::"), - Token::identifier("name"), - ], - "::name", - ); - } - - fn assert_render( - render_fn: impl Fn(RenderingContext) -> Vec, - expected: Vec, - expected_string: &str, - ) { - let crate_ = Crate { - root: Id(String::from("1:2:3")), - crate_version: None, - includes_private: false, - index: HashMap::new(), - paths: HashMap::new(), - external_crates: HashMap::new(), - format_version: 0, - }; - let context = RenderingContext { - crate_: &crate_, - id_to_items: HashMap::new(), - }; - - let actual = render_fn(context); - - assert_eq!(actual, expected); - assert_eq!( - tokens::tokens_to_string(&actual), - expected_string.to_string() - ); - } -} diff --git a/crux_cli/src/codegen/public_api/tokens.rs b/crux_cli/src/codegen/public_api/tokens.rs deleted file mode 100644 index f911bf5fc..000000000 --- a/crux_cli/src/codegen/public_api/tokens.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! Contains all token handling logic. -#[cfg(doc)] -use super::public_item::PublicItem; - -/// A token in a rendered [`PublicItem`], used to apply syntax coloring in downstream applications. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Token { - /// A symbol, like `=` or `::<` - Symbol(String), - /// A qualifier, like `pub` or `const` - Qualifier(String), - /// The kind of an item, like `function` or `trait` - Kind(String), - /// Whitespace, a single space - Whitespace, - /// An identifier, like variable names or parts of the path of an item - Identifier(String), - /// An annotation, used e.g. for Rust attributes. - Annotation(String), - /// The identifier self, the text can be `self` or `Self` - Self_(String), - /// The identifier for a function, like `fn_arg` in `comprehensive_api::functions::fn_arg` - Function(String), - /// A lifetime including the apostrophe `'`, like `'a` - Lifetime(String), - /// A keyword, like `impl`, `where`, or `dyn` - Keyword(String), - /// A generic parameter, like `T` - Generic(String), - /// A primitive type, like `usize` - Primitive(String), - /// A non-primitive type, like the name of a struct or a trait - Type(String), -} - -impl Token { - /// A symbol, like `=` or `::<` - pub(crate) fn symbol(text: impl Into) -> Self { - Self::Symbol(text.into()) - } - /// A qualifier, like `pub` or `const` - pub(crate) fn qualifier(text: impl Into) -> Self { - Self::Qualifier(text.into()) - } - /// The kind of an item, like `function` or `trait` - pub(crate) fn kind(text: impl Into) -> Self { - Self::Kind(text.into()) - } - /// An identifier, like variable names or parts of the path of an item - pub(crate) fn identifier(text: impl Into) -> Self { - Self::Identifier(text.into()) - } - /// The identifier self, the text can be `self` or `Self` - pub(crate) fn self_(text: impl Into) -> Self { - Self::Self_(text.into()) - } - /// The identifier for a function, like `fn_arg` in `comprehensive_api::functions::fn_arg` - pub(crate) fn function(text: impl Into) -> Self { - Self::Function(text.into()) - } - /// A lifetime including the apostrophe `'`, like `'a` - pub(crate) fn lifetime(text: impl Into) -> Self { - Self::Lifetime(text.into()) - } - /// A keyword, like `impl` - pub(crate) fn keyword(text: impl Into) -> Self { - Self::Keyword(text.into()) - } - /// A generic, like `T` - pub(crate) fn generic(text: impl Into) -> Self { - Self::Generic(text.into()) - } - /// A primitive type, like `usize` - pub(crate) fn primitive(text: impl Into) -> Self { - Self::Primitive(text.into()) - } - /// A type, like `Iterator` - pub(crate) fn type_(text: impl Into) -> Self { - Self::Type(text.into()) - } - /// Give the length of the inner text of this token - #[allow(clippy::len_without_is_empty)] - #[allow(dead_code)] - #[must_use] - pub fn len(&self) -> usize { - self.text().len() - } - /// Get the inner text of this token - #[must_use] - pub fn text(&self) -> &str { - match self { - Self::Symbol(l) - | Self::Qualifier(l) - | Self::Kind(l) - | Self::Identifier(l) - | Self::Annotation(l) - | Self::Self_(l) - | Self::Function(l) - | Self::Lifetime(l) - | Self::Keyword(l) - | Self::Generic(l) - | Self::Primitive(l) - | Self::Type(l) => l, - Self::Whitespace => " ", - } - } -} - -pub(crate) fn tokens_to_string(tokens: &[Token]) -> String { - tokens.iter().map(Token::text).collect() -} diff --git a/crux_cli/src/command_runner.rs b/crux_cli/src/command_runner.rs deleted file mode 100644 index 761b47b6d..000000000 --- a/crux_cli/src/command_runner.rs +++ /dev/null @@ -1,68 +0,0 @@ -// inspired by @fasterthanlime's brilliant post https://fasterthanli.me/articles/a-terminal-case-of-linux -// and Jakub Kądziołka's great follow up https://compilercrim.es/amos-nerdsniped-me/ - -use anyhow::{bail, Result}; -use std::convert::TryFrom; -use tokio::{io::AsyncReadExt, process::Command}; -use tokio_fd::AsyncFd; - -pub async fn run(cmd: &mut Command) -> Result<()> { - let (primary_fd, secondary_fd) = open_terminal(); - - unsafe { - cmd.pre_exec(move || { - if libc::login_tty(secondary_fd) != 0 { - panic!("couldn't set the controlling terminal or something"); - } - Ok(()) - }) - }; - let mut child = cmd.spawn()?; - - let mut buf = vec![0u8; 1024]; - let mut primary = AsyncFd::try_from(primary_fd)?; - - loop { - tokio::select! { - n = primary.read(&mut buf) => { - let n = n?; - let slice = &buf[..n]; - - let s = std::str::from_utf8(slice)?; - print!("{}", s); - }, - - status = child.wait() => { - match status { - Ok(s) => { - if s.success() { - break; - } - bail!("command failed with {}", s) - } - Err(e) => bail!(e), - } - }, - } - } - - Ok(()) -} - -fn open_terminal() -> (i32, i32) { - let mut primary_fd: i32 = -1; - let mut secondary_fd: i32 = -1; - unsafe { - let ret = libc::openpty( - &mut primary_fd, - &mut secondary_fd, - std::ptr::null_mut(), - std::ptr::null_mut(), - std::ptr::null_mut(), - ); - if ret != 0 { - panic!("Failed to openpty!"); - } - }; - (primary_fd, secondary_fd) -} diff --git a/crux_cli/src/main.rs b/crux_cli/src/main.rs index 2443c6ab0..7e5ec43d4 100644 --- a/crux_cli/src/main.rs +++ b/crux_cli/src/main.rs @@ -6,7 +6,6 @@ use args::Cli; mod args; mod codegen; -mod command_runner; mod config; mod diff; mod doctor;