Skip to content

Commit 245b7a7

Browse files
authored
Merge branch 'master' into solargraph_force_version
2 parents 733b261 + 9c707a2 commit 245b7a7

File tree

17 files changed

+543
-214
lines changed

17 files changed

+543
-214
lines changed

CHANGELOG.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,45 @@
1+
## 0.57.0 - September 16, 2025
2+
- Support ActiveSupport::Concern pattern for class methods (#948)
3+
- More CI checks (#996)
4+
- Linting / type annotation fixes (#999)
5+
- Avoid overlapping chdir calls in multiple threads (#1007)
6+
- Fix kwarg generation in ApiMap::SourceToYard (#1003)
7+
- Enable Steep typechecking of Solargraph code (#1004)
8+
- Fix convention requires (#1008)
9+
- Plugin Util: Add Combination Priority (#1010)
10+
- [regression] Fix crash while typechecking files with Struct use (#1031)
11+
- Remove yard reference from gemfile (#1033)
12+
- Allow newer RBS gem versions, exclude incompatible ones (#995)
13+
- Look for external requires before cataloging bench (#1021)
14+
- Remove Library#folding_ranges (#904)
15+
- Complain in strong type-checking if an @sg-ignore line is not needed (#1011)
16+
- Document a log level env variable (#894)
17+
- Fix hole in type checking evaluation (#1009)
18+
- Improve typechecking error message (#1014)
19+
- Internal strict type-checking fixes (#1013)
20+
- Reproduce and fix a ||= (or-asgn) evaluation issue (#1017)
21+
- Define closure for Pin::Symbol, for completeness (#1027)
22+
- Fix 'all!' config to reporters (#1018)
23+
- Fix DocMap.all_rbs_collection_gems_in_memory return type (#1037)
24+
- Fix RuboCop linting errors in regular expressions (#1038)
25+
- Resolve class aliases via Constant pins (#1029)
26+
- Speed-up LSP completion response times (#1035)
27+
- Revert "Resolve class aliases via Constant pins (#1029)" (#1041)
28+
- Avoid stack errors when resolving method aliases (#1040)
29+
- [regression] Refine order of object convention method pins (#1036)
30+
- Fix crash while generating activesupport pins (#1043)
31+
- Type annotation improvements (#1016)
32+
- Resolve class aliases via Constant pins (#1048)
33+
- Understand "Parser::AST::Node < AST::Node" in RBS (#1060)
34+
- Factor out require_paths logic to its own class (#1062)
35+
- Fix type errors found in strong typechecking (#1045)
36+
- Run plugin specs separately for perf insights (#1046)
37+
- Run specs from solargraph-rails configured against current code in CI (#892)
38+
- Fix Convention/Plugin pins not being updated on file change (#1028)
39+
- Fix solargraph-rails check (#1073)
40+
- Flow sensitive typing handles x.is_a? without an argument (#1070)
41+
- Refactor reference pin handling (#1058)
42+
143
## 0.56.2 - July 29, 2025
244
- Add support for Ruby Data.define (#970)
345
- Ensure that pin locations are always populated (#965)

lib/solargraph/api_map.rb

Lines changed: 41 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class ApiMap
1313
autoload :SourceToYard, 'solargraph/api_map/source_to_yard'
1414
autoload :Store, 'solargraph/api_map/store'
1515
autoload :Index, 'solargraph/api_map/index'
16+
autoload :Constants, 'solargraph/api_map/constants'
1617

1718
# @return [Array<String>]
1819
attr_reader :unresolved_requires
@@ -261,19 +262,13 @@ def namespace_exists? name, context = ''
261262
# @return [Array<Solargraph::Pin::Base>]
262263
def get_constants namespace, *contexts
263264
namespace ||= ''
264-
contexts.push '' if contexts.empty?
265-
cached = cache.get_constants(namespace, contexts)
266-
return cached.clone unless cached.nil?
267-
skip = Set.new
268-
result = []
269-
contexts.each do |context|
270-
fqns = qualify(namespace, context)
271-
visibility = [:public]
272-
visibility.push :private if fqns == context
273-
result.concat inner_get_constants(fqns, visibility, skip)
274-
end
275-
cache.set_constants(namespace, contexts, result)
276-
result
265+
gates = contexts.clone
266+
gates.push '' if contexts.empty? && namespace.empty?
267+
gates.push namespace unless namespace.empty?
268+
store.constants
269+
.collect(gates)
270+
.select { |pin| namespace.empty? || contexts.empty? || pin.namespace == namespace }
271+
.select { |pin| pin.visibility == :public || pin.namespace == namespace }
277272
end
278273

279274
# @param namespace [String]
@@ -299,44 +294,15 @@ def get_namespace_pins namespace, context
299294
# Should not be prefixed with '::'.
300295
# @return [String, nil] fully qualified tag
301296
def qualify tag, context_tag = ''
302-
return tag if ['Boolean', 'self', nil].include?(tag)
303-
304-
context_type = ComplexType.try_parse(context_tag).force_rooted
305-
return unless context_type
306-
307-
type = ComplexType.try_parse(tag)
308-
return unless type
309-
return tag if type.literal?
310-
311-
context_type = ComplexType.try_parse(context_tag)
312-
return unless context_type
313-
314-
fqns = qualify_namespace(type.rooted_namespace, context_type.rooted_namespace)
315-
return unless fqns
316-
317-
fqns + type.substring
297+
store.constants.qualify(tag, context_tag)
318298
end
319299

320-
# Determine fully qualified namespace for a given namespace used
321-
# inside the definition of another tag ("context"). This method
322-
# will start the search in the specified context until it finds a
323-
# match for the namespace.
300+
# Get a fully qualified namespace from a reference pin.
324301
#
325-
# @param namespace [String, nil] The namespace to
326-
# match
327-
# @param context_namespace [String] The context namespace in which the
328-
# tag was referenced; start from here to resolve the name
329-
# @return [String, nil] fully qualified namespace
330-
def qualify_namespace(namespace, context_namespace = '')
331-
cached = cache.get_qualified_namespace(namespace, context_namespace)
332-
return cached.clone unless cached.nil?
333-
result = if namespace.start_with?('::')
334-
inner_qualify(namespace[2..-1], '', Set.new)
335-
else
336-
inner_qualify(namespace, context_namespace, Set.new)
337-
end
338-
cache.set_qualified_namespace(namespace, context_namespace, result)
339-
result
302+
# @param pin [Pin::Reference]
303+
# @return [String, nil]
304+
def dereference(pin)
305+
store.constants.dereference(pin)
340306
end
341307

342308
# @param fqns [String]
@@ -361,11 +327,10 @@ def get_instance_variable_pins(namespace, scope = :instance)
361327
result = []
362328
used = [namespace]
363329
result.concat store.get_instance_variables(namespace, scope)
364-
sc = qualify_lower(store.get_superclass(namespace), namespace)
365-
until sc.nil? || used.include?(sc)
366-
used.push sc
367-
result.concat store.get_instance_variables(sc, scope)
368-
sc = qualify_lower(store.get_superclass(sc), sc)
330+
sc_fqns = namespace
331+
while (sc = store.get_superclass(sc_fqns))
332+
sc_fqns = store.constants.dereference(sc)
333+
result.concat store.get_instance_variables(sc_fqns, scope)
369334
end
370335
result
371336
end
@@ -660,13 +625,22 @@ def bundled? filename
660625
# @param sub [String] The subclass
661626
# @return [Boolean]
662627
def super_and_sub?(sup, sub)
663-
fqsup = qualify(sup)
664-
cls = qualify(sub)
665-
tested = []
666-
until fqsup.nil? || cls.nil? || tested.include?(cls)
667-
return true if cls == fqsup
668-
tested.push cls
669-
cls = qualify_superclass(cls)
628+
sup = ComplexType.try_parse(sup)
629+
sub = ComplexType.try_parse(sub)
630+
# @todo If two literals are different values of the same type, it would
631+
# make more sense for super_and_sub? to return true, but there are a
632+
# few callers that currently expect this to be false.
633+
return false if sup.literal? && sub.literal? && sup.to_s != sub.to_s
634+
sup = sup.simplify_literals.to_s
635+
sub = sub.simplify_literals.to_s
636+
return true if sup == sub
637+
sc_fqns = sub
638+
while (sc = store.get_superclass(sc_fqns))
639+
sc_new = store.constants.dereference(sc)
640+
# Cyclical inheritance is invalid
641+
return false if sc_new == sc_fqns
642+
sc_fqns = sc_new
643+
return true if sc_fqns == sup
670644
end
671645
false
672646
end
@@ -679,7 +653,7 @@ def super_and_sub?(sup, sub)
679653
#
680654
# @return [Boolean]
681655
def type_include?(host_ns, module_ns)
682-
store.get_includes(host_ns).map { |inc_tag| ComplexType.parse(inc_tag).name }.include?(module_ns)
656+
store.get_includes(host_ns).map { |inc_tag| inc_tag.parametrized_tag.name }.include?(module_ns)
683657
end
684658

685659
# @param pins [Enumerable<Pin::Base>]
@@ -773,7 +747,7 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false
773747

774748
if deep && scope == :instance
775749
store.get_prepends(fqns).reverse.each do |im|
776-
fqim = qualify(im, fqns)
750+
fqim = store.constants.dereference(im)
777751
result.concat inner_get_methods(fqim, scope, visibility, deep, skip, true) unless fqim.nil?
778752
end
779753
end
@@ -787,7 +761,7 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false
787761
result.concat convention_methods_by_reference
788762

789763
if scope == :instance
790-
store.get_include_pins(fqns).reverse.each do |ref|
764+
store.get_includes(fqns).reverse.each do |ref|
791765
const = get_constants('', *ref.closure.gates).find { |pin| pin.path.end_with? ref.name }
792766
if const.is_a?(Pin::Namespace)
793767
result.concat inner_get_methods(const.path, scope, visibility, deep, skip, true)
@@ -807,7 +781,7 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false
807781
else
808782
logger.info { "ApiMap#inner_get_methods(#{fqns}, #{scope}, #{visibility}, #{deep}, #{skip}) - looking for get_extends() from #{fqns}" }
809783
store.get_extends(fqns).reverse.each do |em|
810-
fqem = qualify(em, fqns)
784+
fqem = store.constants.dereference(em)
811785
result.concat inner_get_methods(fqem, :instance, visibility, deep, skip, true) unless fqem.nil?
812786
end
813787
rooted_sc_tag = qualify_superclass(rooted_tag)
@@ -828,95 +802,15 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false
828802
result
829803
end
830804

831-
# @param fqns [String]
832-
# @param visibility [Array<Symbol>]
833-
# @param skip [Set<String>]
834-
# @return [Array<Pin::Base>]
835-
def inner_get_constants fqns, visibility, skip
836-
return [] if fqns.nil? || skip.include?(fqns)
837-
skip.add fqns
838-
result = []
839-
store.get_prepends(fqns).each do |is|
840-
result.concat inner_get_constants(qualify(is, fqns), [:public], skip)
841-
end
842-
result.concat store.get_constants(fqns, visibility)
843-
.sort { |a, b| a.name <=> b.name }
844-
store.get_includes(fqns).each do |is|
845-
result.concat inner_get_constants(qualify(is, fqns), [:public], skip)
846-
end
847-
fqsc = qualify_superclass(fqns)
848-
unless %w[Object BasicObject].include?(fqsc)
849-
result.concat inner_get_constants(fqsc, [:public], skip)
850-
end
851-
result
852-
end
853-
854805
# @return [Hash]
855806
def path_macros
856807
@path_macros ||= {}
857808
end
858809

859-
# @param namespace [String]
860-
# @param context [String]
861-
# @return [String, nil]
862-
def qualify_lower namespace, context
863-
qualify namespace, context.split('::')[0..-2].join('::')
864-
end
865-
866810
# @param fq_sub_tag [String]
867811
# @return [String, nil]
868812
def qualify_superclass fq_sub_tag
869-
fq_sub_type = ComplexType.try_parse(fq_sub_tag)
870-
fq_sub_ns = fq_sub_type.name
871-
sup_tag = store.get_superclass(fq_sub_tag)
872-
sup_type = ComplexType.try_parse(sup_tag)
873-
sup_ns = sup_type.name
874-
return nil if sup_tag.nil?
875-
parts = fq_sub_ns.split('::')
876-
last = parts.pop
877-
parts.pop if last == sup_ns
878-
qualify(sup_tag, parts.join('::'))
879-
end
880-
881-
# @param name [String] Namespace to fully qualify
882-
# @param root [String] The context to search
883-
# @param skip [Set<String>] Contexts already searched
884-
# @return [String, nil] Fully qualified ("rooted") namespace
885-
def inner_qualify name, root, skip
886-
return name if name == ComplexType::GENERIC_TAG_NAME
887-
return nil if name.nil?
888-
return nil if skip.include?(root)
889-
skip.add root
890-
possibles = []
891-
if name == ''
892-
if root == ''
893-
return ''
894-
else
895-
return inner_qualify(root, '', skip)
896-
end
897-
else
898-
return name if root == '' && store.namespace_exists?(name)
899-
roots = root.to_s.split('::')
900-
while roots.length > 0
901-
fqns = roots.join('::') + '::' + name
902-
return fqns if store.namespace_exists?(fqns)
903-
incs = store.get_includes(roots.join('::'))
904-
incs.each do |inc|
905-
foundinc = inner_qualify(name, inc, skip)
906-
possibles.push foundinc unless foundinc.nil?
907-
end
908-
roots.pop
909-
end
910-
if possibles.empty?
911-
incs = store.get_includes('')
912-
incs.each do |inc|
913-
foundinc = inner_qualify(name, inc, skip)
914-
possibles.push foundinc unless foundinc.nil?
915-
end
916-
end
917-
return name if store.namespace_exists?(name)
918-
return possibles.last
919-
end
813+
store.qualify_superclass fq_sub_tag
920814
end
921815

922816
# Get the namespace's type (Class or Module).
@@ -1041,9 +935,9 @@ def should_erase_generics_when_done?(namespace_pin, rooted_type)
1041935
has_generics?(namespace_pin) && !can_resolve_generics?(namespace_pin, rooted_type)
1042936
end
1043937

1044-
# @param namespace_pin [Pin::Namespace]
938+
# @param namespace_pin [Pin::Namespace, Pin::Constant]
1045939
def has_generics?(namespace_pin)
1046-
namespace_pin && !namespace_pin.generics.empty?
940+
namespace_pin.is_a?(Pin::Namespace) && !namespace_pin.generics.empty?
1047941
end
1048942

1049943
# @param namespace_pin [Pin::Namespace]

0 commit comments

Comments
 (0)