feat: support doc comments for functions args and validator params

- Add support to the formatter for these doc comments
- Add a new field to `Arg` `doc: Option<String>`
- Don't attach docs immediately after typechecking a module
  - instead we should do it on demand in docs, build, and lsp
  - the check command doesn't need to have any docs attached
  - doing it more lazily defers the computation until later making
    typechecking feedback a bit faster
- Add support for function arg and validator param docs in
  `attach_module_docs` methods
- Update some snapshots
- Add put_doc to Arg

closes #685
This commit is contained in:
rvcas 2023-10-16 13:33:14 -04:00
parent 10b9dc2042
commit e5801f9c19
No known key found for this signature in database
GPG Key ID: C09B64E263F7D68C
16 changed files with 205 additions and 34 deletions

View File

@ -543,6 +543,7 @@ pub struct Arg<T> {
pub arg_name: ArgName, pub arg_name: ArgName,
pub location: Span, pub location: Span,
pub annotation: Option<Annotation>, pub annotation: Option<Annotation>,
pub doc: Option<String>,
pub tipo: T, pub tipo: T,
} }
@ -553,12 +554,17 @@ impl<A> Arg<A> {
arg_name: self.arg_name, arg_name: self.arg_name,
location: self.location, location: self.location,
annotation: self.annotation, annotation: self.annotation,
doc: self.doc,
} }
} }
pub fn get_variable_name(&self) -> Option<&str> { pub fn get_variable_name(&self) -> Option<&str> {
self.arg_name.get_variable_name() self.arg_name.get_variable_name()
} }
pub fn put_doc(&mut self, new_doc: String) {
self.doc = Some(new_doc);
}
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]

View File

@ -680,12 +680,19 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap<FunctionAccessKey, Ty
location: Span::empty(), location: Span::empty(),
is_validator_param: false, is_validator_param: false,
}, },
doc: None,
location: Span::empty(), location: Span::empty(),
annotation: None, annotation: None,
tipo: bool(), tipo: bool(),
}], }],
can_error: false, can_error: false,
doc: None, doc: Some(
indoc::indoc! {
r#"
/// Like `!`, but as a function. Handy for chaining using the pipe operator `|>` or to pass as a function.
"#
}.to_string()
),
location: Span::empty(), location: Span::empty(),
name: "not".to_string(), name: "not".to_string(),
public: true, public: true,
@ -732,6 +739,7 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap<FunctionAccessKey, Ty
}, },
location: Span::empty(), location: Span::empty(),
annotation: None, annotation: None,
doc: None,
tipo: a_var.clone(), tipo: a_var.clone(),
}], }],
can_error: false, can_error: false,
@ -746,7 +754,14 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap<FunctionAccessKey, Ty
}, },
name: "a".to_string(), name: "a".to_string(),
}, },
doc: None, doc: Some(
indoc::indoc! {
r#"
A function that returns its argument. Handy as a default behavior sometimes.
"#
}
.to_string(),
),
location: Span::empty(), location: Span::empty(),
name: "identity".to_string(), name: "identity".to_string(),
public: true, public: true,
@ -780,6 +795,7 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap<FunctionAccessKey, Ty
}, },
location: Span::empty(), location: Span::empty(),
annotation: None, annotation: None,
doc: None,
tipo: a_var.clone(), tipo: a_var.clone(),
}, },
Arg { Arg {
@ -790,6 +806,7 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap<FunctionAccessKey, Ty
}, },
location: Span::empty(), location: Span::empty(),
annotation: None, annotation: None,
doc: None,
tipo: b_var, tipo: b_var,
}, },
], ],
@ -804,7 +821,21 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap<FunctionAccessKey, Ty
}, },
name: "a".to_string(), name: "a".to_string(),
}, },
doc: None, doc: Some(
indoc::indoc! {
r#"
A function that always return its first argument. Handy in folds and maps.
```aiken
let always_14 = always(14, _)
always_14(42) == 14
always_14(1337) == 14
always_14(0) == 14
```
"#
}
.to_string(),
),
location: Span::empty(), location: Span::empty(),
name: "always".to_string(), name: "always".to_string(),
public: true, public: true,
@ -841,6 +872,7 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap<FunctionAccessKey, Ty
}, },
location: Span::empty(), location: Span::empty(),
annotation: None, annotation: None,
doc: None,
tipo: input_type.clone(), tipo: input_type.clone(),
}], }],
body: TypedExpr::Fn { body: TypedExpr::Fn {
@ -857,6 +889,7 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap<FunctionAccessKey, Ty
}, },
location: Span::empty(), location: Span::empty(),
annotation: None, annotation: None,
doc: None,
tipo: b_var.clone(), tipo: b_var.clone(),
}, },
Arg { Arg {
@ -868,6 +901,7 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap<FunctionAccessKey, Ty
}, },
location: Span::empty(), location: Span::empty(),
annotation: None, annotation: None,
doc: None,
tipo: a_var.clone(), tipo: a_var.clone(),
}, },
], ],
@ -920,7 +954,22 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap<FunctionAccessKey, Ty
}), }),
return_annotation: None, return_annotation: None,
}, },
doc: None, doc: Some(
indoc::indoc! {
r#"
A function that flips the arguments of a function.
```aiken
pub fn titleize(left: String, right: String) {}
titleize("Hello", "World") // "Hello, World!"
flip(titleize)("Hello", "World") // "World, Hello!"
```
"#
}
.to_string(),
),
location: Span::empty(), location: Span::empty(),
name: "flip".to_string(), name: "flip".to_string(),
public: true, public: true,

View File

@ -614,6 +614,7 @@ impl UntypedExpr {
holes.push(ast::Arg { holes.push(ast::Arg {
location: Span::empty(), location: Span::empty(),
annotation: None, annotation: None,
doc: None,
arg_name: ast::ArgName::Named { arg_name: ast::ArgName::Named {
label: name.clone(), label: name.clone(),
name, name,

View File

@ -462,6 +462,8 @@ impl<'comments> Formatter<'comments> {
fn fn_arg<'a, A>(&mut self, arg: &'a Arg<A>) -> Document<'a> { fn fn_arg<'a, A>(&mut self, arg: &'a Arg<A>) -> Document<'a> {
let comments = self.pop_comments(arg.location.start); let comments = self.pop_comments(arg.location.start);
let doc_comments = self.doc_comments(arg.location.start);
let doc = match &arg.annotation { let doc = match &arg.annotation {
None => arg.arg_name.to_doc(), None => arg.arg_name.to_doc(),
Some(a) => arg Some(a) => arg
@ -472,6 +474,8 @@ impl<'comments> Formatter<'comments> {
} }
.group(); .group();
let doc = doc_comments.append(doc.group()).group();
commented(doc, comments) commented(doc, comments)
} }
@ -525,6 +529,13 @@ impl<'comments> Formatter<'comments> {
other_fun: &'a Option<UntypedFunction>, other_fun: &'a Option<UntypedFunction>,
end_position: usize, end_position: usize,
) -> Document<'a> { ) -> Document<'a> {
// validator(params)
let v_head = "validator".to_doc().append(if !params.is_empty() {
wrap_args(params.iter().map(|e| (self.fn_arg(e), false)))
} else {
nil()
});
let fun_comments = self.pop_comments(fun.location.start); let fun_comments = self.pop_comments(fun.location.start);
let fun_doc_comments = self.doc_comments(fun.location.start); let fun_doc_comments = self.doc_comments(fun.location.start);
let first_fn = self let first_fn = self
@ -574,13 +585,6 @@ impl<'comments> Formatter<'comments> {
None => v_body.nest(INDENT), None => v_body.nest(INDENT),
}; };
// validator(params)
let v_head = "validator".to_doc().append(if !params.is_empty() {
wrap_args(params.iter().map(|e| (self.fn_arg(e), false)))
} else {
nil()
});
v_head v_head
.append(" {") .append(" {")
.append(v_body) .append(v_body)

View File

@ -82,6 +82,7 @@ pub fn param(is_validator_param: bool) -> impl Parser<Token, ast::UntypedArg, Er
.map_with_span(|(arg_name, annotation), span| ast::Arg { .map_with_span(|(arg_name, annotation), span| ast::Arg {
location: span, location: span,
annotation, annotation,
doc: None,
tipo: (), tipo: (),
arg_name, arg_name,
}) })

View File

@ -17,6 +17,7 @@ Validator(
}, },
location: 21..26, location: 21..26,
annotation: None, annotation: None,
doc: None,
tipo: (), tipo: (),
}, },
Arg { Arg {
@ -28,6 +29,7 @@ Validator(
}, },
location: 28..32, location: 28..32,
annotation: None, annotation: None,
doc: None,
tipo: (), tipo: (),
}, },
Arg { Arg {
@ -39,6 +41,7 @@ Validator(
}, },
location: 34..37, location: 34..37,
annotation: None, annotation: None,
doc: None,
tipo: (), tipo: (),
}, },
], ],
@ -67,6 +70,7 @@ Validator(
}, },
location: 64..68, location: 64..68,
annotation: None, annotation: None,
doc: None,
tipo: (), tipo: (),
}, },
Arg { Arg {
@ -78,6 +82,7 @@ Validator(
}, },
location: 70..73, location: 70..73,
annotation: None, annotation: None,
doc: None,
tipo: (), tipo: (),
}, },
], ],

View File

@ -17,6 +17,7 @@ Validator(
}, },
location: 21..26, location: 21..26,
annotation: None, annotation: None,
doc: None,
tipo: (), tipo: (),
}, },
Arg { Arg {
@ -28,6 +29,7 @@ Validator(
}, },
location: 28..32, location: 28..32,
annotation: None, annotation: None,
doc: None,
tipo: (), tipo: (),
}, },
Arg { Arg {
@ -39,6 +41,7 @@ Validator(
}, },
location: 34..37, location: 34..37,
annotation: None, annotation: None,
doc: None,
tipo: (), tipo: (),
}, },
], ],

View File

@ -49,6 +49,7 @@ pub fn parser() -> impl Parser<Token, UntypedExpr, Error = ParseError> {
is_validator_param: false, is_validator_param: false,
}, },
annotation: arg_annotation.clone(), annotation: arg_annotation.clone(),
doc: None,
location, location,
tipo: (), tipo: (),
}, },
@ -60,6 +61,7 @@ pub fn parser() -> impl Parser<Token, UntypedExpr, Error = ParseError> {
is_validator_param: false, is_validator_param: false,
}, },
annotation: arg_annotation, annotation: arg_annotation,
doc: None,
location, location,
tipo: (), tipo: (),
}, },

View File

@ -50,6 +50,7 @@ pub fn params() -> impl Parser<Token, ast::UntypedArg, Error = ParseError> {
.map_with_span(|(arg_name, annotation), span| ast::Arg { .map_with_span(|(arg_name, annotation), span| ast::Arg {
location: span, location: span,
annotation, annotation,
doc: None,
tipo: (), tipo: (),
arg_name, arg_name,
}) })

View File

@ -22,6 +22,7 @@ Fn {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
], ],

View File

@ -40,6 +40,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
Arg { Arg {
@ -58,6 +59,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
], ],
@ -133,6 +135,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
Arg { Arg {
@ -151,6 +154,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
], ],
@ -226,6 +230,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
Arg { Arg {
@ -244,6 +249,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
], ],
@ -319,6 +325,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
Arg { Arg {
@ -337,6 +344,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
], ],
@ -405,6 +413,7 @@ Sequence {
}, },
location: 106..108, location: 106..108,
annotation: None, annotation: None,
doc: None,
tipo: (), tipo: (),
}, },
Arg { Arg {
@ -416,6 +425,7 @@ Sequence {
}, },
location: 106..108, location: 106..108,
annotation: None, annotation: None,
doc: None,
tipo: (), tipo: (),
}, },
], ],
@ -484,6 +494,7 @@ Sequence {
}, },
location: 129..131, location: 129..131,
annotation: None, annotation: None,
doc: None,
tipo: (), tipo: (),
}, },
Arg { Arg {
@ -495,6 +506,7 @@ Sequence {
}, },
location: 129..131, location: 129..131,
annotation: None, annotation: None,
doc: None,
tipo: (), tipo: (),
}, },
], ],
@ -570,6 +582,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
Arg { Arg {
@ -588,6 +601,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
], ],
@ -663,6 +677,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
Arg { Arg {
@ -681,6 +696,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
], ],
@ -756,6 +772,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
Arg { Arg {
@ -774,6 +791,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
], ],
@ -849,6 +867,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
Arg { Arg {
@ -867,6 +886,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
], ],
@ -942,6 +962,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
Arg { Arg {
@ -960,6 +981,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
], ],
@ -1035,6 +1057,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
Arg { Arg {
@ -1053,6 +1076,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
], ],
@ -1128,6 +1152,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
Arg { Arg {
@ -1146,6 +1171,7 @@ Sequence {
arguments: [], arguments: [],
}, },
), ),
doc: None,
tipo: (), tipo: (),
}, },
], ],

View File

@ -49,6 +49,7 @@ Sequence {
}, },
location: 0..0, location: 0..0,
annotation: None, annotation: None,
doc: None,
tipo: (), tipo: (),
}, },
], ],
@ -78,6 +79,7 @@ Sequence {
}, },
location: 52..53, location: 52..53,
annotation: None, annotation: None,
doc: None,
tipo: (), tipo: (),
}, },
], ],

View File

@ -848,6 +848,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
arg_name, arg_name,
annotation, annotation,
location, location,
doc,
.. ..
} = arg; } = arg;
@ -870,6 +871,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
location, location,
annotation, annotation,
tipo, tipo,
doc,
}) })
} }

View File

@ -42,7 +42,7 @@ impl LspProject {
let modules = self.project.modules(); let modules = self.project.modules();
for module in modules.into_iter() { for mut module in modules.into_iter() {
let path = module let path = module
.input_path .input_path
.canonicalize() .canonicalize()
@ -55,6 +55,8 @@ impl LspProject {
let source = SourceInfo { path, line_numbers }; let source = SourceInfo { path, line_numbers };
module.attach_doc_and_module_comments();
self.sources.insert(module.name.to_string(), source); self.sources.insert(module.name.to_string(), source);
self.modules.insert(module.name.to_string(), module); self.modules.insert(module.name.to_string(), module);
} }

View File

@ -181,14 +181,17 @@ where
output_path: destination.clone(), output_path: destination.clone(),
}); });
let doc_files = docs::generate_all( let modules = self
&self.root, .checked_modules
&self.config, .values_mut()
self.checked_modules
.values()
.filter(|CheckedModule { package, .. }| package == &self.config.name.to_string()) .filter(|CheckedModule { package, .. }| package == &self.config.name.to_string())
.collect(), .map(|m| {
); m.attach_doc_and_module_comments();
&*m
})
.collect();
let doc_files = docs::generate_all(&self.root, &self.config, modules);
for file in doc_files { for file in doc_files {
let path = destination.join(file.path); let path = destination.join(file.path);
@ -270,6 +273,10 @@ where
path: self.blueprint_path(), path: self.blueprint_path(),
}); });
self.checked_modules.values_mut().for_each(|m| {
m.attach_doc_and_module_comments();
});
let mut generator = self.checked_modules.new_generator( let mut generator = self.checked_modules.new_generator(
&self.functions, &self.functions,
&self.data_types, &self.data_types,
@ -641,7 +648,7 @@ where
self.module_types self.module_types
.insert(name.clone(), ast.type_info.clone()); .insert(name.clone(), ast.type_info.clone());
let mut checked_module = CheckedModule { let checked_module = CheckedModule {
kind, kind,
extra, extra,
name: name.clone(), name: name.clone(),
@ -651,8 +658,6 @@ where
input_path: path, input_path: path,
}; };
checked_module.attach_doc_and_module_comments();
self.checked_modules.insert(name, checked_module); self.checked_modules.insert(name, checked_module);
} }
} }

View File

@ -1,8 +1,8 @@
use crate::error::Error; use crate::error::Error;
use aiken_lang::{ use aiken_lang::{
ast::{ ast::{
DataType, Definition, Located, ModuleKind, TypedDataType, TypedFunction, TypedModule, DataType, Definition, Function, Located, ModuleKind, TypedDataType, TypedFunction,
TypedValidator, UntypedModule, TypedModule, TypedValidator, UntypedModule, Validator,
}, },
gen_uplc::{ gen_uplc::{
builder::{DataTypeKey, FunctionAccessKey}, builder::{DataTypeKey, FunctionAccessKey},
@ -208,18 +208,25 @@ impl CheckedModule {
def.put_doc(doc); def.put_doc(doc);
} }
if let Definition::DataType(DataType { constructors, .. }) = def { match def {
Definition::DataType(DataType { constructors, .. }) => {
for constructor in constructors { for constructor in constructors {
let docs: Vec<&str> = let docs: Vec<&str> = comments_before(
comments_before(&mut doc_comments, constructor.location.start, &self.code); &mut doc_comments,
constructor.location.start,
&self.code,
);
if !docs.is_empty() { if !docs.is_empty() {
let doc = docs.join("\n"); let doc = docs.join("\n");
constructor.put_doc(doc); constructor.put_doc(doc);
} }
for argument in constructor.arguments.iter_mut() { for argument in constructor.arguments.iter_mut() {
let docs: Vec<&str> = let docs: Vec<&str> = comments_before(
comments_before(&mut doc_comments, argument.location.start, &self.code); &mut doc_comments,
argument.location.start,
&self.code,
);
if !docs.is_empty() { if !docs.is_empty() {
let doc = docs.join("\n"); let doc = docs.join("\n");
argument.put_doc(doc); argument.put_doc(doc);
@ -227,6 +234,60 @@ impl CheckedModule {
} }
} }
} }
Definition::Fn(Function { arguments, .. }) => {
for argument in arguments {
let docs: Vec<&str> =
comments_before(&mut doc_comments, argument.location.start, &self.code);
if !docs.is_empty() {
let doc = docs.join("\n");
argument.put_doc(doc);
}
}
}
Definition::Validator(Validator {
params,
fun,
other_fun,
..
}) => {
for param in params {
let docs: Vec<&str> =
comments_before(&mut doc_comments, param.location.start, &self.code);
if !docs.is_empty() {
let doc = docs.join("\n");
param.put_doc(doc);
}
}
for argument in fun.arguments.iter_mut() {
let docs: Vec<&str> =
comments_before(&mut doc_comments, argument.location.start, &self.code);
if !docs.is_empty() {
let doc = docs.join("\n");
argument.put_doc(doc);
}
}
if let Some(fun) = other_fun {
for argument in fun.arguments.iter_mut() {
let docs: Vec<&str> = comments_before(
&mut doc_comments,
argument.location.start,
&self.code,
);
if !docs.is_empty() {
let doc = docs.join("\n");
argument.put_doc(doc);
}
}
}
}
_ => (),
}
} }
} }
} }