Skip to content

Commit 8881293

Browse files
committed
Store sidecar .rbs signatures separately from method objects
Sidecar .rbs signatures were being merged into MethodAttr#type_signature_lines during load, which forced the store to keep a @rbs_signature_method_attrs tracker so reloads could clear the previously-assigned signatures. Method objects ended up owning data that didn't come from their source file, and every reload had to undo that mutation. Move the signatures to a dedicated @rbs_signatures hash on the store and look them up at render time: * Store#merge_rbs_signatures now just stores the hash; the iteration over every class and method is gone. * Store#rbs_signature_for(method_attr) does the lookup, including the initialize -> .new singleton mapping and the alias fallthrough to the canonical method. * Store#clear_rbs_signatures becomes a one-liner setting the hash to nil. * The @rbs_signature_method_attrs tracker is deleted along with assign_rbs_signature, and the unused rbs_key helper is gone too. Read sites use `method.type_signature_lines || store.rbs_signature_for(method)` so inline `#:` annotations still win over sidecar signatures: * Generator::Aliki#type_signature_html — falls back to the store lookup. * aliki/class.rhtml — computes the HTML once via `if (sig_html = ...)`. * RI::Driver — same `||` fallthrough before rendering. With the mutation gone, the server's reparse_and_refresh no longer needs to call load_rbs_signatures when only .rb files change. Newly-added methods resolve through the existing string-keyed hash at render time, so the reload is only required when .rbs files themselves change. Ordering between load and Store#complete no longer matters either; reverse the two in rdoc.rb and server.rb so the flow reads "load all data, then finalize." Tests in rdoc_store_test.rb assert the new lookup path; two integration tests (rdoc_rdoc_test, rdoc_server_test) follow suit. Two new tests pin the invariants: sidecar load doesn't touch inline #: lines, and lookups return nil when no signatures are loaded. The old test_merge_rbs_signatures_does_not_overwrite_inline_annotations is gone — it became tautological once merge stopped touching method objects, and the new test_rbs_signature_for_does_not_overwrite_inline_lookup already pins the meaningful invariant.
1 parent e9d0be4 commit 8881293

9 files changed

Lines changed: 85 additions & 91 deletions

File tree

lib/rdoc/generator/aliki.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def write_search_index
122122
# Returns nil if no type signature is present.
123123

124124
def type_signature_html(method_attr, from_path)
125-
lines = method_attr.type_signature_lines
125+
lines = method_attr.type_signature_lines || @store.rbs_signature_for(method_attr)
126126
return unless lines
127127

128128
RDoc::RbsHelper.signature_to_html(

lib/rdoc/generator/template/aliki/class.rhtml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@
9393
<span class="method-name"><%= h attrib.name %></span>
9494
<span class="attribute-access-type">[<%= attrib.rw %>]</span>
9595
</a>
96-
<%- if attrib.type_signature_lines %>
97-
<span class="method-type-signature"><code><%= type_signature_html(attrib, klass.path) %></code></span>
96+
<%- if (sig_html = type_signature_html(attrib, klass.path)) %>
97+
<span class="method-type-signature"><code><%= sig_html %></code></span>
9898
<%- end %>
9999
</div>
100100

@@ -154,8 +154,8 @@
154154
</div>
155155
<%- end %>
156156

157-
<%- if method.type_signature_lines %>
158-
<pre class="method-type-signature"><code><%= type_signature_html(method, klass.path) %></code></pre>
157+
<%- if (sig_html = type_signature_html(method, klass.path)) %>
158+
<pre class="method-type-signature"><code><%= sig_html %></code></pre>
159159
<%- end %>
160160
</div>
161161

lib/rdoc/rdoc.rb

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -474,8 +474,8 @@ def document(options)
474474

475475
@options.default_title = "RDoc Documentation"
476476

477-
@store.complete @options.visibility
478477
load_rbs_signatures
478+
@store.complete @options.visibility
479479

480480
start_server
481481
exit
@@ -490,20 +490,22 @@ def document(options)
490490
@store.load_cache
491491

492492
rbs_signatures_changed = rbs_signatures_changed?
493-
# When only sig/*.rbs changed, no Ruby file would be reparsed and the
494-
# cached HTML pipeline keeps no in-memory class data across runs, so
495-
# force a full reparse to give merge_rbs_signatures a populated store.
493+
# When only sig/*.rbs changed, no Ruby file would be reparsed under
494+
# normal mtime checks. The store cache holds class metadata but not
495+
# live RDoc::Context objects, so the generator would have nothing to
496+
# iterate. Force a full reparse so updated signatures show up in
497+
# the rendered output.
496498
@last_modified.clear if rbs_signatures_changed
497499

498500
file_info = parse_files @options.files
499501
record_rbs_signature_mtimes
500502

501503
@options.default_title = "RDoc Documentation"
502504

503-
@store.complete @options.visibility
504-
505505
load_rbs_signatures
506506

507+
@store.complete @options.visibility
508+
507509
@stats.coverage_level = @options.coverage_report
508510

509511
if @options.coverage_report then

lib/rdoc/ri/driver.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1415,7 +1415,8 @@ def render_method(out, store, method, name) # :nodoc:
14151415
out << RDoc::Markup::Rule.new(1)
14161416

14171417
render_method_arguments out, method.arglists
1418-
render_method_type_signature out, method.type_signature_lines if method.type_signature_lines
1418+
sig = method.type_signature_lines || store.rbs_signature_for(method)
1419+
render_method_type_signature out, sig if sig
14191420
render_method_superclass out, method
14201421
if method.is_alias_for
14211422
al = method.is_alias_for

lib/rdoc/server.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -408,20 +408,20 @@ def reparse_and_refresh(changed_files, removed_files, rbs_changed: false)
408408
$stderr.puts "Re-parsed #{changed_file_names.join(', ')} (#{duration_ms}ms)"
409409
end
410410

411-
@store.complete(@options.visibility)
412-
@store.invalidate_type_name_lookup unless changed_files.empty? && removed_files.empty?
413-
414-
if rbs_changed || !changed_files.empty?
411+
if rbs_changed
415412
duration_ms = measure do
416413
@rdoc.load_rbs_signatures
417414
@rdoc.record_rbs_signature_mtimes
418415
@rdoc.rbs_signature_files.each do |file|
419416
@file_mtimes[file] = RDoc.safe_mtime(file)
420417
end
421418
end
422-
$stderr.puts "Reloaded RBS signatures (#{duration_ms}ms)" if rbs_changed
419+
$stderr.puts "Reloaded RBS signatures (#{duration_ms}ms)"
423420
end
424421

422+
@store.complete(@options.visibility)
423+
@store.invalidate_type_name_lookup unless changed_files.empty? && removed_files.empty?
424+
425425
@generator.refresh_store_data
426426
@page_cache.clear
427427
@last_change_time = Time.now.to_f

lib/rdoc/store.rb

Lines changed: 28 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -381,61 +381,46 @@ def type_name_lookup
381381
end
382382

383383
##
384-
# Merges RBS type signatures into code objects.
385-
# Inline #: annotations take priority and are not overwritten.
384+
# Stores RBS type signatures loaded from sidecar .rbs files, keyed by
385+
# "ClassName#method" or "ClassName.method". Replaces any previously
386+
# stored set, so passing +{}+ clears it. Inline +#:+ annotations on
387+
# method objects are NOT touched — those are owned by the source file.
386388

387389
def merge_rbs_signatures(signatures)
388-
clear_rbs_signatures
389-
390-
all_classes_and_modules.each do |cm|
391-
cm.method_list.each do |method|
392-
next if method.type_signature_lines
393-
394-
sig = signatures[rbs_key(cm, method)]
395-
396-
# RBS keys constructors as #initialize, but RDoc renames them to .new
397-
if !sig && method.name == 'new' && method.singleton
398-
sig = signatures["#{cm.full_name}#initialize"]
399-
end
400-
401-
assign_rbs_signature method, sig if sig
402-
end
403-
404-
cm.attributes.each do |attr|
405-
next if attr.type_signature_lines
406-
407-
if (sig = signatures[rbs_key(cm, attr)])
408-
assign_rbs_signature attr, sig
409-
end
410-
end
411-
end
390+
@rbs_signatures = signatures
412391
end
413392

414-
def assign_rbs_signature(method_attr, signature) # :nodoc:
415-
@rbs_signature_method_attrs ||= []
393+
##
394+
# Returns the RBS type signature lines for +method_attr+ from loaded
395+
# sidecar +.rbs+ files, or +nil+ if none. Falls through to the
396+
# canonical method for aliases, and handles +initialize+ -> +.new+
397+
# singleton mapping.
416398

417-
method_attr.type_signature_lines = signature
418-
@rbs_signature_method_attrs << method_attr
399+
def rbs_signature_for(method_attr)
400+
return nil unless @rbs_signatures
401+
cm = method_attr.parent
402+
return nil unless cm.respond_to?(:full_name)
419403

420-
method_attr.aliases.each do |aliased|
421-
next if aliased.type_signature_lines
422-
aliased.type_signature_lines = signature
423-
@rbs_signature_method_attrs << aliased
424-
end
425-
end
404+
key = method_attr.singleton ? "#{cm.full_name}.#{method_attr.name}" : "#{cm.full_name}##{method_attr.name}"
405+
sig = @rbs_signatures[key]
426406

427-
def clear_rbs_signatures # :nodoc:
428-
return unless @rbs_signature_method_attrs
407+
# RBS keys constructors as #initialize, but RDoc renames them to .new
408+
if !sig && method_attr.name == 'new' && method_attr.singleton
409+
sig = @rbs_signatures["#{cm.full_name}#initialize"]
410+
end
429411

430-
@rbs_signature_method_attrs.each do |method_attr|
431-
method_attr.type_signature_lines = nil
412+
# For aliases, fall through to the canonical method (its inline #:
413+
# takes precedence over any sidecar signature on the alias's name).
414+
if !sig && method_attr.is_alias_for
415+
canonical = method_attr.is_alias_for
416+
sig = canonical.type_signature_lines || rbs_signature_for(canonical)
432417
end
433418

434-
@rbs_signature_method_attrs.clear
419+
sig
435420
end
436421

437-
def rbs_key(cm, method_attr) # :nodoc:
438-
method_attr.singleton ? "#{cm.full_name}.#{method_attr.name}" : "#{cm.full_name}##{method_attr.name}"
422+
def clear_rbs_signatures # :nodoc:
423+
@rbs_signatures = nil
439424
end
440425

441426
##

test/rdoc/rdoc_rdoc_test.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def greet: () -> String
106106

107107
example = store.find_class_or_module 'Example'
108108
greet = example.find_method 'greet', false
109-
assert_equal ['() -> String'], greet.type_signature_lines
109+
assert_equal ['() -> String'], store.rbs_signature_for(greet)
110110
end
111111
end
112112

@@ -132,7 +132,7 @@ def greet: () -> String
132132
example.add_method method
133133

134134
@rdoc.load_rbs_signatures
135-
assert_equal ['() -> String'], method.type_signature_lines
135+
assert_equal ['() -> String'], @rdoc.store.rbs_signature_for(method)
136136

137137
File.write sig, "class Example\n def greet: ( -> "
138138
@options.verbosity = 2
@@ -141,7 +141,7 @@ def greet: () -> String
141141
end
142142

143143
assert_includes err, 'Failed to load RBS type signatures'
144-
assert_nil method.type_signature_lines
144+
assert_nil @rdoc.store.rbs_signature_for(method)
145145
end
146146
end
147147

test/rdoc/rdoc_server_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,6 @@ def greet: () -> String
9999

100100
example = @rdoc.store.find_class_or_module 'Example'
101101
greet = example.find_method 'greet', false
102-
assert_equal ['() -> String'], greet.type_signature_lines
102+
assert_equal ['() -> String'], @rdoc.store.rbs_signature_for(greet)
103103
end
104104
end

test/rdoc/rdoc_store_test.rb

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,16 +1302,19 @@ def test_merge_rbs_signatures
13021302
'Object#language' => ['String']
13031303
)
13041304

1305-
assert_equal ['(String name) -> void'], m.type_signature_lines
1306-
assert_equal ['String'], a.type_signature_lines
1305+
assert_equal ['(String name) -> void'], @s.rbs_signature_for(m)
1306+
assert_equal ['String'], @s.rbs_signature_for(a)
1307+
# Inline #: lines on the method are untouched
1308+
assert_nil m.type_signature_lines
1309+
assert_nil a.type_signature_lines
13071310
end
13081311

13091312
def test_merge_rbs_signatures_singleton_method
13101313
@s.merge_rbs_signatures(
13111314
'Object.cmethod' => ['() -> String']
13121315
)
13131316

1314-
assert_equal ['() -> String'], @cmeth.type_signature_lines
1317+
assert_equal ['() -> String'], @s.rbs_signature_for(@cmeth)
13151318
end
13161319

13171320
def test_merge_rbs_signatures_constructor
@@ -1323,22 +1326,22 @@ def test_merge_rbs_signatures_constructor
13231326
'Object#initialize' => ['(String name) -> void']
13241327
)
13251328

1326-
assert_equal ['(String name) -> void'], ctor.type_signature_lines
1329+
assert_equal ['(String name) -> void'], @s.rbs_signature_for(ctor)
13271330
end
13281331

13291332
def test_merge_rbs_signatures_clears_signatures_removed_in_subsequent_merge
13301333
@s.merge_rbs_signatures(
13311334
'Object#method' => ['() -> String']
13321335
)
1333-
assert_equal ['() -> String'], @meth.type_signature_lines
1336+
assert_equal ['() -> String'], @s.rbs_signature_for(@meth)
13341337

13351338
# Subsequent merge no longer mentions the key — the signature must be
13361339
# cleared rather than left stale, so live-reload reflects deletions.
13371340
@s.merge_rbs_signatures({})
1338-
assert_nil @meth.type_signature_lines
1341+
assert_nil @s.rbs_signature_for(@meth)
13391342
end
13401343

1341-
def test_merge_rbs_signatures_propagates_to_method_alias
1344+
def test_rbs_signature_for_propagates_to_method_alias
13421345
original = RDoc::AnyMethod.new nil, 'original'
13431346
original.record_location @top_level
13441347
@klass.add_method original
@@ -1351,11 +1354,11 @@ def test_merge_rbs_signatures_propagates_to_method_alias
13511354
'Object#original' => ['() -> String']
13521355
)
13531356

1354-
assert_equal ['() -> String'], original.type_signature_lines
1355-
assert_equal ['() -> String'], aliased.type_signature_lines
1357+
assert_equal ['() -> String'], @s.rbs_signature_for(original)
1358+
assert_equal ['() -> String'], @s.rbs_signature_for(aliased)
13561359
end
13571360

1358-
def test_merge_rbs_signatures_propagates_to_attribute_alias
1361+
def test_rbs_signature_for_propagates_to_attribute_alias
13591362
original = RDoc::Attr.new nil, 'language', 'R', ''
13601363
original.record_location @top_level
13611364
@klass.add_attribute original
@@ -1368,8 +1371,8 @@ def test_merge_rbs_signatures_propagates_to_attribute_alias
13681371
'Object#language' => ['String']
13691372
)
13701373

1371-
assert_equal ['String'], original.type_signature_lines
1372-
assert_equal ['String'], aliased.type_signature_lines
1374+
assert_equal ['String'], @s.rbs_signature_for(original)
1375+
assert_equal ['String'], @s.rbs_signature_for(aliased)
13731376
end
13741377

13751378
def test_merge_rbs_signatures_keeps_instance_and_singleton_attributes_separate
@@ -1386,8 +1389,24 @@ def test_merge_rbs_signatures_keeps_instance_and_singleton_attributes_separate
13861389
'Object.language' => ['Integer']
13871390
)
13881391

1389-
assert_equal ['String'], instance_attr.type_signature_lines
1390-
assert_equal ['Integer'], singleton_attr.type_signature_lines
1392+
assert_equal ['String'], @s.rbs_signature_for(instance_attr)
1393+
assert_equal ['Integer'], @s.rbs_signature_for(singleton_attr)
1394+
end
1395+
1396+
def test_rbs_signature_for_returns_nil_when_no_signatures_loaded
1397+
assert_nil @s.rbs_signature_for(@meth)
1398+
end
1399+
1400+
def test_rbs_signature_for_does_not_overwrite_inline_lookup
1401+
# Inline #: lives on the method itself; sidecar lookup is separate.
1402+
@meth.type_signature_lines = ['() -> String # inline']
1403+
@s.merge_rbs_signatures(
1404+
'Object#method' => ['() -> String # sidecar']
1405+
)
1406+
1407+
# Both sources are readable; the consumer chooses precedence (inline wins).
1408+
assert_equal ['() -> String # inline'], @meth.type_signature_lines
1409+
assert_equal ['() -> String # sidecar'], @s.rbs_signature_for(@meth)
13911410
end
13921411

13931412
def test_type_name_lookup
@@ -1434,17 +1453,4 @@ def test_type_name_lookup_top_level_class_wins_over_nested_namesake
14341453
end
14351454

14361455

1437-
def test_merge_rbs_signatures_does_not_overwrite_inline_annotations
1438-
m = RDoc::AnyMethod.new(nil, 'greet')
1439-
m.params = '(name)'
1440-
m.type_signature_lines = ['(String) -> void']
1441-
@klass.add_method m
1442-
1443-
@s.merge_rbs_signatures(
1444-
'Object#greet' => ['(String name, ?Integer count) -> void']
1445-
)
1446-
1447-
assert_equal ['(String) -> void'], m.type_signature_lines
1448-
end
1449-
14501456
end

0 commit comments

Comments
 (0)