diff --git a/examples/abi_supertraits/src/main.sw b/examples/abi_supertraits/src/main.sw index 1e2715f284f..161a2a12eb5 100644 --- a/examples/abi_supertraits/src/main.sw +++ b/examples/abi_supertraits/src/main.sw @@ -1,5 +1,10 @@ contract; +struct Foo {} +impl ABIsupertrait for Foo { + fn foo() {} +} + trait ABIsupertrait { fn foo(); } diff --git a/forc-plugins/forc-doc/src/assets/ayu.css b/forc-plugins/forc-doc/src/assets/ayu.css index dc1cf9d418b..a2883435f97 100644 --- a/forc-plugins/forc-doc/src/assets/ayu.css +++ b/forc-plugins/forc-doc/src/assets/ayu.css @@ -31,14 +31,12 @@ h1.fqn a { h4 { border: none; } -.in-band { - background-color: #0f1419; -} .docblock code { color: #ffb454; } .code-header { color: #e6e1cf; + margin-left: 1em; } .docblock pre > code, pre > code { @@ -189,7 +187,7 @@ nav.main .separator { border: 1px solid #5c6773; } a { - color: #c5c5c5; + color: #c5c5c5; } .sidebar h2 a, .sidebar h3 a { @@ -491,4 +489,4 @@ details.dir-entry summary:focus { } #all-types { background-color: #14191f; -} \ No newline at end of file +} diff --git a/forc-plugins/forc-doc/src/assets/swaydoc.css b/forc-plugins/forc-doc/src/assets/swaydoc.css index 1ce46119f83..d6c3b3c3d10 100644 --- a/forc-plugins/forc-doc/src/assets/swaydoc.css +++ b/forc-plugins/forc-doc/src/assets/swaydoc.css @@ -152,7 +152,8 @@ h2, border-bottom: 1px solid var(--headings-border-bottom-color); } h3.code-header { - font-size: 1.125rem; + font-size: 1em; + font-weight: 600; } h4.code-header { font-size: 1rem; @@ -160,7 +161,6 @@ h4.code-header { .code-header { font-weight: 600; border-bottom-style: none; - margin: 0; padding: 0; margin-top: 0.6em; margin-bottom: 0.4em; @@ -249,7 +249,7 @@ ol ol { margin-bottom: 0.625em; } p { - margin: 0 0 .6em 0; + margin: 0 0 0.6em 0; } summary { outline: none; @@ -375,13 +375,13 @@ nav.sub { padding-left: 24px; } .sidebar .location { - border: 1px solid; - font-size: 17px; - margin: 30px 10px 20px 10px; - text-align: center; - word-wrap: break-word; - font-weight: inherit; - padding: 0; + border: 1px solid; + font-size: 17px; + margin: 30px 10px 20px 10px; + text-align: center; + word-wrap: break-word; + font-weight: inherit; + padding: 0; } .swaydoc.source .sidebar { width: 50px; @@ -639,7 +639,7 @@ h2.location a { } .content .in-band { flex-grow: 1; - margin: 0px; + margin-top: 0px; padding: 0px; overflow-wrap: break-word; overflow-wrap: anywhere; @@ -815,6 +815,7 @@ a { .in-band:hover > .anchor, .impl:hover > .anchor, .method.trait-impl:hover > .anchor, +.method.has-srclink:hover > .anchor, .type.trait-impl:hover > .anchor, .associatedconstant.trait-impl:hover > .anchor, .associatedtype.trait-impl:hover > .anchor { @@ -824,7 +825,6 @@ a { .anchor { display: none; position: absolute; - left: -0.5em; background: none !important; } .anchor.field { @@ -1865,9 +1865,6 @@ details.swaydoc-toggle[open] > summary.hideme::after { .method-toggle[open] { margin-bottom: 2em; } -.implementors-toggle[open] { - margin-bottom: 2em; -} #trait-implementations-list .method-toggle, #synthetic-implementations-list .method-toggle, #blanket-implementations-list .method-toggle { @@ -2002,6 +1999,6 @@ details.swaydoc-toggle[open] > summary.hideme::after { .example-links ul { margin-bottom: 0; } -#all-types>p { - margin: 5px 0; +#all-types > p { + margin: 5px 0; } diff --git a/forc-plugins/forc-doc/src/doc/descriptor.rs b/forc-plugins/forc-doc/src/doc/descriptor.rs index 05d790d0d83..68c19f9b3e3 100644 --- a/forc-plugins/forc-doc/src/doc/descriptor.rs +++ b/forc-plugins/forc-doc/src/doc/descriptor.rs @@ -30,7 +30,7 @@ pub(crate) enum Descriptor { } impl Descriptor { - /// Decides whether a [TyDecl] is [Descriptor::Documentable]. + /// Decides whether a [TyDecl] is [Descriptor::Documentable] and returns a [Document] if so. pub(crate) fn from_typed_decl( decl_engine: &DeclEngine, ty_decl: &ty::TyDecl, @@ -70,6 +70,7 @@ impl Descriptor { attrs_opt: attrs_opt.clone(), item_context: ItemContext { context_opt: context, + impl_traits: None, }, }, raw_attributes: attrs_opt, @@ -106,6 +107,7 @@ impl Descriptor { attrs_opt: attrs_opt.clone(), item_context: ItemContext { context_opt: context, + impl_traits: None, }, }, raw_attributes: attrs_opt, @@ -153,6 +155,7 @@ impl Descriptor { attrs_opt: attrs_opt.clone(), item_context: ItemContext { context_opt: context, + impl_traits: None, }, }, raw_attributes: attrs_opt, @@ -194,6 +197,7 @@ impl Descriptor { attrs_opt: attrs_opt.clone(), item_context: ItemContext { context_opt: context, + impl_traits: None, }, }, raw_attributes: attrs_opt, @@ -228,6 +232,7 @@ impl Descriptor { attrs_opt: attrs_opt.clone(), item_context: ItemContext { context_opt: context, + impl_traits: None, }, }, raw_attributes: attrs_opt, @@ -285,7 +290,10 @@ impl Descriptor { fn_decl.span.as_str(), )), attrs_opt: attrs_opt.clone(), - item_context: ItemContext { context_opt: None }, + item_context: ItemContext { + context_opt: None, + impl_traits: None, + }, }, raw_attributes: attrs_opt, })) @@ -315,7 +323,10 @@ impl Descriptor { const_decl.span.as_str(), ), attrs_opt: attrs_opt.clone(), - item_context: ItemContext { context_opt: None }, + item_context: ItemContext { + context_opt: None, + impl_traits: None, + }, }, raw_attributes: attrs_opt, })) diff --git a/forc-plugins/forc-doc/src/doc/mod.rs b/forc-plugins/forc-doc/src/doc/mod.rs index e4022d80ff3..0acb8dc36d6 100644 --- a/forc-plugins/forc-doc/src/doc/mod.rs +++ b/forc-plugins/forc-doc/src/doc/mod.rs @@ -6,8 +6,9 @@ use anyhow::Result; use std::option::Option; use sway_core::{ decl_engine::DeclEngine, - language::ty::{TyAstNodeContent, TyProgram, TySubmodule}, + language::ty::{TyAstNodeContent, TyDecl, TyImplTrait, TyProgram, TySubmodule}, }; +use sway_types::Spanned; mod descriptor; pub mod module; @@ -63,17 +64,22 @@ impl Document { ) -> Result { // the first module prefix will always be the project name let mut docs: Documentation = Default::default(); + let mut impl_traits: Vec = Vec::new(); for ast_node in &typed_program.root.all_nodes { if let TyAstNodeContent::Declaration(ref decl) = ast_node.content { - let desc = Descriptor::from_typed_decl( - decl_engine, - decl, - ModuleInfo::from_ty_module(vec![project_name.to_owned()], None), - document_private_items, - )?; + if let TyDecl::ImplTrait(impl_trait) = decl { + impl_traits.push(decl_engine.get_impl_trait(&impl_trait.decl_id)) + } else { + let desc = Descriptor::from_typed_decl( + decl_engine, + decl, + ModuleInfo::from_ty_module(vec![project_name.to_owned()], None), + document_private_items, + )?; - if let Descriptor::Documentable(doc) = desc { - docs.push(doc) + if let Descriptor::Documentable(doc) = desc { + docs.push(doc) + } } } } @@ -89,18 +95,48 @@ impl Document { decl_engine, typed_submodule, &mut docs, + &mut impl_traits, &module_prefix, document_private_items, )?; } } + // match for the spans to add the impl_traits to their corresponding doc: + // currently this compares the spans as str, but this needs to change + // to compare the actual types + if !impl_traits.is_empty() { + for doc in &mut docs { + let mut impl_vec: Vec = Vec::new(); + + match doc.item_body.ty_decl { + TyDecl::StructDecl(ref struct_decl) => { + for impl_trait in &impl_traits { + if struct_decl.name.as_str() + == impl_trait.implementing_for.span.as_str() + && struct_decl.name.as_str() + != impl_trait.trait_name.suffix.span().as_str() + { + impl_vec.push(impl_trait.clone()); + } + } + } + _ => continue, + } + + if !impl_vec.is_empty() { + doc.item_body.item_context.impl_traits = Some(impl_vec); + } + } + } + Ok(docs) } fn from_ty_submodule( decl_engine: &DeclEngine, typed_submodule: &TySubmodule, docs: &mut Documentation, + impl_traits: &mut Vec, module_prefix: &ModuleInfo, document_private_items: bool, ) -> Result<()> { @@ -110,23 +146,29 @@ impl Document { .push(typed_submodule.mod_name_span.as_str().to_owned()); for ast_node in &typed_submodule.module.all_nodes { if let TyAstNodeContent::Declaration(ref decl) = ast_node.content { - let desc = Descriptor::from_typed_decl( - decl_engine, - decl, - new_submodule_prefix.clone(), - document_private_items, - )?; + if let TyDecl::ImplTrait(impl_trait) = decl { + impl_traits.push(decl_engine.get_impl_trait(&impl_trait.decl_id)) + } else { + let desc = Descriptor::from_typed_decl( + decl_engine, + decl, + new_submodule_prefix.clone(), + document_private_items, + )?; - if let Descriptor::Documentable(doc) = desc { - docs.push(doc) + if let Descriptor::Documentable(doc) = desc { + docs.push(doc) + } } } } + for (_, submodule) in &typed_submodule.module.submodules { Document::from_ty_submodule( decl_engine, submodule, docs, + impl_traits, &new_submodule_prefix, document_private_items, )?; diff --git a/forc-plugins/forc-doc/src/render/item/components.rs b/forc-plugins/forc-doc/src/render/item/components.rs index 31861d8ba02..1c0d20b6e3a 100644 --- a/forc-plugins/forc-doc/src/render/item/components.rs +++ b/forc-plugins/forc-doc/src/render/item/components.rs @@ -104,8 +104,9 @@ impl Renderable for ItemBody { let decl_ty = ty_decl.doc_name(); let block_title = ty_decl.as_block_title(); let sidebar = sidebar.render(render_plan.clone())?; - let item_context = (item_context.context_opt.is_some()) - .then(|| -> Result> { item_context.render(render_plan.clone()) }); + let item_context = (item_context.context_opt.is_some() + || item_context.impl_traits.is_some()) + .then(|| -> Result> { item_context.render(render_plan.clone()) }); let sway_hjs = module_info.to_html_shorthand_path_string("assets/highlight.js"); let rendered_module_anchors = module_info.get_anchors()?; diff --git a/forc-plugins/forc-doc/src/render/item/context.rs b/forc-plugins/forc-doc/src/render/item/context.rs index f6a7b898dca..3d346f07875 100644 --- a/forc-plugins/forc-doc/src/render/item/context.rs +++ b/forc-plugins/forc-doc/src/render/item/context.rs @@ -6,10 +6,12 @@ use crate::{ }, RenderPlan, }; -use anyhow::{anyhow, Result}; +use anyhow::Result; use horrorshow::{box_html, Raw, RenderBox, Template}; use std::{collections::BTreeMap, fmt::Write}; -use sway_core::language::ty::{TyEnumVariant, TyStorageField, TyStructField, TyTraitFn}; +use sway_core::language::ty::{ + TyEnumVariant, TyImplTrait, TyStorageField, TyStructField, TyTraitFn, TyTraitItem, +}; /// The actual context of the item displayed by [ItemContext]. /// This uses [ContextType] to determine how to represent the context of an item. @@ -44,6 +46,7 @@ impl Context { impl Renderable for Context { fn render(self, render_plan: RenderPlan) -> Result> { let mut rendered_list: Vec = Vec::new(); + let mut is_method_block = false; match self.context_type { ContextType::StructFields(fields) => { for field in fields { @@ -130,6 +133,7 @@ impl Renderable for Context { } } ContextType::RequiredMethods(methods) => { + is_method_block = true; for method in methods { let mut fn_sig = format!("fn {}(", method.name.as_str()); for param in &method.parameters { @@ -153,73 +157,103 @@ impl Renderable for Context { } write!(fn_sig, ") -> {}", method.return_type_span.as_str())?; let multiline = fn_sig.chars().count() >= 60; - + let fn_sig = format!("fn {}(", method.name); let method_id = format!("tymethod.{}", method.name.as_str()); - rendered_list.push(box_html! { - div(class="methods") { - div(id=&method_id, class="method has-srclink") { - h4(class="code-header") { - : "fn "; - a(class="fnname", href=format!("{IDENTITY}{method_id}")) { - : method.name.as_str(); + let method_attrs = method.attributes.clone(); + + let rendered_method = box_html! { + div(id=&method_id, class="method has-srclink") { + a(href=format!("{IDENTITY}{method_id}"), class="anchor"); + h4(class="code-header") { + : "fn "; + a(class="fnname", href=format!("{IDENTITY}{method_id}")) { + : method.name.as_str(); + } + : "("; + @ if multiline { + @ for param in &method.parameters { + br; + : " "; + @ if param.is_reference { + : "ref"; + } + @ if param.is_mutable { + : "mut "; + } + @ if param.is_self() { + : "self," + } else { + : param.name.as_str(); + : ": "; + : param.type_argument.span.as_str(); + : "," + } } - : "("; - @ if multiline { - @ for param in &method.parameters { - br; - : " "; - @ if param.is_reference { - : "ref"; - } - @ if param.is_mutable { - : "mut "; - } - @ if param.is_self() { - : "self," - } else { - : param.name.as_str(); - : ": "; - : param.type_argument.span.as_str(); - : "," - } + br; + : ")"; + } else { + @ for param in &method.parameters { + @ if param.is_reference { + : "ref"; } - br; - : ")"; - } else { - @ for param in &method.parameters { - @ if param.is_reference { - : "ref"; - } - @ if param.is_mutable { - : "mut "; - } - @ if param.is_self() { - : "self" - } else { - : param.name.as_str(); - : ": "; - : param.type_argument.span.as_str(); - } - @ if param.name.as_str() - != method.parameters.last() - .expect("no last element in trait method parameters list") - .name.as_str() { - : ", "; - } + @ if param.is_mutable { + : "mut "; + } + @ if param.is_self() { + : "self" + } else { + : param.name.as_str(); + : ": "; + : param.type_argument.span.as_str(); + } + @ if param.name.as_str() + != method.parameters.last() + .expect("no last element in trait method parameters list") + .name.as_str() { + : ", "; } - : ") -> "; } + : ")"; + } + @ if !method.return_type_span.as_str().contains(&fn_sig) { + : " -> "; : method.return_type_span.as_str(); } } } - }.into_string()?); + }.into_string()?; + + rendered_list.push( + box_html! { + @ if !method_attrs.is_empty() { + details(class="swaydoc-toggle open") { + summary { + : Raw(rendered_method); + } + div(class="docblock") { + : Raw(method_attrs.to_html_string()); + } + } + } else { + : Raw(rendered_method); + } + } + .into_string()?, + ); } } }; Ok(box_html! { - @ for item in rendered_list { - : Raw(item); + @ if is_method_block { + div(class="methods") { + @ for item in rendered_list { + : Raw(item); + } + } + } else { + @ for item in rendered_list { + : Raw(item); + } } }) } @@ -227,7 +261,10 @@ impl Renderable for Context { #[derive(Clone, Debug)] /// The context section of an item that appears in the page [ItemBody]. pub(crate) struct ItemContext { + /// [Context] can be fields on a struct, variants of an enum, etc. pub(crate) context_opt: Option, + /// The traits implemented for this type. + pub(crate) impl_traits: Option>, // TODO: All other Implementation types, eg // implementations on foreign types, method implementations, etc. } @@ -309,23 +346,212 @@ impl ItemContext { } impl Renderable for ItemContext { fn render(self, render_plan: RenderPlan) -> Result> { - let (title, rendered_list) = match self.context_opt { + let context_opt = match self.context_opt { Some(context) => { let title = context.context_type.as_block_title(); - let rendered_list = context.render(render_plan)?; - Ok((title, rendered_list)) + let rendered_list = context.render(render_plan.clone())?; + let lct = title.html_title_string(); + Some( + box_html! { + h2(id=&lct, class=format!("{} small-section-header", &lct)) { + : title.as_str(); + a(class="anchor", href=format!("{IDENTITY}{lct}")); + } + : rendered_list; + } + .into_string()?, + ) } - None => Err(anyhow!( - "Safeguard against render call on empty context failed." - )), - }?; - let lct = title.html_title_string(); + None => None, + }; + + let impl_traits = match self.impl_traits { + Some(impl_traits) => { + let mut impl_vec: Vec<_> = Vec::new(); + for impl_trait in impl_traits { + impl_vec.push(impl_trait.render(render_plan.clone())?) + } + Some(impl_vec) + } + None => None, + }; + Ok(box_html! { - h2(id=&lct, class=format!("{} small-section-header", &lct)) { - : title.as_str(); - a(class="anchor", href=format!("{IDENTITY}{lct}")); + @ if let Some(context) = context_opt { + : Raw(context); + } + @ if impl_traits.is_some() { + h2(id="trait-implementations", class="small-section-header") { + : "Trait Implementations"; + a(href=format!("{IDENTITY}trait-implementations"), class="anchor"); + } + div(id="trait-implementations-list") { + @ for impl_trait in impl_traits.unwrap() { + : impl_trait; + } + } + } + }) + } +} +impl Renderable for TyImplTrait { + fn render(self, render_plan: RenderPlan) -> Result> { + let TyImplTrait { + trait_name, + impl_type_parameters: _, + trait_type_arguments: _, + items, + trait_decl_ref: _, + implementing_for, + .. + } = self; + + let mut rendered_items = Vec::new(); + for item in items { + rendered_items.push(item.render(render_plan.clone())?) + } + + let impl_for = box_html! { + div(id=format!("impl-{}", trait_name.suffix.as_str()), class="impl has-srclink") { + a(href=format!("{IDENTITY}impl-{}", trait_name.suffix.as_str()), class="anchor"); + h3(class="code-header in-band") { + : "impl "; + : trait_name.suffix.as_str(); // TODO: add links + : " for "; + : implementing_for.span.as_str(); + } + } + } + .into_string()?; + + Ok(box_html! { + // check if the implementation has methods + @ if !rendered_items.is_empty() { + details(class="swaydoc-toggle implementors-toggle") { + summary { + : Raw(impl_for); + } + div(class="impl-items") { + @ for item in rendered_items { + : item; + } + } + } + } else { + : Raw(impl_for); + } + }) + } +} +impl Renderable for TyTraitItem { + fn render(self, render_plan: RenderPlan) -> Result> { + let item = match self { + TyTraitItem::Fn(item_fn) => item_fn, + TyTraitItem::Constant(_) => unimplemented!("Constant Trait items not yet implemented"), + }; + let method = render_plan.decl_engine.get_function(item.id()); + let attributes = method.attributes.to_html_string(); + + let mut fn_sig = format!("fn {}(", method.name.as_str()); + for param in &method.parameters { + let mut param_str = String::new(); + if param.is_reference { + write!(param_str, "ref ")?; + } + if param.is_mutable { + write!(param_str, "mut ")?; + } + if param.is_self() { + write!(param_str, "self,")?; + } else { + write!( + fn_sig, + "{} {},", + param.name.as_str(), + param.type_argument.span.as_str() + )?; + } + } + write!(fn_sig, ") -> {}", method.return_type.span.as_str())?; + let multiline = fn_sig.chars().count() >= 60; + + let method_id = format!("method.{}", method.name.as_str()); + + let impl_list = box_html! { + div(id=format!("method.{}", item.name().as_str()), class="method trait-impl") { + a(href=format!("{IDENTITY}method.{}", item.name().as_str()), class="anchor"); + h4(class="code-header") { + : "fn "; + a(class="fnname", href=format!("{IDENTITY}{method_id}")) { + : method.name.as_str(); + } + : "("; + @ if multiline { + @ for param in &method.parameters { + br; + : " "; + @ if param.is_reference { + : "ref"; + } + @ if param.is_mutable { + : "mut "; + } + @ if param.is_self() { + : "self," + } else { + : param.name.as_str(); + : ": "; + : param.type_argument.span.as_str(); + : "," + } + } + br; + : ")"; + } else { + @ for param in &method.parameters { + @ if param.is_reference { + : "ref"; + } + @ if param.is_mutable { + : "mut "; + } + @ if param.is_self() { + : "self" + } else { + : param.name.as_str(); + : ": "; + : param.type_argument.span.as_str(); + } + @ if param.name.as_str() + != method.parameters.last() + .expect("no last element in trait method parameters list") + .name.as_str() { + : ", "; + } + } + : ")"; + } + @ if method.span.as_str().contains("->") { + : " -> "; + : method.return_type.span.as_str(); + } + } + } + }.into_string()?; + + Ok(box_html! { + @ if !attributes.is_empty() { + details(class="swaydoc-toggle method-toggle", open) { + summary { + : Raw(impl_list); + } + div(class="doc-block") { + : Raw(attributes); + } + } + } else { + : Raw(impl_list); } - : rendered_list; }) } } diff --git a/forc-plugins/forc-doc/src/tests/data/impl_traits/Forc.lock b/forc-plugins/forc-doc/src/tests/data/impl_traits/Forc.lock new file mode 100644 index 00000000000..c2d86f47208 --- /dev/null +++ b/forc-plugins/forc-doc/src/tests/data/impl_traits/Forc.lock @@ -0,0 +1,8 @@ +[[package]] +name = 'core' +source = 'path+from-root-0C306348E3D846F3' + +[[package]] +name = 'impl_traits' +source = 'member' +dependencies = ['core'] diff --git a/forc-plugins/forc-doc/src/tests/data/impl_traits/Forc.toml b/forc-plugins/forc-doc/src/tests/data/impl_traits/Forc.toml new file mode 100644 index 00000000000..40cad41fc6d --- /dev/null +++ b/forc-plugins/forc-doc/src/tests/data/impl_traits/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "lib.sw" +license = "Apache-2.0" +name = "impl_traits" + +[dependencies] +core = { path = "../../../../../../sway-lib-core" } diff --git a/forc-plugins/forc-doc/src/tests/data/impl_traits/src/bar.sw b/forc-plugins/forc-doc/src/tests/data/impl_traits/src/bar.sw new file mode 100644 index 00000000000..11cc0c6c84f --- /dev/null +++ b/forc-plugins/forc-doc/src/tests/data/impl_traits/src/bar.sw @@ -0,0 +1,16 @@ +library; + +use ::foo::{Foo, Baz}; + +pub struct Bar {} + +impl Foo for Bar { + /// something more about foo(); + fn foo() {} +} +impl Baz for Bar {} +impl Bar { + fn foo_bar() { + Self::foo() + } +} \ No newline at end of file diff --git a/forc-plugins/forc-doc/src/tests/data/impl_traits/src/foo.sw b/forc-plugins/forc-doc/src/tests/data/impl_traits/src/foo.sw new file mode 100644 index 00000000000..790a5ad7777 --- /dev/null +++ b/forc-plugins/forc-doc/src/tests/data/impl_traits/src/foo.sw @@ -0,0 +1,7 @@ +library; + +pub trait Foo { + /// something about foo... + fn foo(); +} +pub trait Baz {} \ No newline at end of file diff --git a/forc-plugins/forc-doc/src/tests/data/impl_traits/src/lib.sw b/forc-plugins/forc-doc/src/tests/data/impl_traits/src/lib.sw new file mode 100644 index 00000000000..dc83faa41ce --- /dev/null +++ b/forc-plugins/forc-doc/src/tests/data/impl_traits/src/lib.sw @@ -0,0 +1,4 @@ +library; + +mod foo; +mod bar; \ No newline at end of file