Skip to content

Display RBS type signatures in documentation#1665

Open
st0012 wants to merge 2 commits intomasterfrom
integrate-rbs
Open

Display RBS type signatures in documentation#1665
st0012 wants to merge 2 commits intomasterfrom
integrate-rbs

Conversation

@st0012
Copy link
Copy Markdown
Member

@st0012 st0012 commented Mar 30, 2026

Summary

  • Display RBS type signatures in HTML documentation and RI terminal output
  • Source type information from inline #: annotations and .rbs files (including RBS stdlib)
  • Link type names in signatures to their documentation pages

type-signatures

Details

New module: RDoc::RbsHelper

  • Loads RBS signatures from sig/ directories and stdlib declarations via RBS::EnvironmentLoader
  • Validates inline #: annotations using RBS::Parser, warns with file:line on invalid signatures
  • Renders type signatures as HTML with type names linked to their documentation pages using a.rbs-type

Parser integration (PrismRuby)

  • Extracts #: annotation lines from comments via RBS_SIG_LINE constant
  • Attaches parsed type signatures to MethodAttr#type_signature
  • Inline annotations take priority over .rbs file signatures

Store

  • merge_rbs_signatures fills in type signatures from loaded .rbs files where no inline annotation exists
  • type_name_lookup builds a cached name-to-path map for type linking; ambiguous unqualified names are excluded to avoid wrong links

Display

  • Aliki theme: method signatures render as styled <pre> blocks with dotted separator; attribute signatures render inline after the [RW] badge
  • RI driver: type signatures display as verbatim blocks after argument lists
  • JS updated to exclude type signature blocks from copy-button wrapping

Serialization

  • MARSHAL_VERSION bumped to 4 for both AnyMethod and Attr (additive — appends type_signature to marshal array)

CI/Dependencies

  • Minimum Ruby bumped to 3.2 (required by rbs 4.0)
  • Added rbs >= 4.0.0 and prism >= 1.6.0 dependencies
  • Dropped JRuby/TruffleRuby from CI matrix (rbs has a C extension)

@matzbot
Copy link
Copy Markdown
Collaborator

matzbot commented Mar 30, 2026

🚀 Preview deployment available at: https://b146eebc.rdoc-6cd.pages.dev (commit: fd4b149)

@st0012 st0012 changed the base branch from master to signature-card-design April 2, 2026 17:29
@st0012 st0012 force-pushed the signature-card-design branch from 251f5db to be86678 Compare April 2, 2026 18:05
@st0012 st0012 force-pushed the integrate-rbs branch 4 times, most recently from a469122 to bfcd437 Compare April 2, 2026 22:17
@st0012 st0012 force-pushed the signature-card-design branch from be86678 to 4b26c40 Compare April 9, 2026 14:54
Base automatically changed from signature-card-design to master April 9, 2026 15:04
@st0012 st0012 requested a review from Copilot April 9, 2026 19:51
Copy link
Copy Markdown

Copilot AI left a comment

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 first-class support for RBS type signatures in RDoc by extracting inline #: annotations during parsing, optionally loading signatures from .rbs files, and rendering those signatures in both HTML (Aliki theme) and ri terminal output.

Changes:

  • Extract #: type signatures from comment blocks in the Prism parser and attach them to RDoc::MethodAttr objects.
  • Load/merge signatures from RBS sources into the store (without overwriting inline annotations) and add type-name linking in HTML output.
  • Render signatures in Aliki HTML + ri, update marshal formats, and bump runtime requirements (Ruby >= 3.2, add rbs dependency).

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
lib/rdoc/parser/prism_ruby.rb Extracts/validates inline #: signatures and attaches them to methods/attrs.
lib/rdoc/rbs_support.rb New RBS integration: validation, signature loading, and HTML linking.
lib/rdoc/store.rb Adds signature merge logic and cached type-name lookup for linking.
lib/rdoc/generator/aliki.rb Exposes type_signature_html helper for templates (linked type names).
lib/rdoc/generator/template/aliki/class.rhtml Renders type signatures for methods and attributes in HTML output.
lib/rdoc/generator/template/aliki/css/rdoc.css Styles signature blocks and linked type names.
lib/rdoc/generator/template/aliki/js/aliki.js Avoids wrapping signature <pre> blocks with “copy” UI.
lib/rdoc/ri/driver.rb Prints method type signatures in ri output.
lib/rdoc/code_object/method_attr.rb Adds type_signature storage + line splitting helper.
lib/rdoc/code_object/any_method.rb Bumps marshal version and persists type_signature.
lib/rdoc/code_object/attr.rb Bumps marshal version and persists type_signature.
lib/rdoc/rdoc.rb Loads RBS signatures after store completion and merges into objects.
rdoc.gemspec Bumps Ruby minimum to 3.2, adds rbs dependency, bumps prism minimum.
Gemfile Always includes mini_racer on MRI (Ruby >= 3.2 now required).
.github/workflows/test.yml Aligns CI with Ruby >= 3.2 and updated Prism versions.
test/rdoc/parser/prism_ruby_test.rb Adds Prism-only tests for inline signature extraction.
test/rdoc/rbs_support_test.rb New tests for validation, loading, and HTML linking.
test/rdoc/rdoc_store_test.rb Tests signature merging + type_name_lookup behavior/caching.
test/rdoc/ri/driver_test.rb Tests ri output includes a type signature.
test/rdoc/code_object/any_method_test.rb Tests marshal persistence for method type signatures.
test/rdoc/code_object/attr_test.rb Tests marshal persistence for attribute type signatures.

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

