Skip to content

feat(candid): preserve doc comments on exported types and fields#707

Merged
lwshang merged 5 commits into
masterfrom
feat/type-doc-comments
Mar 15, 2026
Merged

feat(candid): preserve doc comments on exported types and fields#707
lwshang merged 5 commits into
masterfrom
feat/type-doc-comments

Conversation

@sasa-tomic

@sasa-tomic sasa-tomic commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

Solution

  • Add TypeDoc, FieldDoc, and TypeDocs types to carry doc metadata alongside the type graph
  • Extend CandidType derive to extract Rust doc comments via new _ty_doc() hook; store per-TypeId in thread-local DOC_ENV
  • Update pretty-printer to render docs above type definitions, record fields, and variant members

Details

  • Docs flow through TypeContainer which maps Rust TypeId docs to final Candid export names
  • Tuples with field docs fall back to explicit numeric field syntax

Meta

  • This is needed for my work on Immutable Object Storage. I currently have some hacks to add doc comments to .did files, but everyone would benefit if this was fixed properly.

### Solution
- Add `TypeDoc`, `FieldDoc`, and `TypeDocs` types to carry doc metadata alongside the type graph
- Extend `CandidType` derive to extract Rust doc comments via new `_ty_doc()` hook; store per-TypeId in thread-local DOC_ENV
- Update pretty-printer to render docs above type definitions, record fields, and variant members

### Details
- Docs flow through `TypeContainer` which maps Rust TypeId docs to final Candid export names
- Tuples with field docs fall back to explicit numeric field syntax

### Meta
- updated tests accordingly
@sasa-tomic sasa-tomic self-assigned this Mar 13, 2026
@sasa-tomic sasa-tomic marked this pull request as ready for review March 13, 2026 08:09
@sasa-tomic sasa-tomic requested a review from a team as a code owner March 13, 2026 08:09
@github-actions

github-actions Bot commented Mar 13, 2026

Copy link
Copy Markdown
Name Max Mem (Kb) Encode Decode
blob 4_224 4_207_756 ($\textcolor{red}{0.03\%}$) 2_122_451 ($\textcolor{red}{0.00\%}$)
btreemap 75_456 4_735_768_820 ($\textcolor{red}{0.00\%}$) 15_190_905_531 ($\textcolor{green}{-0.57\%}$)
nns 192 ($\textcolor{red}{50.00\%}$) 2_026_699 ($\textcolor{green}{-0.28\%}$) 5_742_377 ($\textcolor{red}{3.02\%}$)
nns_list_proposal 1_216 ($\textcolor{red}{11.76\%}$) 7_051_377 ($\textcolor{red}{3.08\%}$) 67_867_335 ($\textcolor{green}{-1.38\%}$)
option_list 128 8_080_447 ($\textcolor{red}{0.05\%}$) 26_548_979 ($\textcolor{green}{-1.77\%}$)
text 6_336 4_204_597 ($\textcolor{red}{0.03\%}$) 7_877_794 ($\textcolor{green}{-0.00\%}$)
variant_list 128 8_132_869 ($\textcolor{red}{0.03\%}$) 25_173_973 ($\textcolor{green}{-1.23\%}$)
vec_int16 16_704 123_694_780 ($\textcolor{red}{0.00\%}$) 1_015_045_686 ($\textcolor{green}{-4.72\%}$)
  • Parser cost: 17_069_949 ($\textcolor{green}{-0.01\%}$)
  • Extra args: 3_416_301 ($\textcolor{green}{-0.34\%}$)
Click to see raw report
---------------------------------------------------

Benchmark: blob
  total:
    instructions: 6.33 M (0.02%) (change within noise threshold)
    heap_increase: 66 pages (no change)
    stable_memory_increase: 0 pages (no change)

  1. Encoding (scope):
    calls: 1 (no change)
    instructions: 4.21 M (0.03%) (change within noise threshold)
    heap_increase: 66 pages (no change)
    stable_memory_increase: 0 pages (no change)

  2. Decoding (scope):
    calls: 1 (no change)
    instructions: 2.12 M (0.00%) (change within noise threshold)
    heap_increase: 0 pages (no change)
    stable_memory_increase: 0 pages (no change)

---------------------------------------------------

Benchmark: btreemap
  total:
    instructions: 19.93 B (-0.43%) (change within noise threshold)
    heap_increase: 1179 pages (no change)
    stable_memory_increase: 0 pages (no change)

  1. Encoding (scope):
    calls: 1 (no change)
    instructions: 4.74 B (0.00%) (change within noise threshold)
    heap_increase: 159 pages (no change)
    stable_memory_increase: 0 pages (no change)

  2. Decoding (scope):
    calls: 1 (no change)
    instructions: 15.19 B (-0.57%) (change within noise threshold)
    heap_increase: 1020 pages (no change)
    stable_memory_increase: 0 pages (no change)

---------------------------------------------------

Benchmark: extra_args
  total:
    instructions: 3.42 M (-0.34%) (change within noise threshold)
    heap_increase: 0 pages (no change)
    stable_memory_increase: 0 pages (no change)

---------------------------------------------------

Benchmark: nns
  total:
    instructions: 25.68 M (0.62%) (change within noise threshold)
    heap_increase: 3 pages (regressed by 50.00%)
    stable_memory_increase: 0 pages (no change)

  0. Parsing (scope):
    calls: 1 (no change)
    instructions: 17.07 M (-0.01%) (change within noise threshold)
    heap_increase: 3 pages (regressed by 50.00%)
    stable_memory_increase: 0 pages (no change)

  1. Encoding (scope):
    calls: 1 (no change)
    instructions: 2.03 M (-0.28%) (change within noise threshold)
    heap_increase: 0 pages (no change)
    stable_memory_increase: 0 pages (no change)

  2. Decoding (scope):
    calls: 1 (no change)
    instructions: 5.74 M (regressed by 3.02%)
    heap_increase: 0 pages (no change)
    stable_memory_increase: 0 pages (no change)

---------------------------------------------------

Benchmark: nns_list_proposal
  total:
    instructions: 74.92 M (-0.97%) (change within noise threshold)
    heap_increase: 19 pages (regressed by 11.76%)
    stable_memory_increase: 0 pages (no change)

  1. Encoding (scope):
    calls: 1 (no change)
    instructions: 7.05 M (regressed by 3.08%)
    heap_increase: 5 pages (regressed by 66.67%)
    stable_memory_increase: 0 pages (no change)

  2. Decoding (scope):
    calls: 1 (no change)
    instructions: 67.87 M (-1.38%) (change within noise threshold)
    heap_increase: 14 pages (no change)
    stable_memory_increase: 0 pages (no change)

---------------------------------------------------

Benchmark: option_list
  total:
    instructions: 34.63 M (-1.35%) (change within noise threshold)
    heap_increase: 2 pages (no change)
    stable_memory_increase: 0 pages (no change)

  1. Encoding (scope):
    calls: 1 (no change)
    instructions: 8.08 M (0.05%) (change within noise threshold)
    heap_increase: 0 pages (no change)
    stable_memory_increase: 0 pages (no change)

  2. Decoding (scope):
    calls: 1 (no change)
    instructions: 26.55 M (-1.77%) (change within noise threshold)
    heap_increase: 2 pages (no change)
    stable_memory_increase: 0 pages (no change)

---------------------------------------------------

Benchmark: text
  total:
    instructions: 12.08 M (0.01%) (change within noise threshold)
    heap_increase: 99 pages (no change)
    stable_memory_increase: 0 pages (no change)

  1. Encoding (scope):
    calls: 1 (no change)
    instructions: 4.20 M (0.03%) (change within noise threshold)
    heap_increase: 66 pages (no change)
    stable_memory_increase: 0 pages (no change)

  2. Decoding (scope):
    calls: 1 (no change)
    instructions: 7.88 M (-0.00%) (change within noise threshold)
    heap_increase: 33 pages (no change)
    stable_memory_increase: 0 pages (no change)

---------------------------------------------------

Benchmark: variant_list
  total:
    instructions: 33.31 M (-0.92%) (change within noise threshold)
    heap_increase: 2 pages (no change)
    stable_memory_increase: 0 pages (no change)

  1. Encoding (scope):
    calls: 1 (no change)
    instructions: 8.13 M (0.03%) (change within noise threshold)
    heap_increase: 0 pages (no change)
    stable_memory_increase: 0 pages (no change)

  2. Decoding (scope):
    calls: 1 (no change)
    instructions: 25.17 M (-1.23%) (change within noise threshold)
    heap_increase: 2 pages (no change)
    stable_memory_increase: 0 pages (no change)

---------------------------------------------------

Benchmark: vec_int16
  total:
    instructions: 1.14 B (improved by 4.23%)
    heap_increase: 261 pages (no change)
    stable_memory_increase: 0 pages (no change)

  1. Encoding (scope):
    calls: 1 (no change)
    instructions: 123.69 M (0.00%) (change within noise threshold)
    heap_increase: 261 pages (no change)
    stable_memory_increase: 0 pages (no change)

  2. Decoding (scope):
    calls: 1 (no change)
    instructions: 1.02 B (improved by 4.72%)
    heap_increase: 0 pages (no change)
    stable_memory_increase: 0 pages (no change)

---------------------------------------------------

Summary:
  instructions:
    status:   Improvements detected 🟢
    counts:   [total 9 | regressed 0 | improved 1 | new 0 | unchanged 8]
    change:   [max +158.87K | p75 +1.26K | median -310.76K | p25 -737.41K | min -87.03M]
    change %: [max +0.62% | p75 +0.01% | median -0.43% | p25 -0.97% | min -4.23%]

  heap_increase:
    status:   Regressions detected 🔴
    counts:   [total 9 | regressed 2 | improved 0 | new 0 | unchanged 7]
    change:   [max +2 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max +50.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

  stable_memory_increase:
    status:   No significant changes 👍
    counts:   [total 9 | regressed 0 | improved 0 | new 0 | unchanged 9]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

---------------------------------------------------

Only significant changes:
| status | name                           | calls |    ins |  ins Δ% |  HI |   HI Δ% | SMI |  SMI Δ% |
|--------|--------------------------------|-------|--------|---------|-----|---------|-----|---------|
|   +    | nns_list_proposal::1. Encoding |     1 |  7.05M |  +3.08% |   5 | +66.67% |   0 |   0.00% |
|   +    | nns::2. Decoding               |     1 |  5.74M |  +3.02% |   0 |   0.00% |   0 |   0.00% |
|   +    | nns                            |       | 25.68M |  +0.62% |   3 | +50.00% |   0 |   0.00% |
|   +    | nns::0. Parsing                |     1 | 17.07M |  -0.01% |   3 | +50.00% |   0 |   0.00% |
|   +    | nns_list_proposal              |       | 74.92M |  -0.97% |  19 | +11.76% |   0 |   0.00% |
|   -    | vec_int16                      |       |  1.14B |  -4.23% | 261 |   0.00% |   0 |   0.00% |
|   -    | vec_int16::2. Decoding         |     1 |  1.02B |  -4.72% |   0 |   0.00% |   0 |   0.00% |

ins = instructions, HI = heap_increase, SMI = stable_memory_increase, Δ% = percent change

---------------------------------------------------
Successfully persisted results to canbench_results.yml

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for preserving Rust doc comments on exported Candid types and their fields/variants when generating .did files. Currently, doc comments are only preserved for service methods but not for data types defined via #[derive(CandidType)].

Changes:

  • Introduces TypeDoc, FieldDoc, and TypeDocs types to carry doc metadata alongside the type graph, with a parallel metadata flow through thread-local DOC_ENV storage and TypeContainer
  • Extends the CandidType derive macro to extract Rust doc comments via a new _ty_doc() trait hook, and refactors extract_doc_comments into a shared docs module
  • Updates the pretty-printer to render doc comments above type definitions, record fields, and variant members, with a tuple fallback to explicit numeric fields when tuple members carry docs

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
spec/Type-doc-comments.md Design document describing the motivation, approach, alternatives, and properties of the feature
rust/candid_derive/src/docs.rs Shared extract_doc_comments helper extracted from func.rs
rust/candid_derive/src/lib.rs Registers the new docs module
rust/candid_derive/src/derive.rs Extends CandidType derive to extract and emit doc metadata for structs, enums, fields, and variants
rust/candid_derive/src/func.rs Uses shared doc extractor; wires TypeDocs from TypeContainer into DocComments at export time
rust/candid/src/types/mod.rs Adds _ty_doc() default method to CandidType trait; calls env_doc during type registration
rust/candid/src/types/internal.rs Defines TypeDoc, FieldDoc, TypeDocs; extends TypeContainer with doc association; adds DOC_ENV thread-local
rust/candid/src/pretty/candid.rs Adds doc-aware pretty-printing for types and fields; extends DocComments with type def support
rust/candid/tests/types.rs End-to-end tests for doc comments on types/fields/variants and round-trip parsing verification

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread rust/candid/src/types/mod.rs Outdated
Comment thread rust/candid/src/pretty/candid.rs
lwshang and others added 4 commits March 15, 2026 11:20
…ith_doc

Reuse the already-computed `id` in `CandidType::ty()` instead of calling
`Self::id()` again for `env_doc`. Refactor `pp_ty_with_doc` to delegate
non-Record/Variant arms to `pp_ty_inner`, removing ~25 lines of duplication.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@lwshang lwshang merged commit b51c0c5 into master Mar 15, 2026
11 checks passed
@lwshang lwshang deleted the feat/type-doc-comments branch March 15, 2026 15:57
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.

3 participants