mirror of
https://github.com/FuelLabs/sway.git
synced 2025-08-09 21:28:17 +00:00

## Description This PR implements the `__dbg(...)` intrinsic, which is very similar to Rust `dbg!(...)` macro. Up until now, it has being the norm to use `__log` to debug values. Given that this is NOT the first use case for log, we have always found some issues with it: log does not work on predicates, log does not show "private" fields like `Vec::capacity` and others. To solve these problems `__dbg` is being introduced: 1 - it will work on all program types, including predicates; 2 - it also prints the file name, line and column; 3 - All types will have an automatic implementation of Debug if possible, which can still be customized. 4 - Even `raw_ptr` and other non "loggable" types, have `Debug` impls. 5 - `__dbg` will be completely stripped in the release build by default. It can be turned on again if needed. So this: ``` // Aggregates let _ = __dbg((1u64, 2u64)); let _ = __dbg([1u64, 2u64]); // Structs and Enums let _ = __dbg(S { }); let _ = __dbg(E::None); let _ = __dbg(E::Some(S { })); ``` will generate this: ``` [src/main.sw:19:13] = (1, 2) [src/main.sw:20:13] = [1, 2] [src/main.sw:23:13] = S { } [src/main.sw:24:13] = None [src/main.sw:25:13] = E(S { }) ``` How does this work? `__dbg(value)` intrinsic is desugared into `{ let f = Formatter{}; f.print_str(...); let value = value; value.fmt(f); value }`. `Formatter` is similar to Rust's one. The difference is that we still do not support string formatting, so the `Formatter` has a lot of `print_*` functions. And each `print` function calls a "syscall". This `syscall` uses `ecal` under the hood and it follows unix write syscall schema. ```sway // ssize_t write(int fd, const void buf[.count], size_t count); fn syscall_write(fd: u64, buf: raw_ptr, count: u64) { asm(id: 1000, fd: fd, buf: buf, count: count) { ecal id fd buf count; } } ``` For that to work, the VM interpreter must have its `EcalState` setup and interpret syscall number 1000 as `write`. This PR does this for `forc test` and our `e2e test suite`. Each test in `forc test` will capture these calls and only print to the terminal when requested with the `--log` flag. ## Garbage Collector and auto generated Before, we were associating all auto-generated code with a pseudo file called "<autogenerated>.sw" that was never garbage collected. This generated a problem inside the LSP when the `auto_impl.rs` ran a second time because of a collision in the "shareable type" map. When we try to solve this collision, choosing to keep the old value or to insert the new, the type inside the map points to already collected types and the compiler panics. This is a known problem. The workaround for this is to break the auto-generated code into multiple files. Now they are named "main.autogenerated.sw", for example. We create one pseudo-file for each real file that needs one. When we garbage collect one file, `main.sw`, for example, we also collect its associated auto-generated file. ## Checklist - [ ] 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) - [x] 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.
240 lines
6.9 KiB
Rust
240 lines
6.9 KiB
Rust
pub mod cli;
|
|
pub mod doc;
|
|
pub mod render;
|
|
pub mod search;
|
|
pub mod tests;
|
|
|
|
use anyhow::{bail, Result};
|
|
use cli::Command;
|
|
use doc::Documentation;
|
|
use forc_pkg as pkg;
|
|
use forc_pkg::{
|
|
manifest::{GenericManifestFile, ManifestFile},
|
|
PackageManifestFile,
|
|
};
|
|
use forc_tracing::println_action_green;
|
|
use forc_util::default_output_directory;
|
|
use render::RenderedDocumentation;
|
|
use std::sync::Arc;
|
|
use std::{
|
|
fs,
|
|
path::{Path, PathBuf},
|
|
};
|
|
use sway_core::{language::ty::TyProgram, BuildTarget, Engines};
|
|
|
|
pub const ASSETS_DIR_NAME: &str = "static.files";
|
|
|
|
/// Information passed to the render phase to get TypeInfo, CallPath or visibility for type anchors.
|
|
#[derive(Clone)]
|
|
pub struct RenderPlan<'e> {
|
|
no_deps: bool,
|
|
document_private_items: bool,
|
|
engines: &'e Engines,
|
|
}
|
|
|
|
impl<'e> RenderPlan<'e> {
|
|
pub fn new(
|
|
no_deps: bool,
|
|
document_private_items: bool,
|
|
engines: &'e Engines,
|
|
) -> RenderPlan<'e> {
|
|
Self {
|
|
no_deps,
|
|
document_private_items,
|
|
engines,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct ProgramInfo<'a> {
|
|
pub ty_program: Arc<TyProgram>,
|
|
pub engines: &'a Engines,
|
|
pub manifest: &'a ManifestFile,
|
|
pub pkg_manifest: &'a PackageManifestFile,
|
|
}
|
|
|
|
pub fn compile_html(
|
|
build_instructions: &Command,
|
|
get_doc_dir: &dyn Fn(&Command) -> String,
|
|
) -> Result<(PathBuf, Box<PackageManifestFile>)> {
|
|
// get manifest directory
|
|
let dir = if let Some(ref path) = build_instructions.path {
|
|
PathBuf::from(path)
|
|
} else {
|
|
std::env::current_dir()?
|
|
};
|
|
let manifest = ManifestFile::from_dir(dir)?;
|
|
let ManifestFile::Package(pkg_manifest) = &manifest else {
|
|
bail!("forc-doc does not support workspaces.")
|
|
};
|
|
|
|
// create doc path
|
|
let out_path = default_output_directory(manifest.dir());
|
|
let doc_dir = get_doc_dir(build_instructions);
|
|
let doc_path = out_path.join(doc_dir);
|
|
if doc_path.exists() {
|
|
std::fs::remove_dir_all(&doc_path)?;
|
|
}
|
|
fs::create_dir_all(&doc_path)?;
|
|
|
|
println_action_green(
|
|
"Compiling",
|
|
&format!(
|
|
"{} ({})",
|
|
pkg_manifest.project_name(),
|
|
manifest.dir().to_string_lossy()
|
|
),
|
|
);
|
|
|
|
let member_manifests = manifest.member_manifests()?;
|
|
let lock_path = manifest.lock_path()?;
|
|
|
|
let ipfs_node = build_instructions.ipfs_node.clone().unwrap_or_default();
|
|
let plan = pkg::BuildPlan::from_lock_and_manifests(
|
|
&lock_path,
|
|
&member_manifests,
|
|
build_instructions.locked,
|
|
build_instructions.offline,
|
|
&ipfs_node,
|
|
)?;
|
|
|
|
let engines = Engines::default();
|
|
let tests_enabled = build_instructions.document_private_items;
|
|
let mut compile_results = pkg::check(
|
|
&plan,
|
|
BuildTarget::default(),
|
|
build_instructions.silent,
|
|
None,
|
|
tests_enabled,
|
|
&engines,
|
|
None,
|
|
&build_instructions.experimental.experimental,
|
|
&build_instructions.experimental.no_experimental,
|
|
sway_core::DbgGeneration::Full,
|
|
)?;
|
|
|
|
let raw_docs = if build_instructions.no_deps {
|
|
let Some(ty_program) = compile_results
|
|
.pop()
|
|
.and_then(|(programs, _handler)| programs)
|
|
.and_then(|p| p.typed.ok())
|
|
else {
|
|
bail! {
|
|
"documentation could not be built from manifest located at '{}'",
|
|
pkg_manifest.path().display()
|
|
}
|
|
};
|
|
let program_info = ProgramInfo {
|
|
ty_program,
|
|
engines: &engines,
|
|
manifest: &manifest,
|
|
pkg_manifest,
|
|
};
|
|
build_docs(program_info, &doc_path, build_instructions)?
|
|
} else {
|
|
let order = plan.compilation_order();
|
|
let graph = plan.graph();
|
|
let manifest_map = plan.manifest_map();
|
|
let mut raw_docs = Documentation(Vec::new());
|
|
|
|
for (node, (compile_result, _handler)) in order.iter().zip(compile_results) {
|
|
let id = &graph[*node].id();
|
|
if let Some(pkg_manifest_file) = manifest_map.get(id) {
|
|
let manifest_file = ManifestFile::from_dir(pkg_manifest_file.path())?;
|
|
let Some(ty_program) = compile_result.and_then(|programs| programs.typed.ok())
|
|
else {
|
|
bail!(
|
|
"documentation could not be built from manifest located at '{}'",
|
|
pkg_manifest_file.path().display()
|
|
)
|
|
};
|
|
let program_info = ProgramInfo {
|
|
ty_program,
|
|
engines: &engines,
|
|
manifest: &manifest_file,
|
|
pkg_manifest: pkg_manifest_file,
|
|
};
|
|
raw_docs
|
|
.0
|
|
.extend(build_docs(program_info, &doc_path, build_instructions)?.0);
|
|
}
|
|
}
|
|
raw_docs
|
|
};
|
|
search::write_search_index(&doc_path, &raw_docs)?;
|
|
|
|
Ok((doc_path, pkg_manifest.to_owned()))
|
|
}
|
|
|
|
fn build_docs(
|
|
program_info: ProgramInfo,
|
|
doc_path: &Path,
|
|
build_instructions: &Command,
|
|
) -> Result<Documentation> {
|
|
let Command {
|
|
document_private_items,
|
|
no_deps,
|
|
..
|
|
} = *build_instructions;
|
|
let ProgramInfo {
|
|
ty_program,
|
|
engines,
|
|
manifest,
|
|
pkg_manifest,
|
|
} = program_info;
|
|
|
|
println_action_green(
|
|
"Building",
|
|
&format!(
|
|
"documentation for {} ({})",
|
|
pkg_manifest.project_name(),
|
|
manifest.dir().to_string_lossy()
|
|
),
|
|
);
|
|
|
|
let raw_docs = Documentation::from_ty_program(
|
|
engines,
|
|
pkg_manifest.project_name(),
|
|
&ty_program,
|
|
document_private_items,
|
|
)?;
|
|
let root_attributes = (!ty_program.root_module.attributes.is_empty())
|
|
.then_some(ty_program.root_module.attributes.clone());
|
|
let forc_version = pkg_manifest
|
|
.project
|
|
.forc_version
|
|
.as_ref()
|
|
.map(|ver| format!("Forc v{}.{}.{}", ver.major, ver.minor, ver.patch));
|
|
// render docs to HTML
|
|
let rendered_docs = RenderedDocumentation::from_raw_docs(
|
|
raw_docs.clone(),
|
|
RenderPlan::new(no_deps, document_private_items, engines),
|
|
root_attributes,
|
|
&ty_program.kind,
|
|
forc_version,
|
|
)?;
|
|
|
|
// write file contents to doc folder
|
|
write_content(rendered_docs, doc_path)?;
|
|
println_action_green("Finished", pkg_manifest.project_name());
|
|
|
|
Ok(raw_docs)
|
|
}
|
|
|
|
fn write_content(rendered_docs: RenderedDocumentation, doc_path: &Path) -> Result<()> {
|
|
for doc in rendered_docs.0 {
|
|
let mut doc_path = doc_path.to_path_buf();
|
|
for prefix in doc.module_info.module_prefixes {
|
|
doc_path.push(prefix);
|
|
}
|
|
fs::create_dir_all(&doc_path)?;
|
|
doc_path.push(doc.html_filename);
|
|
fs::write(&doc_path, doc.file_contents.0.as_bytes())?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
const DOC_DIR_NAME: &str = "doc";
|
|
pub fn get_doc_dir(_build_instructions: &Command) -> String {
|
|
DOC_DIR_NAME.into()
|
|
}
|