From d96abed46d5d8dbe08bfaf6076e8e9ffa7c7618e Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Tue, 4 Apr 2023 04:47:11 +0200 Subject: [PATCH 1/3] Render more documentation --- crates/rune-macros/src/any.rs | 8 +- crates/rune-macros/src/context.rs | 2 + crates/rune/src/any.rs | 3 +- crates/rune/src/compile/context.rs | 8 +- crates/rune/src/compile/function_meta.rs | 50 +- crates/rune/src/compile/module.rs | 14 +- crates/rune/src/doc/html.rs | 715 +++++++++++-------- crates/rune/src/doc/static/function.html.hbs | 2 +- crates/rune/src/doc/static/module.html.hbs | 12 +- crates/rune/src/doc/static/type.html.hbs | 4 +- crates/rune/src/hash.rs | 29 +- crates/rune/src/hash/into_hash.rs | 5 +- crates/rune/src/lib.rs | 5 +- crates/rune/src/modules/iter.rs | 58 +- crates/rune/src/modules/mem.rs | 2 +- crates/rune/src/modules/vec.rs | 2 +- crates/rune/src/params.rs | 72 ++ crates/rune/src/runtime/any_obj.rs | 16 +- crates/rune/src/runtime/mod.rs | 4 +- crates/rune/src/runtime/protocol.rs | 696 +++++++++--------- crates/rune/src/runtime/type_info.rs | 57 +- crates/rune/src/runtime/type_of.rs | 42 +- crates/rune/src/runtime/value.rs | 2 +- tests/tests/bug_344.rs | 14 +- 24 files changed, 1017 insertions(+), 805 deletions(-) create mode 100644 crates/rune/src/params.rs diff --git a/crates/rune-macros/src/any.rs b/crates/rune-macros/src/any.rs index 250598a35..e0b8095bc 100644 --- a/crates/rune-macros/src/any.rs +++ b/crates/rune-macros/src/any.rs @@ -387,6 +387,7 @@ where raw_str, shared, type_info, + any_type_info, type_of, unsafe_from_value, unsafe_to_value, @@ -436,12 +437,17 @@ where #impl_named impl #impl_generics #type_of for #ident #ty_generics #where_clause { + #[inline] fn type_hash() -> #hash { ::type_hash() } + #[inline] fn type_info() -> #type_info { - #type_info::Any(#raw_str::from_str(std::any::type_name::())) + #type_info::Any(#any_type_info::new( + #raw_str::from_str(std::any::type_name::()), + ::type_hash() + )) } } diff --git a/crates/rune-macros/src/context.rs b/crates/rune-macros/src/context.rs index a72ce7c42..035f288c9 100644 --- a/crates/rune-macros/src/context.rs +++ b/crates/rune-macros/src/context.rs @@ -644,6 +644,7 @@ impl Context { token_stream: quote!(#module::macros::TokenStream), tuple: quote!(#module::runtime::Tuple), type_info: quote!(#module::runtime::TypeInfo), + any_type_info: quote!(#module::runtime::AnyTypeInfo), type_of: quote!(#module::runtime::TypeOf), unit_struct: quote!(#module::runtime::UnitStruct), unsafe_from_value: quote!(#module::runtime::UnsafeFromValue), @@ -686,6 +687,7 @@ pub(crate) struct Tokens { pub(crate) token_stream: TokenStream, pub(crate) tuple: TokenStream, pub(crate) type_info: TokenStream, + pub(crate) any_type_info: TokenStream, pub(crate) type_of: TokenStream, pub(crate) unit_struct: TokenStream, pub(crate) unsafe_from_value: TokenStream, diff --git a/crates/rune/src/any.rs b/crates/rune/src/any.rs index 68a5e2c26..be5658228 100644 --- a/crates/rune/src/any.rs +++ b/crates/rune/src/any.rs @@ -1,5 +1,6 @@ use crate::compile::Named; -use crate::Hash; +use crate::hash::Hash; + pub use rune_macros::Any; /// A trait which can be stored inside of an [AnyObj](crate::runtime::AnyObj). diff --git a/crates/rune/src/compile/context.rs b/crates/rune/src/compile/context.rs index e86f0993d..f64c763c9 100644 --- a/crates/rune/src/compile/context.rs +++ b/crates/rune/src/compile/context.rs @@ -35,7 +35,7 @@ pub struct PrivMeta { impl PrivMeta { pub(crate) fn new( - module: &Module, + #[cfg_attr(not(feature = "doc"), allow(unused))] module: &Module, hash: Hash, item: ItemBuf, kind: meta::Kind, @@ -288,6 +288,7 @@ impl Context { } /// Look up signature of function. + #[cfg(feature = "doc")] pub(crate) fn lookup_signature(&self, hash: Hash) -> Option<&meta::Signature> { self.functions_info.get(&hash) } @@ -604,6 +605,7 @@ impl Context { }; let hash = assoc + .name .kind .hash(key.type_hash) .with_parameters(key.parameters); @@ -613,7 +615,7 @@ impl Context { is_async: assoc.is_async, args: assoc.args, kind: meta::SignatureKind::Instance { - name: assoc.kind.clone(), + name: assoc.name.kind.clone(), self_type_info: info.type_info.clone(), }, }; @@ -639,7 +641,7 @@ impl Context { // // The other alternatives are protocol functions (which are not free) // and plain hashes. - if let AssociatedFunctionKind::Instance(name) = &assoc.kind { + if let AssociatedFunctionKind::Instance(name) = &assoc.name.kind { let item = info.item.extended(name); self.names.insert(&item); diff --git a/crates/rune/src/compile/function_meta.rs b/crates/rune/src/compile/function_meta.rs index 9fa9928f3..da983ba6b 100644 --- a/crates/rune/src/compile/function_meta.rs +++ b/crates/rune/src/compile/function_meta.rs @@ -5,18 +5,20 @@ use crate::compile::module::{ AssocType, AssociatedFunctionKey, AsyncFunction, AsyncInstFn, Function, InstFn, }; use crate::compile::{IntoComponent, ItemBuf, Named}; -use crate::hash::{Hash, Params}; +use crate::hash::Hash; +#[cfg(feature = "doc")] +use crate::runtime::TypeInfo; use crate::runtime::{FunctionHandler, Protocol}; mod sealed { - use crate::hash::Params; + use crate::params::Params; use crate::runtime::Protocol; pub trait Sealed {} impl Sealed for &str {} impl Sealed for Protocol {} - impl Sealed for Params {} + impl Sealed for Params {} } /// Type used to collect and store function metadata through the @@ -135,6 +137,8 @@ impl ToInstance for &str { AssociatedFunctionName { kind: AssociatedFunctionKind::Instance(self.into()), parameters: Hash::EMPTY, + #[cfg(feature = "doc")] + parameter_type_infos: vec![], } } } @@ -145,40 +149,8 @@ impl ToFieldFunction for &str { AssociatedFunctionName { kind: AssociatedFunctionKind::FieldFn(protocol, self.into()), parameters: Hash::EMPTY, - } - } -} - -impl ToInstance for Params -where - T: ToInstance, - P: IntoIterator, - P::Item: std::hash::Hash, -{ - #[inline] - fn to_instance(self) -> AssociatedFunctionName { - let info = self.name.to_instance(); - - AssociatedFunctionName { - kind: info.kind, - parameters: Hash::parameters(self.parameters), - } - } -} - -impl ToFieldFunction for Params -where - T: ToFieldFunction, - P: IntoIterator, - P::Item: std::hash::Hash, -{ - #[inline] - fn to_field_function(self, protocol: Protocol) -> AssociatedFunctionName { - let info = self.name.to_field_function(protocol); - - AssociatedFunctionName { - kind: info.kind, - parameters: Hash::parameters(self.parameters), + #[cfg(feature = "doc")] + parameter_type_infos: vec![], } } } @@ -192,6 +164,8 @@ pub struct AssociatedFunctionName { pub kind: AssociatedFunctionKind, /// Parameters hash. pub parameters: Hash, + #[cfg(feature = "doc")] + pub parameter_type_infos: Vec, } impl AssociatedFunctionName { @@ -199,6 +173,8 @@ impl AssociatedFunctionName { Self { kind: AssociatedFunctionKind::IndexFn(protocol, index), parameters: Hash::EMPTY, + #[cfg(feature = "doc")] + parameter_type_infos: vec![], } } } diff --git a/crates/rune/src/compile/module.rs b/crates/rune/src/compile/module.rs index 45e401af0..2d8d9482c 100644 --- a/crates/rune/src/compile/module.rs +++ b/crates/rune/src/compile/module.rs @@ -202,16 +202,18 @@ pub(crate) enum TypeSpecification { #[derive(Clone)] #[non_exhaustive] pub struct AssociatedFunction { + /// Handle of the associated function. pub(crate) handler: Arc, + /// Type information of the associated function. pub(crate) type_info: TypeInfo, /// If the function is asynchronous. - pub is_async: bool, + pub(crate) is_async: bool, /// Arguments the function receives. - pub args: Option, - /// The kind of the associated function. - pub kind: AssociatedFunctionKind, + pub(crate) args: Option, + /// The full name of the associated function. + pub(crate) name: AssociatedFunctionName, /// The documentation of the associated function. - pub docs: Docs, + pub(crate) docs: Docs, } /// A key that identifies an associated function. @@ -1071,7 +1073,7 @@ impl Module { type_info: data.ty.type_info, is_async: data.is_async, args: data.args, - kind: data.name.kind, + name: data.name, docs, }; diff --git a/crates/rune/src/doc/html.rs b/crates/rune/src/doc/html.rs index 242b2d2c2..fb9b7e13b 100644 --- a/crates/rune/src/doc/html.rs +++ b/crates/rune/src/doc/html.rs @@ -11,7 +11,7 @@ use syntect::highlighting::ThemeSet; use syntect::html::{self, ClassStyle, ClassedHTMLGenerator}; use syntect::parsing::{SyntaxReference, SyntaxSet}; -use crate::collections::{BTreeSet, VecDeque}; +use crate::collections::{BTreeSet, HashMap, VecDeque}; use crate::compile::{AssociatedFunctionKind, ComponentRef, Item, ItemBuf}; use crate::doc::context::{Function, Kind, Signature}; use crate::doc::templating; @@ -40,7 +40,40 @@ struct Shared { css: Vec, } +enum ItemPath { + Type, + Struct, + Module, + Function, +} + +fn build_item_path(name: &str, item: &Item, kind: ItemPath, path: &mut RelativePathBuf) { + if item.is_empty() { + path.push(name); + } else { + for c in item.iter() { + let string = match c { + ComponentRef::Crate(string) => string, + ComponentRef::Str(string) => string, + _ => continue, + }; + + path.push(string); + } + } + + path.set_extension(match kind { + ItemPath::Type => "type.html", + ItemPath::Struct => "struct.html", + ItemPath::Module => "module.html", + ItemPath::Function => "fn.html", + }); +} + struct Ctxt<'a> { + root: &'a Path, + item: ItemBuf, + path: RelativePathBuf, name: &'a str, context: &'a Context<'a>, fonts: &'a [RelativePathBuf], @@ -53,12 +86,228 @@ struct Ctxt<'a> { } impl Ctxt<'_> { - fn shared(&self, dir: &RelativePath) -> Shared { + fn set_path(&mut self, item: ItemBuf, kind: ItemPath) { + self.path = RelativePathBuf::new(); + build_item_path(self.name, &item, kind, &mut self.path); + self.item = item; + } + + fn dir(&self) -> &RelativePath { + self.path.parent().unwrap_or(RelativePath::new("")) + } + + fn shared(&self) -> Shared { + let dir = self.dir(); + Shared { fonts: self.fonts.iter().map(|f| dir.relative(f)).collect(), css: self.css.iter().map(|f| dir.relative(f)).collect(), } } + + /// Write the current file. + fn write_file(&self, contents: C) -> Result<()> + where + C: FnOnce(&Self) -> Result, + { + let p = self.path.to_path(self.root); + tracing::info!("writing: {}", p.display()); + ensure_parent_dir(&p)?; + let data = contents(self)?; + fs::write(&p, data).with_context(|| p.display().to_string())?; + Ok(()) + } + + /// Render rust code. + fn render_code(&self, lines: I) -> String + where + I: IntoIterator, + I::Item: AsRef, + { + let syntax = match self.syntax_set.find_syntax_by_token(RUST_TOKEN) { + Some(syntax) => syntax, + None => self.syntax_set.find_syntax_plain_text(), + }; + + self.render_code_by_syntax(lines, syntax) + } + + /// Render documentation. + fn render_code_by_syntax(&self, lines: I, syntax: &SyntaxReference) -> String + where + I: IntoIterator, + I::Item: AsRef, + { + let mut buf = String::new(); + + let mut gen = ClassedHTMLGenerator::new_with_class_style( + syntax, + &self.syntax_set, + ClassStyle::Spaced, + ); + + for line in lines { + let line = line.as_ref(); + let line = line.strip_prefix(' ').unwrap_or(line); + + if line.starts_with('#') { + continue; + } + + buf.clear(); + buf.push_str(line); + buf.push('\n'); + let _ = gen.parse_html_for_line_which_includes_newline(&buf); + } + + gen.finalize() + } + + /// Render documentation. + fn render_docs(&self, docs: &[S]) -> Result> + where + S: AsRef, + { + use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag}; + use std::fmt::Write; + + struct Filter<'a> { + cx: &'a Ctxt<'a>, + parser: Parser<'a, 'a>, + codeblock: Option<&'a SyntaxReference>, + } + + impl<'a> Filter<'a> { + fn new(cx: &'a Ctxt<'a>, parser: Parser<'a, 'a>) -> Self { + Self { + cx, + parser, + codeblock: None, + } + } + } + + impl<'a> Iterator for Filter<'a> { + type Item = Event<'a>; + + #[inline] + fn next(&mut self) -> Option { + let e = self.parser.next()?; + + match (e, self.codeblock) { + (Event::Start(Tag::CodeBlock(kind)), _) => { + self.codeblock = None; + + if let CodeBlockKind::Fenced(fences) = &kind { + for token in fences.split(',') { + let token = match token.trim() { + RUNE_TOKEN => RUST_TOKEN, + token => token, + }; + + if let Some(syntax) = self.cx.syntax_set.find_syntax_by_token(token) + { + self.codeblock = Some(syntax); + return Some(Event::Start(Tag::CodeBlock(kind))); + } + } + } + + if self.codeblock.is_none() { + self.codeblock = self.cx.syntax_set.find_syntax_by_token(RUST_TOKEN); + } + + if self.codeblock.is_none() { + self.codeblock = Some(self.cx.syntax_set.find_syntax_plain_text()); + } + + Some(Event::Start(Tag::CodeBlock(kind))) + } + (Event::End(Tag::CodeBlock(kind)), Some(_)) => { + self.codeblock = None; + Some(Event::End(Tag::CodeBlock(kind))) + } + (Event::Text(text), syntax) => { + if let Some(syntax) = syntax { + let html = self.cx.render_code_by_syntax(text.lines(), syntax); + Some(Event::Html(CowStr::Boxed(html.into()))) + } else { + let mut buf = String::new(); + + for line in text.lines() { + let line = line.strip_prefix(' ').unwrap_or(line); + + if line.starts_with('#') { + continue; + } + + buf.push_str(line); + buf.push('\n'); + } + + Some(Event::Text(CowStr::Boxed(buf.into()))) + } + } + (event, _) => Some(event), + } + } + } + + if docs.is_empty() { + return Ok(None); + } + + let mut o = String::new(); + write!(o, "
")?; + let mut input = String::new(); + + for line in docs { + let line = line.as_ref(); + let line = line.strip_prefix(' ').unwrap_or(line); + input.push_str(line); + input.push('\n'); + } + + let mut options = Options::empty(); + options.insert(Options::ENABLE_STRIKETHROUGH); + let parser = Filter::new(self, Parser::new_ext(&input, options)); + let mut out = String::new(); + pulldown_cmark::html::push_html(&mut out, parser); + write!(o, "{out}")?; + write!(o, "
")?; + Ok(Some(o)) + } + + #[inline] + fn item_path(&self, item: &Item, kind: ItemPath) -> RelativePathBuf { + let mut path = RelativePathBuf::new(); + build_item_path(self.name, item, kind, &mut path); + path + } + + /// Build banklinks for the current item. + fn module_path_html(&self, last: bool) -> String { + let mut module = Vec::new(); + let mut iter = self.item.iter(); + let dir = self.dir(); + + while iter.next_back().is_some() { + if let Some(name) = iter.as_item().last() { + let url = dir.relative(self.item_path(iter.as_item(), ItemPath::Module)); + module.push(format!("{name}")); + } + } + + module.reverse(); + + if last { + if let Some(name) = self.item.last() { + module.push(format!("{name}")); + } + } + + module.join("::") + } } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -120,7 +369,25 @@ pub fn write_html( .with_context(|| syntax_css.to_owned())?; css.push(syntax_css.to_owned()); - let cx = Ctxt { + // Collect an ordered set of modules, so we have a baseline of what to render when. + let mut initial = BTreeSet::new(); + let mut children = HashMap::>::new(); + + for module in context.iter_modules() { + initial.insert(Build::Module(module)); + + if let Some(parent) = module.parent() { + children + .entry(parent.to_owned()) + .or_default() + .insert(module); + } + } + + let mut cx = Ctxt { + root, + item: ItemBuf::new(), + path: RelativePathBuf::new(), name, context: &context, fonts: &fonts, @@ -132,36 +399,34 @@ pub fn write_html( syntax_set, }; - // Collect an ordered set of modules, so we have a baseline of what to render when. - let mut initial = BTreeSet::new(); - - for module in cx.context.iter_modules() { - initial.insert(Build::Module(module)); - } - let mut queue = initial.into_iter().collect::>(); let mut modules = Vec::new(); while let Some(build) = queue.pop_front() { match build { - Build::Type(m, hash) => { - type_(&cx, "Type", ItemPath::Type, &m, hash, root)?; + Build::Type(item, hash) => { + cx.set_path(item, ItemPath::Type); + type_(&cx, "Type", hash)?; } - Build::Struct(m, hash) => { - type_(&cx, "Struct", ItemPath::Struct, &m, hash, root)?; + Build::Struct(item, hash) => { + cx.set_path(item, ItemPath::Struct); + type_(&cx, "Struct", hash)?; } - Build::Function(m) => { - function(&cx, &m, root)?; + Build::Function(item) => { + cx.set_path(item, ItemPath::Function); + function(&cx)?; } Build::Module(item) => { - let path = module(&cx, item, root, &mut queue)?; - modules.push((item, path)); + cx.set_path(item.to_owned(), ItemPath::Module); + module(&cx, &mut queue, &children)?; + modules.push((item, cx.path.clone())); } } } - index(&cx, root, &modules)?; + cx.path = RelativePath::new("index.html").to_owned(); + index(&cx, &modules)?; Ok(()) } @@ -173,14 +438,14 @@ fn copy_file<'a>( ) -> Result<&'a RelativePath, Error> { let path = RelativePath::new(name); let file_path = path.to_path(root); - ensure_parent_dir(&file_path)?; tracing::info!("writing: {}", file_path.display()); + ensure_parent_dir(&file_path)?; fs::write(&file_path, file.data.as_ref()).with_context(|| file_path.display().to_string())?; Ok(path) } #[tracing::instrument(skip_all)] -fn index(cx: &Ctxt<'_>, root: &Path, mods: &[(&Item, RelativePathBuf)]) -> Result<()> { +fn index(cx: &Ctxt<'_>, mods: &[(&Item, RelativePathBuf)]) -> Result<()> { #[derive(Serialize)] struct Params<'a> { #[serde(flatten)] @@ -195,42 +460,38 @@ fn index(cx: &Ctxt<'_>, root: &Path, mods: &[(&Item, RelativePathBuf)]) -> Resul path: &'a RelativePath, } - let p = root.join("index.html"); - let dir = RelativePath::new(""); - let mut modules = Vec::new(); for (item, path) in mods { modules.push(Module { item, path }); } - let data = cx.index_template.render(&Params { - shared: cx.shared(dir), - modules: &modules, - })?; - - tracing::info!("writing: {}", p.display()); - fs::write(&p, data).with_context(|| p.display().to_string())?; - Ok(()) + cx.write_file(|cx| { + cx.index_template.render(&Params { + shared: cx.shared(), + modules: &modules, + }) + }) } /// Build a single module. #[tracing::instrument(skip_all)] fn module( cx: &Ctxt<'_>, - m: &Item, - root: &Path, queue: &mut VecDeque, -) -> Result { + children: &HashMap>, +) -> Result<()> { #[derive(Serialize)] struct Params<'a> { #[serde(flatten)] shared: Shared, #[serde(serialize_with = "serialize_item")] item: &'a Item, + module: String, types: Vec>, structs: Vec>, functions: Vec>, + modules: Vec>, } #[derive(Serialize)] @@ -265,15 +526,23 @@ fn module( doc: Option, } - let path = item_path(cx, m, ItemPath::Module); - let dir = path.parent().unwrap_or(RelativePath::new("")); + #[derive(Serialize)] + struct Module<'a> { + #[serde(serialize_with = "serialize_item")] + item: &'a Item, + #[serde(serialize_with = "serialize_component_ref")] + name: ComponentRef<'a>, + path: RelativePathBuf, + } + let module = cx.module_path_html(true); let mut types = Vec::new(); let mut structs = Vec::new(); let mut functions = Vec::new(); + let mut modules = Vec::new(); - for name in cx.context.iter_components(m) { - let item = m.join([name]); + for name in cx.context.iter_components(&cx.item) { + let item = cx.item.join([name]); let meta = match cx.context.meta(&item) { Some(meta) => meta, @@ -283,7 +552,7 @@ fn module( match meta.kind { Kind::Unknown { .. } => { queue.push_front(Build::Type(item.clone(), meta.hash)); - let path = dir.relative(item_path(cx, &item, ItemPath::Type)); + let path = cx.dir().relative(cx.item_path(&item, ItemPath::Type)); types.push(Type { item, path, @@ -293,7 +562,7 @@ fn module( } Kind::Struct { .. } => { queue.push_front(Build::Struct(item.clone(), meta.hash)); - let path = dir.relative(item_path(cx, &item, ItemPath::Struct)); + let path = cx.dir().relative(cx.item_path(&item, ItemPath::Struct)); structs.push(Struct { item, path, @@ -307,7 +576,7 @@ fn module( functions.push(Function { is_async: f.is_async, - path: dir.relative(item_path(cx, &item, ItemPath::Function)), + path: cx.dir().relative(cx.item_path(&item, ItemPath::Function)), item, name, args: args_to_string(f.args, f.signature)?, @@ -321,37 +590,37 @@ fn module( } } - let p = path.to_path(root); - ensure_parent_dir(&p)?; + for item in children.get(&cx.item).into_iter().flatten() { + let path = cx.dir().relative(cx.item_path(item, ItemPath::Module)); + let name = item.last().context("missing name of module")?; - let data = cx.module_template.render(&Params { - shared: cx.shared(dir), - item: m, - types, - structs, - functions, - })?; + modules.push(Module { item, name, path }) + } - tracing::info!("writing: {}", p.display()); - fs::write(&p, data).with_context(|| p.display().to_string())?; - Ok(path) + cx.write_file(|cx| { + cx.module_template.render(&Params { + shared: cx.shared(), + item: &cx.item, + module, + types, + structs, + functions, + modules, + }) + }) } /// Build an unknown type. -// #[tracing::instrument(skip_all)] -fn type_( - cx: &Ctxt<'_>, - what: &str, - path: ItemPath, - m: &Item, - hash: Hash, - root: &Path, -) -> Result { +#[tracing::instrument(skip_all)] +fn type_(cx: &Ctxt<'_>, what: &str, hash: Hash) -> Result<()> { #[derive(Serialize)] struct Params<'a> { what: &'a str, #[serde(flatten)] shared: Shared, + module: String, + #[serde(serialize_with = "serialize_component_ref")] + name: ComponentRef<'a>, #[serde(serialize_with = "serialize_item")] item: &'a Item, methods: Vec>, @@ -370,17 +639,17 @@ fn type_( is_async: bool, name: &'a str, args: String, + parameters: Option, doc: Option, } - let path = item_path(cx, m, path); - let dir = path.parent().unwrap_or(RelativePath::new("")); - + let module = cx.module_path_html(false); + let name = cx.item.last().context("missing module name")?; let mut protocols = Vec::new(); let mut methods = Vec::new(); - for name in cx.context.iter_components(m) { - let item = m.join([name]); + for name in cx.context.iter_components(&cx.item) { + let item = cx.item.join([name]); let meta = match cx.context.meta(&item) { Some(meta) => meta, @@ -399,6 +668,7 @@ fn type_( is_async: f.is_async, name, args: args_to_string(f.args, f.signature)?, + parameters: None, doc: cx.render_docs(meta.docs)?, }); } @@ -410,62 +680,79 @@ fn type_( } for f in cx.context.associated(hash) { - match &f.kind { - AssociatedFunctionKind::Protocol(protocol) => { - let doc = if f.docs.lines().is_empty() { - cx.render_docs(protocol.doc)? - } else { - cx.render_docs(f.docs.lines())? - }; - - let repr = protocol.repr.map(|line| cx.render_code([line])); - - protocols.push(Protocol { - name: protocol.name, - repr, - doc, - }); + let value; + + let (protocol, value) = match &f.name.kind { + AssociatedFunctionKind::Protocol(protocol) => (protocol, "value"), + AssociatedFunctionKind::FieldFn(protocol, field) => { + value = format!("value.{field}"); + (protocol, value.as_str()) + } + AssociatedFunctionKind::IndexFn(protocol, index) => { + value = format!("value.{index}"); + (protocol, value.as_str()) } - AssociatedFunctionKind::FieldFn(..) => {} - AssociatedFunctionKind::IndexFn(..) => {} AssociatedFunctionKind::Instance(name) => { let doc = cx.render_docs(f.docs.lines())?; + let mut list = Vec::new(); + + for p in &f.name.parameter_type_infos { + list.push(p.to_string()); + } + + let parameters = (!list.is_empty()).then(|| list.join(", ")); + methods.push(Method { is_async: f.is_async, name, args: args_to_string(f.docs.args(), Signature::Instance { args: f.args })?, + parameters, doc, }); continue; } - } - } + }; + + let doc = if f.docs.lines().is_empty() { + cx.render_docs(protocol.doc)? + } else { + cx.render_docs(f.docs.lines())? + }; - let p = path.to_path(root); - ensure_parent_dir(&p)?; + let repr = protocol + .repr + .map(|line| cx.render_code([line.replace("$value", value.as_ref())])); - let data = cx.type_template.render(&Params { - what, - shared: cx.shared(dir), - item: m, - methods, - protocols, - })?; + protocols.push(Protocol { + name: protocol.name, + repr, + doc, + }); + } - tracing::info!("writing: {}", p.display()); - fs::write(&p, data).with_context(|| p.display().to_string())?; - Ok(path) + cx.write_file(|cx| { + cx.type_template.render(&Params { + what, + shared: cx.shared(), + module, + name, + item: &cx.item, + methods, + protocols, + }) + }) } /// Build a function. #[tracing::instrument(skip_all)] -fn function(cx: &Ctxt<'_>, item: &Item, root: &Path) -> Result { +fn function(cx: &Ctxt<'_>) -> Result<()> { #[derive(Serialize)] struct Params<'a> { #[serde(flatten)] shared: Shared, + module: String, #[serde(serialize_with = "serialize_item")] item: &'a Item, #[serde(serialize_with = "serialize_component_ref")] @@ -474,10 +761,7 @@ fn function(cx: &Ctxt<'_>, item: &Item, root: &Path) -> Result doc: Option, } - let path = item_path(cx, item, ItemPath::Function); - let dir = path.parent().unwrap_or(RelativePath::new("")); - - let meta = cx.context.meta(item).context("missing function")?; + let meta = cx.context.meta(&cx.item).context("missing function")?; let (args, signature) = match meta.kind { Kind::Function(Function { @@ -488,220 +772,19 @@ fn function(cx: &Ctxt<'_>, item: &Item, root: &Path) -> Result _ => bail!("found meta, but not a function"), }; - let name = item.last().context("missing function name")?; - + let name = cx.item.last().context("missing function name")?; let doc = cx.render_docs(meta.docs)?; - let p = path.to_path(root); - ensure_parent_dir(&p)?; - - let data = cx.function_template.render(&Params { - shared: cx.shared(dir), - item, - name, - args: args_to_string(args, signature)?, - doc, - })?; - - tracing::info!("writing: {}", p.display()); - fs::write(&p, data).with_context(|| p.display().to_string())?; - Ok(path) -} - -enum ItemPath { - Type, - Struct, - Module, - Function, -} - -fn item_path(cx: &Ctxt<'_>, item: &Item, kind: ItemPath) -> RelativePathBuf { - let mut path = RelativePathBuf::new(); - - if item.is_empty() { - path.push(cx.name); - } else { - for c in item.iter() { - let string = match c { - ComponentRef::Crate(string) => string, - ComponentRef::Str(string) => string, - _ => continue, - }; - - path.push(string); - } - } - - path.set_extension(match kind { - ItemPath::Type => "type.html", - ItemPath::Struct => "struct.html", - ItemPath::Module => "module.html", - ItemPath::Function => "fn.html", - }); - - path -} - -impl Ctxt<'_> { - /// Render rust code. - fn render_code(&self, lines: I) -> String - where - I: IntoIterator, - I::Item: AsRef, - { - let syntax = match self.syntax_set.find_syntax_by_token(RUST_TOKEN) { - Some(syntax) => syntax, - None => self.syntax_set.find_syntax_plain_text(), - }; - - self.render_code_by_syntax(lines, syntax) - } - - /// Render documentation. - fn render_code_by_syntax(&self, lines: I, syntax: &SyntaxReference) -> String - where - I: IntoIterator, - I::Item: AsRef, - { - let mut buf = String::new(); - - let mut gen = ClassedHTMLGenerator::new_with_class_style( - syntax, - &self.syntax_set, - ClassStyle::Spaced, - ); - - for line in lines { - let line = line.as_ref(); - let line = line.strip_prefix(' ').unwrap_or(line); - - if line.starts_with('#') { - continue; - } - - buf.clear(); - buf.push_str(line); - buf.push('\n'); - let _ = gen.parse_html_for_line_which_includes_newline(&buf); - } - - gen.finalize() - } - - /// Render documentation. - fn render_docs(&self, docs: &[S]) -> Result> - where - S: AsRef, - { - use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag}; - use std::fmt::Write; - - struct Filter<'a> { - cx: &'a Ctxt<'a>, - parser: Parser<'a, 'a>, - codeblock: Option<&'a SyntaxReference>, - } - - impl<'a> Filter<'a> { - fn new(cx: &'a Ctxt<'a>, parser: Parser<'a, 'a>) -> Self { - Self { - cx, - parser, - codeblock: None, - } - } - } - - impl<'a> Iterator for Filter<'a> { - type Item = Event<'a>; - - #[inline] - fn next(&mut self) -> Option { - let e = self.parser.next()?; - - match (e, self.codeblock) { - (Event::Start(Tag::CodeBlock(kind)), _) => { - self.codeblock = None; - - if let CodeBlockKind::Fenced(fences) = &kind { - for token in fences.split(',') { - let token = match token.trim() { - RUNE_TOKEN => RUST_TOKEN, - token => token, - }; - - if let Some(syntax) = self.cx.syntax_set.find_syntax_by_token(token) - { - self.codeblock = Some(syntax); - return Some(Event::Start(Tag::CodeBlock(kind))); - } - } - } - - if self.codeblock.is_none() { - self.codeblock = self.cx.syntax_set.find_syntax_by_token(RUST_TOKEN); - } - - if self.codeblock.is_none() { - self.codeblock = Some(self.cx.syntax_set.find_syntax_plain_text()); - } - - Some(Event::Start(Tag::CodeBlock(kind))) - } - (Event::End(Tag::CodeBlock(kind)), Some(_)) => { - self.codeblock = None; - Some(Event::End(Tag::CodeBlock(kind))) - } - (Event::Text(text), syntax) => { - if let Some(syntax) = syntax { - let html = self.cx.render_code_by_syntax(text.lines(), syntax); - Some(Event::Html(CowStr::Boxed(html.into()))) - } else { - let mut buf = String::new(); - - for line in text.lines() { - let line = line.strip_prefix(' ').unwrap_or(line); - - if line.starts_with('#') { - continue; - } - - buf.push_str(line); - buf.push('\n'); - } - - Some(Event::Text(CowStr::Boxed(buf.into()))) - } - } - (event, _) => Some(event), - } - } - } - - if docs.is_empty() { - return Ok(None); - } - - let mut o = String::new(); - write!(o, "
")?; - let mut input = String::new(); - - for line in docs { - let line = line.as_ref(); - let line = line.strip_prefix(' ').unwrap_or(line); - input.push_str(line); - input.push('\n'); - } - - let mut options = Options::empty(); - options.insert(Options::ENABLE_STRIKETHROUGH); - let parser = Filter::new(self, Parser::new_ext(&input, options)); - let mut out = String::new(); - pulldown_cmark::html::push_html(&mut out, parser); - write!(o, "{out}")?; - write!(o, "
")?; - Ok(Some(o)) - } + cx.write_file(|cx| { + cx.function_template.render(&Params { + shared: cx.shared(), + module: cx.module_path_html(false), + item: &cx.item, + name, + args: args_to_string(args, signature)?, + doc, + }) + }) } /// Helper to serialize an item. diff --git a/crates/rune/src/doc/static/function.html.hbs b/crates/rune/src/doc/static/function.html.hbs index 10726fac8..d8fb5e848 100644 --- a/crates/rune/src/doc/static/function.html.hbs +++ b/crates/rune/src/doc/static/function.html.hbs @@ -3,7 +3,7 @@ {{#each fonts}}{{/each}} {{#each css}}{{/each}} -

