Skip to content

lsp: document every built-in member, complexity included - #43

Merged
simontreanor merged 1 commit into
mainfrom
feat/stdlib-member-docs
Jul 31, 2026
Merged

lsp: document every built-in member, complexity included#43
simontreanor merged 1 commit into
mainfrom
feat/stdlib-member-docs

Conversation

@simontreanor

Copy link
Copy Markdown
Owner

Follow-up to #42, and groundwork for the four remaining sweep PRs.

Big-O was written down in DESIGN prose and in comments beside the schemes, which is nowhere a user can see it. Hover on a built-in showed its type and effect and no prose at all, since doc comments only ever came from a user's own declarations. Completion items carried a label and nothing else — not even the signature.

types::MEMBER_DOCS now gives all 151 built-in members a one-line description, keyed by the name a user writes. The server appends it to hover below the type, exactly where a ## doc already goes, and attaches it to completion items along with the rendered signature as detail.

List.contains        a -> List a -> bool
                     Whether the list holds this element. O(n) linear scan — Set.contains is O(1).

List.updateAt        int -> 'a -> List 'a -> List 'a
                     A fresh list with one index replaced. O(n) — it copies; an index outside
                     the list changes nothing.

Complexity is stated wherever it is not obvious or not what a reader would assume, which is the whole point: a List backed by a Python list makes cost easy to misjudge. contains is a linear scan where Set.contains is O(1); updateAt/insertAt/removeAt copy; distinct hashes; Map.add copies, though a fold that builds a map lowers to an in-place loop.

A user's own declaration wins over a built-in of the same name — there is a test where a user function called max does not inherit the prelude's documentation.

Signatures cannot drift: they are rendered once on first use by seeding a fresh environment and showing each scheme, rather than being written out by hand a second time. Constraints are not spelled in the signature; where comparison or num matters, the member's doc line says so.

Three tests pin the table to the prelude constants — every member is documented, every entry names a real member, every member has a signature. That is the part that pays off later: the four remaining sweep PRs (Seq, Map/Set, String, Option/Result, ~60 members) cannot add an undocumented member, so the enforcement is in place before the members arrive rather than leaving them to backfill.

Plus four LSP tests: hover on a qualified member, hover on a global, a user declaration shadowing a built-in name, and a completion item carrying both signature and documentation.

Big-O was written down in DESIGN prose and in comments beside the schemes,
which is nowhere a user can see it. Hover showed a built-in member's type
and effect and no prose at all, since doc comments only ever came from a
user's own declarations, and completion items carried a label and nothing
else — not even the signature.

`types::MEMBER_DOCS` gives all 151 built-in members a one-line description,
keyed by the name a user writes. The language server appends it to hover
below the type, exactly where a `##` doc already goes, and attaches it to
completion items along with the rendered signature as `detail`. A user's
own declaration wins over a built-in of the same name.

Complexity is stated wherever it is not obvious or not what a reader would
assume, which is the point: a `List` backed by a Python list makes cost
easy to misjudge. `List.contains` is a linear scan where `Set.contains` is
O(1); `updateAt`, `insertAt` and `removeAt` copy; `distinct` hashes;
`Map.add` copies, though a fold that builds a map lowers to an in-place
loop.

Signatures render once on first use, by seeding a fresh environment and
showing each scheme, so they cannot drift from the schemes themselves.
Constraints are not spelled in the signature; where `comparison` or `num`
matters the member's doc line says so.

Three tests pin the table to the prelude constants: every member is
documented, every entry names a real member, and every member has a
signature. So the four remaining sweep PRs cannot add an undocumented
member — the enforcement lands before the members do.
@simontreanor
simontreanor merged commit 1ff131b into main Jul 31, 2026
11 checks passed
@simontreanor
simontreanor deleted the feat/stdlib-member-docs branch July 31, 2026 15:45
simontreanor added a commit that referenced this pull request Jul 31, 2026
Two dogfooding reports from real programs, and the standard-library sweep
they triggered.

Language:

* a `type` declaration can name an imported type, bare or module-qualified
  (#36) — the one gap that changed a program's architecture rather than its
  phrasing, forcing two modules into one file
* field access resolves from the base's type when it is known, so two
  records may share a field name without prefixes (#37)
* parameters destructure: tuples (#38), records (#40), and `_`
* a direct self tail call lowers to a loop, so an interactive turn loop no
  longer walks the stack (#39, #41)

Standard library — about 115 new members, taking every module to the F#
core set: List (#42), Seq (#44), Set and Map (#46), String (#47), Option
and Result (#48), then a member-by-member FSharp.Core audit (#51). Every
built-in member now carries a one-line description and its complexity in
hover and completion (#43, #49), enforced by tests.

Fixes:

* `pyfun run` on a single file gives the program its own stdin, so an
  interactive program is runnable by the command whose job is running
  programs (#35)
* a partially applied lambda closes over its argument instead of being
  wrapped, so `List.map ((+) 2)` emits `lambda b: 2 + b` (#52)
* every multi-argument callback's scheme put the effect variable on the
  wrong arrows, so `List.fold` could never accept an effectful folder (#51)
* `Seq.empty` lowered to a bare `iter()`, a TypeError (#51)

One source-incompatible change, which is why this is 0.4.0 and not 0.3.1:
a dotted `extern` target whose module prefix cannot be decided from the
text is now a compile error naming the `extern import` to add (#50).
`sys.stdout.flush` used to emit `import sys.stdout` and fail at runtime;
declaring `extern import sys` fixes it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant