Skip to content

Commit

Permalink
feat: Add primitive types to forc-doc (#6032)
Browse files Browse the repository at this point in the history
## Description

Closes #5968

Adds a new "Primitive" type in the sidebar and to the search bar. The
descriptions are hard coded since there is nowhere to parse the
documentation from. The list of primitives is also hardcoded, so we will
need to update forc-doc if/when new primitives are added to the
language.

Primitives will be documented show up in the side bar for any package
that has implementations for a primitive. This is why it currently shows
up for both std and core.

Side bar nav:
![May-27-2024
16-54-34](https://github.com/FuelLabs/sway/assets/47993817/30efc6d9-43d3-455b-baac-5daf7b19955f)

Search bar:
![May-27-2024
16-54-53](https://github.com/FuelLabs/sway/assets/47993817/9e988c12-f84d-4012-bd88-a646514841ce)

## Checklist

- [x] I have linked to any relevant issues.
- [x] I have commented my code, particularly in hard-to-understand
areas.
- [x] I have updated the documentation where relevant (API docs, the
reference, and the Sway book).
- [ ] If my change requires substantial documentation changes, I have
[requested support from the DevRel
team](https://github.com/FuelLabs/devrel-requests/issues/new/choose)
- [ ] I have added tests that prove my fix is effective or that my
feature works.
- [ ] I have added (or requested a maintainer to add) the necessary
`Breaking*` or `New Feature` labels where relevant.
- [x] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [x] I have requested a review from the relevant team or maintainers.

---------

Co-authored-by: Joshua Batty <joshpbatty@gmail.com>
  • Loading branch information
sdankel and JoshuaBatty authored May 28, 2024
1 parent 450d13e commit f342f52
Show file tree
Hide file tree
Showing 13 changed files with 410 additions and 129 deletions.
84 changes: 71 additions & 13 deletions forc-plugins/forc-doc/src/doc/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{
item::{
components::*,
context::{Context, ContextType, ItemContext},
documentable_type::DocumentableType,
},
util::format::{code_block::trim_fn_body, docstring::DocStrings},
},
Expand All @@ -13,7 +14,10 @@ use anyhow::Result;
use sway_core::{
decl_engine::*,
language::ty::{self, TyTraitFn, TyTraitInterfaceItem},
Engines, TypeInfo,
};
use sway_types::{integer_bits::IntegerBits, Ident};
use swayfmt::parse;

trait RequiredMethods {
fn to_methods(&self, decl_engine: &DeclEngine) -> Vec<TyTraitFn>;
Expand All @@ -33,14 +37,13 @@ pub(crate) enum Descriptor {
}

impl Descriptor {
/// Decides whether a [TyDecl] is [Descriptor::Documentable] and returns a [Document] if so.
/// Decides whether a [ty::TyDecl] is [Descriptor::Documentable] and returns a [Document] if so.
pub(crate) fn from_typed_decl(
decl_engine: &DeclEngine,
ty_decl: &ty::TyDecl,
module_info: ModuleInfo,
document_private_items: bool,
) -> Result<Self> {
use swayfmt::parse;
const CONTRACT_STORAGE: &str = "Contract Storage";
match ty_decl {
ty::TyDecl::StructDecl(ty::StructDecl { decl_id, .. }) => {
Expand All @@ -65,7 +68,7 @@ impl Descriptor {
},
item_body: ItemBody {
module_info,
ty_decl: ty_decl.clone(),
ty: DocumentableType::Declared(ty_decl.clone()),
item_name,
code_str: parse::parse_format::<sway_ast::ItemStruct>(
struct_decl.span.as_str(),
Expand Down Expand Up @@ -102,7 +105,7 @@ impl Descriptor {
},
item_body: ItemBody {
module_info,
ty_decl: ty_decl.clone(),
ty: DocumentableType::Declared(ty_decl.clone()),
item_name,
code_str: parse::parse_format::<sway_ast::ItemEnum>(
enum_decl.span.as_str(),
Expand Down Expand Up @@ -150,7 +153,7 @@ impl Descriptor {
},
item_body: ItemBody {
module_info,
ty_decl: ty_decl.clone(),
ty: DocumentableType::Declared(ty_decl.clone()),
item_name,
code_str: parse::parse_format::<sway_ast::ItemTrait>(
trait_decl.span.as_str(),
Expand Down Expand Up @@ -194,7 +197,7 @@ impl Descriptor {
},
item_body: ItemBody {
module_info,
ty_decl: ty_decl.clone(),
ty: DocumentableType::Declared(ty_decl.clone()),
item_name,
code_str: parse::parse_format::<sway_ast::ItemAbi>(abi_decl.span.as_str())?,
attrs_opt: attrs_opt.clone(),
Expand Down Expand Up @@ -227,7 +230,7 @@ impl Descriptor {
},
item_body: ItemBody {
module_info,
ty_decl: ty_decl.clone(),
ty: DocumentableType::Declared(ty_decl.clone()),
item_name,
code_str: parse::parse_format::<sway_ast::ItemStorage>(
storage_decl.span.as_str(),
Expand Down Expand Up @@ -259,7 +262,7 @@ impl Descriptor {
},
item_body: ItemBody {
module_info,
ty_decl: ty_decl.clone(),
ty: DocumentableType::Declared(ty_decl.clone()),
item_name,
code_str: trim_fn_body(parse::parse_format::<sway_ast::ItemFn>(
fn_decl.span.as_str(),
Expand Down Expand Up @@ -292,16 +295,13 @@ impl Descriptor {
},
item_body: ItemBody {
module_info,
ty_decl: ty_decl.clone(),
ty: DocumentableType::Declared(ty_decl.clone()),
item_name,
code_str: parse::parse_format::<sway_ast::ItemConst>(
const_decl.span.as_str(),
)?,
attrs_opt: attrs_opt.clone(),
item_context: ItemContext {
context_opt: None,
..Default::default()
},
item_context: Default::default(),
},
raw_attributes: attrs_opt,
}))
Expand All @@ -310,4 +310,62 @@ impl Descriptor {
_ => Ok(Descriptor::NonDocumentable),
}
}

/// Decides whether a [TypeInfo] is [Descriptor::Documentable] and returns a [Document] if so.
pub(crate) fn from_type_info(
type_info: &TypeInfo,
engines: &Engines,
module_info: ModuleInfo,
) -> Result<Self> {
// Only primitive types will result in a documentable item. All other type documentation should come
// from the a declaration. Since primitive types do not have sway declarations, we can only generate
// documentation from their implementations.
let item_name = Ident::new_no_span(format!("{}", engines.help_out(type_info)));
// Build a fake module info for the primitive type.
let module_info = ModuleInfo {
module_prefixes: vec![module_info.project_name().into()],
attributes: None,
};
// TODO: Find a way to add descriptions without hardcoding them.
let description = match type_info {
TypeInfo::StringSlice => "string slice",
TypeInfo::StringArray(_) => "fixed-length string",
TypeInfo::Boolean => "Boolean true or false",
TypeInfo::B256 => "256 bits (32 bytes), i.e. a hash",
TypeInfo::UnsignedInteger(bits) => match bits {
IntegerBits::Eight => "8-bit unsigned integer",
IntegerBits::Sixteen => "16-bit unsigned integer",
IntegerBits::ThirtyTwo => "32-bit unsigned integer",
IntegerBits::SixtyFour => "64-bit unsigned integer",
IntegerBits::V256 => "256-bit unsigned integer",
},
_ => return Ok(Descriptor::NonDocumentable),
};
let attrs_opt = Some(description.to_string());

match type_info {
TypeInfo::StringSlice
| TypeInfo::StringArray(_)
| TypeInfo::Boolean
| TypeInfo::B256
| TypeInfo::UnsignedInteger(_) => Ok(Descriptor::Documentable(Document {
module_info: module_info.clone(),
item_header: ItemHeader {
module_info: module_info.clone(),
friendly_name: "primitive",
item_name: item_name.clone(),
},
item_body: ItemBody {
module_info,
ty: DocumentableType::Primitive(type_info.clone()),
item_name: item_name.clone(),
code_str: item_name.clone().to_string(),
attrs_opt: attrs_opt.clone(),
item_context: Default::default(),
},
raw_attributes: attrs_opt,
})),
_ => Ok(Descriptor::NonDocumentable),
}
}
}
99 changes: 56 additions & 43 deletions forc-plugins/forc-doc/src/doc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use crate::{
doc::{descriptor::Descriptor, module::ModuleInfo},
render::{
item::{components::*, context::DocImplTrait},
item::{components::*, context::DocImplTrait, documentable_type::DocumentableType},
link::DocLink,
util::format::docstring::{create_preview, DocStrings},
},
Expand All @@ -18,7 +18,7 @@ use sway_core::{
language::ty::{TyAstNodeContent, TyDecl, TyImplTrait, TyModule, TyProgram, TySubmodule},
Engines,
};
use sway_types::{BaseIdent, Named, Spanned};
use sway_types::BaseIdent;

mod descriptor;
pub mod module;
Expand Down Expand Up @@ -72,57 +72,60 @@ impl Documentation {
})
.collect::<HashMap<BaseIdent, ModuleInfo>>();

// Add one documentation page for each primitive type that has an implementation.
for (impl_trait, module_info) in impl_traits.iter() {
let impl_for_type = engines.te().get(impl_trait.implementing_for.type_id);
if let Ok(Descriptor::Documentable(doc)) =
Descriptor::from_type_info(impl_for_type.as_ref(), engines, module_info.clone())
{
if !docs.iter().any(|existing_doc| *existing_doc == doc) {
docs.push(doc);
}
}
}

// 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
for doc in docs.iter_mut() {
let mut impl_trait_vec: Vec<DocImplTrait> = Vec::new();
let mut inherent_impl_vec: Vec<DocImplTrait> = Vec::new();

let decl_name = match doc.item_body.ty_decl {
TyDecl::StructDecl(ref decl) => {
let struct_decl = engines.de().get_struct(&decl.decl_id);
Some(struct_decl.name().to_string())
}
TyDecl::EnumDecl(ref decl) => {
let enum_decl = engines.de().get_enum(&decl.decl_id);
Some(enum_decl.name().to_string())
}
_ => None,
};

if let Some(decl_name) = decl_name {
for (impl_trait, module_info) in impl_traits.iter_mut() {
// Check if this implementation is for this struct/enum.
if decl_name.as_str() == impl_trait.implementing_for.span.as_str() {
let module_info_override = if let Some(decl_module_info) =
trait_decls.get(&impl_trait.trait_name.suffix)
{
Some(decl_module_info.module_prefixes.clone())
} else {
impl_trait.trait_name = impl_trait
.trait_name
.to_fullpath(engines, &typed_program.root.namespace);
None
};
// Check for implementations of the current struct/enum/primitive.
match doc.item_body.ty {
DocumentableType::Declared(TyDecl::StructDecl(_))
| DocumentableType::Declared(TyDecl::EnumDecl(_))
| DocumentableType::Primitive(_) => {
let item_name = doc.item_header.item_name.as_str().to_string();
for (impl_trait, _) in impl_traits.iter_mut() {
// Check if this implementation is for this struct/enum.
if item_name.as_str() == impl_trait.implementing_for.span.as_str() {
let module_info_override = if let Some(decl_module_info) =
trait_decls.get(&impl_trait.trait_name.suffix)
{
Some(decl_module_info.module_prefixes.clone())
} else {
impl_trait.trait_name = impl_trait
.trait_name
.to_fullpath(engines, &typed_program.root.namespace);
None
};

if decl_name.as_str() == impl_trait.trait_name.suffix.span().as_str() {
// If the trait name is the same as the declaration's name, it's an inherent implementation.
inherent_impl_vec.push(DocImplTrait {
impl_for_module: module_info.clone(),
impl_trait: impl_trait.clone(),
module_info_override: None,
});
} else {
// Otherwise, it's an implementation for a trait.
impl_trait_vec.push(DocImplTrait {
impl_for_module: module_info.clone(),
let doc_impl_trait = DocImplTrait {
impl_for_module: doc.module_info.clone(),
impl_trait: impl_trait.clone(),
module_info_override,
});
};

if doc_impl_trait.is_inherent() {
inherent_impl_vec.push(doc_impl_trait);
} else {
impl_trait_vec.push(doc_impl_trait);
}
}
}
}
_ => {}
}

if !impl_trait_vec.is_empty() {
Expand Down Expand Up @@ -203,6 +206,7 @@ impl Documentation {
Ok(())
}
}

/// A finalized Document ready to be rendered. We want to retain all
/// information including spans, fields on structs, variants on enums etc.
#[derive(Clone, Debug)]
Expand All @@ -212,16 +216,17 @@ pub struct Document {
pub item_body: ItemBody,
pub raw_attributes: Option<String>,
}

impl Document {
/// Creates an HTML file name from the [Document].
pub fn html_filename(&self) -> String {
use sway_core::language::ty::TyDecl::StorageDecl;
let name = match &self.item_body.ty_decl {
StorageDecl { .. } => None,
let name = match &self.item_body.ty {
&DocumentableType::Declared(StorageDecl { .. }) => None,
_ => Some(self.item_header.item_name.as_str()),
};

Document::create_html_filename(self.item_body.ty_decl.doc_name(), name)
Document::create_html_filename(self.item_body.ty.doc_name(), name)
}
fn create_html_filename(ty: &str, name: Option<&str>) -> String {
match name {
Expand All @@ -245,6 +250,14 @@ impl Document {
}
}

impl PartialEq for Document {
fn eq(&self, other: &Self) -> bool {
self.item_header.item_name == other.item_header.item_name
&& self.item_header.module_info.module_prefixes
== other.item_header.module_info.module_prefixes
}
}

impl Deref for Documentation {
type Target = Vec<Document>;
fn deref(&self) -> &Self::Target {
Expand Down
Loading

0 comments on commit f342f52

Please sign in to comment.