mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 13:29:12 +00:00
Merge pull request #6569 from faldor20/docs
Add docs to completions and hover
This commit is contained in:
commit
14ba398b5d
24 changed files with 954 additions and 597 deletions
|
@ -18,10 +18,22 @@ pub struct ModuleDocumentation {
|
|||
pub exposed_symbols: VecSet<Symbol>,
|
||||
}
|
||||
|
||||
impl ModuleDocumentation {
|
||||
pub fn get_doc_for_symbol(&self, symbol_to_match: &Symbol) -> Option<String> {
|
||||
self.entries.iter().find_map(|doc| match doc {
|
||||
DocEntry::DocDef(DocDef { symbol, docs, .. }) if symbol == symbol_to_match => {
|
||||
docs.clone()
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DocEntry {
|
||||
DocDef(DocDef),
|
||||
DetachedDoc(String),
|
||||
ModuleDoc(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -174,10 +186,10 @@ fn generate_entry_docs(
|
|||
) -> Vec<DocEntry> {
|
||||
use roc_parse::ast::Pattern;
|
||||
|
||||
let mut acc = Vec::with_capacity(defs.tags.len() + 1);
|
||||
let mut doc_entries = Vec::with_capacity(defs.tags.len() + 1);
|
||||
|
||||
if let Some(docs) = comments_or_new_lines_to_docs(header_comments) {
|
||||
acc.push(DetachedDoc(docs));
|
||||
doc_entries.push(DocEntry::ModuleDoc(docs));
|
||||
}
|
||||
|
||||
let mut before_comments_or_new_lines: Option<&[CommentOrNewline]> = None;
|
||||
|
@ -211,7 +223,7 @@ fn generate_entry_docs(
|
|||
type_vars: Vec::new(),
|
||||
docs,
|
||||
};
|
||||
acc.push(DocEntry::DocDef(doc_def));
|
||||
doc_entries.push(DocEntry::DocDef(doc_def));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -231,13 +243,25 @@ fn generate_entry_docs(
|
|||
symbol: Symbol::new(home, ident_id),
|
||||
docs,
|
||||
};
|
||||
acc.push(DocEntry::DocDef(doc_def));
|
||||
doc_entries.push(DocEntry::DocDef(doc_def));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ValueDef::Body(_, _) => {
|
||||
// TODO generate docs for un-annotated bodies
|
||||
ValueDef::Body(pattern, _) => {
|
||||
if let Pattern::Identifier(identifier) = pattern.value {
|
||||
// Check if this module exposes the def
|
||||
if let Some(ident_id) = ident_ids.get_id(identifier) {
|
||||
let doc_def = DocDef {
|
||||
name: identifier.to_string(),
|
||||
type_annotation: TypeAnnotation::NoTypeAnn,
|
||||
type_vars: Vec::new(),
|
||||
symbol: Symbol::new(home, ident_id),
|
||||
docs,
|
||||
};
|
||||
doc_entries.push(DocEntry::DocDef(doc_def));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ValueDef::Dbg { .. } => {
|
||||
|
@ -252,6 +276,7 @@ fn generate_entry_docs(
|
|||
// Don't generate docs for `expect-fx`s
|
||||
}
|
||||
},
|
||||
|
||||
Ok(type_index) => match &defs.type_defs[type_index.index()] {
|
||||
TypeDef::Alias {
|
||||
header: TypeHeader { name, vars },
|
||||
|
@ -284,7 +309,7 @@ fn generate_entry_docs(
|
|||
docs,
|
||||
symbol: Symbol::new(home, ident_id),
|
||||
};
|
||||
acc.push(DocEntry::DocDef(doc_def));
|
||||
doc_entries.push(DocEntry::DocDef(doc_def));
|
||||
}
|
||||
|
||||
TypeDef::Opaque {
|
||||
|
@ -307,7 +332,7 @@ fn generate_entry_docs(
|
|||
docs,
|
||||
symbol: Symbol::new(home, ident_id),
|
||||
};
|
||||
acc.push(DocEntry::DocDef(doc_def));
|
||||
doc_entries.push(DocEntry::DocDef(doc_def));
|
||||
}
|
||||
|
||||
TypeDef::Ability {
|
||||
|
@ -347,7 +372,7 @@ fn generate_entry_docs(
|
|||
type_vars,
|
||||
docs,
|
||||
};
|
||||
acc.push(DocEntry::DocDef(doc_def));
|
||||
doc_entries.push(DocEntry::DocDef(doc_def));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -359,40 +384,49 @@ fn generate_entry_docs(
|
|||
let it = before_comments_or_new_lines.iter().flat_map(|e| e.iter());
|
||||
|
||||
for detached_doc in detached_docs_from_comments_and_new_lines(it) {
|
||||
acc.push(DetachedDoc(detached_doc));
|
||||
doc_entries.push(DetachedDoc(detached_doc));
|
||||
}
|
||||
|
||||
acc
|
||||
doc_entries
|
||||
}
|
||||
|
||||
/// Does this type contain any types which are not exposed outside the package?
|
||||
/// (If so, we shouldn't try to render a type annotation for it.)
|
||||
fn contains_unexposed_type(
|
||||
ann: &ast::TypeAnnotation,
|
||||
type_ann: &ast::TypeAnnotation,
|
||||
exposed_module_ids: &[ModuleId],
|
||||
module_ids: &ModuleIds,
|
||||
) -> bool {
|
||||
use ast::TypeAnnotation::*;
|
||||
|
||||
match ann {
|
||||
match type_ann {
|
||||
// Apply is the one case that can directly return true.
|
||||
Apply(module_name, _ident, loc_args) => {
|
||||
let apply_module_id = module_ids.get_id(&(*module_name).into());
|
||||
let loc_args_contains_unexposed_type = loc_args.iter().any(|loc_arg| {
|
||||
contains_unexposed_type(&loc_arg.value, exposed_module_ids, module_ids)
|
||||
});
|
||||
|
||||
// If the *ident* was unexposed, we would have gotten a naming error
|
||||
// during canonicalization, so all we need to check is the module.
|
||||
let module_id = module_ids.get_id(&(*module_name).into()).unwrap();
|
||||
if let Some(module_id) = apply_module_id {
|
||||
!exposed_module_ids.contains(&module_id) || loc_args_contains_unexposed_type
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
!exposed_module_ids.contains(&module_id)
|
||||
|| loc_args.iter().any(|loc_arg| {
|
||||
contains_unexposed_type(&loc_arg.value, exposed_module_ids, module_ids)
|
||||
})
|
||||
}
|
||||
Malformed(_) | Inferred | Wildcard | BoundVariable(_) => false,
|
||||
|
||||
Function(loc_args, loc_ret) => {
|
||||
let loc_args_contains_unexposed_type = loc_args.iter().any(|loc_arg| {
|
||||
contains_unexposed_type(&loc_arg.value, exposed_module_ids, module_ids)
|
||||
});
|
||||
|
||||
contains_unexposed_type(&loc_ret.value, exposed_module_ids, module_ids)
|
||||
|| loc_args.iter().any(|loc_arg| {
|
||||
contains_unexposed_type(&loc_arg.value, exposed_module_ids, module_ids)
|
||||
})
|
||||
|| loc_args_contains_unexposed_type
|
||||
}
|
||||
|
||||
Record { fields, ext } => {
|
||||
if let Some(loc_ext) = ext {
|
||||
if contains_unexposed_type(&loc_ext.value, exposed_module_ids, module_ids) {
|
||||
|
@ -422,6 +456,7 @@ fn contains_unexposed_type(
|
|||
|
||||
false
|
||||
}
|
||||
|
||||
Tuple { elems: fields, ext } => {
|
||||
if let Some(loc_ext) = ext {
|
||||
if contains_unexposed_type(&loc_ext.value, exposed_module_ids, module_ids) {
|
||||
|
@ -433,6 +468,7 @@ fn contains_unexposed_type(
|
|||
contains_unexposed_type(&loc_field.value, exposed_module_ids, module_ids)
|
||||
})
|
||||
}
|
||||
|
||||
TagUnion { ext, tags } => {
|
||||
use ast::Tag;
|
||||
|
||||
|
@ -468,14 +504,17 @@ fn contains_unexposed_type(
|
|||
|
||||
false
|
||||
}
|
||||
|
||||
Where(loc_ann, _loc_has_clauses) => {
|
||||
// We assume all the abilities in the `implements` clause are from exported modules.
|
||||
// TODO don't assume this! Instead, look them up and verify.
|
||||
contains_unexposed_type(&loc_ann.value, exposed_module_ids, module_ids)
|
||||
}
|
||||
|
||||
As(loc_ann, _spaces, _type_header) => {
|
||||
contains_unexposed_type(&loc_ann.value, exposed_module_ids, module_ids)
|
||||
}
|
||||
|
||||
SpaceBefore(ann, _) | ast::TypeAnnotation::SpaceAfter(ann, _) => {
|
||||
contains_unexposed_type(ann, exposed_module_ids, module_ids)
|
||||
}
|
||||
|
|
|
@ -217,7 +217,7 @@ fn start_phase<'a>(
|
|||
//
|
||||
// At the end of this loop, dep_idents contains all the information to
|
||||
// resolve a symbol from another module: if it's in here, that means
|
||||
// we have both imported the module and the ident was exported by that mdoule.
|
||||
// we have both imported the module and the ident was exported by that module.
|
||||
for dep_id in deps_by_name.values() {
|
||||
// We already verified that these are all present,
|
||||
// so unwrapping should always succeed here.
|
||||
|
@ -3340,7 +3340,7 @@ fn finish(
|
|||
exposed_types_storage: ExposedTypesStorageSubs,
|
||||
resolved_implementations: ResolvedImplementations,
|
||||
dep_idents: IdentIdsByModule,
|
||||
mut documentation: VecMap<ModuleId, ModuleDocumentation>,
|
||||
documentation: VecMap<ModuleId, ModuleDocumentation>,
|
||||
abilities_store: AbilitiesStore,
|
||||
//
|
||||
#[cfg(debug_assertions)] checkmate: Option<roc_checkmate::Collector>,
|
||||
|
@ -3379,18 +3379,6 @@ fn finish(
|
|||
|
||||
roc_checkmate::dump_checkmate!(checkmate);
|
||||
|
||||
let mut docs_by_module = Vec::with_capacity(state.exposed_modules.len());
|
||||
|
||||
for module_id in state.exposed_modules.iter() {
|
||||
let docs = documentation.remove(module_id).unwrap_or_else(|| {
|
||||
panic!("A module was exposed but didn't have an entry in `documentation` somehow: {module_id:?}");
|
||||
});
|
||||
|
||||
docs_by_module.push(docs);
|
||||
}
|
||||
|
||||
debug_assert_eq!(documentation.len(), 0);
|
||||
|
||||
LoadedModule {
|
||||
module_id: state.root_id,
|
||||
interns,
|
||||
|
@ -3404,10 +3392,11 @@ fn finish(
|
|||
exposed_values,
|
||||
exposed_to_host: exposed_vars_by_symbol.into_iter().collect(),
|
||||
exposed_types_storage,
|
||||
exposed_modules: state.exposed_modules.into(),
|
||||
resolved_implementations,
|
||||
sources,
|
||||
timings: state.timings,
|
||||
docs_by_module,
|
||||
docs_by_module: documentation,
|
||||
abilities_store,
|
||||
exposed_imports: state.module_cache.exposed_imports,
|
||||
imports: state.module_cache.imports,
|
||||
|
@ -5404,36 +5393,22 @@ fn canonicalize_and_constrain<'a>(
|
|||
|
||||
// Generate documentation information
|
||||
// TODO: store timing information?
|
||||
let module_docs = match header_type {
|
||||
HeaderType::App { .. } => None,
|
||||
HeaderType::Platform { .. } | HeaderType::Package { .. } => {
|
||||
// TODO: actually generate docs for platform and package modules.
|
||||
None
|
||||
}
|
||||
HeaderType::Interface { name, .. }
|
||||
| HeaderType::Builtin { name, .. }
|
||||
| HeaderType::Hosted { name, .. }
|
||||
if exposed_module_ids.contains(&parsed.module_id) =>
|
||||
{
|
||||
let module_docs = {
|
||||
let module_name = header_type.get_name();
|
||||
module_name.map(|module_name| {
|
||||
let mut scope = module_output.scope.clone();
|
||||
scope.add_docs_imports();
|
||||
let docs = crate::docs::generate_module_docs(
|
||||
crate::docs::generate_module_docs(
|
||||
scope,
|
||||
module_id,
|
||||
module_ids,
|
||||
name.as_str().into(),
|
||||
module_name.into(),
|
||||
&parsed_defs_for_docs,
|
||||
exposed_module_ids,
|
||||
module_output.exposed_symbols.clone(),
|
||||
parsed.header_comments,
|
||||
);
|
||||
|
||||
Some(docs)
|
||||
}
|
||||
HeaderType::Interface { .. } | HeaderType::Builtin { .. } | HeaderType::Hosted { .. } => {
|
||||
// This module isn't exposed by the platform, so don't generate docs for it!
|
||||
None
|
||||
}
|
||||
)
|
||||
})
|
||||
};
|
||||
|
||||
// _before has an underscore because it's unused in --release builds
|
||||
|
|
|
@ -38,12 +38,13 @@ pub struct LoadedModule {
|
|||
pub exposed_to_host: MutMap<Symbol, Variable>,
|
||||
pub dep_idents: IdentIdsByModule,
|
||||
pub exposed_aliases: MutMap<Symbol, Alias>,
|
||||
pub exposed_modules: Vec<ModuleId>,
|
||||
pub exposed_values: Vec<Symbol>,
|
||||
pub exposed_types_storage: ExposedTypesStorageSubs,
|
||||
pub resolved_implementations: ResolvedImplementations,
|
||||
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
|
||||
pub timings: MutMap<ModuleId, ModuleTiming>,
|
||||
pub docs_by_module: Vec<(ModuleId, ModuleDocumentation)>,
|
||||
pub docs_by_module: VecMap<ModuleId, ModuleDocumentation>,
|
||||
pub abilities_store: AbilitiesStore,
|
||||
pub typechecked: MutMap<ModuleId, CheckedModule>,
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue