diff --git a/Cargo.lock b/Cargo.lock index 7fdb4d4b50..1c7e089908 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3013,6 +3013,8 @@ dependencies = [ "roc_can", "roc_collections", "roc_load", + "roc_module", + "roc_region", ] [[package]] diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index ae1d5015d9..f22ddd6f5e 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -36,6 +36,7 @@ pub struct ModuleOutput { pub problems: Vec, pub ident_ids: IdentIds, pub references: MutSet, + pub scope: Scope, } // TODO trim these down @@ -309,6 +310,7 @@ where } Ok(ModuleOutput { + scope, aliases, rigid_variables, declarations, diff --git a/compiler/can/src/scope.rs b/compiler/can/src/scope.rs index bb4c969ced..9037d6e193 100644 --- a/compiler/can/src/scope.rs +++ b/compiler/can/src/scope.rs @@ -1,4 +1,4 @@ -use roc_collections::all::{ImMap, MutSet}; +use roc_collections::all::{MutSet, SendMap}; use roc_module::ident::{Ident, Lowercase}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_problem::can::RuntimeError; @@ -10,14 +10,14 @@ use roc_types::types::{Alias, Type}; pub struct Scope { /// All the identifiers in scope, mapped to were they were defined and /// the Symbol they resolve to. - idents: ImMap, + idents: SendMap, /// A cache of all the symbols in scope. This makes lookups much /// faster when checking for unused defs and unused arguments. - symbols: ImMap, + symbols: SendMap, /// The type aliases currently in scope - aliases: ImMap, + aliases: SendMap, /// The current module being processed. This will be used to turn /// unqualified idents into Symbols. @@ -28,7 +28,7 @@ impl Scope { pub fn new(home: ModuleId, var_store: &mut VarStore) -> Scope { use roc_types::solved_types::{BuiltinAlias, FreeVars}; let solved_aliases = roc_types::builtin_aliases::aliases(); - let mut aliases = ImMap::default(); + let mut aliases = SendMap::default(); for (symbol, builtin_alias) in solved_aliases { let BuiltinAlias { region, vars, typ } = builtin_alias; @@ -58,7 +58,7 @@ impl Scope { Scope { home, idents: Symbol::default_in_scope(), - symbols: ImMap::default(), + symbols: SendMap::default(), aliases, } } diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index 71a0423ab1..ee580ad0a4 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -1,7 +1,7 @@ use crate::expr::constrain_decls; use roc_builtins::std::StdLib; use roc_can::constraint::{Constraint, LetConstraint}; -use roc_can::module::ModuleOutput; +use roc_can::def::Declaration; use roc_collections::all::{MutMap, MutSet, SendMap}; use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Located, Region}; @@ -22,16 +22,18 @@ pub struct ConstrainedModule { pub constraint: Constraint, } -pub fn constrain_module(module: &ModuleOutput, home: ModuleId) -> Constraint { +pub fn constrain_module( + aliases: &MutMap, + declarations: &[Declaration], + home: ModuleId, +) -> Constraint { let mut send_aliases = SendMap::default(); - for (symbol, alias) in module.aliases.iter() { + for (symbol, alias) in aliases.iter() { send_aliases.insert(*symbol, alias.clone()); } - let decls = &module.declarations; - - constrain_decls(home, decls) + constrain_decls(home, declarations) } #[derive(Debug, Clone)] diff --git a/compiler/load/src/docs.rs b/compiler/load/src/docs.rs index ae46aabdf3..c971188257 100644 --- a/compiler/load/src/docs.rs +++ b/compiler/load/src/docs.rs @@ -1,8 +1,10 @@ use crate::docs::DocEntry::DetatchedDoc; use crate::docs::TypeAnnotation::{Apply, BoundVariable, Record, TagUnion}; use inlinable_string::InlinableString; +use roc_can::scope::Scope; +use roc_collections::all::MutMap; use roc_module::ident::ModuleName; -use roc_module::symbol::IdentIds; +use roc_module::symbol::{IdentIds, Interns, ModuleId}; use roc_parse::ast; use roc_parse::ast::CommentOrNewline; use roc_parse::ast::{AssignedField, Def}; @@ -10,18 +12,19 @@ use roc_region::all::Located; // Documentation generation requirements -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Documentation { pub name: String, pub version: String, pub docs: String, - pub modules: Vec, + pub modules: Vec<(MutMap, Interns)>, } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct ModuleDocumentation { pub name: String, pub entries: Vec, + pub scope: Scope, } #[derive(Debug, Clone)] @@ -75,19 +78,21 @@ pub struct Tag { } pub fn generate_module_docs<'a>( + scope: Scope, module_name: ModuleName, - exposed_ident_ids: &'a IdentIds, + ident_ids: &'a IdentIds, parsed_defs: &'a [Located>], ) -> ModuleDocumentation { let (entries, _) = parsed_defs .iter() .fold((vec![], None), |(acc, maybe_comments_after), def| { - generate_entry_doc(exposed_ident_ids, acc, maybe_comments_after, &def.value) + generate_entry_doc(ident_ids, acc, maybe_comments_after, &def.value) }); ModuleDocumentation { name: module_name.as_str().to_string(), + scope, entries, } } @@ -117,7 +122,7 @@ fn detatched_docs_from_comments_and_new_lines<'a>( } fn generate_entry_doc<'a>( - exposed_ident_ids: &'a IdentIds, + ident_ids: &'a IdentIds, mut acc: Vec, before_comments_or_new_lines: Option<&'a [roc_parse::ast::CommentOrNewline<'a>]>, def: &'a ast::Def<'a>, @@ -135,13 +140,13 @@ fn generate_entry_doc<'a>( acc.push(DetatchedDoc(detatched_doc)); } - generate_entry_doc(exposed_ident_ids, acc, Some(comments_or_new_lines), sub_def) + generate_entry_doc(ident_ids, acc, Some(comments_or_new_lines), sub_def) } Def::SpaceAfter(sub_def, comments_or_new_lines) => { let (new_acc, _) = // If there are comments before, attach to this definition - generate_entry_doc(exposed_ident_ids, acc, before_comments_or_new_lines, sub_def); + generate_entry_doc(ident_ids, acc, before_comments_or_new_lines, sub_def); // Comments after a definition are attached to the next definition (new_acc, Some(comments_or_new_lines)) @@ -150,7 +155,7 @@ fn generate_entry_doc<'a>( Def::Annotation(loc_pattern, _loc_ann) => match loc_pattern.value { Pattern::Identifier(identifier) => { // Check if the definition is exposed - if exposed_ident_ids + if ident_ids .get_id(&InlinableString::from(identifier)) .is_some() { @@ -170,7 +175,7 @@ fn generate_entry_doc<'a>( Def::AnnotatedBody { ann_pattern, .. } => match ann_pattern.value { Pattern::Identifier(identifier) => { // Check if the definition is exposed - if exposed_ident_ids + if ident_ids .get_id(&InlinableString::from(identifier)) .is_some() { diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index d2c9fa1662..ca48c45736 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -3502,9 +3502,14 @@ fn fabricate_effects_module<'a>( problems: can_env.problems, ident_ids: can_env.ident_ids, references: MutSet::default(), + scope, }; - let constraint = constrain_module(&module_output, module_id); + let constraint = constrain_module( + &module_output.aliases, + &module_output.declarations, + module_id, + ); let module = Module { module_id, @@ -3521,6 +3526,7 @@ fn fabricate_effects_module<'a>( let module_docs = ModuleDocumentation { name: String::from(name), entries: Vec::new(), + scope: module_output.scope, }; let constrained_module = ConstrainedModule { @@ -3602,18 +3608,6 @@ where .. } = parsed; - // Generate documentation information - // TODO: store timing information? - let module_docs = match module_name { - ModuleNameEnum::PkgConfig => None, - ModuleNameEnum::App(_) => None, - ModuleNameEnum::Interface(name) => Some(crate::docs::generate_module_docs( - name.as_str().into(), - &exposed_ident_ids, - &parsed_defs, - )), - }; - let mut var_store = VarStore::default(); let canonicalized = canonicalize_module_defs( &arena, @@ -3634,7 +3628,24 @@ where match canonicalized { Ok(module_output) => { - let constraint = constrain_module(&module_output, module_id); + // Generate documentation information + // TODO: store timing information? + let module_docs = match module_name { + ModuleNameEnum::PkgConfig => None, + ModuleNameEnum::App(_) => None, + ModuleNameEnum::Interface(name) => Some(crate::docs::generate_module_docs( + module_output.scope, + name.as_str().into(), + &module_output.ident_ids, + &parsed_defs, + )), + }; + + let constraint = constrain_module( + &module_output.aliases, + &module_output.declarations, + module_id, + ); let module = Module { module_id, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index ad79d30e7d..558ddbec73 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1,6 +1,6 @@ use crate::ident::Ident; use inlinable_string::InlinableString; -use roc_collections::all::{default_hasher, ImMap, MutMap}; +use roc_collections::all::{default_hasher, MutMap, SendMap}; use roc_region::all::Region; use std::collections::HashMap; use std::{fmt, u32}; @@ -697,8 +697,8 @@ macro_rules! define_builtins { /// and what symbols they should resolve to. /// /// This is for type aliases like `Int` and `Str` and such. - pub fn default_in_scope() -> ImMap { - let mut scope = ImMap::default(); + pub fn default_in_scope() -> SendMap { + let mut scope = SendMap::default(); $( $( diff --git a/docs/Cargo.toml b/docs/Cargo.toml index a4b48d884d..2b6c47da05 100644 --- a/docs/Cargo.toml +++ b/docs/Cargo.toml @@ -12,6 +12,8 @@ pulldown-cmark = { version = "0.8", default-features = false } roc_load = { path = "../compiler/load" } roc_builtins = { path = "../compiler/builtins" } roc_can = { path = "../compiler/can" } +roc_module = { path = "../compiler/module" } +roc_region = { path = "../compiler/region" } roc_collections = { path = "../compiler/collections" } bumpalo = { version = "3.2", features = ["collections"] } diff --git a/docs/src/lib.rs b/docs/src/lib.rs index dd06d137cc..ca3b3280d9 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -4,18 +4,22 @@ use roc_can::builtins::builtin_defs_map; use roc_load::docs::{DocEntry, TypeAnnotation}; use roc_load::docs::{ModuleDocumentation, RecordField}; use roc_load::file::LoadingProblem; +use roc_module::symbol::{Interns, ModuleId}; use std::fs; extern crate roc_load; use bumpalo::Bump; +use roc_can::scope::Scope; use roc_collections::all::MutMap; +use roc_region::all::Region; use std::path::{Path, PathBuf}; pub fn generate(filenames: Vec, std_lib: StdLib, build_dir: &Path) { let files_docs = files_to_documentations(filenames, std_lib); + // // TODO: get info from a file like "elm.json" - let package = roc_load::docs::Documentation { + let mut package = roc_load::docs::Documentation { name: "roc/builtins".to_string(), version: "1.0.0".to_string(), docs: "Package introduction or README.".to_string(), @@ -45,36 +49,44 @@ pub fn generate(filenames: Vec, std_lib: StdLib, build_dir: &Path) { ) .expect("TODO gracefully handle failing to make the favicon"); - let template_html = include_str!("./static/index.html"); + let template_html = include_str!("./static/index.html").replace( + "", + render_sidebar( + package + .modules + .iter() + .flat_map(|(docs_by_id, _)| docs_by_id.values()), + ) + .as_str(), + ); // Write each package's module docs html file - for module in &package.modules { - let mut filename = String::new(); - filename.push_str(module.name.as_str()); - filename.push_str(".html"); + for (docs_by_id, interns) in package.modules.iter_mut() { + for module in docs_by_id.values_mut() { + let mut filename = String::new(); + filename.push_str(module.name.as_str()); + filename.push_str(".html"); - let rendered_module = template_html - .replace( - "", - render_module_links(&package.modules).as_str(), - ) - .replace( - "", - render_name_and_version(package.name.as_str(), package.version.as_str()).as_str(), - ) - .replace( - "", - render_main_content(&module).as_str(), - ); + let rendered_module = template_html + .replace( + "", + render_name_and_version(package.name.as_str(), package.version.as_str()) + .as_str(), + ) + .replace( + "", + render_main_content(interns, module).as_str(), + ); - fs::write(build_dir.join(filename), rendered_module) - .expect("TODO gracefully handle failing to write html"); + fs::write(build_dir.join(filename), rendered_module) + .expect("TODO gracefully handle failing to write html"); + } } println!("🎉 Docs generated in {}", build_dir.display()); } -fn render_main_content(module: &ModuleDocumentation) -> String { +fn render_main_content(interns: &Interns, module: &mut ModuleDocumentation) -> String { let mut buf = String::new(); buf.push_str( @@ -119,11 +131,15 @@ fn render_main_content(module: &ModuleDocumentation) -> String { ); if let Some(docs) = &doc_def.docs { - buf.push_str(markdown_to_html(docs.to_string()).as_str()); + buf.push_str( + markdown_to_html(&mut module.scope, interns, docs.to_string()).as_str(), + ); } } DocEntry::DetatchedDoc(docs) => { - buf.push_str(markdown_to_html(docs.to_string()).as_str()); + buf.push_str( + markdown_to_html(&mut module.scope, interns, docs.to_string()).as_str(), + ); } }; } @@ -195,7 +211,7 @@ fn render_name_and_version(name: &str, version: &str) -> String { buf } -fn render_module_links(modules: &[ModuleDocumentation]) -> String { +fn render_sidebar<'a, I: Iterator>(modules: I) -> String { let mut buf = String::new(); for module in modules { @@ -269,7 +285,7 @@ fn render_module_links(modules: &[ModuleDocumentation]) -> String { pub fn files_to_documentations( filenames: Vec, std_lib: StdLib, -) -> Vec { +) -> Vec<(MutMap, Interns)> { let arena = Bump::new(); let mut files_docs = vec![]; @@ -286,7 +302,7 @@ pub fn files_to_documentations( 8, // TODO: Is it okay to hardcode ptr_bytes here? I think it should be fine since we'er only type checking (also, 8 => 32bit system) builtin_defs_map, ) { - Ok(mut loaded) => files_docs.extend(loaded.documentation.drain().map(|x| x.1)), + Ok(loaded) => files_docs.push((loaded.documentation, loaded.interns)), Err(LoadingProblem::FormattedReport(report)) => { println!("{}", report); panic!(); @@ -294,6 +310,7 @@ pub fn files_to_documentations( Err(e) => panic!("{:?}", e), } } + files_docs } @@ -410,11 +427,7 @@ fn type_annotation_to_html(indent_level: usize, buf: &mut String, type_ann: &Typ } } -fn insert_doc_links(markdown: String) -> String { - insert_doc_links_help(markdown) -} - -fn insert_doc_links_help(markdown: String) -> String { +fn insert_doc_links(scope: &mut Scope, interns: &Interns, markdown: String) -> String { let buf = &markdown; let mut result = String::new(); @@ -440,12 +453,18 @@ fn insert_doc_links_help(markdown: String) -> String { result = buf.chars().take(from).collect(); - let doc_link = - make_doc_link(buf.chars().skip(from + 1).take(index - from).collect()); + let doc_link = make_doc_link( + scope, + interns, + &buf.chars() + .skip(from + 1) + .take(index - from) + .collect::(), + ); result.insert_str(from, doc_link.as_str()); - let remainder = insert_doc_links_help(after_link.collect()); + let remainder = insert_doc_links(scope, interns, after_link.collect()); result.push_str(remainder.as_str()); break; @@ -457,48 +476,45 @@ fn insert_doc_links_help(markdown: String) -> String { result } -fn make_doc_link(doc_item: String) -> String { - let mut label = String::new(); - let mut link = String::new(); +fn make_doc_link(scope: &mut Scope, interns: &Interns, doc_item: &str) -> String { + match scope.lookup(&doc_item.into(), Region::zero()) { + Ok(symbol) => { + let module_str = symbol.module_string(interns); + let ident_str = symbol.ident_string(interns); - let mut parts = doc_item.split('.').into_iter().peekable(); + let mut link = String::new(); - while let Some(part) = parts.next() { - label.push_str(part); + link.push_str(module_str); + link.push_str(".html#"); + link.push_str(ident_str); - match parts.peek() { - None => { - link.push_str(".html#"); - link.push_str(part); - } - Some(_) => { - link.push_str(part); - link.push('.'); + let mut buf = String::new(); - label.push('.'); - } + buf.push('['); + buf.push_str(doc_item); + buf.push_str("]("); + + buf.push_str(link.as_str()); + buf.push(')'); + + buf + } + Err(_) => { + panic!( + "Could not find symbol in scope for module link : {}", + doc_item + ) } } - - let mut buf = String::new(); - - buf.push('['); - buf.push_str(label.as_str()); - buf.push_str("]("); - - buf.push_str(link.as_str()); - buf.push(')'); - - buf } -fn markdown_to_html(markdown: String) -> String { +fn markdown_to_html(scope: &mut Scope, interns: &Interns, markdown: String) -> String { use pulldown_cmark::CodeBlockKind; use pulldown_cmark::CowStr; use pulldown_cmark::Event; use pulldown_cmark::Tag::*; - let markdown_with_links = insert_doc_links(markdown); + let markdown_with_links = insert_doc_links(scope, interns, markdown); let markdown_options = pulldown_cmark::Options::empty(); let mut docs_parser = vec![];