@st0012 st0012 force-pushed the integrate-rbs branch 2 times, most recently from 599eacc to 1034e9b Compare April 10, 2026 18:15
@st0012 st0012 marked this pull request as ready for review April 10, 2026 18:32
Copilot AI review requested due to automatic review settings April 10, 2026 18:32
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 3 comments.


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

Comment on lines +868 to +875
def validate_type_signature(sig, line_no)
sig.split("\n").each_with_index do |line, i|
method_error = RDoc::RbsHelper.validate_method_type(line)
next unless method_error
type_error = RDoc::RbsHelper.validate_type(line)
next unless type_error
@options.warn "#{@top_level.relative_name}:#{line_no + i}: invalid RBS type signature: #{line.inspect}"
end
Comment on lines +113 to +114
result[start_in_escaped...end_in_escaped] =
"<a href=\"#{href}\" class=\"rbs-type\">#{escaped_name}</a>"
st0012 added 2 commits April 12, 2026 19:40
RBS 4.0 requires Ruby >= 3.2. Bump RDoc's own minimum to match and
add `rbs >= 4.0.0` as a gemspec dependency. Bump prism minimum to
`>= 1.6.0` (required by rbs). Drop JRuby and TruffleRuby from CI
matrix (rbs has a C extension that cannot build on them).
Add support for displaying RBS type signatures in both HTML and RI
output. Type information is sourced from inline `#:` annotations
(parsed by the Prism parser) and from `.rbs` files in the project's
`sig/` directory plus RBS stdlib declarations.

Implementation:
- `RDoc::RbsHelper` module: loads RBS signatures, validates types,
  renders type signatures as HTML with linked type names
- Parser extracts `#:` annotation lines via `RBS_SIG_LINE` constant,
  validates them, and attaches to `MethodAttr#type_signature`
- `Store#merge_rbs_signatures` fills in signatures from `.rbs` files
  where inline annotations are absent
- `Store#type_name_lookup` maps qualified and unambiguous unqualified
  names to documentation paths for type linking
- Aliki theme: type signatures render as styled `<pre>` blocks under
  method headings, with linked type names using `a.rbs-type` class
- RI driver: type signatures display as verbatim blocks
- `MARSHAL_VERSION` bumped to 4 for `AnyMethod` and `Attr`
@skatkov
Copy link
Copy Markdown

skatkov commented Apr 12, 2026

Do I understand correctly that the sorbet type signature will not work with this implementation?

@st0012
Copy link
Copy Markdown
Member Author

st0012 commented Apr 12, 2026

Yes, because it's not a official signature syntax for Ruby.

@kou
Copy link
Copy Markdown
Member

kou commented Apr 12, 2026

Dropped JRuby/TruffleRuby from CI matrix (rbs has a C extension)

Does this mean that JRuby can't use RDoc with this?

@st0012
Copy link
Copy Markdown
Member Author

st0012 commented Apr 12, 2026

@kou Unfortunately, yes. It's essentially a decision between:

  1. Having rbs as an official dependency with proper version restriction in gemspec, but JRuby won't be supported until it supports rbs
  2. Having rbs as a soft dependency and bail when rbs version doesn't match.
    • Note that with this approach, if a project runs RDoc with bundle exec, rbs needs to be added in the bundle manually/by other gems too

Given that I want to make RBS support a key feature of future RDoc and directly encourage gems to write more RBS signatures, I think 1 is a more reasonable decision.

@st0012
Copy link
Copy Markdown
Member Author

st0012 commented Apr 12, 2026

@headius I assumed JRuby doesn't support rbs 4.0+ due to this failure. Is it true that it's not supported? Or I can actually just tweak the CI config to make it work.

st0012 added a commit to Shopify/ruby that referenced this pull request Apr 12, 2026
Add `rbs` and its dependency `logger` to the bundled gem load paths in
`tool/rdoc-srcdir`, and add the compiled extension path from
`.bundle/extensions/` so the `rbs_extension` C extension can be found.

This is needed because `make html` runs with `--disable-gems`, so
RubyGems cannot resolve the extension path automatically. The extension
is already compiled by `make build-ext` (which `make html` depends on
via `main`), it just wasn't discoverable.

Required for ruby/rdoc#1665 to work with
`make html` in ruby/ruby.
@kou
Copy link
Copy Markdown
Member

kou commented Apr 13, 2026

@soutaro Can RBS add support for JRuby?

@soutaro
Copy link
Copy Markdown
Member

soutaro commented Apr 13, 2026

@kou I'm not sure. I remember @headius was working on FFI with RBS (ruby/rbs#2572). I thought it was something related to JRuby, but I don't have exact context about this.

@kou
Copy link
Copy Markdown
Member

kou commented Apr 13, 2026

Thanks. Let's wait for a response from @headius .

@st0012
Copy link
Copy Markdown
Member Author

st0012 commented Apr 13, 2026

@kou At the same time, I'd appreciate review on the rest of implementation too 🙏

return nil if sig_lines.empty?

text.replace(doc_lines.join)
type_sig = sig_lines.map { |l| l.sub(RBS_SIG_LINE, '').chomp }.join("\n")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that we can simplify this:

Suggested change
type_sig = sig_lines.map { |l| l.sub(RBS_SIG_LINE, '').chomp }.join("\n")
type_sig = sig_lines.map { |l| l.sub(RBS_SIG_LINE, '') }.join

end

def validate_type_signature(sig, line_no)
sig.split("\n").each_with_index do |line, i|
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
sig.split("\n").each_with_index do |line, i|
sig.each_line(chomp: true).with_index do |line, i|

Comment on lines +870 to +873
method_error = RDoc::RbsHelper.validate_method_type(line)
next unless method_error
type_error = RDoc::RbsHelper.validate_type(line)
next unless type_error
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If we don't use error messages in warning messages, valid_XXX? instead of validate_XXX may be better.

decl.members.each do |member|
case member
when RBS::AST::Members::MethodDefinition
key = member.singleton? ? "#{class_name}::#{member.name}" : "#{class_name}##{member.name}"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

#{class_name}.#{member.name} is better for singleton method.

Suggested change
key = member.singleton? ? "#{class_name}::#{member.name}" : "#{class_name}##{member.name}"
key = member.singleton? ? "#{class_name}.#{member.name}" : "#{class_name}##{member.name}"

sigs = member.overloads.map { |o| o.method_type.to_s }
signatures[key] = sigs.join("\n")
when RBS::AST::Members::AttrReader, RBS::AST::Members::AttrWriter, RBS::AST::Members::AttrAccessor
key = "#{class_name}.#{member.name}"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hmm. Can we use # instead of . as the separator because attribute accessors just methods?

top_level = @store.add_file 'file.rb'

m = RDoc::AnyMethod.new nil, 'method'
m.block_params = 'some_block'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
m.block_params = 'some_block'
m.block_params = 'some_block'


klass = @store.find_class_named 'Foo'
bar = klass.method_list.first
assert_equal '(Integer) -> void', bar.type_signature
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we assert bar.comment.text here? (nil? An empty string?)

klass = @store.find_class_named 'Foo'
bar = klass.method_list.first
assert_equal '(String) -> void', bar.type_signature
assert_includes bar.comment.text, 'Documentation here'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we use assert_equal not assert_includes to check type signature doesn't exist in bar.comment?

Suggested change
assert_includes bar.comment.text, 'Documentation here'
assert_equal "Documentation here\n", bar.comment.text

end

def validate_type_signature(sig, line_no)
sig.split("\n").each_with_index do |line, i|
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not familiar with RBS syntax but RBS doesn't support splitting one signature to multiple lines, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants