Skip to content

Commit e1fe877

Browse files
Vtec234cipher1024
authored andcommitted
feat(tactic): make docstrings of imported modules accessible (#81)
1 parent 806a4c5 commit e1fe877

File tree

11 files changed

+142
-52
lines changed

11 files changed

+142
-52
lines changed

doc/changes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ This is the first community release of Lean 3.
3131

3232
* Module documentation is now stored in .olean files to allow documentation to be automatically generated. (#54)
3333

34+
* Documentation of all imported modules is now exposed via the `olean_doc_strings` tactic. (#81)
35+
3436
*Bug Fixes*
3537

3638
* build: fix emscripten build in travis (#68)

library/init/meta/tactic.lean

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,40 @@ It updates the environment in the tactic_state, and returns an expression of the
543543
where l_i's and a_j's are the collected dependencies.
544544
-/
545545
meta constant add_aux_decl (c : name) (type : expr) (val : expr) (is_lemma : bool) : tactic expr
546-
meta constant module_doc_strings : tactic (list (option name × string))
546+
547+
/-- Returns a list of all top-level (`/-!`) docstrings in the active module and imported ones.
548+
The returned object is a list of modules, indexed by `(some filename)` for imported modules
549+
and `none` for the active one, where each module in the list is paired with a list
550+
of `(position_in_file, docstring)` pairs. -/
551+
meta constant olean_doc_strings : tactic (list (option string × (list (pos × string))))
552+
553+
/-- Returns a list of docstrings in the active module. An entry in the list can be either:
554+
- a top-level (`/-!`) docstring, represented as `(none, docstring)`
555+
- a declaration-specific (`/--`) docstring, represented as `(some decl_name, docstring)` -/
556+
meta def module_doc_strings : tactic (list (option name × string)) :=
557+
do
558+
/- Obtain a list of top-level docs in current module. -/
559+
mod_docs ← olean_doc_strings,
560+
let mod_docs: list (list (option name × string)) :=
561+
mod_docs.filter_map (λ d,
562+
if d.1.is_none
563+
then some (d.2.map
564+
(λ pos_doc, ⟨none, pos_doc.2⟩))
565+
else none),
566+
let mod_docs := mod_docs.join,
567+
/- Obtain list of declarations in current module. -/
568+
e ← get_env,
569+
let decls := environment.fold e ([]: list name)
570+
(λ d acc, let n := d.to_name in
571+
if (environment.decl_olean e n).is_none
572+
then n::acc else acc),
573+
/- Map declarations to those which have docstrings. -/
574+
decls ← decls.mfoldl (λa n,
575+
(doc_string n >>=
576+
λ doc, pure $ (some n, doc) :: a)
577+
<|> pure a) [],
578+
pure (mod_docs ++ decls)
579+
547580
/-- Set attribute `attr_name` for constant `c_name` with the given priority.
548581
If the priority is none, then use default -/
549582
meta constant set_basic_attribute (attr_name : name) (c_name : name) (persistent := ff) (prio : option nat := none) : tactic unit

src/frontends/lean/parser.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2402,7 +2402,7 @@ std::string parser::parse_doc_block() {
24022402
}
24032403

24042404
void parser::parse_mod_doc_block() {
2405-
m_env = add_module_doc_string(m_env, m_scanner.get_str_val());
2405+
m_env = add_module_doc_string(m_env, m_scanner.get_str_val(), m_scanner.get_pos_info() );
24062406
next();
24072407
}
24082408

src/library/documentation.cpp

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ Author: Leonardo de Moura
88
#include <algorithm>
99
#include <functional>
1010
#include <cctype>
11+
#include <utility>
12+
#include <vector>
1113
#include "util/sstream.h"
1214
#include "library/module.h"
1315
#include "library/documentation.h"
1416

1517
namespace lean {
1618
struct documentation_ext : public environment_extension {
17-
/** Doc string for the current module being processed. It does not include imported doc strings. */
18-
list<doc_entry> m_module_doc;
19+
/** Doc strings for the current module being processed. It does not include imported doc strings. */
20+
std::vector<std::pair<pos_info, std::string>> m_module_docs;
1921
/** Doc strings for declarations (including imported ones). We store doc_strings for declarations in the .olean files. */
2022
name_map<std::string> m_doc_string_map;
2123
};
@@ -36,25 +38,16 @@ static environment update(environment const & env, documentation_ext const & ext
3638
struct doc_modification : public modification {
3739
LEAN_MODIFICATION("doc")
3840

39-
/** If non-empty, this is a doc on a declaration. Otherwise,
40-
* it's a doc on the whole module. */
4141
name m_decl;
4242
std::string m_doc;
4343

4444
doc_modification() {}
45-
/** A docstring for the entire module. */
46-
doc_modification(std::string const & doc) : m_decl(""), m_doc(doc) {}
4745
/** A docstring for a declaration in the module. */
4846
doc_modification(name const & decl, std::string const & doc) : m_decl(decl), m_doc(doc) {}
4947

5048
void perform(environment & env) const override {
5149
auto ext = get_extension(env);
52-
if (m_decl != "") {
53-
ext.m_doc_string_map.insert(m_decl, m_doc);
54-
}
55-
// Note that we do NOT add anything to `m_module_doc` here, because `perform` is called
56-
// when applying modifications from imported .olean modules whose doc strings are NOT
57-
// part of the current module's documentation.
50+
ext.m_doc_string_map.insert(m_decl, m_doc);
5851
env = update(env, ext);
5952
}
6053

@@ -176,12 +169,12 @@ static std::string process_doc(std::string s) {
176169
return add_lean_suffix_to_code_blocks(s);
177170
}
178171

179-
environment add_module_doc_string(environment const & env, std::string doc) {
172+
environment add_module_doc_string(environment const & env, std::string doc, pos_info pos) {
180173
doc = process_doc(doc);
181174
auto ext = get_extension(env);
182-
ext.m_module_doc = cons(doc_entry(doc), ext.m_module_doc);
175+
ext.m_module_docs.emplace_back(pos, doc);
183176
auto new_env = update(env, ext);
184-
return module::add(new_env, std::make_shared<doc_modification>(doc));
177+
return module::add_doc_string(new_env, doc, pos);
185178
}
186179

187180
environment add_doc_string(environment const & env, name const & n, std::string doc) {
@@ -191,7 +184,6 @@ environment add_doc_string(environment const & env, name const & n, std::string
191184
throw exception(sstream() << "environment already contains a doc string for '" << n << "'");
192185
}
193186
ext.m_doc_string_map.insert(n, doc);
194-
ext.m_module_doc = cons(doc_entry(n, doc), ext.m_module_doc);
195187
auto new_env = update(env, ext);
196188
return module::add(new_env, std::make_shared<doc_modification>(n, doc));
197189
}
@@ -204,10 +196,13 @@ optional<std::string> get_doc_string(environment const & env, name const & n) {
204196
return optional<std::string>();
205197
}
206198

207-
void get_module_doc_strings(environment const & env, buffer<doc_entry> & result) {
199+
void get_module_doc_strings(environment const & env, buffer<mod_doc_entry> & result) {
208200
auto ext = get_extension(env);
209-
to_buffer(ext.m_module_doc, result);
210-
std::reverse(result.begin(), result.end());
201+
auto const & mod_docs = module::get_doc_strings(env);
202+
for (auto const & pr : mod_docs) {
203+
result.push_back({ optional<std::string>{ pr.first }, pr.second });
204+
}
205+
result.push_back({ {}, ext.m_module_docs });
211206
}
212207

213208
void initialize_documentation() {

src/library/documentation.h

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,20 @@ Author: Leonardo de Moura
66
*/
77
#pragma once
88
#include <string>
9+
#include <utility>
10+
#include <vector>
911
#include "kernel/environment.h"
12+
#include "util/message_definitions.h"
13+
1014
namespace lean {
11-
class doc_entry {
12-
optional<name> m_decl_name;
13-
std::string m_doc;
14-
public:
15-
doc_entry(std::string const & doc):m_doc(doc) {}
16-
doc_entry(name const & decl_name, std::string const & doc):m_decl_name(decl_name), m_doc(doc) {}
17-
optional<name> const & get_decl_name() const { return m_decl_name; }
18-
std::string const & get_doc() const { return m_doc; }
15+
struct mod_doc_entry {
16+
optional<std::string> m_mod_name;
17+
std::vector<std::pair<pos_info, std::string>> m_docs;
1918
};
20-
environment add_module_doc_string(environment const & env, std::string doc);
19+
environment add_module_doc_string(environment const & env, std::string doc, pos_info pos);
2120
environment add_doc_string(environment const & env, name const & n, std::string);
2221
optional<std::string> get_doc_string(environment const & env, name const & n);
23-
void get_module_doc_strings(environment const & env, buffer<doc_entry> & result);
22+
void get_module_doc_strings(environment const & env, buffer<mod_doc_entry> & result);
2423
void initialize_documentation();
2524
void finalize_documentation();
2625
}

src/library/module.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ struct module_ext : public environment_extension {
8686
list<name> m_module_decls;
8787
name_set m_module_defs;
8888
name_set m_imported;
89+
/** Top-level doc strings for modules which have them. Lean doesn't have a notion
90+
* of module different from that of a source file, so we use file names to index
91+
* the docstring map. */
92+
// TODO(Vtec234): lean::rb_map misbehaves here for some reason
93+
std::unordered_map<std::string, std::vector<std::pair<pos_info, std::string>>> m_module_docs;
8994
// Map from declaration name to olean file where it was defined
9095
name_map<std::string> m_decl2olean;
9196
name_map<pos_info> m_decl2pos_info;
@@ -416,6 +421,32 @@ struct quot_modification : public modification {
416421
}
417422
};
418423

424+
struct mod_doc_modification : public modification {
425+
LEAN_MODIFICATION("mod_doc")
426+
427+
std::string m_doc;
428+
pos_info m_pos;
429+
430+
mod_doc_modification() {}
431+
/** A module-level docstring. */
432+
mod_doc_modification(std::string const & doc, pos_info pos) : m_doc(doc), m_pos(pos) {}
433+
434+
void perform(environment & env) const override {
435+
// No-op. See `import_module` for actual action
436+
}
437+
438+
void serialize(serializer & s) const override {
439+
s << m_doc << m_pos;
440+
}
441+
442+
static std::shared_ptr<modification const> deserialize(deserializer & d) {
443+
std::string doc;
444+
pos_info pos;
445+
d >> doc >> pos;
446+
return std::make_shared<mod_doc_modification>(doc, pos);
447+
}
448+
};
449+
419450
namespace module {
420451
environment add(environment const & env, std::shared_ptr<modification const> const & modf) {
421452
module_ext ext = get_extension(env);
@@ -506,6 +537,14 @@ environment add(environment const & env, certified_declaration const & d) {
506537
return add_decl_pos_info(new_env, _d.get_name());
507538
}
508539

540+
environment add_doc_string(environment const & env, std::string const & doc, pos_info pos) {
541+
return add(env, std::make_shared<mod_doc_modification>(doc, pos));
542+
}
543+
544+
std::unordered_map<std::string, std::vector<std::pair<pos_info, std::string>>> const & get_doc_strings(environment const & env) {
545+
return get_extension(env).m_module_docs;
546+
}
547+
509548
bool is_definition(environment const & env, name const & n) {
510549
module_ext const & ext = get_extension(env);
511550
return ext.m_module_defs.contains(n);
@@ -693,6 +732,10 @@ void import_module(modification_list const & modifications, std::string const &
693732
env = add_decl_olean(env, dm->m_decl.get_name(), file_name);
694733
} else if (auto im = dynamic_cast<inductive_modification const *>(m.get())) {
695734
env = add_decl_olean(env, im->m_decl.get_decl().m_name, file_name);
735+
} else if (auto mdm = dynamic_cast<mod_doc_modification const *>(m.get())) {
736+
auto ext = get_extension(env);
737+
ext.m_module_docs[file_name].emplace_back(mdm->m_pos, mdm->m_doc);
738+
env = update(env, ext);
696739
}
697740
}
698741
}
@@ -724,13 +767,15 @@ void initialize_module() {
724767
inductive_modification::init();
725768
quot_modification::init();
726769
pos_info_mod::init();
770+
mod_doc_modification::init();
727771
}
728772

729773
void finalize_module() {
730774
quot_modification::finalize();
731775
pos_info_mod::finalize();
732776
inductive_modification::finalize();
733777
decl_modification::finalize();
778+
mod_doc_modification::finalize();
734779
delete g_object_readers;
735780
delete g_ext;
736781
}

src/library/module.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
55
Author: Leonardo de Moura
66
*/
77
#pragma once
8+
#include <unordered_map>
89
#include <string>
910
#include <iostream>
1011
#include <utility>
@@ -143,6 +144,12 @@ environment add_and_perform(environment const & env, std::shared_ptr<modificatio
143144
/** \brief Add the given declaration to the environment, and mark it to be exported. */
144145
environment add(environment const & env, certified_declaration const & d);
145146

147+
/** \brief Adds a module-level doc to the current module. */
148+
environment add_doc_string(environment const & env, std::string const & doc, pos_info pos);
149+
150+
/** \brief Returns the map of module-level docs indexed by source file name. */
151+
std::unordered_map<std::string, std::vector<std::pair<pos_info, std::string>>> const & get_doc_strings(environment const & env);
152+
146153
/** \brief Return true iff \c n is a definition added to the current module using #module::add */
147154
bool is_definition(environment const & env, name const & n);
148155

src/library/tactic/tactic_state.cpp

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -758,22 +758,32 @@ vm_obj tactic_add_doc_string(vm_obj const & n, vm_obj const & doc, vm_obj const
758758
}
759759
}
760760

761-
/* meta constant module_doc_strings : tactic (list (option name × string)) */
762-
vm_obj tactic_module_doc_strings(vm_obj const & _s) {
761+
/* meta constant olean_doc_strings : tactic (list (option string × (list (pos × string)))) */
762+
vm_obj tactic_olean_doc_strings(vm_obj const & _s) {
763763
tactic_state const & s = tactic::to_state(_s);
764-
buffer<doc_entry> entries;
764+
buffer<mod_doc_entry> entries;
765765
get_module_doc_strings(s.env(), entries);
766766
unsigned i = entries.size();
767767
vm_obj r = mk_vm_simple(0);
768768
while (i > 0) {
769769
--i;
770770
vm_obj decl_name;
771-
if (auto d = entries[i].get_decl_name())
771+
if (auto d = entries[i].m_mod_name)
772772
decl_name = mk_vm_some(to_obj(*d));
773773
else
774774
decl_name = mk_vm_none();
775-
vm_obj doc = to_obj(entries[i].get_doc());
776-
vm_obj e = mk_vm_pair(decl_name, doc);
775+
vm_obj lst = mk_vm_simple(0);
776+
unsigned j = entries[i].m_docs.size();
777+
while (j > 0) {
778+
--j;
779+
auto const& doc = entries[i].m_docs[j];
780+
vm_obj line = mk_vm_nat(doc.first.first);
781+
vm_obj column = mk_vm_nat(doc.first.second);
782+
vm_obj pos = mk_vm_constructor(0, line, column); // pos_info
783+
vm_obj doc_obj = mk_vm_pair(pos, to_obj(doc.second));
784+
lst = mk_vm_constructor(1, doc_obj, lst);
785+
}
786+
vm_obj e = mk_vm_pair(decl_name, lst);
777787
r = mk_vm_constructor(1, e, r);
778788
}
779789
return tactic::mk_success(r, s);
@@ -1065,7 +1075,7 @@ void initialize_tactic_state() {
10651075
DECLARE_VM_BUILTIN(name({"tactic", "set_env"}), tactic_set_env);
10661076
DECLARE_VM_BUILTIN(name({"tactic", "doc_string"}), tactic_doc_string);
10671077
DECLARE_VM_BUILTIN(name({"tactic", "add_doc_string"}), tactic_add_doc_string);
1068-
DECLARE_VM_BUILTIN(name({"tactic", "module_doc_strings"}), tactic_module_doc_strings);
1078+
DECLARE_VM_BUILTIN(name({"tactic", "olean_doc_strings"}), tactic_olean_doc_strings);
10691079
DECLARE_VM_BUILTIN(name({"tactic", "open_namespaces"}), tactic_open_namespaces);
10701080
DECLARE_VM_BUILTIN(name({"tactic", "decl_name"}), tactic_decl_name);
10711081
DECLARE_VM_BUILTIN(name({"tactic", "add_aux_decl"}), tactic_add_aux_decl);

src/shell/leandoc.cpp

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -229,23 +229,20 @@ void gen_doc(environment const & env, options const & _opts, std::ostream & out)
229229
options opts = _opts.update_if_undef(name{"pp", "width"}, 100);
230230
auto fmt_factory = lean::mk_pretty_formatter_factory();
231231
auto fmt = fmt_factory(env, opts, ctx);
232-
buffer<doc_entry> entries;
233-
get_module_doc_strings(env, entries);
234-
for (doc_entry const & entry : entries) {
235-
if (auto decl_name = entry.get_decl_name()) {
236-
if (has_brief(entry.get_doc())) {
232+
233+
env.for_each_declaration([&] (declaration const & d) {
234+
if (auto doc = get_doc_string(env, d.get_name())) {
235+
if (has_brief(*doc)) {
237236
std::string brief, body;
238-
std::tie(brief, body) = split_brief_body(entry.get_doc());
239-
gen_decl_doc(out, env, fmt, *decl_name, optional<std::string>(brief));
237+
std::tie(brief, body) = split_brief_body(*doc);
238+
gen_decl_doc(out, env, fmt, d.get_name(), optional<std::string>(brief));
240239
print_block(out, body);
241240
} else {
242-
gen_decl_doc(out, env, fmt, *decl_name, optional<std::string>());
243-
print_block(out, entry.get_doc());
241+
gen_decl_doc(out, env, fmt, d.get_name(), optional<std::string>());
242+
print_block(out, *doc);
244243
}
245-
} else {
246-
print_block(out, entry.get_doc());
247244
}
248245
out << "\n";
249-
}
246+
});
250247
}
251248
}

tests/lean/doc_strings.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ open tactic
1414
run_cmd doc_string `foo.a >>= trace
1515
run_cmd doc_string `foo.b >>= trace
1616
run_cmd doc_string `two >>= trace
17+
run_cmd olean_doc_strings >>= trace
1718
run_cmd module_doc_strings >>= trace

0 commit comments

Comments
 (0)