diff --git a/crates/rune-core/src/item/item.rs b/crates/rune-core/src/item/item.rs index 92c419354..aa55b6624 100644 --- a/crates/rune-core/src/item/item.rs +++ b/crates/rune-core/src/item/item.rs @@ -381,6 +381,12 @@ impl Item { it.next_back()?; Some(it.into_item()) } + + /// Display an unqalified variant of the item which does not include `::` if + /// a crate is present. + pub fn unqalified(&self) -> Unqalified { + Unqalified::new(self) + } } impl AsRef for &Item { @@ -484,3 +490,42 @@ impl PartialEq> for &Item { *self == other.as_item() } } + +/// Display an unqalified path. +pub struct Unqalified<'a> { + item: &'a Item, +} + +impl<'a> Unqalified<'a> { + fn new(item: &'a Item) -> Self { + Self { item } + } +} + +impl fmt::Display for Unqalified<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut it = self.item.iter(); + + if let Some(last) = it.next_back() { + for c in it { + match c { + ComponentRef::Crate(name) => { + write!(f, "{name}::")?; + } + ComponentRef::Str(name) => { + write!(f, "{name}::")?; + } + c => { + write!(f, "{c}::")?; + } + } + } + + write!(f, "{}", last)?; + } else { + f.write_str("{root}")?; + } + + Ok(()) + } +} diff --git a/crates/rune/Cargo.toml b/crates/rune/Cargo.toml index 01a7e98d3..634a45a75 100644 --- a/crates/rune/Cargo.toml +++ b/crates/rune/Cargo.toml @@ -51,7 +51,7 @@ anyhow = { version = "1.0.71", default-features = false, optional = true } bincode = { version = "1.3.3", optional = true } clap = { version = "4.2.7", features = ["derive"], optional = true } codespan-reporting = { version = "0.11.1", optional = true } -handlebars = { version = "5.1.0", optional = true } +handlebars = { version = "6.0.0", optional = true } pulldown-cmark = { version = "0.9.2", optional = true } relative-path = { version = "1.8.0", optional = true, features = ["serde"] } rust-embed = { version = "6.6.1", optional = true } diff --git a/crates/rune/src/cli/doc.rs b/crates/rune/src/cli/doc.rs index 6b30c024b..69fa18040 100644 --- a/crates/rune/src/cli/doc.rs +++ b/crates/rune/src/cli/doc.rs @@ -9,7 +9,7 @@ use crate::alloc::prelude::*; use crate::cli::naming::Naming; use crate::cli::{AssetKind, CommandBase, Config, Entry, EntryPoint, ExitCode, Io, SharedFlags}; use crate::compile::FileSourceLoader; -use crate::{Diagnostics, ItemBuf, Options, Source, Sources}; +use crate::{Diagnostics, Options, Source, Sources}; mod cli { use std::path::PathBuf; @@ -94,9 +94,8 @@ where let mut naming = Naming::default(); for e in entries { - let name = naming.name(&e)?; + let item = naming.item(&e)?; - let item = ItemBuf::with_crate(&name)?; let mut visitor = crate::doc::Visitor::new(&item)?; let mut sources = Sources::new(); diff --git a/crates/rune/src/cli/naming.rs b/crates/rune/src/cli/naming.rs index 94c9327bf..a1cf6136c 100644 --- a/crates/rune/src/cli/naming.rs +++ b/crates/rune/src/cli/naming.rs @@ -1,49 +1,39 @@ -use core::mem::replace; - use std::ffi::OsStr; use crate::alloc::prelude::*; -use crate::alloc::{self, try_format, HashSet, String}; +use crate::alloc::{self, HashMap}; use crate::cli::EntryPoint; -use crate::workspace; +use crate::item::ComponentRef; +use crate::ItemBuf; /// Helper to perform non-conflicting crate naming. #[derive(Default)] pub(crate) struct Naming { - names: HashSet, - count: usize, + names: HashMap, } impl Naming { /// Construct a unique crate name for the given entrypoint. - pub(crate) fn name(&mut self, e: &EntryPoint<'_>) -> alloc::Result { - let name = match &e { + pub(crate) fn item(&mut self, e: &EntryPoint<'_>) -> alloc::Result { + let mut item = match &e { EntryPoint::Path(path) => match path.file_stem().and_then(OsStr::to_str) { - Some(name) => String::try_from(name)?, - None => String::try_from("entry")?, + Some(name) => ItemBuf::with_crate(name)?, + None => ItemBuf::with_crate("entry")?, }, EntryPoint::Package(p) => { let name = p.found.name.as_str(); - - let ext = match &p.found.kind { - workspace::FoundKind::Binary => "bin", - workspace::FoundKind::Test => "test", - workspace::FoundKind::Example => "example", - workspace::FoundKind::Bench => "bench", - }; - - try_format!("{}-{name}-{ext}", p.package.name) + ItemBuf::with_crate_item(&p.package.name, [name])? } }; - // TODO: make it so that we can communicate different entrypoints in the - // visitors context instead of this hackery. - Ok(if !self.names.try_insert(name.try_clone()?)? { - let next = self.count.wrapping_add(1); - let index = replace(&mut self.count, next); - try_format!("{name}{index}") - } else { - name - }) + let values = self.names.entry(item.try_clone()?).or_try_default()?; + + if *values > 0 { + let name = try_format!("{}", *values - 1); + item.push(ComponentRef::Str(&name))?; + } + + *values += 1; + Ok(item) } } diff --git a/crates/rune/src/cli/tests.rs b/crates/rune/src/cli/tests.rs index 4fe5200bc..a96c82178 100644 --- a/crates/rune/src/cli/tests.rs +++ b/crates/rune/src/cli/tests.rs @@ -134,8 +134,7 @@ where }; for entry in entries { - let name = naming.name(&entry)?; - let item = ItemBuf::with_crate(&name)?; + let item = naming.item(&entry)?; let mut sources = Sources::new(); diff --git a/crates/rune/src/compile/context.rs b/crates/rune/src/compile/context.rs index 676e2c595..81db933f5 100644 --- a/crates/rune/src/compile/context.rs +++ b/crates/rune/src/compile/context.rs @@ -902,6 +902,7 @@ impl Context { .entry(i.hash) .or_try_default()? .try_push(i.trait_hash)?; + Ok(()) } diff --git a/crates/rune/src/doc/build.rs b/crates/rune/src/doc/build.rs index 4544c241b..15b33208f 100644 --- a/crates/rune/src/doc/build.rs +++ b/crates/rune/src/doc/build.rs @@ -27,7 +27,7 @@ use crate::doc::{Artifacts, Context, Visitor}; use crate::item::ComponentRef; use crate::runtime::static_type; use crate::std::borrow::ToOwned; -use crate::{Hash, Item, ItemBuf}; +use crate::{Hash, Item}; // InspiredGitHub // Solarized (dark) @@ -40,12 +40,12 @@ const THEME: &str = "base16-eighties.dark"; const RUNEDOC_CSS: &str = "runedoc.css"; pub(crate) struct Builder<'m> { - state: State, + state: State<'m>, builder: rust_alloc::boxed::Box) -> Result + 'm>, } impl<'m> Builder<'m> { - fn new(cx: &Ctxt<'_, '_>, builder: B) -> alloc::Result + fn new(cx: &Ctxt<'_, 'm>, builder: B) -> alloc::Result where B: FnOnce(&Ctxt<'_, '_>) -> Result + 'm, { @@ -135,11 +135,13 @@ pub(crate) fn build( for item in context.iter_modules() { let item = item?; + let meta = context .meta(&item)? .into_iter() .find(|m| matches!(&m.kind, Kind::Module)) .with_context(|| anyhow!("Missing meta for {item}"))?; + initial.try_push_back((Build::Module, meta))?; } @@ -213,8 +215,7 @@ pub(crate) fn build( Build::Module => { cx.set_path(meta)?; builders.try_push(module(&mut cx, meta, &mut queue)?)?; - let item = meta.item.context("Missing item")?; - modules.try_push((item, cx.state.path.clone()))?; + modules.try_push((meta.item, cx.state.path.clone()))?; } } } @@ -329,14 +330,15 @@ pub(crate) struct IndexEntry<'m> { } #[derive(Default, TryClone)] -pub(crate) struct State { +pub(crate) struct State<'m> { #[try_clone(with = RelativePathBuf::clone)] path: RelativePathBuf, - item: ItemBuf, + #[try_clone(copy)] + item: &'m Item, } pub(crate) struct Ctxt<'a, 'm> { - state: State, + state: State<'m>, /// A collection of all items visited. index: Vec>, name: &'a str, @@ -368,18 +370,16 @@ impl<'m> Ctxt<'_, 'm> { kind => bail!("Cannot set path for {kind:?}"), }; - let item = meta.item.context("Missing meta item")?; - self.state.path = RelativePathBuf::new(); - self.state.item = item.try_to_owned()?; + self.state.item = meta.item; - build_item_path(self.name, item, item_kind, &mut self.state.path)?; + build_item_path(self.name, meta.item, item_kind, &mut self.state.path)?; let doc = self.render_line_docs(meta, meta.docs.get(..1).unwrap_or_default())?; self.index.try_push(IndexEntry { path: self.state.path.clone(), - item: Cow::Borrowed(item), + item: Cow::Borrowed(meta.item), kind: IndexKind::Item(item_kind), doc, })?; @@ -508,7 +508,7 @@ impl<'m> Ctxt<'_, 'm> { for (content, params) in tests { self.tests.try_push(Test { - item: self.state.item.try_clone()?, + item: self.state.item.try_to_owned()?, content, params, })?; @@ -527,13 +527,21 @@ impl<'m> Ctxt<'_, 'm> { /// Build backlinks for the current item. fn module_path_html(&self, meta: Meta<'_>, is_module: bool) -> Result { + fn unqualified_component<'a>(c: &'a ComponentRef<'_>) -> &'a dyn fmt::Display { + match c { + ComponentRef::Crate(name) => name, + ComponentRef::Str(name) => name, + c => c, + } + } + let mut module = Vec::new(); - let item = meta.item.context("Missing module item")?; - let mut iter = item.iter(); + let mut iter = meta.item.iter(); while iter.next_back().is_some() { - if let Some(name) = iter.as_item().last() { + if let Some(c) = iter.as_item().last() { + let name: &dyn fmt::Display = unqualified_component(&c); let url = self.item_path(iter.as_item(), ItemKind::Module)?; module.try_push(try_format!("{name}"))?; } @@ -542,7 +550,8 @@ impl<'m> Ctxt<'_, 'm> { module.reverse(); if is_module { - if let Some(name) = item.last() { + if let Some(c) = meta.item.last() { + let name: &dyn fmt::Display = unqualified_component(&c); module.try_push(try_format!("{name}"))?; } } @@ -625,16 +634,12 @@ impl<'m> Ctxt<'_, 'm> { break 'out (None, None, text); }; - let Some(item) = meta.item else { - break 'out (None, Some(kind), text); - }; - let text = match text { Some(text) => Some(text), - None => item.last().and_then(|c| c.as_str()), + None => meta.item.last().and_then(|c| c.as_str()), }; - (Some(self.item_path(item, kind)?), Some(kind), text) + (Some(self.item_path(meta.item, kind)?), Some(kind), text) }; let (path, kind, text) = outcome; @@ -763,14 +768,10 @@ impl<'m> Ctxt<'_, 'm> { let link = link.trim_matches(|c| matches!(c, '`')); let (link, flavor) = flavor(link); - let Some(item) = meta.item else { - return Ok(None); - }; - let item = if matches!(meta.kind, Kind::Module) { - item.join([link])? + meta.item.join([link])? } else { - let Some(parent) = item.parent() else { + let Some(parent) = meta.item.parent() else { return Ok(None); }; @@ -918,7 +919,7 @@ fn module<'m>( #[derive(Serialize)] struct Type<'a> { #[serde(serialize_with = "serialize_item")] - item: ItemBuf, + item: &'a Item, #[serde(serialize_with = "serialize_component_ref")] name: ComponentRef<'a>, path: RelativePathBuf, @@ -929,7 +930,7 @@ fn module<'m>( struct Struct<'a> { path: RelativePathBuf, #[serde(serialize_with = "serialize_item")] - item: ItemBuf, + item: &'a Item, #[serde(serialize_with = "serialize_component_ref")] name: ComponentRef<'a>, doc: Option, @@ -939,7 +940,7 @@ fn module<'m>( struct Enum<'a> { path: RelativePathBuf, #[serde(serialize_with = "serialize_item")] - item: ItemBuf, + item: &'a Item, #[serde(serialize_with = "serialize_component_ref")] name: ComponentRef<'a>, doc: Option, @@ -961,7 +962,7 @@ fn module<'m>( deprecated: Option<&'a str>, path: RelativePathBuf, #[serde(serialize_with = "serialize_item")] - item: ItemBuf, + item: &'a Item, #[serde(serialize_with = "serialize_component_ref")] name: ComponentRef<'a>, args: String, @@ -981,15 +982,13 @@ fn module<'m>( #[derive(Serialize)] struct Trait<'a> { #[serde(serialize_with = "serialize_item")] - item: ItemBuf, + item: &'a Item, #[serde(serialize_with = "serialize_component_ref")] name: ComponentRef<'a>, path: RelativePathBuf, doc: Option, } - let meta_item = meta.item.context("Missing item")?; - let mut types = Vec::new(); let mut structs = Vec::new(); let mut enums = Vec::new(); @@ -998,19 +997,17 @@ fn module<'m>( let mut modules = Vec::new(); let mut traits = Vec::new(); - for (_, name) in cx.context.iter_components(meta_item)? { - let item = meta_item.join([name])?; + for (_, name) in cx.context.iter_components(meta.item)? { + let lookup_item = meta.item.join([name])?; - for m in cx.context.meta(&item)? { + for m in cx.context.meta(&lookup_item)? { match m.kind { Kind::Type { .. } => { queue.try_push_front((Build::Type, m))?; - let path = cx.item_path(&item, ItemKind::Type)?; - types.try_push(Type { - item: item.try_clone()?, - path, + path: cx.item_path(m.item, ItemKind::Type)?, + item: m.item, name, doc: cx.render_line_docs(m, m.docs.get(..1).unwrap_or_default())?, })?; @@ -1018,11 +1015,9 @@ fn module<'m>( Kind::Struct { .. } => { queue.try_push_front((Build::Struct, m))?; - let path = cx.item_path(&item, ItemKind::Struct)?; - structs.try_push(Struct { - item: item.try_clone()?, - path, + path: cx.item_path(m.item, ItemKind::Struct)?, + item: m.item, name, doc: cx.render_line_docs(m, m.docs.get(..1).unwrap_or_default())?, })?; @@ -1030,23 +1025,19 @@ fn module<'m>( Kind::Enum { .. } => { queue.try_push_front((Build::Enum, m))?; - let path = cx.item_path(&item, ItemKind::Enum)?; - enums.try_push(Enum { - item: item.try_clone()?, - path, + path: cx.item_path(m.item, ItemKind::Enum)?, + item: m.item, name, doc: cx.render_line_docs(m, m.docs.get(..1).unwrap_or_default())?, })?; } Kind::Macro => { - let item = m.item.context("Missing macro item")?; - queue.try_push_front((Build::Macro, m))?; macros.try_push(Macro { - path: cx.item_path(item, ItemKind::Macro)?, - item, + path: cx.item_path(m.item, ItemKind::Macro)?, + item: m.item, name, doc: cx.render_line_docs(m, m.docs.get(..1).unwrap_or_default())?, })?; @@ -1061,25 +1052,23 @@ fn module<'m>( functions.try_push(Function { is_async: f.is_async, deprecated: meta.deprecated, - path: cx.item_path(&item, ItemKind::Function)?, - item: item.try_clone()?, + path: cx.item_path(m.item, ItemKind::Function)?, + item: m.item, name, args: cx.args_to_string(f.signature, f.arguments)?, doc: cx.render_line_docs(m, m.docs.get(..1).unwrap_or_default())?, })?; } Kind::Module => { - let item = m.item.context("Missing module item")?; - // Skip over crate items, since they are added separately. - if meta_item.is_empty() && item.as_crate().is_some() { + if meta.item.is_empty() && m.item.as_crate().is_some() { continue; } queue.try_push_front((Build::Module, m))?; - let path = cx.item_path(item, ItemKind::Module)?; - let name = item.last().context("missing name of module")?; + let path = cx.item_path(m.item, ItemKind::Module)?; + let name = m.item.last().context("missing name of module")?; // Prevent multiple entries of a module, with no documentation modules.retain(|module: &Module<'_>| { @@ -1087,7 +1076,7 @@ fn module<'m>( }); modules.try_push(Module { - item, + item: m.item, name, path, doc: cx.render_line_docs(m, m.docs.get(..1).unwrap_or_default())?, @@ -1096,11 +1085,9 @@ fn module<'m>( Kind::Trait { .. } => { queue.try_push_front((Build::Trait, m))?; - let path = cx.item_path(&item, ItemKind::Trait)?; - traits.try_push(Trait { - item: item.try_clone()?, - path, + path: cx.item_path(m.item, ItemKind::Trait)?, + item: m.item, name, doc: cx.render_line_docs(m, m.docs.get(..1).unwrap_or_default())?, })?; @@ -1117,7 +1104,7 @@ fn module<'m>( Ok(Builder::new(cx, move |cx| { cx.module_template.render(&Params { shared: cx.shared()?, - item: meta_item, + item: meta.item, module: cx.module_path_html(meta, true)?, doc, types, @@ -1147,14 +1134,13 @@ fn build_macro<'m>(cx: &mut Ctxt<'_, 'm>, meta: Meta<'m>) -> Result> } let doc = cx.render_docs(meta, meta.docs, true)?; - let item = meta.item.context("Missing item")?; - let name = item.last().context("Missing macro name")?; + let name = meta.item.last().context("Missing macro name")?; Ok(Builder::new(cx, move |cx| { cx.macro_template.render(&Params { shared: cx.shared()?, module: cx.module_path_html(meta, false)?, - item, + item: meta.item, name, doc, }) @@ -1170,6 +1156,8 @@ fn build_function<'m>(cx: &mut Ctxt<'_, 'm>, meta: Meta<'m>) -> Result, module: String, is_async: bool, + is_test: bool, + is_bench: bool, deprecated: Option<&'a str>, #[serde(serialize_with = "serialize_item")] item: &'a Item, @@ -1194,17 +1182,17 @@ fn build_function<'m>(cx: &mut Ctxt<'_, 'm>, meta: Meta<'m>) -> Result(item: &Item, serializer: S) -> Result where S: Serializer, { - serializer.collect_str(item) + serializer.collect_str(&item.unqalified()) } /// Helper to serialize a component ref. diff --git a/crates/rune/src/doc/build/type_.rs b/crates/rune/src/doc/build/type_.rs index bd9537a6c..dad9bdbda 100644 --- a/crates/rune/src/doc/build/type_.rs +++ b/crates/rune/src/doc/build/type_.rs @@ -1,3 +1,5 @@ +use core::mem::replace; + use anyhow::{Context, Result}; use relative_path::RelativePathBuf; use serde::Serialize; @@ -62,8 +64,6 @@ pub(super) fn build_assoc_fns<'m>( Vec>, Vec>, )> { - let meta_item = meta.item.context("Missing meta item")?; - let (variants, protocols, methods) = associated_for_hash(cx, meta.hash, meta, true)?; let mut index = Vec::new(); @@ -77,7 +77,7 @@ pub(super) fn build_assoc_fns<'m>( .state .path .with_file_name(format!("{name}#method.{}", m.name)), - item: Cow::Owned(meta_item.join([m.name])?), + item: Cow::Owned(meta.item.join([m.name])?), kind: IndexKind::Method, doc: m.line_doc.try_clone()?, })?; @@ -89,7 +89,7 @@ pub(super) fn build_assoc_fns<'m>( .state .path .with_file_name(format!("{name}#variant.{}", m.name)), - item: Cow::Owned(meta_item.join([m.name])?), + item: Cow::Owned(meta.item.join([m.name])?), kind: IndexKind::Variant, doc: m.line_doc.try_clone()?, })?; @@ -102,7 +102,7 @@ pub(super) fn build_assoc_fns<'m>( let item = 'item: { for meta in cx.context.meta_by_hash(hash)? { match meta.kind { - Kind::Trait => break 'item meta.item.context("Missing trait item")?, + Kind::Trait => break 'item meta.item, _ => continue, } } @@ -144,101 +144,103 @@ fn associated_for_hash<'m>( let mut protocols = Vec::new(); let mut methods = Vec::new(); - for assoc in cx.context.associated(hash) { - match assoc { - Assoc::Variant(variant) => { - let line_doc = - cx.render_line_docs(meta, variant.docs.get(..1).unwrap_or_default())?; + for hash in cx.context.associated(hash) { + for assoc in cx.context.associated_meta(hash) { + match assoc { + Assoc::Variant(variant) => { + let line_doc = + cx.render_line_docs(meta, variant.docs.get(..1).unwrap_or_default())?; - let doc = cx.render_docs(meta, variant.docs, capture_tests)?; + let doc = cx.render_docs(meta, variant.docs, capture_tests)?; - variants.try_push(Variant { - name: variant.name, - line_doc, - doc, - })?; - } - Assoc::Fn(assoc) => { - let value; - - let (protocol, value, field) = match assoc.kind { - AssocFnKind::Protocol(protocol) => (protocol, "value", None), - AssocFnKind::FieldFn(protocol, field) => { - value = format!("value.{field}"); - (protocol, value.as_str(), Some(field)) - } - AssocFnKind::IndexFn(protocol, index) => { - value = format!("value.{index}"); - (protocol, value.as_str(), None) - } - AssocFnKind::Method(name, sig) => { - let line_doc = - cx.render_line_docs(meta, assoc.docs.get(..1).unwrap_or_default())?; - - cx.state.item.push(name)?; - let doc = cx.render_docs(meta, assoc.docs, capture_tests)?; - cx.state.item.pop()?; - - let parameters = if !assoc.parameter_types.is_empty() { - let mut s = String::new(); - let mut it = assoc.parameter_types.iter().peekable(); - - while let Some(hash) = it.next() { - cx.write_link(&mut s, *hash, None, &[])?; - - if it.peek().is_some() { - write!(s, ", ")?; - } + variants.try_push(Variant { + name: variant.name, + line_doc, + doc, + })?; + } + Assoc::Fn(assoc) => { + let value; + + let (protocol, value, field) = match assoc.kind { + AssocFnKind::Protocol(protocol) => (protocol, "value", None), + AssocFnKind::FieldFn(protocol, field) => { + value = format!("value.{field}"); + (protocol, value.as_str(), Some(field)) + } + AssocFnKind::IndexFn(protocol, index) => { + value = format!("value.{index}"); + (protocol, value.as_str(), None) + } + AssocFnKind::Method(item, name, sig) => { + // NB: Regular associated functions are documented by the trait itself. + if assoc.trait_hash.is_some() { + continue; } - Some(s) - } else { - None - }; - - let method = Method { - is_async: assoc.is_async, - deprecated: assoc.deprecated, - name, - args: cx.args_to_string(sig, assoc.arguments)?, - parameters, - return_type: cx.return_type(assoc.return_type)?, - line_doc, - doc, - }; - - methods.try_push(method)?; - continue; - } - }; - - // NB: Regular associated functions are documented by the trait itself. - if assoc.trait_hash.is_some() { - continue; - } + let line_doc = + cx.render_line_docs(meta, assoc.docs.get(..1).unwrap_or_default())?; + + let old = replace(&mut cx.state.item, item); + let doc = cx.render_docs(meta, assoc.docs, capture_tests)?; + cx.state.item = old; - let doc = if assoc.docs.is_empty() { - cx.render_docs(meta, protocol.doc, false)? - } else { - cx.render_docs(meta, assoc.docs, capture_tests)? - }; - - let repr = if let Some(repr) = protocol.repr { - Some(cx.render_code([repr.replace("$value", value.as_ref())])?) - } else { - None - }; - - let protocol = Protocol { - name: protocol.name, - field, - repr, - return_type: cx.return_type(assoc.return_type)?, - doc, - deprecated: assoc.deprecated, - }; - - protocols.try_push(protocol)?; + let parameters = if !assoc.parameter_types.is_empty() { + let mut s = String::new(); + let mut it = assoc.parameter_types.iter().peekable(); + + while let Some(hash) = it.next() { + cx.write_link(&mut s, *hash, None, &[])?; + + if it.peek().is_some() { + write!(s, ", ")?; + } + } + + Some(s) + } else { + None + }; + + let method = Method { + is_async: assoc.is_async, + deprecated: assoc.deprecated, + name, + args: cx.args_to_string(sig, assoc.arguments)?, + parameters, + return_type: cx.return_type(assoc.return_type)?, + line_doc, + doc, + }; + + methods.try_push(method)?; + continue; + } + }; + + let doc = if assoc.docs.is_empty() { + cx.render_docs(meta, protocol.doc, false)? + } else { + cx.render_docs(meta, assoc.docs, capture_tests)? + }; + + let repr = if let Some(repr) = protocol.repr { + Some(cx.render_code([repr.replace("$value", value.as_ref())])?) + } else { + None + }; + + let protocol = Protocol { + name: protocol.name, + field, + repr, + return_type: cx.return_type(assoc.return_type)?, + doc, + deprecated: assoc.deprecated, + }; + + protocols.try_push(protocol)?; + } } } } @@ -273,8 +275,7 @@ pub(crate) fn build<'m>( let module = cx.module_path_html(meta, false)?; let (protocols, methods, _, index, traits) = build_assoc_fns(cx, meta)?; - let item = meta.item.context("Missing type item")?; - let name = item.last().context("Missing module name")?; + let name = meta.item.last().context("Missing module name")?; let doc = cx.render_docs(meta, meta.docs, true)?; @@ -285,7 +286,7 @@ pub(crate) fn build<'m>( what_class, module, name, - item, + item: meta.item, methods, protocols, traits, diff --git a/crates/rune/src/doc/context.rs b/crates/rune/src/doc/context.rs index f17e46ca7..d9d7d9684 100644 --- a/crates/rune/src/doc/context.rs +++ b/crates/rune/src/doc/context.rs @@ -21,12 +21,12 @@ pub(crate) struct Meta<'a> { /// Kind of the meta item. pub(crate) kind: Kind<'a>, /// Item of the meta. - pub(crate) item: Option<&'a Item>, + pub(crate) item: &'a Item, + /// Type hash for the meta item. + pub(crate) hash: Hash, /// The meta source. #[allow(unused)] pub(crate) source: MetaSource<'a>, - /// Type hash for the meta item. - pub(crate) hash: Hash, /// Indicates if the item is deprecated. pub(crate) deprecated: Option<&'a str>, /// Documentation for the meta item. @@ -36,6 +36,8 @@ pub(crate) struct Meta<'a> { #[derive(Debug, Clone, Copy)] pub(crate) struct Function<'a> { pub(crate) is_async: bool, + pub(crate) is_test: bool, + pub(crate) is_bench: bool, pub(crate) signature: Signature, pub(crate) arguments: Option<&'a [meta::DocArgument]>, pub(crate) return_type: &'a meta::DocType, @@ -51,7 +53,7 @@ pub(crate) enum AssocFnKind<'a> { /// An index function with the given protocol. IndexFn(Protocol, usize), /// The instance function refers to the given named instance fn. - Method(&'a str, Signature), + Method(&'a Item, &'a str, Signature), } /// Information on an associated function. @@ -120,16 +122,36 @@ impl<'a> Context<'a> { } /// Iterate over all types associated with the given hash. - pub(crate) fn associated(&self, hash: Hash) -> impl Iterator> { + pub(crate) fn associated(&self, hash: Hash) -> impl Iterator + '_ { let visitors = self .visitors .iter() - .flat_map(move |v| visitor_to_associated(hash, v).into_iter().flatten()); + .flat_map(move |v| { + v.associated + .get(&hash) + .map(Vec::as_slice) + .unwrap_or_default() + }) + .copied(); let context = self .context .into_iter() - .flat_map(move |c| c.associated(hash).flat_map(|a| context_to_associated(c, a))); + .flat_map(move |c| c.associated(hash)); + + visitors.chain(context) + } + + pub(crate) fn associated_meta(&self, hash: Hash) -> impl Iterator> + '_ { + let visitors = self + .visitors + .iter() + .flat_map(move |v| visitor_to_associated(v, hash)); + + let context = self + .context + .into_iter() + .flat_map(move |c| context_to_associated(c, hash)); visitors.chain(context) } @@ -204,6 +226,8 @@ impl<'a> Context<'a> { } fn context_meta_to_meta(&self, meta: &'a ContextMeta) -> Option> { + let item = meta.item.as_deref()?; + let kind = match &meta.kind { meta::Kind::Type { .. } => Kind::Type, meta::Kind::Struct { .. } => Kind::Struct, @@ -212,9 +236,13 @@ impl<'a> Context<'a> { meta::Kind::Function { associated: None, signature: f, + is_test, + is_bench, .. } => Kind::Function(Function { is_async: f.is_async, + is_test: *is_test, + is_bench: *is_bench, signature: Signature::Function, arguments: f.arguments.as_deref(), return_type: &f.return_type, @@ -222,9 +250,13 @@ impl<'a> Context<'a> { meta::Kind::Function { associated: Some(..), signature: f, + is_test, + is_bench, .. } => Kind::Function(Function { is_async: f.is_async, + is_test: *is_test, + is_bench: *is_bench, signature: Signature::Instance, arguments: f.arguments.as_deref(), return_type: &f.return_type, @@ -242,7 +274,7 @@ impl<'a> Context<'a> { Some(Meta { kind, source: MetaSource::Context, - item: meta.item.as_deref(), + item, hash: meta.hash, deprecated: meta.deprecated.as_deref(), docs: meta.docs.lines(), @@ -251,55 +283,67 @@ impl<'a> Context<'a> { /// Iterate over known modules. pub(crate) fn iter_modules(&self) -> impl IntoIterator> + '_ { - self.visitors.iter().map(|v| v.base.try_clone()).chain( - self.context - .into_iter() - .flat_map(|c| c.iter_crates().map(ItemBuf::with_crate)), - ) - } -} + let visitors = self + .visitors + .iter() + .flat_map(|v| v.base.as_crate().map(ItemBuf::with_crate)); -fn visitor_to_associated(hash: Hash, visitor: &Visitor) -> Option>> { - let associated = visitor.associated.get(&hash)?; + let contexts = self + .context + .into_iter() + .flat_map(|c| c.iter_crates().map(ItemBuf::with_crate)); - Some(associated.iter().flat_map(move |hash| { - let data = visitor.data.get(hash)?; + visitors.chain(contexts) + } +} - let (associated, trait_hash, signature) = match &data.kind { - Some(meta::Kind::Function { - associated, - trait_hash, - signature, - .. - }) => (associated, trait_hash, signature), - Some(meta::Kind::Variant { .. }) => { - return Some(Assoc::Variant(AssocVariant { - name: data.item.last()?.as_str()?, - docs: &data.docs, - })); - } - _ => return None, - }; +fn visitor_to_associated(visitor: &Visitor, hash: Hash) -> impl Iterator> + '_ { + let associated = visitor.associated.get(&hash).into_iter(); + + associated.flat_map(move |a| { + a.iter().flat_map(move |hash| { + let data = visitor.data.get(hash)?; + + let (associated, trait_hash, signature) = match &data.kind { + Some(meta::Kind::Function { + associated, + trait_hash, + signature, + .. + }) => (associated, trait_hash, signature), + Some(meta::Kind::Variant { .. }) => { + return Some(Assoc::Variant(AssocVariant { + name: data.item.last()?.as_str()?, + docs: &data.docs, + })); + } + _ => return None, + }; - let kind = match associated { - Some(meta::AssociatedKind::Instance(name)) => { - AssocFnKind::Method(name.as_ref(), Signature::Instance) - } - None => AssocFnKind::Method(data.item.last()?.as_str()?, Signature::Function), - _ => return None, - }; + let kind = match associated { + Some(meta::AssociatedKind::Instance(name)) => { + AssocFnKind::Method(&data.item, name.as_ref(), Signature::Instance) + } + None => AssocFnKind::Method( + &data.item, + data.item.last()?.as_str()?, + Signature::Function, + ), + _ => return None, + }; - Some(Assoc::Fn(AssocFn { - kind, - trait_hash: *trait_hash, - is_async: signature.is_async, - arguments: signature.arguments.as_deref(), - return_type: &signature.return_type, - parameter_types: &[], - deprecated: data.deprecated.as_deref(), - docs: &data.docs, - })) - })) + Some(Assoc::Fn(AssocFn { + kind, + trait_hash: *trait_hash, + is_async: signature.is_async, + arguments: signature.arguments.as_deref(), + return_type: &signature.return_type, + parameter_types: &[], + deprecated: data.deprecated.as_deref(), + docs: &data.docs, + })) + }) + }) } fn context_to_associated(context: &crate::Context, hash: Hash) -> Option> { @@ -329,7 +373,7 @@ fn context_to_associated(context: &crate::Context, hash: Hash) -> Option { - AssocFnKind::Method(name, Signature::Instance) + AssocFnKind::Method(meta.item.as_ref()?, name, Signature::Instance) } }; @@ -350,8 +394,9 @@ fn context_to_associated(context: &crate::Context, hash: Hash) -> Option { - let name = meta.item.as_deref()?.last()?.as_str()?; - let kind = AssocFnKind::Method(name, Signature::Function); + let item = meta.item.as_deref()?; + let name = item.last()?.as_str()?; + let kind = AssocFnKind::Method(item, name, Signature::Function); Some(Assoc::Fn(AssocFn { kind, @@ -380,9 +425,13 @@ fn visitor_meta_to_meta<'a>(base: &'a Item, data: &'a VisitorData) -> Meta<'a> { Some(meta::Kind::Function { associated, signature: f, + is_test, + is_bench, .. }) => Kind::Function(Function { is_async: f.is_async, + is_test: *is_test, + is_bench: *is_bench, signature: match associated { Some(..) => Signature::Instance, None => Signature::Function, @@ -396,7 +445,7 @@ fn visitor_meta_to_meta<'a>(base: &'a Item, data: &'a VisitorData) -> Meta<'a> { Meta { source: MetaSource::Source(base), - item: Some(&data.item), + item: &data.item, hash: data.hash, deprecated: None, docs: data.docs.as_slice(), diff --git a/crates/rune/src/doc/static/function.html.hbs b/crates/rune/src/doc/static/function.html.hbs index 4d4d9ff6e..e457df97e 100644 --- a/crates/rune/src/doc/static/function.html.hbs +++ b/crates/rune/src/doc/static/function.html.hbs @@ -1,9 +1,15 @@ {{#> layout}} -

