Skip to content

Commit 5b88684

Browse files
authored
Index arbitrary pinsets (#937)
* Faster ApiMap::Store updates * Store accepts arbitrary pinsets * Remove unnecessary index copying * Store#catalog avoids cloning for empty merges
1 parent 25decb9 commit 5b88684

6 files changed

Lines changed: 112 additions & 31 deletions

File tree

lib/solargraph/api_map.rb

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def index pins
6666
@source_map_hash = {}
6767
implicit.clear
6868
cache.clear
69-
store.update! @@core_map.pins, pins
69+
store.update @@core_map.pins, pins
7070
self
7171
end
7272

@@ -85,11 +85,9 @@ def map source
8585
# @param bench [Bench]
8686
# @return [self]
8787
def catalog bench
88-
old_api_hash = @source_map_hash&.values&.map(&:api_hash)
89-
need_to_uncache = (old_api_hash != bench.source_maps.map(&:api_hash))
90-
# @todo Work around #to_h problem in current Ruby head (3.5)
91-
@source_map_hash = bench.source_maps.map { |s| [s.filename, s] }.to_h
92-
pins = bench.source_maps.flat_map(&:pins).flatten
88+
@source_map_hash = bench.source_map_hash
89+
iced_pins = bench.icebox.flat_map(&:pins)
90+
live_pins = bench.live_map&.pins || []
9391
implicit.clear
9492
source_map_hash.each_value do |map|
9593
implicit.merge map.environ
@@ -98,11 +96,8 @@ def catalog bench
9896
if @unresolved_requires != unresolved_requires || @doc_map&.uncached_gemspecs&.any?
9997
@doc_map = DocMap.new(unresolved_requires, [], bench.workspace.rbs_collection_path) # @todo Implement gem preferences
10098
@unresolved_requires = unresolved_requires
101-
need_to_uncache = true
10299
end
103-
store.update! @@core_map.pins + @doc_map.pins, implicit.pins + pins
104-
@cache.clear if need_to_uncache
105-
100+
@cache.clear if store.update(@@core_map.pins, @doc_map.pins, implicit.pins, iced_pins, live_pins)
106101
@missing_docs = [] # @todo Implement missing docs
107102
self
108103
end
@@ -111,7 +106,7 @@ def catalog bench
111106
# that this overload of 'protected' will typecheck @sg-ignore
112107
# @sg-ignore
113108
protected def equality_fields
114-
[self.class, @source_map_hash, implicit, @doc_map, @unresolved_requires, @missing_docs]
109+
[self.class, @source_map_hash, implicit, @doc_map, @unresolved_requires]
115110
end
116111

117112
# @return [::Array<Gem::Specification>]

lib/solargraph/api_map/index.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module Solargraph
44
class ApiMap
55
class Index
66
# @param pins [Array<Pin::Base>]
7-
def initialize pins
7+
def initialize pins = []
88
catalog pins
99
end
1010

lib/solargraph/api_map/store.rb

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,37 @@ class ApiMap
66
# core.
77
#
88
class Store
9-
# @param static [Enumerable<Pin::Base>]
10-
# @param dynamic [Enumerable<Pin::Base>]
11-
def initialize static = [], dynamic = []
12-
@static_index = Index.new(static)
13-
@dynamic = dynamic
14-
@index = @static_index.merge(dynamic)
9+
# @param pinsets [Array<Enumerable<Pin::Base>>]
10+
def initialize *pinsets
11+
catalog pinsets
1512
end
1613

1714
# @return [Array<Solargraph::Pin::Base>]
1815
def pins
1916
index.pins
2017
end
2118

22-
# @param static [Enumerable<Pin::Base>]
23-
# @param dynamic [Enumerable<Pin::Base>]
24-
def update! static = [], dynamic = []
19+
# @param pinsets [Array<Enumerable<Pin::Base>>]
20+
# @return [Boolean] True if the index was updated
21+
def update *pinsets
22+
return catalog(pinsets) if pinsets.length != @pinsets.length
23+
24+
changed = pinsets.find_index.with_index { |pinset, idx| @pinsets[idx] != pinset }
25+
return false unless changed
26+
2527
# @todo Fix this map
2628
@fqns_pins_map = nil
27-
if @static_index.pins == static
28-
return self if @dynamic == dynamic
29-
else
30-
@static_index = Index.new(static)
29+
return catalog(pinsets) if changed == 0
30+
31+
pinsets[changed..].each_with_index do |pins, idx|
32+
@pinsets[changed + idx] = pins
33+
@indexes[changed + idx] = if pins.empty?
34+
@indexes[changed + idx - 1]
35+
else
36+
@indexes[changed + idx - 1].merge(pins)
37+
end
3138
end
32-
@dynamic = dynamic
33-
@index = @static_index.merge(dynamic)
34-
self
39+
true
3540
end
3641

3742
def to_s
@@ -189,7 +194,22 @@ def fqns_pins fqns
189194

190195
private
191196

192-
attr_reader :index
197+
def index
198+
@indexes.last
199+
end
200+
201+
def catalog pinsets
202+
@pinsets = pinsets
203+
@indexes = []
204+
pinsets.each do |pins|
205+
if @indexes.last && pins.empty?
206+
@indexes.push @indexes.last
207+
else
208+
@indexes.push(@indexes.last&.merge(pins) || Solargraph::ApiMap::Index.new(pins))
209+
end
210+
end
211+
true
212+
end
193213

194214
# @return [Hash{::Array(String, String) => ::Array<Pin::Namespace>}]
195215
def fqns_pins_map

lib/solargraph/bench.rb

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,34 @@ class Bench
1111
# @return [Workspace]
1212
attr_reader :workspace
1313

14+
# @return [SourceMap]
15+
attr_reader :live_map
16+
1417
# @return [Set<String>]
1518
attr_reader :external_requires
1619

1720
# @param source_maps [Array<SourceMap>, Set<SourceMap>]
1821
# @param workspace [Workspace]
22+
# @param live_map [SourceMap, nil]
1923
# @param external_requires [Array<String>, Set<String>]
20-
def initialize source_maps: [], workspace: Workspace.new, external_requires: []
24+
def initialize source_maps: [], workspace: Workspace.new, live_map: nil, external_requires: []
2125
@source_maps = source_maps.to_set
2226
@workspace = workspace
27+
@live_map = live_map
2328
@external_requires = external_requires.reject { |path| workspace.would_require?(path) }
2429
.compact
2530
.to_set
2631
end
32+
33+
# @return [Hash{String => SourceMap}]
34+
def source_map_hash
35+
# @todo Work around #to_h bug in current Ruby head (3.5) with #map#to_h
36+
@source_map_hash ||= source_maps.map { |s| [s.filename, s] }
37+
.to_h
38+
end
39+
40+
def icebox
41+
@icebox ||= (source_maps - [live_map])
42+
end
2743
end
2844
end

lib/solargraph/library.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,8 @@ def bench
430430
Bench.new(
431431
source_maps: source_map_hash.values,
432432
workspace: workspace,
433-
external_requires: external_requires
433+
external_requires: external_requires,
434+
live_map: @current ? source_map_hash[@current.filename] : nil
434435
)
435436
end
436437

spec/api_map/store_spec.rb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,51 @@
1+
# frozen_string_literal: true
2+
13
describe Solargraph::ApiMap::Store do
4+
it 'indexes multiple pinsets' do
5+
foo_pin = Solargraph::Pin::Namespace.new(name: 'Foo')
6+
bar_pin = Solargraph::Pin::Namespace.new(name: 'Bar')
7+
store = Solargraph::ApiMap::Store.new([foo_pin], [bar_pin])
8+
9+
expect(store.get_path_pins('Foo')).to eq([foo_pin])
10+
expect(store.get_path_pins('Bar')).to eq([bar_pin])
11+
end
12+
13+
it 'indexes empty pinsets' do
14+
foo_pin = Solargraph::Pin::Namespace.new(name: 'Foo')
15+
16+
store = Solargraph::ApiMap::Store.new([], [foo_pin])
17+
expect(store.get_path_pins('Foo')).to eq([foo_pin])
18+
end
19+
20+
it 'updates existing pinsets' do
21+
foo_pin = Solargraph::Pin::Namespace.new(name: 'Foo')
22+
bar_pin = Solargraph::Pin::Namespace.new(name: 'Bar')
23+
baz_pin = Solargraph::Pin::Namespace.new(name: 'Baz')
24+
store = Solargraph::ApiMap::Store.new([foo_pin], [bar_pin])
25+
store.update([foo_pin], [baz_pin])
26+
27+
expect(store.get_path_pins('Foo')).to eq([foo_pin])
28+
expect(store.get_path_pins('Baz')).to eq([baz_pin])
29+
expect(store.get_path_pins('Bar')).to be_empty
30+
end
31+
32+
it 'updates new pinsets' do
33+
foo_pin = Solargraph::Pin::Namespace.new(name: 'Foo')
34+
bar_pin = Solargraph::Pin::Namespace.new(name: 'Bar')
35+
store = Solargraph::ApiMap::Store.new([foo_pin])
36+
store.update([foo_pin], [bar_pin])
37+
38+
expect(store.get_path_pins('Foo')).to eq([foo_pin])
39+
expect(store.get_path_pins('Bar')).to eq([bar_pin])
40+
end
41+
42+
it 'updates empty stores' do
43+
foo_pin = Solargraph::Pin::Namespace.new(name: 'Foo')
44+
bar_pin = Solargraph::Pin::Namespace.new(name: 'Bar')
45+
store = Solargraph::ApiMap::Store.new
46+
store.update([foo_pin, bar_pin])
47+
48+
expect(store.get_path_pins('Foo')).to eq([foo_pin])
49+
expect(store.get_path_pins('Bar')).to eq([bar_pin])
50+
end
251
end

0 commit comments

Comments
 (0)