Merge pull request #6569 from faldor20/docs

Add docs to completions and hover
This commit is contained in:
Richard Feldman 2024-03-16 11:22:58 -04:00 committed by GitHub
commit 14ba398b5d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 954 additions and 597 deletions

View file

@ -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)
}

View file

@ -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

View file

@ -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>,