Function {{literal module}}::{{name}}

Overview
+
+

Function {{literal module}}::{{name}}

Overview
-{{#if is_async}}async {{/if}} fn {{name}}({{literal args}}){{#if this.return_type}} -> {{literal this.return_type}}{{/if}} + {{#if is_test}}
#[test]
{{/if}} + {{#if is_bench}}
#[bench]
{{/if}} + {{#if is_async}}async {{/if}} + fn + {{name}}({{literal args}}){{#if this.return_type}} -> {{literal this.return_type}}{{/if}}
+ {{#if deprecated}}
Deprecated:{{deprecated}}
{{/if}} {{#if doc}}{{literal doc}}{{/if}} {{/layout}} diff --git a/crates/rune/src/doc/static/runedoc.css.hbs b/crates/rune/src/doc/static/runedoc.css.hbs index 094ab75b8..f4bf86993 100644 --- a/crates/rune/src/doc/static/runedoc.css.hbs +++ b/crates/rune/src/doc/static/runedoc.css.hbs @@ -306,3 +306,7 @@ a { font-size: 1.2rem; color: var(--link-color); } + +.attribute { + color: var(--text-color); +} diff --git a/crates/rune/src/doc/static/type.html.hbs b/crates/rune/src/doc/static/type.html.hbs index ef897b689..5f6a414a3 100644 --- a/crates/rune/src/doc/static/type.html.hbs +++ b/crates/rune/src/doc/static/type.html.hbs @@ -34,28 +34,14 @@ {{/each}} {{/if}} -{{#if protocols}} -

Protocols

- -{{#each protocols}} -
-
- protocol {{this.name}} {{field}} - {{#if this.deprecated}}
Deprecated:{{this.deprecated}}
{{/if}} -
- {{#if this.repr}}{{literal this.repr}}{{/if}} - {{#if this.doc}}{{literal this.doc}}{{/if}} -
-{{/each}} -{{/if}} - {{#if traits}} -

Traits implemented

+

Trait Implementations

{{#each traits}}
impl {{literal this.module}}::{{this.name}} + for {{../name}}
@@ -82,4 +68,19 @@ {{/each}} {{/if}} +{{#if protocols}} +

Protocols

+ +{{#each protocols}} +
+
+ protocol {{this.name}} {{field}} + {{#if this.deprecated}}
Deprecated:{{this.deprecated}}
{{/if}} +
+ {{#if this.repr}}{{literal this.repr}}{{/if}} + {{#if this.doc}}{{literal this.doc}}{{/if}} +
+{{/each}} +{{/if}} + {{/layout}} diff --git a/crates/rune/src/doc/visitor.rs b/crates/rune/src/doc/visitor.rs index f5188c019..9b634d305 100644 --- a/crates/rune/src/doc/visitor.rs +++ b/crates/rune/src/doc/visitor.rs @@ -41,11 +41,7 @@ pub struct Visitor { impl Visitor { /// Construct a visitor with the given base component. - pub fn new(base: I) -> alloc::Result - where - I: IntoIterator, - I::Item: IntoComponent, - { + pub fn new(base: impl IntoIterator) -> alloc::Result { let mut this = Self { base: base.into_iter().try_collect::()?, names: Names::default(), @@ -54,13 +50,23 @@ impl Visitor { associated: HashMap::new(), }; - let hash = Hash::type_hash(&this.base); this.names.insert(&this.base)?; - this.data.try_insert( - hash, - VisitorData::new(this.base.try_clone()?, hash, Some(meta::Kind::Module)), - )?; - this.item_to_hash.try_insert(this.base.try_clone()?, hash)?; + + let mut it = this.base.iter(); + + while !it.as_item().is_empty() { + let hash = Hash::type_hash(it.as_item()); + + this.data.try_insert( + hash, + VisitorData::new(it.as_item().try_to_owned()?, hash, Some(meta::Kind::Module)), + )?; + + this.item_to_hash + .try_insert(it.as_item().try_to_owned()?, hash)?; + it.next_back(); + } + Ok(this) } @@ -74,6 +80,22 @@ impl Visitor { pub(crate) fn get_by_hash(&self, hash: Hash) -> Option<&VisitorData> { self.data.get(&hash) } + + fn get_or_insert(&mut self, item: &Item) -> Result<&mut VisitorData, MetaError> { + let item = self.base.join(item)?; + let hash = Hash::type_hash(&item); + + tracing::trace!(?item, ?hash, "getting"); + + let data = match self.data.entry(hash) { + hash_map::Entry::Occupied(e) => e.into_mut(), + hash_map::Entry::Vacant(e) => { + e.try_insert(VisitorData::new(item.try_to_owned()?, hash, None))? + } + }; + + Ok(data) + } } impl CompileVisitor for Visitor { @@ -84,22 +106,19 @@ impl CompileVisitor for Visitor { } let item = self.base.join(meta.item)?; - tracing::trace!(base = ?self.base, meta = ?meta.item, ?item, "register meta"); + let hash = Hash::type_hash(&item); + + tracing::trace!(base = ?self.base, meta = ?meta.item, ?item, ?hash, "register meta"); self.names.insert(&item)?; - self.item_to_hash - .try_insert(item.try_to_owned()?, meta.hash)?; + self.item_to_hash.try_insert(item.try_to_owned()?, hash)?; - match self.data.entry(meta.hash) { + match self.data.entry(hash) { hash_map::Entry::Occupied(e) => { e.into_mut().kind = Some(meta.kind.try_clone()?); } hash_map::Entry::Vacant(e) => { - e.try_insert(VisitorData::new( - item, - meta.hash, - Some(meta.kind.try_clone()?), - ))?; + e.try_insert(VisitorData::new(item, hash, Some(meta.kind.try_clone()?)))?; } } @@ -107,7 +126,7 @@ impl CompileVisitor for Visitor { self.associated .entry(container) .or_try_default()? - .try_push(meta.hash)?; + .try_push(hash)?; } Ok(()) @@ -117,28 +136,14 @@ impl CompileVisitor for Visitor { &mut self, _location: &dyn Located, item: &Item, - hash: Hash, + _: Hash, string: &str, ) -> Result<(), MetaError> { - // Documentation comments are literal source lines, so they're newline - // terminated. Since we perform our own internal newlines conversion - // these need to be trimmed - at least between each doc item. - fn newlines(c: char) -> bool { - matches!(c, '\n' | '\r') - } - - let item = self.base.join(item)?; - tracing::trace!(?item, "visiting comment"); - - let data = match self.data.entry(hash) { - hash_map::Entry::Occupied(e) => e.into_mut(), - hash_map::Entry::Vacant(e) => { - e.try_insert(VisitorData::new(item.try_to_owned()?, hash, None))? - } - }; + let data = self.get_or_insert(item)?; data.docs .try_push(string.trim_end_matches(newlines).try_to_owned()?)?; + Ok(()) } @@ -146,25 +151,24 @@ impl CompileVisitor for Visitor { &mut self, _location: &dyn Located, item: &Item, - hash: Hash, + _: Hash, field: &str, string: &str, ) -> Result<(), MetaError> { - let item = self.base.join(item)?; - tracing::trace!(?item, "visiting field comment"); - - let data = match self.data.entry(hash) { - hash_map::Entry::Occupied(e) => e.into_mut(), - hash_map::Entry::Vacant(e) => { - e.try_insert(VisitorData::new(item.try_to_owned()?, hash, None))? - } - }; + let data = self.get_or_insert(item)?; data.field_docs .entry(field.try_into()?) .or_try_default()? - .try_push(string.try_to_owned()?)?; + .try_push(string.trim_end_matches(newlines).try_to_owned()?)?; Ok(()) } } + +// Documentation comments are literal source lines, so they're newline +// terminated. Since we perform our own internal newlines conversion +// these need to be trimmed - at least between each doc item. +fn newlines(c: char) -> bool { + matches!(c, '\n' | '\r') +} diff --git a/crates/rune/src/indexing/index.rs b/crates/rune/src/indexing/index.rs index 39be36613..34f9a5278 100644 --- a/crates/rune/src/indexing/index.rs +++ b/crates/rune/src/indexing/index.rs @@ -505,13 +505,15 @@ pub(crate) fn file(idx: &mut Indexer<'_, '_>, ast: &mut ast::File) -> compile::R for doc in p.parse_all::(resolve_context!(idx.q), &ast.attributes)? { let (span, doc) = doc?; + let doc_string = doc.doc_string.resolve(resolve_context!(idx.q))?; + idx.q .visitor .visit_doc_comment( &DynLocation::new(idx.source_id, &span), idx.q.pool.module_item(idx.item.module), idx.q.pool.module_item_hash(idx.item.module), - &doc.doc_string.resolve(resolve_context!(idx.q))?, + &doc_string, ) .with_span(span)?; } diff --git a/crates/rune/tests/streams.rn b/crates/rune/tests/streams.rn index 86bb9a9c7..dc95785b9 100644 --- a/crates/rune/tests/streams.rn +++ b/crates/rune/tests/streams.rn @@ -1,9 +1,13 @@ +//! Test that async streams work. + async fn foo(n) { yield n; yield n + 1; yield n + 2; } +/// Select over two async streams and ensure that the expected numerical value +//matches. #[test] async fn select_streams() { let count = 0;