Function {{name}}({{args}})

+

Function {{literal module}}::{{name}}({{args}})

{{#if this.doc}}{{literal this.doc}}{{/if}} diff --git a/crates/rune/src/doc/static/module.html.hbs b/crates/rune/src/doc/static/module.html.hbs index 03dbf64ec..fa2c61af4 100644 --- a/crates/rune/src/doc/static/module.html.hbs +++ b/crates/rune/src/doc/static/module.html.hbs @@ -3,7 +3,7 @@ {{#each fonts}}{{/each}} {{#each css}}{{/each}} -

Module {{item}}

+

Module {{literal module}}

{{#if types}}

Types

@@ -34,5 +34,15 @@ {{/each}} {{/if}} + +{{#if modules}} +

Modules

+ +{{#each modules}} +
+ {{this.name}} +
+{{/each}} +{{/if}} diff --git a/crates/rune/src/doc/static/type.html.hbs b/crates/rune/src/doc/static/type.html.hbs index 9cdb50c0f..6dab49f0e 100644 --- a/crates/rune/src/doc/static/type.html.hbs +++ b/crates/rune/src/doc/static/type.html.hbs @@ -3,7 +3,7 @@ {{#each fonts}}{{/each}} {{#each css}}{{/each}} -

{{what}} {{item}}

+

{{what}} {{literal module}}::{{name}}

{{#if methods}}

Methods

@@ -11,7 +11,7 @@ {{#each methods}}
- {{#if this.is_async}}async {{/if}}fn {{this.name}}({{this.args}}) + {{#if this.is_async}}async {{/if}}fn {{this.name}}{{#if this.parameters}}<{{literal this.parameters}}>{{/if}}({{this.args}})
{{#if this.doc}}{{literal this.doc}}{{/if}}
diff --git a/crates/rune/src/hash.rs b/crates/rune/src/hash.rs index cc5cd0dd6..09c06d7e6 100644 --- a/crates/rune/src/hash.rs +++ b/crates/rune/src/hash.rs @@ -174,33 +174,6 @@ impl fmt::Debug for Hash { } } -/// Helper to register a parameterized function. -/// -/// This is used to wrap the name of the function in order to associated -/// parameters with it. -#[derive(Clone, Copy)] -pub struct Params { - pub(crate) name: T, - pub(crate) parameters: P, -} - -impl Params { - /// Construct a new parameters wrapper. - pub const fn new(name: T, parameters: P) -> Self { - Self { name, parameters } - } -} - -impl IntoHash for Params -where - T: IntoHash, -{ - #[inline] - fn into_hash(self) -> Hash { - self.name.into_hash() - } -} - /// Helper to build a parameters hash. pub(crate) struct ParametersBuilder { hasher: XxHash64, @@ -222,6 +195,6 @@ impl ParametersBuilder { } pub(crate) fn finish(&self) -> Hash { - Hash(self.hasher.finish()) + Hash::new(self.hasher.finish()) } } diff --git a/crates/rune/src/hash/into_hash.rs b/crates/rune/src/hash/into_hash.rs index 632d097b1..d109dd4ad 100644 --- a/crates/rune/src/hash/into_hash.rs +++ b/crates/rune/src/hash/into_hash.rs @@ -1,7 +1,8 @@ use crate::hash::Hash; mod sealed { - use crate::hash::{Hash, Params}; + use crate::hash::Hash; + use crate::params::Params; use crate::runtime::Protocol; pub trait Sealed {} @@ -9,7 +10,7 @@ mod sealed { impl Sealed for &str {} impl Sealed for Hash {} impl Sealed for Protocol {} - impl Sealed for Params {} + impl Sealed for Params {} } /// Trait for types which can be converted into a [Hash]. diff --git a/crates/rune/src/lib.rs b/crates/rune/src/lib.rs index 57408bb62..759dd9ddd 100644 --- a/crates/rune/src/lib.rs +++ b/crates/rune/src/lib.rs @@ -204,7 +204,10 @@ pub mod diagnostics; pub use self::diagnostics::Diagnostics; mod hash; -pub use self::hash::{Hash, Params, ToTypeHash}; +pub use self::hash::{Hash, ToTypeHash}; + +mod params; +pub use self::params::Params; mod hir; diff --git a/crates/rune/src/modules/iter.rs b/crates/rune/src/modules/iter.rs index c4f7682ba..f0432789c 100644 --- a/crates/rune/src/modules/iter.rs +++ b/crates/rune/src/modules/iter.rs @@ -1,5 +1,6 @@ //! The `std::iter` module. +use crate as rune; use crate::runtime::{FromValue, Iterator, Object, Protocol, Tuple, TypeOf, Value, Vec, VmError}; use crate::{ContextError, Module, Params}; @@ -10,12 +11,9 @@ pub fn module() -> Result { // Sorted for ease of finding module.inst_fn("chain", Iterator::chain)?; - module.inst_fn( - Params::new("collect", [Object::type_hash()]), - collect_object, - )?; - module.inst_fn(Params::new("collect", [Vec::type_hash()]), collect_vec)?; - module.inst_fn(Params::new("collect", [Tuple::type_hash()]), collect_tuple)?; + module.inst_fn(Params::new("collect", [Object::type_of()]), collect_object)?; + module.inst_fn(Params::new("collect", [Vec::type_of()]), collect_vec)?; + module.inst_fn(Params::new("collect", [Tuple::type_of()]), collect_tuple)?; module.inst_fn("enumerate", Iterator::enumerate)?; module.inst_fn("filter", Iterator::filter)?; module.inst_fn("find", Iterator::find)?; @@ -37,21 +35,55 @@ pub fn module() -> Result { module.inst_fn(Protocol::NEXT, Iterator::next)?; module.inst_fn(Protocol::INTO_ITER, >::from)?; - module.function(["range"], new_range)?; - module.function(["empty"], new_empty)?; - module.function(["once"], new_once)?; + module.function_meta(range)?; + module.function_meta(empty)?; + module.function_meta(once)?; Ok(module) } -fn new_empty() -> Iterator { +/// Construct an iterator which produces no values. +/// +/// # Examples +/// +/// ``` +/// use std::iter::empty; +/// +/// assert!(empty().next().is_none()); +/// assert_eq!(empty().collect::(), []); +/// ``` +#[rune::function] +fn empty() -> Iterator { Iterator::empty() } -fn new_once(v: Value) -> Iterator { - Iterator::once(v) +/// Construct an iterator which produces a single `value` once. +/// +/// # Examples +/// +/// ``` +/// use std::iter::once; +/// +/// assert!(once(42).next().is_some()); +/// assert_eq!(once(42).collect::(), [42]); +/// ``` +#[rune::function] +fn once(value: Value) -> Iterator { + Iterator::once(value) } -fn new_range(start: i64, end: i64) -> Iterator { +/// Produce an iterator which starts at the range `start` and ends at the value +/// `end` (exclusive). +/// +/// # Examples +/// +/// ``` +/// use std::iter::range; +/// +/// assert!(range(0, 3).next().is_some()); +/// assert_eq!(range(0, 3).collect::(), [0, 1, 2]); +/// ``` +#[rune::function] +fn range(start: i64, end: i64) -> Iterator { Iterator::from_double_ended("std::iter::Range", start..end) } diff --git a/crates/rune/src/modules/mem.rs b/crates/rune/src/modules/mem.rs index 8cc3fe526..0766c7d4e 100644 --- a/crates/rune/src/modules/mem.rs +++ b/crates/rune/src/modules/mem.rs @@ -11,7 +11,6 @@ pub fn module() -> Result { Ok(module) } -#[rune::function] /// Explicitly drop the given value, freeing up any memory associated with it. /// /// Normally values are dropped as they go out of scope, but with this method it @@ -25,6 +24,7 @@ pub fn module() -> Result { /// let v = [1, 2, 3]; /// drop(v); /// ``` +#[rune::function] fn drop(value: Value) -> Result<(), VmError> { value.take()?; Ok(()) diff --git a/crates/rune/src/modules/vec.rs b/crates/rune/src/modules/vec.rs index 04d8bbb20..b326abdbc 100644 --- a/crates/rune/src/modules/vec.rs +++ b/crates/rune/src/modules/vec.rs @@ -25,7 +25,7 @@ pub fn module() -> Result { module.inst_fn(Protocol::INDEX_SET, Vec::set)?; // TODO: parameterize with generics. - module.inst_fn(Params::new("sort", [i64::type_hash()]), sort_int)?; + module.inst_fn(Params::new("sort", [i64::type_of()]), sort_int)?; Ok(module) } diff --git a/crates/rune/src/params.rs b/crates/rune/src/params.rs new file mode 100644 index 000000000..9da3d657e --- /dev/null +++ b/crates/rune/src/params.rs @@ -0,0 +1,72 @@ +use crate::compile::{AssociatedFunctionName, ToFieldFunction, ToInstance}; +use crate::hash::{Hash, IntoHash}; +use crate::runtime::{FullTypeOf, Protocol}; + +/// Helper to register a parameterized function. +/// +/// This is used to wrap the name of the function in order to associated +/// parameters with it. +#[derive(Clone)] +pub struct Params { + pub(crate) name: T, + pub(crate) parameters: [FullTypeOf; N], +} + +impl Params { + /// Construct a new parameters wrapper. + pub const fn new(name: T, parameters: [FullTypeOf; N]) -> Self { + Self { name, parameters } + } +} + +impl IntoHash for Params +where + T: IntoHash, +{ + #[inline] + fn into_hash(self) -> Hash { + self.name.into_hash() + } +} + +impl ToInstance for Params +where + T: ToInstance, +{ + #[inline] + fn to_instance(self) -> AssociatedFunctionName { + let info = self.name.to_instance(); + + AssociatedFunctionName { + kind: info.kind, + parameters: Hash::parameters(self.parameters.iter().map(|t| t.hash)), + #[cfg(feature = "doc")] + parameter_type_infos: self + .parameters + .iter() + .map(|t| t.type_info.clone()) + .collect(), + } + } +} + +impl ToFieldFunction for Params +where + T: ToFieldFunction, +{ + #[inline] + fn to_field_function(self, protocol: Protocol) -> AssociatedFunctionName { + let info = self.name.to_field_function(protocol); + + AssociatedFunctionName { + kind: info.kind, + parameters: Hash::parameters(self.parameters.iter().map(|p| p.hash)), + #[cfg(feature = "doc")] + parameter_type_infos: self + .parameters + .iter() + .map(|p| p.type_info.clone()) + .collect(), + } + } +} diff --git a/crates/rune/src/runtime/any_obj.rs b/crates/rune/src/runtime/any_obj.rs index b0d5eeec9..ab60d9fea 100644 --- a/crates/rune/src/runtime/any_obj.rs +++ b/crates/rune/src/runtime/any_obj.rs @@ -1,13 +1,17 @@ //! Helper types for a holder of data. -use crate::runtime::RawStr; -use crate::{Any, Hash}; use std::any; use std::fmt; use std::mem::ManuallyDrop; use std::ops::{Deref, DerefMut}; + use thiserror::Error; +use crate::any::Any; +use crate::hash::Hash; +use crate::runtime::AnyTypeInfo; +use crate::runtime::{RawStr, TypeInfo}; + /// Errors raised during casting operations. #[derive(Debug, Error)] #[allow(missing_docs)] @@ -448,6 +452,14 @@ impl AnyObj { pub fn type_hash(&self) -> Hash { (self.vtable.type_hash)() } + + /// Access full type info for type. + pub fn type_info(&self) -> TypeInfo { + TypeInfo::Any(AnyTypeInfo::new_from( + (self.vtable.type_name)(), + self.vtable.type_hash, + )) + } } impl Drop for AnyObj { diff --git a/crates/rune/src/runtime/mod.rs b/crates/rune/src/runtime/mod.rs index 5969abb43..4e9e526e3 100644 --- a/crates/rune/src/runtime/mod.rs +++ b/crates/rune/src/runtime/mod.rs @@ -95,8 +95,8 @@ pub use self::static_type::{ pub use self::stream::Stream; pub use self::to_value::{ToValue, UnsafeToValue}; pub use self::tuple::Tuple; -pub use self::type_info::TypeInfo; -pub use self::type_of::TypeOf; +pub use self::type_info::{AnyTypeInfo, TypeInfo}; +pub use self::type_of::{FullTypeOf, TypeOf}; pub use self::unit::{Unit, UnitFn}; pub use self::value::{Rtti, Struct, TupleStruct, UnitStruct, Value, VariantRtti}; pub use self::variant::{Variant, VariantData}; diff --git a/crates/rune/src/runtime/protocol.rs b/crates/rune/src/runtime/protocol.rs index 011b02bdb..1f08d5924 100644 --- a/crates/rune/src/runtime/protocol.rs +++ b/crates/rune/src/runtime/protocol.rs @@ -39,6 +39,8 @@ impl ToInstance for Protocol { AssociatedFunctionName { kind: AssociatedFunctionKind::Protocol(self), parameters: Hash::EMPTY, + #[cfg(feature = "doc")] + parameter_type_infos: vec![], } } } @@ -93,370 +95,334 @@ impl hash::Hash for Protocol { } } +macro_rules! define { + ( + $( + $(#[$($meta:meta)*])* + $vis:vis const $ident:ident: Protocol = Protocol { + name: $name:expr, + hash: $hash:expr, + repr: $repr:expr, + doc: $doc:expr $(,)? + }; + )* + ) => { + $( + $(#[$($meta)*])* + $vis const $ident: Protocol = Protocol { + name: $name, + hash: Hash::new($hash), + #[cfg(feature = "doc")] + repr: $repr, + #[cfg(feature = "doc")] + doc: &$doc, + }; + )* + } +} + impl Protocol { - /// The function to access a field. - pub const GET: Protocol = Protocol { - name: "get", - hash: Hash::new(0x504007af1a8485a4), - #[cfg(feature = "doc")] - repr: Some("let value = a.field"), - #[cfg(feature = "doc")] - doc: &["Allows a get operation to work."], - }; - - /// The function to set a field. - pub const SET: Protocol = Protocol { - name: "set", - hash: Hash::new(0x7d13d47fd8efef5a), - #[cfg(feature = "doc")] - repr: Some("a.field = b"), - #[cfg(feature = "doc")] - doc: &["Allows a set operation to work."], - }; - - /// The function to access an index. - pub const INDEX_GET: Protocol = Protocol { - name: "index_get", - hash: Hash::new(0xadb5b27e2a4d2dec), - #[cfg(feature = "doc")] - repr: Some("let value = a[index]"), - #[cfg(feature = "doc")] - doc: &["Allows an indexing get operation to work."], - }; - - /// The function to set an index. - pub const INDEX_SET: Protocol = Protocol { - name: "index_set", - hash: Hash::new(0x162943f7bd03ad36), - #[cfg(feature = "doc")] - repr: Some("a[index] = b"), - #[cfg(feature = "doc")] - doc: &["Allows an indexing set operation to work."], - }; - - /// Check two types for equality. - pub const EQ: Protocol = Protocol { - name: "eq", - hash: Hash::new(0x418f5becbf885806), - #[cfg(feature = "doc")] - repr: Some("if a == b { }"), - #[cfg(feature = "doc")] - doc: &["Allows an equality operation to work."], - }; - - /// The function to implement for the addition operation. - pub const ADD: Protocol = Protocol { - name: "add", - hash: Hash::new(0xe4ecf51fa0bf1076), - #[cfg(feature = "doc")] - repr: Some("let value = a + b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `+` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the addition assign operation. - pub const ADD_ASSIGN: Protocol = Protocol { - name: "add_assign", - hash: Hash::new(0x42451ccb0a2071a9), - #[cfg(feature = "doc")] - repr: Some("a += b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `+=` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the subtraction operation. - pub const SUB: Protocol = Protocol { - name: "sub", - hash: Hash::new(0x6fa86a5f18d0bf71), - #[cfg(feature = "doc")] - repr: Some("let value = a - b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `-` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the subtraction assign operation. - pub const SUB_ASSIGN: Protocol = Protocol { - name: "sub_assign", - hash: Hash::new(0x5939bb56a1415284), - #[cfg(feature = "doc")] - repr: Some("a -= b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `-=` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the multiply operation. - pub const MUL: Protocol = Protocol { - name: "mul", - hash: Hash::new(0xb09e99dc94091d1c), - #[cfg(feature = "doc")] - repr: Some("let value = a * b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `*` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the multiply assign operation. - pub const MUL_ASSIGN: Protocol = Protocol { - name: "mul_assign", - hash: Hash::new(0x29a54b727f980ebf), - #[cfg(feature = "doc")] - repr: Some("a *= b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `*=` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the division operation. - pub const DIV: Protocol = Protocol { - name: "div", - hash: Hash::new(0xf26d6eea1afca6e8), - #[cfg(feature = "doc")] - repr: Some("let value = a / b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `/` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the division assign operation. - pub const DIV_ASSIGN: Protocol = Protocol { - name: "div_assign", - hash: Hash::new(0x4dd087a8281c04e6), - #[cfg(feature = "doc")] - repr: Some("a /= b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `/=` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the remainder operation. - pub const REM: Protocol = Protocol { - name: "rem", - hash: Hash::new(0x5c6293639c74e671), - #[cfg(feature = "doc")] - repr: Some("let value = a % b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `%` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the remainder assign operation. - pub const REM_ASSIGN: Protocol = Protocol { - name: "rem_assign", - hash: Hash::new(0x3a8695980e77baf4), - #[cfg(feature = "doc")] - repr: Some("a %= b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `%=` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the bitwise and operation. - pub const BIT_AND: Protocol = Protocol { - name: "bit_and", - hash: Hash::new(0x0e11f20d940eebe8), - #[cfg(feature = "doc")] - repr: Some("let value = a & b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `&` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the bitwise and assign operation. - pub const BIT_AND_ASSIGN: Protocol = Protocol { - name: "bit_and_assign", - hash: Hash::new(0x95cb1ba235dfb5ec), - #[cfg(feature = "doc")] - repr: Some("a &= b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `&=` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the bitwise xor operation. - pub const BIT_XOR: Protocol = Protocol { - name: "bit_xor", - hash: Hash::new(0xa3099c54e1de4cbf), - #[cfg(feature = "doc")] - repr: Some("let value = a ^ b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `^` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the bitwise xor assign operation. - pub const BIT_XOR_ASSIGN: Protocol = Protocol { - name: "bit_xor_assign", - hash: Hash::new(0x01fa9706738f9867), - #[cfg(feature = "doc")] - repr: Some("a ^= b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `^=` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the bitwise or operation. - pub const BIT_OR: Protocol = Protocol { - name: "bit_or", - hash: Hash::new(0x05010afceb4a03d0), - #[cfg(feature = "doc")] - repr: Some("let value = a | b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `|` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the bitwise xor assign operation. - pub const BIT_OR_ASSIGN: Protocol = Protocol { - name: "bit_or_assign", - hash: Hash::new(0x606d79ff1750a7ec), - #[cfg(feature = "doc")] - repr: Some("a |= b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `|=` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the bitwise shift left operation. - pub const SHL: Protocol = Protocol { - name: "shl", - hash: Hash::new(0x6845f7d0cc9e002d), - #[cfg(feature = "doc")] - repr: Some("let value = a << b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `<<` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the bitwise shift left assign operation. - pub const SHL_ASSIGN: Protocol = Protocol { - name: "shl_assign", - hash: Hash::new(0xdc4702d0307ba27b), - #[cfg(feature = "doc")] - repr: Some("a <<= b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `<<=` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the bitwise shift right operation. - pub const SHR: Protocol = Protocol { - name: "shr", - hash: Hash::new(0x6b485e8e6e58fbc8), - #[cfg(feature = "doc")] - repr: Some("let value = a >> b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `>>` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// The function to implement for the bitwise shift right assign operation. - pub const SHR_ASSIGN: Protocol = Protocol { - name: "shr_assign", - hash: Hash::new(0x61ff7c46ff00e74a), - #[cfg(feature = "doc")] - repr: Some("a >>= b"), - #[cfg(feature = "doc")] - doc: &[ - "Allows the `>>=` operator to apply to values of this type, where the current type is the left-hand side." - ] - }; - - /// Protocol function used by template strings. - pub const STRING_DISPLAY: Protocol = Protocol { - name: "string_display", - hash: Hash::new(0x811b62957ea9d9f9), - #[cfg(feature = "doc")] - repr: Some("println(\"{}\", value)"), - #[cfg(feature = "doc")] - doc: &["Allows the value to be display printed."], - }; - - /// Protocol function used by custom debug impls. - pub const STRING_DEBUG: Protocol = Protocol { - name: "string_debug", - hash: Hash::new(0x4064e3867aaa0717), - #[cfg(feature = "doc")] - repr: Some("println(\"{:?}\", value)"), - #[cfg(feature = "doc")] - doc: &["Allows the value to be debug printed."], - }; - - /// Function used to convert an argument into an iterator. - pub const INTO_ITER: Protocol = Protocol { - name: "into_iter", - hash: Hash::new(0x15a85c8d774b4065), - #[cfg(feature = "doc")] - repr: Some("for item in value { }"), - #[cfg(feature = "doc")] - doc: &["Allows the value to be converted into an iterator in a for-loop."], - }; - - /// The function to call to continue iteration. - pub const NEXT: Protocol = Protocol { - name: "next", - hash: Hash::new(0xc3cde069de2ba320), - #[cfg(feature = "doc")] - repr: None, - #[cfg(feature = "doc")] - doc: &["Allows iteration to be advanced for the type, this is used for iterators."], - }; - - /// Function used to convert an argument into a future. - /// - /// Signature: `fn(Value) -> Future`. - pub const INTO_FUTURE: Protocol = Protocol { - name: "into_future", - hash: Hash::new(0x596e6428deabfda2), - #[cfg(feature = "doc")] - repr: Some("value.await"), - #[cfg(feature = "doc")] - doc: &["This protocol allows the type to be converted into a future by awaiting them."], - }; - - /// Coerce a value into a type name. This is stored as a constant. - pub const INTO_TYPE_NAME: Protocol = Protocol { - name: "into_type_name", - hash: Hash::new(0xbffd08b816c24682), - #[cfg(feature = "doc")] - repr: None, - #[cfg(feature = "doc")] - doc: &[ - "This protocol allows the type to be converted into a string which represents the type name.", - ] - }; - - /// Function used to test if a value is a specific variant. - /// - /// Signature: `fn(self, usize) -> bool`. - pub const IS_VARIANT: Protocol = Protocol { - name: "is_variant", - hash: Hash::new(0xc030d82bbd4dabe8), - #[cfg(feature = "doc")] - repr: None, - #[cfg(feature = "doc")] - doc: &["Test if the provided argument is a variant."], - }; + define! { + /// The function to access a field. + pub const GET: Protocol = Protocol { + name: "get", + hash: 0x504007af1a8485a4, + repr: Some("let value = $value.field"), + doc: ["Allows a get operation to work."], + }; + + /// The function to set a field. + pub const SET: Protocol = Protocol { + name: "set", + hash: 0x7d13d47fd8efef5a, + repr: Some("$value.field = b"), + doc: ["Allows a set operation to work."], + }; + + /// The function to access an index. + pub const INDEX_GET: Protocol = Protocol { + name: "index_get", + hash: 0xadb5b27e2a4d2dec, + repr: Some("let value = $value[index]"), + doc: ["Allows an indexing get operation to work."], + }; + + /// The function to set an index. + pub const INDEX_SET: Protocol = Protocol { + name: "index_set", + hash: 0x162943f7bd03ad36, + repr: Some("$value[index] = b"), + doc: ["Allows an indexing set operation to work."], + }; + + /// Check two types for equality. + pub const EQ: Protocol = Protocol { + name: "eq", + hash: 0x418f5becbf885806, + repr: Some("if $value == b { }"), + doc: ["Allows an equality operation to work."], + }; + + /// The function to implement for the addition operation. + pub const ADD: Protocol = Protocol { + name: "add", + hash: 0xe4ecf51fa0bf1076, + repr: Some("let value = $value + b"), + doc: [ + "Allows the `+` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the addition assign operation. + pub const ADD_ASSIGN: Protocol = Protocol { + name: "add_assign", + hash: 0x42451ccb0a2071a9, + repr: Some("$value += b"), + doc: [ + "Allows the `+=` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the subtraction operation. + pub const SUB: Protocol = Protocol { + name: "sub", + hash: 0x6fa86a5f18d0bf71, + repr: Some("let value = $value - b"), + doc: [ + "Allows the `-` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the subtraction assign operation. + pub const SUB_ASSIGN: Protocol = Protocol { + name: "sub_assign", + hash: 0x5939bb56a1415284, + repr: Some("$value -= b"), + doc: [ + "Allows the `-=` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the multiply operation. + pub const MUL: Protocol = Protocol { + name: "mul", + hash: 0xb09e99dc94091d1c, + repr: Some("let value = $value * b"), + doc: [ + "Allows the `*` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the multiply assign operation. + pub const MUL_ASSIGN: Protocol = Protocol { + name: "mul_assign", + hash: 0x29a54b727f980ebf, + repr: Some("$value *= b"), + doc: [ + "Allows the `*=` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the division operation. + pub const DIV: Protocol = Protocol { + name: "div", + hash: 0xf26d6eea1afca6e8, + repr: Some("let value = $value / b"), + doc: [ + "Allows the `/` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the division assign operation. + pub const DIV_ASSIGN: Protocol = Protocol { + name: "div_assign", + hash: 0x4dd087a8281c04e6, + repr: Some("$value /= b"), + doc: [ + "Allows the `/=` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the remainder operation. + pub const REM: Protocol = Protocol { + name: "rem", + hash: 0x5c6293639c74e671, + repr: Some("let value = $value % b"), + doc: [ + "Allows the `%` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the remainder assign operation. + pub const REM_ASSIGN: Protocol = Protocol { + name: "rem_assign", + hash: 0x3a8695980e77baf4, + repr: Some("$value %= b"), + doc: [ + "Allows the `%=` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the bitwise and operation. + pub const BIT_AND: Protocol = Protocol { + name: "bit_and", + hash: 0x0e11f20d940eebe8, + repr: Some("let value = $value & b"), + doc: [ + "Allows the `&` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the bitwise and assign operation. + pub const BIT_AND_ASSIGN: Protocol = Protocol { + name: "bit_and_assign", + hash: 0x95cb1ba235dfb5ec, + repr: Some("$value &= b"), + doc: [ + "Allows the `&=` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the bitwise xor operation. + pub const BIT_XOR: Protocol = Protocol { + name: "bit_xor", + hash: 0xa3099c54e1de4cbf, + repr: Some("let value = $value ^ b"), + doc: [ + "Allows the `^` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the bitwise xor assign operation. + pub const BIT_XOR_ASSIGN: Protocol = Protocol { + name: "bit_xor_assign", + hash: 0x01fa9706738f9867, + repr: Some("$value ^= b"), + doc: [ + "Allows the `^=` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the bitwise or operation. + pub const BIT_OR: Protocol = Protocol { + name: "bit_or", + hash: 0x05010afceb4a03d0, + repr: Some("let value = $value | b"), + doc: [ + "Allows the `|` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the bitwise xor assign operation. + pub const BIT_OR_ASSIGN: Protocol = Protocol { + name: "bit_or_assign", + hash: 0x606d79ff1750a7ec, + repr: Some("$value |= b"), + doc: [ + "Allows the `|=` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the bitwise shift left operation. + pub const SHL: Protocol = Protocol { + name: "shl", + hash: 0x6845f7d0cc9e002d, + repr: Some("let value = $value << b"), + doc: [ + "Allows the `<<` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the bitwise shift left assign operation. + pub const SHL_ASSIGN: Protocol = Protocol { + name: "shl_assign", + hash: 0xdc4702d0307ba27b, + repr: Some("$value <<= b"), + doc: [ + "Allows the `<<=` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the bitwise shift right operation. + pub const SHR: Protocol = Protocol { + name: "shr", + hash: 0x6b485e8e6e58fbc8, + repr: Some("let value = $value >> b"), + doc: [ + "Allows the `>>` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// The function to implement for the bitwise shift right assign operation. + pub const SHR_ASSIGN: Protocol = Protocol { + name: "shr_assign", + hash: 0x61ff7c46ff00e74a, + repr: Some("$value >>= b"), + doc: [ + "Allows the `>>=` operator to apply to values of this type, where the current type is the left-hand side." + ], + }; + + /// Protocol function used by template strings. + pub const STRING_DISPLAY: Protocol = Protocol { + name: "string_display", + hash: 0x811b62957ea9d9f9, + repr: Some("println(\"{}\", $value)"), + doc: ["Allows the value to be display printed."], + }; + + /// Protocol function used by custom debug impls. + pub const STRING_DEBUG: Protocol = Protocol { + name: "string_debug", + hash: 0x4064e3867aaa0717, + repr: Some("println(\"{:?}\", $value)"), + doc: ["Allows the value to be debug printed."], + }; + + /// Function used to convert an argument into an iterator. + pub const INTO_ITER: Protocol = Protocol { + name: "into_iter", + hash: 0x15a85c8d774b4065, + repr: Some("for item in $value { }"), + doc: ["Allows the value to be converted into an iterator in a for-loop."], + }; + + /// The function to call to continue iteration. + pub const NEXT: Protocol = Protocol { + name: "next", + hash: 0xc3cde069de2ba320, + repr: None, + doc: ["Allows iteration to be advanced for the type, this is used for iterators."], + }; + + /// Function used to convert an argument into a future. + /// + /// Signature: `fn(Value) -> Future`. + pub const INTO_FUTURE: Protocol = Protocol { + name: "into_future", + hash: 0x596e6428deabfda2, + repr: Some("value.await"), + doc: ["This protocol allows the type to be converted into a future by awaiting them."], + }; + + /// Coerce a value into a type name. This is stored as a constant. + pub const INTO_TYPE_NAME: Protocol = Protocol { + name: "into_type_name", + hash: 0xbffd08b816c24682, + repr: None, + doc: [ + "This protocol allows the type to be converted into a string which represents the type name.", + ], + }; + + /// Function used to test if a value is a specific variant. + /// + /// Signature: `fn(self, usize) -> bool`. + pub const IS_VARIANT: Protocol = Protocol { + name: "is_variant", + hash: 0xc030d82bbd4dabe8, + repr: None, + doc: ["Test if the provided argument is a variant."], + }; + } } diff --git a/crates/rune/src/runtime/type_info.rs b/crates/rune/src/runtime/type_info.rs index 35fe1bd10..1a32d729b 100644 --- a/crates/rune/src/runtime/type_info.rs +++ b/crates/rune/src/runtime/type_info.rs @@ -1,15 +1,18 @@ -use crate::runtime::{RawStr, Rtti, StaticType, VariantRtti}; use std::fmt; use std::sync::Arc; +use crate::hash::Hash; +use crate::runtime::{RawStr, Rtti, StaticType, VariantRtti}; + /// Type information about a value, that can be printed for human consumption /// through its [Display][fmt::Display] implementation. #[derive(Debug, Clone)] +#[non_exhaustive] pub enum TypeInfo { /// The static type of a value. StaticType(&'static StaticType), /// Reference to an external type. - Any(RawStr), + Any(AnyTypeInfo), /// A named type. Typed(Arc), /// A variant. @@ -17,22 +20,60 @@ pub enum TypeInfo { } impl fmt::Display for TypeInfo { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::StaticType(ty) => { - write!(fmt, "{}", ty.name)?; + write!(f, "{}", ty.name)?; } - Self::Any(type_name) => { - write!(fmt, "{}", *type_name)?; + Self::Any(info) => { + write!(f, "{}", info.name)?; } Self::Typed(rtti) => { - write!(fmt, "{}", rtti.item)?; + write!(f, "{}", rtti.item)?; } Self::Variant(rtti) => { - write!(fmt, "{}", rtti.item)?; + write!(f, "{}", rtti.item)?; } } Ok(()) } } + +/// Type information for the [`Any`] type. +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] +pub struct AnyTypeInfo { + /// The name of the type. + pub name: RawStr, + /// The hash of the type. + #[cfg(feature = "doc")] + #[allow(unused)] + // TODO: will be used to lookup meta for a given type when generating documentation. + pub(crate) hash: Hash, +} + +impl AnyTypeInfo { + /// Private constructor, use at your own risk. + #[doc(hidden)] + pub fn new(name: RawStr, #[cfg_attr(not(feature = "doc"), allow(unused))] hash: Hash) -> Self { + Self { + name, + #[cfg(feature = "doc")] + hash, + } + } + + /// Private constructor, use at your own risk which can optionally construct a hash from a provided function. + #[doc(hidden)] + pub fn new_from( + name: RawStr, + #[cfg_attr(not(feature = "doc"), allow(unused))] hash: fn() -> Hash, + ) -> Self { + Self { + name, + #[cfg(feature = "doc")] + hash: hash(), + } + } +} diff --git a/crates/rune/src/runtime/type_of.rs b/crates/rune/src/runtime/type_of.rs index ac1168512..8e2490407 100644 --- a/crates/rune/src/runtime/type_of.rs +++ b/crates/rune/src/runtime/type_of.rs @@ -1,8 +1,26 @@ use crate::runtime::{Mut, Ref, TypeInfo}; use crate::Hash; +/// Full type information. +#[derive(Clone)] +pub struct FullTypeOf { + pub(crate) hash: Hash, + #[cfg(feature = "doc")] + pub(crate) type_info: TypeInfo, +} + /// Trait used for Rust types for which we can determine the runtime type of. pub trait TypeOf { + /// Type information for the given type. + #[inline] + fn type_of() -> FullTypeOf { + FullTypeOf { + hash: Self::type_hash(), + #[cfg(feature = "doc")] + type_info: Self::type_info(), + } + } + /// Convert into a type hash. fn type_hash() -> Hash; @@ -11,56 +29,64 @@ pub trait TypeOf { } /// Blanket implementation for references. -impl TypeOf for &T +impl TypeOf for &T where - T: TypeOf, + T: ?Sized + TypeOf, { + #[inline] fn type_hash() -> Hash { T::type_hash() } + #[inline] fn type_info() -> TypeInfo { T::type_info() } } /// Blanket implementation for mutable references. -impl TypeOf for &mut T +impl TypeOf for &mut T where - T: TypeOf, + T: ?Sized + TypeOf, { + #[inline] fn type_hash() -> Hash { T::type_hash() } + #[inline] fn type_info() -> TypeInfo { T::type_info() } } /// Blanket implementation for owned references. -impl TypeOf for Ref +impl TypeOf for Ref where - T: TypeOf, + T: ?Sized + TypeOf, { + #[inline] fn type_hash() -> Hash { T::type_hash() } + #[inline] fn type_info() -> TypeInfo { T::type_info() } } /// Blanket implementation for owned mutable references. -impl TypeOf for Mut +impl TypeOf for Mut where - T: TypeOf, + T: ?Sized + TypeOf, { + #[inline] fn type_hash() -> Hash { T::type_hash() } + #[inline] fn type_info() -> TypeInfo { T::type_info() } diff --git a/crates/rune/src/runtime/value.rs b/crates/rune/src/runtime/value.rs index d5d1f46cf..f0c9406c8 100644 --- a/crates/rune/src/runtime/value.rs +++ b/crates/rune/src/runtime/value.rs @@ -949,7 +949,7 @@ impl Value { Self::TupleStruct(tuple) => tuple.borrow_ref()?.type_info(), Self::Struct(object) => object.borrow_ref()?.type_info(), Self::Variant(empty) => empty.borrow_ref()?.type_info(), - Self::Any(any) => TypeInfo::Any(any.borrow_ref()?.type_name()), + Self::Any(any) => any.borrow_ref()?.type_info(), }) } diff --git a/tests/tests/bug_344.rs b/tests/tests/bug_344.rs index 55f1746d5..8c84cd935 100644 --- a/tests/tests/bug_344.rs +++ b/tests/tests/bug_344.rs @@ -7,14 +7,16 @@ //! //! See: https://github.com/rune-rs/rune/issues/344 -use futures_executor::block_on; -use rune::compile::Named; -use rune::runtime::{RawRef, RawStr, Stack, TypeInfo, TypeOf, UnsafeFromValue, VmError}; -use rune::{Any, Context, Hash, InstallWith, Module, Value}; use std::any; use std::cell::Cell; use std::rc::Rc; +use futures_executor::block_on; + +use rune::compile::Named; +use rune::runtime::{AnyTypeInfo, RawRef, RawStr, Stack, TypeInfo, TypeOf, UnsafeFromValue, VmError}; +use rune::{Any, Context, Hash, InstallWith, Module, Value}; + #[test] fn bug_344_function() -> rune::Result<()> { let mut context = Context::new(); @@ -172,12 +174,14 @@ impl Named for GuardCheck { } impl TypeOf for GuardCheck { + #[inline] fn type_hash() -> Hash { ::type_hash() } + #[inline] fn type_info() -> TypeInfo { - TypeInfo::Any(::BASE_NAME) + TypeInfo::Any(AnyTypeInfo::new(::BASE_NAME, ::type_hash())) } } From fc6a0b549c9ad9278281caa81183105f461febbf Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Tue, 4 Apr 2023 05:14:03 +0200 Subject: [PATCH 2/3] Start work on lookup meta by hash --- crates/rune/src/compile/context.rs | 5 ++ crates/rune/src/compile/function_meta.rs | 10 ++-- crates/rune/src/doc/context.rs | 72 +++++++++++++++--------- crates/rune/src/doc/html.rs | 8 ++- crates/rune/src/doc/visitor.rs | 39 ++++++++++--- crates/rune/src/params.rs | 12 +--- crates/rune/src/runtime/protocol.rs | 2 +- crates/rune/src/runtime/type_of.rs | 4 -- 8 files changed, 93 insertions(+), 59 deletions(-) diff --git a/crates/rune/src/compile/context.rs b/crates/rune/src/compile/context.rs index f64c763c9..bc370ec00 100644 --- a/crates/rune/src/compile/context.rs +++ b/crates/rune/src/compile/context.rs @@ -287,6 +287,11 @@ impl Context { self.meta.get(name) } + /// Lookup meta by its hash. + pub(crate) fn lookup_meta_by_hash(&self, hash: Hash) -> Option<&PrivMeta> { + todo!() + } + /// Look up signature of function. #[cfg(feature = "doc")] pub(crate) fn lookup_signature(&self, hash: Hash) -> Option<&meta::Signature> { diff --git a/crates/rune/src/compile/function_meta.rs b/crates/rune/src/compile/function_meta.rs index da983ba6b..ce8156a04 100644 --- a/crates/rune/src/compile/function_meta.rs +++ b/crates/rune/src/compile/function_meta.rs @@ -6,8 +6,6 @@ use crate::compile::module::{ }; use crate::compile::{IntoComponent, ItemBuf, Named}; use crate::hash::Hash; -#[cfg(feature = "doc")] -use crate::runtime::TypeInfo; use crate::runtime::{FunctionHandler, Protocol}; mod sealed { @@ -138,7 +136,7 @@ impl ToInstance for &str { kind: AssociatedFunctionKind::Instance(self.into()), parameters: Hash::EMPTY, #[cfg(feature = "doc")] - parameter_type_infos: vec![], + parameter_types: vec![], } } } @@ -150,7 +148,7 @@ impl ToFieldFunction for &str { kind: AssociatedFunctionKind::FieldFn(protocol, self.into()), parameters: Hash::EMPTY, #[cfg(feature = "doc")] - parameter_type_infos: vec![], + parameter_types: vec![], } } } @@ -165,7 +163,7 @@ pub struct AssociatedFunctionName { /// Parameters hash. pub parameters: Hash, #[cfg(feature = "doc")] - pub parameter_type_infos: Vec, + pub parameter_types: Vec, } impl AssociatedFunctionName { @@ -174,7 +172,7 @@ impl AssociatedFunctionName { kind: AssociatedFunctionKind::IndexFn(protocol, index), parameters: Hash::EMPTY, #[cfg(feature = "doc")] - parameter_type_infos: vec![], + parameter_types: vec![], } } } diff --git a/crates/rune/src/doc/context.rs b/crates/rune/src/doc/context.rs index cb5745721..8377d1427 100644 --- a/crates/rune/src/doc/context.rs +++ b/crates/rune/src/doc/context.rs @@ -1,3 +1,4 @@ +use crate::compile::context::PrivMeta; use crate::compile::meta; use crate::compile::{AssociatedFunction, ComponentRef, IntoComponent, Item}; use crate::doc::Visitor; @@ -72,39 +73,31 @@ impl<'a> Context<'a> { .chain(tail) } + /// Get a meta item by its hash. + pub(crate) fn meta_by_hash(&self, hash: Hash) -> Option> { + for visitor in self.visitors { + if let Some(m) = visitor.get_by_hash(hash) { + return Some(visitor_meta_to_meta(m, visitor, hash)); + } + } + + let meta = self.context.lookup_meta_by_hash(hash)?; + Some(self.context_meta_to_meta(meta)?) + } + /// Lookup Meta. pub(crate) fn meta(&self, item: &Item) -> Option> { for visitor in self.visitors { - if let Some(m) = visitor.meta.get(item) { - let kind = match m { - meta::Kind::Unknown { .. } => Kind::Unknown, - meta::Kind::Struct { .. } => Kind::Struct, - meta::Kind::Variant { .. } => Kind::Variant, - meta::Kind::Enum => Kind::Enum, - meta::Kind::Function { is_async, args, .. } => Kind::Function(Function { - is_async: *is_async, - args: None, - signature: Signature::Function { args: *args }, - }), - _ => Kind::Unsupported, - }; - - let docs = visitor - .docs - .get(item) - .map(Vec::as_slice) - .unwrap_or_default(); - - return Some(Meta { - hash: Hash::type_hash(item), - docs, - kind, - }); + if let Some((hash, m)) = visitor.get(item) { + return Some(visitor_meta_to_meta(m, visitor, hash)); } } let meta = self.context.lookup_meta(item)?; + Some(self.context_meta_to_meta(meta)?) + } + fn context_meta_to_meta(&self, meta: &'a PrivMeta) -> Option> { let kind = match &meta.kind { meta::Kind::Unknown { .. } => Kind::Unknown, meta::Kind::Struct { .. } => Kind::Struct, @@ -138,11 +131,13 @@ impl<'a> Context<'a> { _ => Kind::Unsupported, }; - Some(Meta { + let m = Meta { hash: meta.hash, docs: meta.docs.lines(), kind, - }) + }; + + Some(m) } /// Iterate over known modules. @@ -153,3 +148,26 @@ impl<'a> Context<'a> { .chain(self.context.iter_meta().map(|(_, m)| m.module.as_ref())) } } + +fn visitor_meta_to_meta<'a>(m: &'a meta::Kind, visitor: &'a Visitor, hash: Hash) -> Meta<'a> { + let kind = match m { + meta::Kind::Unknown { .. } => Kind::Unknown, + meta::Kind::Struct { .. } => Kind::Struct, + meta::Kind::Variant { .. } => Kind::Variant, + meta::Kind::Enum => Kind::Enum, + meta::Kind::Function { is_async, args, .. } => Kind::Function(Function { + is_async: *is_async, + args: None, + signature: Signature::Function { args: *args }, + }), + _ => Kind::Unsupported, + }; + + let docs = visitor + .docs + .get(&hash) + .map(Vec::as_slice) + .unwrap_or_default(); + + Meta { hash, docs, kind } +} diff --git a/crates/rune/src/doc/html.rs b/crates/rune/src/doc/html.rs index fb9b7e13b..2ab91d883 100644 --- a/crates/rune/src/doc/html.rs +++ b/crates/rune/src/doc/html.rs @@ -697,8 +697,12 @@ fn type_(cx: &Ctxt<'_>, what: &str, hash: Hash) -> Result<()> { let mut list = Vec::new(); - for p in &f.name.parameter_type_infos { - list.push(p.to_string()); + for hash in &f.name.parameter_types { + if let Some(meta) = cx.context.meta_by_hash(*hash) { + list.push(String::from("META")); + } else { + list.push(String::from("?")); + } } let parameters = (!list.is_empty()).then(|| list.join(", ")); diff --git a/crates/rune/src/doc/visitor.rs b/crates/rune/src/doc/visitor.rs index 435bc49a8..b2f30a3c8 100644 --- a/crates/rune/src/doc/visitor.rs +++ b/crates/rune/src/doc/visitor.rs @@ -1,15 +1,17 @@ -use crate::collections::{BTreeMap, HashMap}; +use crate::collections::HashMap; use crate::compile::{ meta, CompileVisitor, IntoComponent, Item, ItemBuf, Location, MetaRef, Names, }; +use crate::hash::Hash; /// Visitor used to collect documentation from rune sources. pub struct Visitor { pub(crate) base: ItemBuf, pub(crate) names: Names, - pub(crate) meta: BTreeMap, - pub(crate) docs: HashMap>, - pub(crate) field_docs: HashMap, Vec>>, + pub(crate) meta: HashMap, + pub(crate) docs: HashMap>, + pub(crate) field_docs: HashMap, Vec>>, + pub(crate) item_to_hash: HashMap, } impl Visitor { @@ -22,23 +24,39 @@ impl Visitor { Self { base: base.into_iter().collect(), names: Names::default(), - meta: BTreeMap::default(), + meta: HashMap::default(), + item_to_hash: HashMap::new(), docs: HashMap::default(), field_docs: HashMap::default(), } } + + /// Get meta by item. + pub(crate) fn get(&self, item: &Item) -> Option<(Hash, &meta::Kind)> { + let hash = self.item_to_hash.get(item)?; + Some((*hash, self.meta.get(hash)?)) + } + + /// Get meta by hash. + pub(crate) fn get_by_hash(&self, hash: Hash) -> Option<&meta::Kind> { + self.meta.get(&hash) + } } impl CompileVisitor for Visitor { fn register_meta(&mut self, meta: MetaRef<'_>) { let item = self.base.join(meta.item); - self.meta.insert(item.to_owned(), meta.kind.clone()); + self.item_to_hash.insert(item.to_owned(), meta.hash); + self.meta.insert(meta.hash, meta.kind.clone()); self.names.insert(item); } fn visit_doc_comment(&mut self, _location: Location, item: &Item, string: &str) { let item = self.base.join(item); - self.docs.entry(item).or_default().push(string.to_owned()); + + if let Some(hash) = self.item_to_hash.get(&item) { + self.docs.entry(*hash).or_default().push(string.to_owned()); + } } fn visit_field_doc_comment( @@ -49,7 +67,10 @@ impl CompileVisitor for Visitor { string: &str, ) { let item = self.base.join(item); - let map = self.field_docs.entry(item).or_default(); - map.entry(field.into()).or_default().push(string.to_owned()); + + if let Some(hash) = self.item_to_hash.get(&item) { + let map = self.field_docs.entry(*hash).or_default(); + map.entry(field.into()).or_default().push(string.to_owned()); + } } } diff --git a/crates/rune/src/params.rs b/crates/rune/src/params.rs index 9da3d657e..df42386bc 100644 --- a/crates/rune/src/params.rs +++ b/crates/rune/src/params.rs @@ -41,11 +41,7 @@ where kind: info.kind, parameters: Hash::parameters(self.parameters.iter().map(|t| t.hash)), #[cfg(feature = "doc")] - parameter_type_infos: self - .parameters - .iter() - .map(|t| t.type_info.clone()) - .collect(), + parameter_types: self.parameters.iter().map(|t| t.hash).collect(), } } } @@ -62,11 +58,7 @@ where kind: info.kind, parameters: Hash::parameters(self.parameters.iter().map(|p| p.hash)), #[cfg(feature = "doc")] - parameter_type_infos: self - .parameters - .iter() - .map(|p| p.type_info.clone()) - .collect(), + parameter_types: self.parameters.iter().map(|p| p.hash).collect(), } } } diff --git a/crates/rune/src/runtime/protocol.rs b/crates/rune/src/runtime/protocol.rs index 1f08d5924..451d8deca 100644 --- a/crates/rune/src/runtime/protocol.rs +++ b/crates/rune/src/runtime/protocol.rs @@ -40,7 +40,7 @@ impl ToInstance for Protocol { kind: AssociatedFunctionKind::Protocol(self), parameters: Hash::EMPTY, #[cfg(feature = "doc")] - parameter_type_infos: vec![], + parameter_types: vec![], } } } diff --git a/crates/rune/src/runtime/type_of.rs b/crates/rune/src/runtime/type_of.rs index 8e2490407..56b9a7cde 100644 --- a/crates/rune/src/runtime/type_of.rs +++ b/crates/rune/src/runtime/type_of.rs @@ -5,8 +5,6 @@ use crate::Hash; #[derive(Clone)] pub struct FullTypeOf { pub(crate) hash: Hash, - #[cfg(feature = "doc")] - pub(crate) type_info: TypeInfo, } /// Trait used for Rust types for which we can determine the runtime type of. @@ -16,8 +14,6 @@ pub trait TypeOf { fn type_of() -> FullTypeOf { FullTypeOf { hash: Self::type_hash(), - #[cfg(feature = "doc")] - type_info: Self::type_info(), } } From 68b660f88f17afdf5a89b70f95c3f82aa59ca622 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Tue, 4 Apr 2023 06:00:47 +0200 Subject: [PATCH 3/3] Lookup generic parameters by hash --- benches/benches/primes.rn | 1 + crates/rune-macros/src/any.rs | 5 +- crates/rune/src/compile/compile_visitor.rs | 5 +- crates/rune/src/compile/context.rs | 20 +++--- crates/rune/src/compile/meta.rs | 3 +- crates/rune/src/compile/pool.rs | 6 ++ crates/rune/src/doc.rs | 1 + crates/rune/src/doc/context.rs | 36 +++++----- crates/rune/src/doc/html.rs | 43 +++++++++--- crates/rune/src/doc/static/runedoc.css | 5 ++ crates/rune/src/doc/static/type.html.hbs | 2 +- crates/rune/src/doc/visitor.rs | 77 ++++++++++++++++------ crates/rune/src/indexing/index.rs | 59 +++++++++-------- crates/rune/src/modules/iter.rs | 6 +- crates/rune/src/query/mod.rs | 1 + crates/rune/src/runtime/any_obj.rs | 5 +- crates/rune/src/runtime/type_info.rs | 29 +------- tests/tests/bug_344.rs | 2 +- tests/tests/compiler_docs.rs | 6 +- 19 files changed, 183 insertions(+), 129 deletions(-) diff --git a/benches/benches/primes.rn b/benches/benches/primes.rn index f89ba44ff..da5500a9f 100644 --- a/benches/benches/primes.rn +++ b/benches/benches/primes.rn @@ -1,5 +1,6 @@ const MAX_NUMBER_TO_CHECK = 1000; +/// Find prime numbers. #[bench] fn find_primes(b) { let prime_mask = []; diff --git a/crates/rune-macros/src/any.rs b/crates/rune-macros/src/any.rs index e0b8095bc..8aaaa508a 100644 --- a/crates/rune-macros/src/any.rs +++ b/crates/rune-macros/src/any.rs @@ -444,10 +444,7 @@ where #[inline] fn type_info() -> #type_info { - #type_info::Any(#any_type_info::new( - #raw_str::from_str(std::any::type_name::()), - ::type_hash() - )) + #type_info::Any(#any_type_info::new(#raw_str::from_str(std::any::type_name::()))) } } diff --git a/crates/rune/src/compile/compile_visitor.rs b/crates/rune/src/compile/compile_visitor.rs index d2d120556..8ca1ac666 100644 --- a/crates/rune/src/compile/compile_visitor.rs +++ b/crates/rune/src/compile/compile_visitor.rs @@ -1,5 +1,6 @@ use crate::ast::Span; use crate::compile::{Item, Location, MetaRef}; +use crate::hash::Hash; use crate::SourceId; /// A visitor that will be called for every language item compiled. @@ -24,7 +25,8 @@ pub trait CompileVisitor { /// /// This can be called in any order, before or after /// [CompileVisitor::visit_meta] for any given item. - fn visit_doc_comment(&mut self, _location: Location, _item: &Item, _docstr: &str) {} + fn visit_doc_comment(&mut self, _location: Location, _item: &Item, _hash: Hash, _docstr: &str) { + } /// Visit anterior `///`-style comments, and interior `//!`-style doc /// comments for a field contained in a struct / enum variant struct. @@ -35,6 +37,7 @@ pub trait CompileVisitor { &mut self, _location: Location, _item: &Item, + _hash: Hash, _field: &str, _docstr: &str, ) { diff --git a/crates/rune/src/compile/context.rs b/crates/rune/src/compile/context.rs index bc370ec00..6c8091ed9 100644 --- a/crates/rune/src/compile/context.rs +++ b/crates/rune/src/compile/context.rs @@ -95,7 +95,9 @@ pub struct Context { /// Whether or not to include the prelude when constructing a new unit. has_default_modules: bool, /// Item metadata in the context. - meta: HashMap, + meta: HashMap, + /// Store item to hash mapping. + item_to_hash: HashMap, /// Information on functions. functions_info: HashMap, /// Registered native function handlers. @@ -283,13 +285,14 @@ impl Context { } /// Access the context meta for the given item. - pub(crate) fn lookup_meta(&self, name: &Item) -> Option<&PrivMeta> { - self.meta.get(name) + pub(crate) fn lookup_meta(&self, item: &Item) -> Option<&PrivMeta> { + let hash = self.item_to_hash.get(item)?; + self.meta.get(hash) } /// Lookup meta by its hash. pub(crate) fn lookup_meta_by_hash(&self, hash: Hash) -> Option<&PrivMeta> { - todo!() + self.meta.get(&hash) } /// Look up signature of function. @@ -299,8 +302,8 @@ impl Context { } /// Iterate over all metadata in the context. - pub fn iter_meta(&self) -> impl Iterator + '_ { - self.meta.iter().map(|(item, meta)| (item.as_ref(), meta)) + pub fn iter_meta(&self) -> impl Iterator + '_ { + self.meta.values() } /// Check if unit contains the given name by prefix. @@ -348,7 +351,7 @@ impl Context { /// Install the given meta. fn install_meta(&mut self, meta: PrivMeta) -> Result<(), ContextError> { - match self.meta.entry(meta.item.clone()) { + match self.meta.entry(meta.hash) { hash_map::Entry::Occupied(e) => { return Err(ContextError::ConflictingMeta { existing: Box::new(e.get().info()), @@ -356,6 +359,7 @@ impl Context { }); } hash_map::Entry::Vacant(e) => { + self.item_to_hash.insert(meta.item.clone(), meta.hash); e.insert(meta); } } @@ -674,7 +678,7 @@ impl Context { self.functions.insert(hash, assoc.handler.clone()); - if !self.meta.contains_key(&item) { + if !self.item_to_hash.contains_key(&item) { self.install_meta(PrivMeta::new( module, type_hash, diff --git a/crates/rune/src/compile/meta.rs b/crates/rune/src/compile/meta.rs index 37038130d..5604d3f6a 100644 --- a/crates/rune/src/compile/meta.rs +++ b/crates/rune/src/compile/meta.rs @@ -14,8 +14,7 @@ use crate::hash::Hash; use crate::parse::{Id, ParseError, ResolveContext}; use crate::runtime::{ConstValue, TypeInfo}; -/// Provides a human-readable description of a meta item. This is cheaper to use -/// than [Meta] because it avoids having to clone some data. +/// A meta reference to an item being compiled. #[derive(Debug, Clone)] #[non_exhaustive] pub struct MetaRef<'a> { diff --git a/crates/rune/src/compile/pool.rs b/crates/rune/src/compile/pool.rs index 2d3fcf3f9..1dd1112ff 100644 --- a/crates/rune/src/compile/pool.rs +++ b/crates/rune/src/compile/pool.rs @@ -115,6 +115,12 @@ impl Pool { self.item(id) } + /// Get the hash associated with a module item. + pub(crate) fn module_item_hash(&self, id: ModId) -> Hash { + let id = self.module(id).item; + self.item_type_hash(id) + } + /// Get by item id. pub(crate) fn module_by_item(&self, id: ItemId) -> Option<&ModMeta> { Some(self.module(*self.item_to_mod.get(&id)?)) diff --git a/crates/rune/src/doc.rs b/crates/rune/src/doc.rs index ee98d910b..322aaeb4c 100644 --- a/crates/rune/src/doc.rs +++ b/crates/rune/src/doc.rs @@ -10,3 +10,4 @@ pub use self::html::write_html; mod visitor; pub use self::visitor::Visitor; +pub(crate) use self::visitor::VisitorData; diff --git a/crates/rune/src/doc/context.rs b/crates/rune/src/doc/context.rs index 8377d1427..46edeee04 100644 --- a/crates/rune/src/doc/context.rs +++ b/crates/rune/src/doc/context.rs @@ -1,12 +1,14 @@ use crate::compile::context::PrivMeta; use crate::compile::meta; use crate::compile::{AssociatedFunction, ComponentRef, IntoComponent, Item}; -use crate::doc::Visitor; +use crate::doc::{Visitor, VisitorData}; use crate::runtime::ConstValue; use crate::Hash; #[derive(Debug, Clone, Copy)] pub(crate) struct Meta<'a> { + /// Item of the meta. + pub(crate) item: &'a Item, /// Type hash for the meta item. pub(crate) hash: Hash, /// Kind of the meta item. @@ -76,25 +78,25 @@ impl<'a> Context<'a> { /// Get a meta item by its hash. pub(crate) fn meta_by_hash(&self, hash: Hash) -> Option> { for visitor in self.visitors { - if let Some(m) = visitor.get_by_hash(hash) { - return Some(visitor_meta_to_meta(m, visitor, hash)); + if let Some(data) = visitor.get_by_hash(hash) { + return Some(visitor_meta_to_meta(data)); } } let meta = self.context.lookup_meta_by_hash(hash)?; - Some(self.context_meta_to_meta(meta)?) + self.context_meta_to_meta(meta) } /// Lookup Meta. pub(crate) fn meta(&self, item: &Item) -> Option> { for visitor in self.visitors { - if let Some((hash, m)) = visitor.get(item) { - return Some(visitor_meta_to_meta(m, visitor, hash)); + if let Some(data) = visitor.get(item) { + return Some(visitor_meta_to_meta(data)); } } let meta = self.context.lookup_meta(item)?; - Some(self.context_meta_to_meta(meta)?) + self.context_meta_to_meta(meta) } fn context_meta_to_meta(&self, meta: &'a PrivMeta) -> Option> { @@ -132,6 +134,7 @@ impl<'a> Context<'a> { }; let m = Meta { + item: &meta.item, hash: meta.hash, docs: meta.docs.lines(), kind, @@ -145,12 +148,12 @@ impl<'a> Context<'a> { self.visitors .iter() .map(|v| v.base.as_ref()) - .chain(self.context.iter_meta().map(|(_, m)| m.module.as_ref())) + .chain(self.context.iter_meta().map(|m| m.module.as_ref())) } } -fn visitor_meta_to_meta<'a>(m: &'a meta::Kind, visitor: &'a Visitor, hash: Hash) -> Meta<'a> { - let kind = match m { +fn visitor_meta_to_meta(data: &VisitorData) -> Meta<'_> { + let kind = match &data.kind { meta::Kind::Unknown { .. } => Kind::Unknown, meta::Kind::Struct { .. } => Kind::Struct, meta::Kind::Variant { .. } => Kind::Variant, @@ -163,11 +166,10 @@ fn visitor_meta_to_meta<'a>(m: &'a meta::Kind, visitor: &'a Visitor, hash: Hash) _ => Kind::Unsupported, }; - let docs = visitor - .docs - .get(&hash) - .map(Vec::as_slice) - .unwrap_or_default(); - - Meta { hash, docs, kind } + Meta { + item: &data.item, + hash: data.hash, + docs: data.docs.as_slice(), + kind, + } } diff --git a/crates/rune/src/doc/html.rs b/crates/rune/src/doc/html.rs index 2ab91d883..8a65b2fe5 100644 --- a/crates/rune/src/doc/html.rs +++ b/crates/rune/src/doc/html.rs @@ -129,7 +129,10 @@ impl Ctxt<'_> { None => self.syntax_set.find_syntax_plain_text(), }; - self.render_code_by_syntax(lines, syntax) + format!( + "
{}
", + self.render_code_by_syntax(lines, syntax) + ) } /// Render documentation. @@ -286,7 +289,7 @@ impl Ctxt<'_> { } /// Build banklinks for the current item. - fn module_path_html(&self, last: bool) -> String { + fn module_path_html(&self, is_module: bool) -> String { let mut module = Vec::new(); let mut iter = self.item.iter(); let dir = self.dir(); @@ -294,15 +297,15 @@ impl Ctxt<'_> { while iter.next_back().is_some() { if let Some(name) = iter.as_item().last() { let url = dir.relative(self.item_path(iter.as_item(), ItemPath::Module)); - module.push(format!("{name}")); + module.push(format!("{name}")); } } module.reverse(); - if last { + if is_module { if let Some(name) = self.item.last() { - module.push(format!("{name}")); + module.push(format!("{name}")); } } @@ -407,11 +410,11 @@ pub fn write_html( match build { Build::Type(item, hash) => { cx.set_path(item, ItemPath::Type); - type_(&cx, "Type", hash)?; + type_(&cx, "Type", "type", hash)?; } Build::Struct(item, hash) => { cx.set_path(item, ItemPath::Struct); - type_(&cx, "Struct", hash)?; + type_(&cx, "Struct", "struct", hash)?; } Build::Function(item) => { cx.set_path(item, ItemPath::Function); @@ -611,11 +614,12 @@ fn module( } /// Build an unknown type. -#[tracing::instrument(skip_all)] -fn type_(cx: &Ctxt<'_>, what: &str, hash: Hash) -> Result<()> { +// #[tracing::instrument(skip_all)] +fn type_(cx: &Ctxt<'_>, what: &str, what_class: &str, hash: Hash) -> Result<()> { #[derive(Serialize)] struct Params<'a> { what: &'a str, + what_class: &'a str, #[serde(flatten)] shared: Shared, module: String, @@ -699,7 +703,25 @@ fn type_(cx: &Ctxt<'_>, what: &str, hash: Hash) -> Result<()> { for hash in &f.name.parameter_types { if let Some(meta) = cx.context.meta_by_hash(*hash) { - list.push(String::from("META")); + match &meta.kind { + Kind::Unknown => { + let path = + cx.dir().relative(cx.item_path(meta.item, ItemPath::Type)); + let name = meta.item.last().context("missing name")?; + list.push(format!("{name}")); + } + Kind::Struct => { + let path = + cx.dir().relative(cx.item_path(meta.item, ItemPath::Struct)); + let name = meta.item.last().context("missing name")?; + list.push(format!( + "{name}" + )); + } + _ => { + list.push(String::from("?")); + } + } } else { list.push(String::from("?")); } @@ -739,6 +761,7 @@ fn type_(cx: &Ctxt<'_>, what: &str, hash: Hash) -> Result<()> { cx.write_file(|cx| { cx.type_template.render(&Params { what, + what_class, shared: cx.shared(), module, name, diff --git a/crates/rune/src/doc/static/runedoc.css b/crates/rune/src/doc/static/runedoc.css index 2d38448aa..0aae8b109 100644 --- a/crates/rune/src/doc/static/runedoc.css +++ b/crates/rune/src/doc/static/runedoc.css @@ -6,6 +6,7 @@ --headings-border-bottom-color: #d2d2d2; --type-link-color: #2dbfb8; --fn-link-color: #2bab63; + --mod-link-color: #d2991d; } body { @@ -74,6 +75,10 @@ a { color: var(--fn-link-color); } +.module { + color: var(--mod-link-color); +} + .protocol { color: var(--fn-link-color); } diff --git a/crates/rune/src/doc/static/type.html.hbs b/crates/rune/src/doc/static/type.html.hbs index 6dab49f0e..656a3d295 100644 --- a/crates/rune/src/doc/static/type.html.hbs +++ b/crates/rune/src/doc/static/type.html.hbs @@ -3,7 +3,7 @@ {{#each fonts}}{{/each}} {{#each css}}{{/each}} -

{{what}} {{literal module}}::{{name}}

+

{{what}} {{literal module}}::{{name}}

{{#if methods}}

Methods

diff --git a/crates/rune/src/doc/visitor.rs b/crates/rune/src/doc/visitor.rs index b2f30a3c8..fb11a570a 100644 --- a/crates/rune/src/doc/visitor.rs +++ b/crates/rune/src/doc/visitor.rs @@ -1,16 +1,34 @@ -use crate::collections::HashMap; +use crate::collections::{hash_map, HashMap}; use crate::compile::{ meta, CompileVisitor, IntoComponent, Item, ItemBuf, Location, MetaRef, Names, }; use crate::hash::Hash; +pub(crate) struct VisitorData { + pub(crate) item: ItemBuf, + pub(crate) hash: Hash, + pub(crate) kind: meta::Kind, + pub(crate) docs: Vec, + pub(crate) field_docs: HashMap, Vec>, +} + +impl VisitorData { + fn new(item: ItemBuf, hash: Hash, kind: meta::Kind) -> Self { + Self { + item, + hash, + kind, + docs: Vec::new(), + field_docs: HashMap::new(), + } + } +} + /// Visitor used to collect documentation from rune sources. pub struct Visitor { pub(crate) base: ItemBuf, pub(crate) names: Names, - pub(crate) meta: HashMap, - pub(crate) docs: HashMap>, - pub(crate) field_docs: HashMap, Vec>>, + pub(crate) data: HashMap, pub(crate) item_to_hash: HashMap, } @@ -24,53 +42,70 @@ impl Visitor { Self { base: base.into_iter().collect(), names: Names::default(), - meta: HashMap::default(), + data: HashMap::default(), item_to_hash: HashMap::new(), - docs: HashMap::default(), - field_docs: HashMap::default(), } } /// Get meta by item. - pub(crate) fn get(&self, item: &Item) -> Option<(Hash, &meta::Kind)> { + pub(crate) fn get(&self, item: &Item) -> Option<&VisitorData> { let hash = self.item_to_hash.get(item)?; - Some((*hash, self.meta.get(hash)?)) + self.data.get(hash) } /// Get meta by hash. - pub(crate) fn get_by_hash(&self, hash: Hash) -> Option<&meta::Kind> { - self.meta.get(&hash) + pub(crate) fn get_by_hash(&self, hash: Hash) -> Option<&VisitorData> { + self.data.get(&hash) } } impl CompileVisitor for Visitor { fn register_meta(&mut self, meta: MetaRef<'_>) { let item = self.base.join(meta.item); + tracing::trace!(?item, "registering meta"); + + self.names.insert(&item); self.item_to_hash.insert(item.to_owned(), meta.hash); - self.meta.insert(meta.hash, meta.kind.clone()); - self.names.insert(item); + + match self.data.entry(meta.hash) { + hash_map::Entry::Occupied(e) => { + e.into_mut().kind = meta.kind.clone(); + } + hash_map::Entry::Vacant(e) => { + e.insert(VisitorData::new(item, meta.hash, meta.kind.clone())); + } + } } - fn visit_doc_comment(&mut self, _location: Location, item: &Item, string: &str) { + fn visit_doc_comment(&mut self, _location: Location, item: &Item, hash: Hash, string: &str) { let item = self.base.join(item); + tracing::trace!(?item, "visiting comment"); - if let Some(hash) = self.item_to_hash.get(&item) { - self.docs.entry(*hash).or_default().push(string.to_owned()); - } + let data = self + .data + .entry(hash) + .or_insert_with(|| VisitorData::new(item.to_owned(), hash, meta::Kind::Unknown)); + data.docs.push(string.to_owned()); } fn visit_field_doc_comment( &mut self, _location: Location, item: &Item, + hash: Hash, field: &str, string: &str, ) { let item = self.base.join(item); + tracing::trace!(?item, "visiting field comment"); - if let Some(hash) = self.item_to_hash.get(&item) { - let map = self.field_docs.entry(*hash).or_default(); - map.entry(field.into()).or_default().push(string.to_owned()); - } + let data = self + .data + .entry(hash) + .or_insert_with(|| VisitorData::new(item.to_owned(), hash, meta::Kind::Unknown)); + data.field_docs + .entry(field.into()) + .or_default() + .push(string.to_owned()); } } diff --git a/crates/rune/src/indexing/index.rs b/crates/rune/src/indexing/index.rs index b9c0b08b4..7ed46e385 100644 --- a/crates/rune/src/indexing/index.rs +++ b/crates/rune/src/indexing/index.rs @@ -595,6 +595,7 @@ pub(crate) fn file(ast: &mut ast::File, idx: &mut Indexer<'_>) -> CompileResult< idx.q.visitor.visit_doc_comment( Location::new(idx.source_id, span), idx.q.pool.module_item(idx.mod_item), + idx.q.pool.module_item_hash(idx.mod_item), &doc.doc_string.resolve(ctx)?, ); } @@ -1258,21 +1259,31 @@ fn item_enum(ast: &mut ast::ItemEnum, idx: &mut Indexer<'_>) -> CompileResult<() )); } - let ctx = resolve_context!(idx.q); let span = variant.name.span(); - let name = variant.name.resolve(ctx)?; + let name = variant.name.resolve(resolve_context!(idx.q))?; let _guard = idx.items.push_name(name.as_ref()); + let item_meta = idx.q.insert_new_item( + &idx.items, + Location::new(idx.source_id, span), + idx.mod_item, + Visibility::Public, + &docs, + )?; + variant.id = item_meta.id; + + let ctx = resolve_context!(idx.q); + for (field, _) in variant.body.fields() { let mut attrs = Attributes::new(field.attributes.to_vec()); let docs = Doc::collect_from(ctx, &mut attrs)?; let name = field.name.resolve(ctx)?; - let item = idx.items.item(); for doc in docs { idx.q.visitor.visit_field_doc_comment( Location::new(idx.source_id, doc.span), - &item, + idx.q.pool.item(item_meta.item), + idx.q.pool.item_type_hash(item_meta.item), name, doc.doc_string.resolve(ctx)?.as_ref(), ); @@ -1286,15 +1297,6 @@ fn item_enum(ast: &mut ast::ItemEnum, idx: &mut Indexer<'_>) -> CompileResult<() } } - let item_meta = idx.q.insert_new_item( - &idx.items, - Location::new(idx.source_id, span), - idx.mod_item, - Visibility::Public, - &docs, - )?; - variant.id = item_meta.id; - idx.q .index_variant(item_meta, enum_item.id, variant.clone(), index)?; } @@ -1307,8 +1309,7 @@ fn item_struct(ast: &mut ast::ItemStruct, idx: &mut Indexer<'_>) -> CompileResul let span = ast.span(); let mut attrs = Attributes::new(ast.attributes.to_vec()); - let ctx = resolve_context!(idx.q); - let docs = Doc::collect_from(ctx, &mut attrs)?; + let docs = Doc::collect_from(resolve_context!(idx.q), &mut attrs)?; if let Some(first) = attrs.remaining() { return Err(CompileError::msg( @@ -1317,19 +1318,31 @@ fn item_struct(ast: &mut ast::ItemStruct, idx: &mut Indexer<'_>) -> CompileResul )); } - let ident = ast.ident.resolve(ctx)?; + let ident = ast.ident.resolve(resolve_context!(idx.q))?; let _guard = idx.items.push_name(ident); + let visibility = ast_to_visibility(&ast.visibility)?; + let item_meta = idx.q.insert_new_item( + &idx.items, + Location::new(idx.source_id, span), + idx.mod_item, + visibility, + &docs, + )?; + ast.id = item_meta.id; + + let ctx = resolve_context!(idx.q); + for (field, _) in ast.body.fields() { let mut attrs = Attributes::new(field.attributes.to_vec()); let docs = Doc::collect_from(ctx, &mut attrs)?; let name = field.name.resolve(ctx)?; - let item = idx.items.item(); for doc in docs { idx.q.visitor.visit_field_doc_comment( Location::new(idx.source_id, doc.span), - &item, + idx.q.pool.item(item_meta.item), + idx.q.pool.item_type_hash(item_meta.item), name, doc.doc_string.resolve(ctx)?.as_ref(), ); @@ -1348,16 +1361,6 @@ fn item_struct(ast: &mut ast::ItemStruct, idx: &mut Indexer<'_>) -> CompileResul } } - let visibility = ast_to_visibility(&ast.visibility)?; - let item_meta = idx.q.insert_new_item( - &idx.items, - Location::new(idx.source_id, span), - idx.mod_item, - visibility, - &docs, - )?; - ast.id = item_meta.id; - idx.q.index_struct(item_meta, Box::new(ast.clone()))?; Ok(()) } diff --git a/crates/rune/src/modules/iter.rs b/crates/rune/src/modules/iter.rs index f0432789c..753274d66 100644 --- a/crates/rune/src/modules/iter.rs +++ b/crates/rune/src/modules/iter.rs @@ -45,7 +45,7 @@ pub fn module() -> Result { /// /// # Examples /// -/// ``` +/// ```rune /// use std::iter::empty; /// /// assert!(empty().next().is_none()); @@ -60,7 +60,7 @@ fn empty() -> Iterator { /// /// # Examples /// -/// ``` +/// ```rune /// use std::iter::once; /// /// assert!(once(42).next().is_some()); @@ -76,7 +76,7 @@ fn once(value: Value) -> Iterator { /// /// # Examples /// -/// ``` +/// ```rune /// use std::iter::range; /// /// assert!(range(0, 3).next().is_some()); diff --git a/crates/rune/src/query/mod.rs b/crates/rune/src/query/mod.rs index 4ab7b8f83..dc5bad68e 100644 --- a/crates/rune/src/query/mod.rs +++ b/crates/rune/src/query/mod.rs @@ -321,6 +321,7 @@ impl<'a> Query<'a> { self.visitor.visit_doc_comment( Location::new(location.source_id, doc.span), self.pool.item(item), + self.pool.item_type_hash(item), doc.doc_string.resolve(ctx)?.as_ref(), ); } diff --git a/crates/rune/src/runtime/any_obj.rs b/crates/rune/src/runtime/any_obj.rs index ab60d9fea..02b8d467d 100644 --- a/crates/rune/src/runtime/any_obj.rs +++ b/crates/rune/src/runtime/any_obj.rs @@ -455,10 +455,7 @@ impl AnyObj { /// Access full type info for type. pub fn type_info(&self) -> TypeInfo { - TypeInfo::Any(AnyTypeInfo::new_from( - (self.vtable.type_name)(), - self.vtable.type_hash, - )) + TypeInfo::Any(AnyTypeInfo::new((self.vtable.type_name)())) } } diff --git a/crates/rune/src/runtime/type_info.rs b/crates/rune/src/runtime/type_info.rs index 1a32d729b..aae030bfb 100644 --- a/crates/rune/src/runtime/type_info.rs +++ b/crates/rune/src/runtime/type_info.rs @@ -1,7 +1,6 @@ use std::fmt; use std::sync::Arc; -use crate::hash::Hash; use crate::runtime::{RawStr, Rtti, StaticType, VariantRtti}; /// Type information about a value, that can be printed for human consumption @@ -40,40 +39,18 @@ impl fmt::Display for TypeInfo { } } -/// Type information for the [`Any`] type. +/// Type information for the [`Any`][crate::Any] type. #[derive(Debug, Clone, Copy)] #[non_exhaustive] pub struct AnyTypeInfo { /// The name of the type. pub name: RawStr, - /// The hash of the type. - #[cfg(feature = "doc")] - #[allow(unused)] - // TODO: will be used to lookup meta for a given type when generating documentation. - pub(crate) hash: Hash, } impl AnyTypeInfo { /// Private constructor, use at your own risk. #[doc(hidden)] - pub fn new(name: RawStr, #[cfg_attr(not(feature = "doc"), allow(unused))] hash: Hash) -> Self { - Self { - name, - #[cfg(feature = "doc")] - hash, - } - } - - /// Private constructor, use at your own risk which can optionally construct a hash from a provided function. - #[doc(hidden)] - pub fn new_from( - name: RawStr, - #[cfg_attr(not(feature = "doc"), allow(unused))] hash: fn() -> Hash, - ) -> Self { - Self { - name, - #[cfg(feature = "doc")] - hash: hash(), - } + pub fn new(name: RawStr) -> Self { + Self { name } } } diff --git a/tests/tests/bug_344.rs b/tests/tests/bug_344.rs index 8c84cd935..cd867ca45 100644 --- a/tests/tests/bug_344.rs +++ b/tests/tests/bug_344.rs @@ -181,7 +181,7 @@ impl TypeOf for GuardCheck { #[inline] fn type_info() -> TypeInfo { - TypeInfo::Any(AnyTypeInfo::new(::BASE_NAME, ::type_hash())) + TypeInfo::Any(AnyTypeInfo::new(::BASE_NAME)) } } diff --git a/tests/tests/compiler_docs.rs b/tests/tests/compiler_docs.rs index 9af733844..60f433d62 100644 --- a/tests/tests/compiler_docs.rs +++ b/tests/tests/compiler_docs.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; use rune::compile::{Location, Item, CompileVisitor}; -use rune::{Context, Diagnostics}; +use rune::{Context, Diagnostics, Hash}; use rune_tests::sources; struct DocVisitor { @@ -9,11 +9,11 @@ struct DocVisitor { } impl CompileVisitor for DocVisitor { - fn visit_doc_comment(&mut self, _: Location, item: &Item, doc: &str) { + fn visit_doc_comment(&mut self, _: Location, item: &Item, _: Hash, doc: &str) { self.collected.entry(item.to_string()).or_default().push(doc.to_string()); } - fn visit_field_doc_comment(&mut self, _: Location, item: &Item, field: &str, doc: &str) { + fn visit_field_doc_comment(&mut self, _: Location, item: &Item, _: Hash, field: &str, doc: &str) { self.collected.entry(format!("{item}.{field}")).or_default().push(doc.to_string()); } }