holistic-ruby 0.1.4 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +5 -5
  4. data/exe/holistic-ruby +0 -1
  5. data/lib/holistic/application.rb +12 -4
  6. data/lib/holistic/database/migrations.rb +23 -0
  7. data/lib/holistic/database/node.rb +37 -0
  8. data/lib/holistic/database/relation.rb +21 -0
  9. data/lib/holistic/database.rb +57 -0
  10. data/lib/holistic/document/file/record.rb +10 -0
  11. data/lib/holistic/document/file/repository.rb +24 -0
  12. data/lib/holistic/document/file/store.rb +13 -0
  13. data/lib/holistic/document/location.rb +4 -6
  14. data/lib/holistic/document/unsaved/record.rb +12 -3
  15. data/lib/holistic/extensions/ruby/stdlib.rb +8 -8
  16. data/lib/holistic/language_server/requests/lifecycle/initialize.rb +5 -10
  17. data/lib/holistic/language_server/requests/text_document/completion.rb +16 -5
  18. data/lib/holistic/language_server/requests/text_document/did_close.rb +5 -9
  19. data/lib/holistic/language_server/requests/text_document/did_open.rb +1 -0
  20. data/lib/holistic/language_server/requests/text_document/did_save.rb +5 -9
  21. data/lib/holistic/language_server/requests/text_document/find_references.rb +7 -4
  22. data/lib/holistic/language_server/requests/text_document/go_to_definition.rb +3 -2
  23. data/lib/holistic/language_server/stdio/parser.rb +2 -2
  24. data/lib/holistic/language_server/stdio/start.rb +1 -1
  25. data/lib/holistic/ruby/autocompletion/suggest.rb +45 -25
  26. data/lib/holistic/ruby/parser/constant_resolution.rb +11 -11
  27. data/lib/holistic/ruby/parser/live_editing/process_file_changed.rb +23 -23
  28. data/lib/holistic/ruby/parser/program_visitor.rb +62 -29
  29. data/lib/holistic/ruby/parser.rb +51 -11
  30. data/lib/holistic/ruby/reference/delete.rb +18 -0
  31. data/lib/holistic/ruby/reference/find_referenced_scope.rb +2 -2
  32. data/lib/holistic/ruby/reference/record.rb +15 -8
  33. data/lib/holistic/ruby/reference/repository.rb +19 -41
  34. data/lib/holistic/ruby/reference/store.rb +29 -0
  35. data/lib/holistic/ruby/scope/delete.rb +29 -0
  36. data/lib/holistic/ruby/scope/lexical.rb +11 -0
  37. data/lib/holistic/ruby/scope/list_class_methods.rb +19 -0
  38. data/lib/holistic/ruby/scope/list_instance_methods.rb +19 -0
  39. data/lib/holistic/ruby/scope/list_references.rb +3 -3
  40. data/lib/holistic/ruby/scope/location.rb +9 -9
  41. data/lib/holistic/ruby/scope/outline.rb +10 -10
  42. data/lib/holistic/ruby/scope/record.rb +20 -50
  43. data/lib/holistic/ruby/scope/repository.rb +29 -26
  44. data/lib/holistic/ruby/scope/store.rb +45 -0
  45. data/lib/holistic/ruby/type_inference/clue/method_call.rb +1 -0
  46. data/lib/holistic/ruby/type_inference/clue/reference_to_superclass.rb +9 -0
  47. data/lib/holistic/ruby/type_inference/clue/scope_reference.rb +1 -0
  48. data/lib/holistic/ruby/type_inference/processing_queue.rb +28 -0
  49. data/lib/holistic/ruby/type_inference/resolver/class_method.rb +9 -0
  50. data/lib/holistic/ruby/type_inference/resolver/instance_method.rb +9 -0
  51. data/lib/holistic/ruby/type_inference/resolver/scope.rb +24 -0
  52. data/lib/holistic/ruby/type_inference/solve.rb +25 -69
  53. data/lib/holistic/ruby/type_inference/solve_pending_references.rb +3 -1
  54. data/lib/holistic/version.rb +1 -1
  55. metadata +21 -10
  56. data/lib/holistic/database/table.rb +0 -78
  57. data/lib/holistic/document/file.rb +0 -36
  58. data/lib/holistic/ruby/parser/table_of_contents.rb +0 -17
  59. data/lib/holistic/ruby/reference/register.rb +0 -15
  60. data/lib/holistic/ruby/reference/unregister.rb +0 -11
  61. data/lib/holistic/ruby/scope/register.rb +0 -31
  62. data/lib/holistic/ruby/scope/unregister.rb +0 -27
  63. data/lib/holistic/ruby/type_inference/conclusion.rb +0 -20
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Ruby::Reference
4
+ module Store
5
+ extend self
6
+
7
+ def call(database:, processing_queue:, scope:, clues:, location:)
8
+ record = Record.new(location.identifier, { identifier: location.identifier, clues:, location: })
9
+
10
+ reference = database.store(location.identifier, record)
11
+
12
+ reference.relation(:located_in_scope).add!(scope)
13
+ reference.relation(:reference_defined_in_file).add!(location.file)
14
+
15
+ # resolving reference to superclasses need to happen before resolving reference to methods because the
16
+ # relation ancestor-descentand needs to exist beforehand.
17
+ # in other words, if we try to resolve a reference to a method *before* resolving the superclass
18
+ # we might get a miss.
19
+ should_resolve_type_inference_with_high_priority =
20
+ reference.find_clue(::Holistic::Ruby::TypeInference::Clue::ReferenceToSuperclass).present?
21
+
22
+ if should_resolve_type_inference_with_high_priority
23
+ processing_queue.push_with_high_priority(reference)
24
+ else
25
+ processing_queue.push(reference)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Ruby::Scope
4
+ module Delete
5
+ extend self
6
+
7
+ def call(database:, fully_qualified_name:, file_path:)
8
+ scope = database.find(fully_qualified_name)
9
+
10
+ return :scope_not_found if scope.nil?
11
+
12
+ location_to_remove = scope.locations.find { |scope_location| scope_location.declaration.file.path == file_path }
13
+
14
+ return :scope_not_defined_in_speciefied_file if location_to_remove.nil?
15
+
16
+ scope.locations.delete(location_to_remove)
17
+
18
+ scope.relation(:scope_defined_in_file).delete!(location_to_remove.declaration.file)
19
+
20
+ if scope.locations.empty?
21
+ scope.relation(:lexical_parent).delete!(scope.lexical_parent)
22
+
23
+ database.delete(fully_qualified_name)
24
+ end
25
+
26
+ :definition_unregistered
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Ruby::Scope::Lexical
4
+ extend self
5
+
6
+ def descendant?(child:, parent:)
7
+ child_parent = child.lexical_parent
8
+
9
+ child_parent.present? && (child_parent == parent || descendant?(child: child_parent, parent:))
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Ruby::Scope::ListClassMethods
4
+ extend self
5
+
6
+ def call(scope:)
7
+ class_methods = scope.lexical_children.filter(&:class_method?)
8
+ class_method_names = ::Set.new(class_methods.map(&:name))
9
+
10
+ ancestor_methods = scope.ancestors.flat_map do |ancestor|
11
+ ancestor_methods = call(scope: ancestor)
12
+
13
+ # reject parent methods that were overriden by the subclass
14
+ ancestor_methods.reject { |method| class_method_names.include?(method.name) }
15
+ end
16
+
17
+ class_methods + ancestor_methods
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Ruby::Scope::ListInstanceMethods
4
+ extend self
5
+
6
+ def call(scope:)
7
+ instance_methods = scope.lexical_children.filter(&:instance_method?)
8
+ instance_method_names = ::Set.new(instance_methods.map(&:name))
9
+
10
+ ancestor_methods = scope.ancestors.flat_map do |ancestor|
11
+ ancestor_methods = call(scope: ancestor)
12
+
13
+ # reject parent methods that were overriden by the subclass
14
+ ancestor_methods.reject { |method| instance_method_names.include?(method.name) }
15
+ end
16
+
17
+ instance_methods + ancestor_methods
18
+ end
19
+ end
@@ -5,16 +5,16 @@ module Holistic::Ruby::Scope
5
5
  extend self
6
6
 
7
7
  QueryReferencesRecursively = ->(application, scope) do
8
- references_to_scope = application.references.list_references_to(scope.fully_qualified_name)
8
+ references_to_scope = scope.referenced_by.to_a
9
9
 
10
- references_to_child_scopes = scope.children.flat_map { QueryReferencesRecursively.call(application, _1) }
10
+ references_to_child_scopes = scope.lexical_children.flat_map { QueryReferencesRecursively.call(application, _1) }
11
11
 
12
12
  references_to_scope + references_to_child_scopes
13
13
  end
14
14
 
15
15
  Relevance = ->(reference) do
16
16
  # TODO: should the location answer the kind of file it is? application code, config, spec, etc. Not sure.
17
- looks_like_a_spec = reference.location.file_path.include?("_spec.rb") || reference.location.file_path.include?("_test.rb")
17
+ looks_like_a_spec = reference.location.file.path.include?("_spec.rb") || reference.location.file.path.include?("_test.rb")
18
18
 
19
19
  looks_like_a_spec ? 1 : 0
20
20
  end
@@ -3,13 +3,11 @@
3
3
  module Holistic::Ruby::Scope
4
4
  class Location
5
5
  class Collection
6
- attr_reader :scope, :items
6
+ attr_reader :scope_name, :items
7
7
 
8
- def initialize(scope, location)
9
- raise ::ArgumentError if location.present? && !location.is_a?(Location)
10
-
11
- @scope = scope
12
- @items = location.nil? ? [] : [location]
8
+ def initialize(scope_name)
9
+ @scope_name = scope_name
10
+ @items = []
13
11
  end
14
12
 
15
13
  def main
@@ -18,17 +16,19 @@ module Holistic::Ruby::Scope
18
16
 
19
17
  delegate :<<, to: :items
20
18
  delegate :each, to: :items
19
+ delegate :find, to: :items
21
20
  delegate :map, to: :items
22
- delegate :reject!, to: :items
21
+ delegate :delete, to: :items
23
22
  delegate :any?, to: :items
23
+ delegate :empty?, to: :items
24
24
 
25
25
  private
26
26
 
27
27
  def location_matching_scope_name
28
- scope_name_in_snake_case = scope.name.underscore
28
+ scope_name_in_snake_case = scope_name.underscore
29
29
 
30
30
  items.find do |location|
31
- ::File.basename(location.declaration.file_path) == "#{scope_name_in_snake_case}.rb"
31
+ ::File.basename(location.declaration.file.path) == "#{scope_name_in_snake_case}.rb"
32
32
  end
33
33
  end
34
34
  end
@@ -13,38 +13,38 @@ module Holistic::Ruby::Scope
13
13
  )
14
14
 
15
15
  QueryChildScopesRecursively = ->(application, scope) do
16
- scope.children + scope.children.flat_map { QueryChildScopesRecursively[application, _1] }
16
+ scope.lexical_children.to_a + scope.lexical_children.flat_map { QueryChildScopesRecursively[application, _1] }
17
17
  end
18
18
 
19
19
  QueryDependenciesRecursively = ->(application, outlined_scope, scope) do
20
20
  is_local_dependency = ->(reference) do
21
- scope = application.scopes.find_by_fully_qualified_name(reference.conclusion.dependency_identifier)
21
+ scope = reference.referenced_scope
22
22
 
23
- scope.eql?(outlined_scope) || scope.descendant?(outlined_scope)
23
+ scope == outlined_scope || Lexical.descendant?(child: scope, parent: outlined_scope)
24
24
  end
25
25
 
26
26
  dependencies = []
27
27
 
28
28
  scope.locations.each do |scope_location|
29
29
  application.references
30
- .list_references_in_file(scope_location.declaration.file_path)
31
- .filter { |reference| reference.scope == scope }
32
- .filter { |reference| reference.conclusion.dependency_identifier.present? }
30
+ .list_references_in_file(scope_location.declaration.file.path)
31
+ .filter { |reference| reference.located_in_scope == scope }
32
+ .filter { |reference| reference.referenced_scope.present? }
33
33
  .reject(&is_local_dependency)
34
34
  .tap { dependencies.concat(_1) }
35
35
  end
36
36
 
37
- scope.children.map(&QueryDependenciesRecursively.curry[application, outlined_scope]).flatten.concat(dependencies)
37
+ scope.lexical_children.map(&QueryDependenciesRecursively.curry[application, outlined_scope]).flatten.concat(dependencies)
38
38
  end
39
39
 
40
40
  def call(application:, scope:)
41
41
  declarations = QueryChildScopesRecursively.call(application, scope).sort_by { _1.fully_qualified_name }
42
42
 
43
- dependencies = QueryDependenciesRecursively.call(application, scope, scope).uniq { _1.conclusion.dependency_identifier }
43
+ dependencies = QueryDependenciesRecursively.call(application, scope, scope).uniq
44
44
 
45
- references = application.references.list_references_to(scope.fully_qualified_name)
45
+ references = scope.referenced_by
46
46
 
47
- dependants = references.map { |reference| reference.scope }.uniq
47
+ dependants = references.map { |reference| reference.located_in_scope }.uniq
48
48
 
49
49
  Result.new(declarations:, dependencies:, references:, dependants:)
50
50
  end
@@ -1,56 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Holistic::Ruby::Scope
4
- class Record
5
- attr_reader :kind, :name, :parent, :children, :locations
6
-
7
- def initialize(kind:, name:, parent:, location: nil)
8
- @kind = kind
9
- @name = name
10
- @parent = parent
11
- @locations = Location::Collection.new(self, location)
12
- @children = []
13
- end
14
-
15
- def fully_qualified_name
16
- return "" if root?
17
-
18
- separator =
19
- case kind
20
- when Kind::INSTANCE_METHOD then "#"
21
- when Kind::CLASS_METHOD then "."
22
- else "::"
23
- end
24
-
25
- "#{parent.fully_qualified_name}#{separator}#{name}"
26
- end
27
-
28
- def root?
29
- parent.nil?
30
- end
31
-
32
- def lambda?
33
- kind == Kind::LAMBDA
34
- end
35
-
36
- def class?
37
- kind == Kind::CLASS
38
- end
39
-
40
- def module?
41
- kind == Kind::MODULE
42
- end
43
-
44
- def instance_method?
45
- kind == Kind::INSTANCE_METHOD
46
- end
47
-
48
- def class_method?
49
- kind == Kind::CLASS_METHOD
50
- end
51
-
52
- def descendant?(other)
53
- parent.present? && (parent == other || parent.descendant?(other))
4
+ class Record < ::Holistic::Database::Node
5
+ def fully_qualified_name = attr(:fully_qualified_name)
6
+ def locations = attr(:locations)
7
+ def name = attr(:name)
8
+ def kind = attr(:kind)
9
+
10
+ def lexical_parent = has_one(:lexical_parent)
11
+ def lexical_children = has_many(:lexical_children)
12
+ def ancestors = has_many(:ancestors)
13
+ def descendants = has_many(:descendants)
14
+ def referenced_by = has_many(:referenced_by)
15
+
16
+ def root? = kind == Kind::ROOT
17
+ def class? = kind == Kind::CLASS
18
+ def class_method? = kind == Kind::CLASS_METHOD
19
+ def instance_method? = kind == Kind::INSTANCE_METHOD
20
+ def method? = class_method? || instance_method?
21
+
22
+ def inspect
23
+ "<#{self.class.name} kind=#{kind} fully_qualified_name=#{fully_qualified_name}>"
54
24
  end
55
25
  end
56
26
  end
@@ -2,48 +2,51 @@
2
2
 
3
3
  module Holistic::Ruby::Scope
4
4
  class Repository
5
- attr_reader :table
6
-
7
- def initialize
8
- @table = ::Holistic::Database::Table.new(primary_attribute: :fully_qualified_name, indices: [:file_paths])
9
- end
10
-
11
- def register_scope(scope)
12
- table.update({
13
- fully_qualified_name: scope.fully_qualified_name,
14
- file_paths: scope.locations.map { |scope_location| scope_location.declaration.file_path },
15
- scope:
16
- })
5
+ attr_reader :database, :root
6
+
7
+ def initialize(database:)
8
+ @root = database.store(
9
+ "root_scope",
10
+ Record.new("root_scope", {
11
+ fully_qualified_name: "::",
12
+ name: "::",
13
+ kind: Kind::ROOT
14
+ })
15
+ )
16
+
17
+ @database = database
17
18
  end
18
19
 
19
- def find_by_fully_qualified_name(fully_qualified_name)
20
- table.find(fully_qualified_name).try(:dig, :scope)
20
+ def find(fully_qualified_name)
21
+ database.find(fully_qualified_name)
21
22
  end
22
23
 
23
24
  def find_by_cursor(cursor)
24
- table.filter(:file_paths, cursor.file_path).map { _1[:scope] }.each do |scope|
25
- return scope if scope.locations.any? { _1.declaration.contains?(cursor) }
25
+ database.find(cursor.file_path)&.then do |file|
26
+ file.defines_scopes.find do |scope|
27
+ scope.locations.any? { _1.declaration.contains?(cursor) }
28
+ end
26
29
  end
27
-
28
- nil
29
30
  end
30
31
 
31
32
  def find_inner_most_scope_by_cursor(cursor)
32
- scopes = table.filter(:file_paths, cursor.file_path).map { _1[:scope] }
33
+ file = database.find(cursor.file_path)
34
+
35
+ return nil if file.nil?
33
36
 
34
- matching_scopes = scopes.filter do |scope|
35
- scope.locations.any? { _1.body.contains?(cursor) }
37
+ matching_scopes = file.defines_scopes.filter_map do |scope|
38
+ scope.locations.find { |location| location.body.contains?(cursor) }&.then do |location|
39
+ { location:, scope: }
40
+ end
36
41
  end
37
42
 
38
- matching_scopes.last
39
- end
43
+ inner_most_matching_scope = matching_scopes.sort_by { |match| match[:location].declaration.start_line }.last
40
44
 
41
- def delete_by_fully_qualified_name(fully_qualified_name)
42
- table.delete(fully_qualified_name)
45
+ inner_most_matching_scope&.then { _1[:scope] }
43
46
  end
44
47
 
45
48
  def list_scopes_in_file(file_path)
46
- table.filter(:file_paths, file_path).map { _1[:scope] }
49
+ database.find(file_path)&.defines_scopes || []
47
50
  end
48
51
  end
49
52
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Ruby::Scope
4
+ module Store
5
+ extend self
6
+
7
+ def call(database:, lexical_parent:, kind:, name:, location:)
8
+ fully_qualified_name = build_fully_qualified_name(lexical_parent:, kind:, name:)
9
+
10
+ scope = database.find(fully_qualified_name)
11
+
12
+ if scope.nil?
13
+ record = Record.new(fully_qualified_name, { fully_qualified_name:, name:, kind:, locations: Location::Collection.new(name) })
14
+
15
+ scope = database.store(fully_qualified_name, record)
16
+ end
17
+
18
+ scope.locations << location
19
+
20
+ scope.relation(:lexical_parent).add!(lexical_parent)
21
+ scope.relation(:scope_defined_in_file).add!(location.declaration.file)
22
+
23
+ scope
24
+ end
25
+
26
+ private
27
+
28
+ def build_fully_qualified_name(lexical_parent:, kind:, name:)
29
+ parent_fully_qualified_name =
30
+ case lexical_parent.kind
31
+ when Kind::ROOT then ""
32
+ else lexical_parent.fully_qualified_name
33
+ end
34
+
35
+ separator =
36
+ case kind
37
+ when Kind::INSTANCE_METHOD then "#"
38
+ when Kind::CLASS_METHOD then "."
39
+ else "::"
40
+ end
41
+
42
+ "#{parent_fully_qualified_name}#{separator}#{name}"
43
+ end
44
+ end
45
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Holistic::Ruby::TypeInference::Clue
4
+ # TODO: Rename to ReferenceToMethod
4
5
  MethodCall = ::Data.define(
5
6
  :nesting,
6
7
  :method_name,
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Ruby::TypeInference::Clue
4
+ ReferenceToSuperclass = ::Data.define(:subclass_scope) do
5
+ def to_s
6
+ "superclass of #{subclass_scope.fully_qualified_name}"
7
+ end
8
+ end
9
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Holistic::Ruby::TypeInference::Clue
4
+ # TODO: rename to ReferenceToScope
4
5
  ScopeReference = ::Struct.new(
5
6
  :nesting,
6
7
  :resolution_possibilities,
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Ruby::TypeInference
4
+ class ProcessingQueue
5
+ def initialize
6
+ @high_priority_queue = ::Queue.new
7
+ @queue = ::Queue.new
8
+ end
9
+
10
+ def push(item)
11
+ @queue.push(item)
12
+ end
13
+
14
+ def push_with_high_priority(item)
15
+ @high_priority_queue.push(item)
16
+ end
17
+
18
+ def empty?
19
+ @high_priority_queue.empty? && @queue.empty?
20
+ end
21
+
22
+ def pop
23
+ return @high_priority_queue.pop if @high_priority_queue.size > 0
24
+
25
+ @queue.pop
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Ruby::TypeInference::Resolver::ClassMethod
4
+ extend self
5
+
6
+ def resolve(scope:, method_name:)
7
+ ::Holistic::Ruby::Scope::ListClassMethods.call(scope:).find { _1.name == method_name }
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Ruby::TypeInference::Resolver::InstanceMethod
4
+ extend self
5
+
6
+ def resolve(scope:, method_name:)
7
+ ::Holistic::Ruby::Scope::ListInstanceMethods.call(scope:).find { _1.name == method_name }
8
+ end
9
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Ruby::TypeInference::Resolver::Scope
4
+ extend self
5
+
6
+ def resolve(application:, nesting:, resolution_possibilities:)
7
+ resolution_possibilities = ["::"] if nesting.root_scope_resolution?
8
+
9
+ resolution_possibilities.each do |resolution_candidate|
10
+ fully_qualified_scope_name =
11
+ if resolution_candidate == "::"
12
+ "::#{nesting.to_s}"
13
+ else
14
+ "#{resolution_candidate}::#{nesting.to_s}"
15
+ end
16
+
17
+ scope = application.scopes.find(fully_qualified_scope_name)
18
+
19
+ return scope if scope.present?
20
+ end
21
+
22
+ nil
23
+ end
24
+ end
@@ -5,52 +5,47 @@ module Holistic::Ruby::TypeInference
5
5
  extend self
6
6
 
7
7
  def call(application:, reference:)
8
- reference.conclusion =
8
+ referenced_scope =
9
9
  solve_scope_reference(application:, reference:) ||
10
- solve_method_call(application:, reference:) ||
11
- Conclusion.unresolved
10
+ solve_method_call(application:, reference:)
12
11
 
13
- application.references.register_reference(reference)
12
+ if referenced_scope
13
+ reference.relation(:referenced_scope).add!(referenced_scope)
14
+
15
+ # NOTE: should this be an event that is handled by stdlib? I guess inheritance support with dedicated syntax
16
+ # is part of the language core, so it makes sense being here. Let me think about this for a bit.
17
+ reference.find_clue(Clue::ReferenceToSuperclass)&.then do |reference_to_superclass_clue|
18
+ referenced_scope.relation(:descendants).add!(reference_to_superclass_clue.subclass_scope)
19
+ end
20
+ end
14
21
  end
15
22
 
16
23
  private
17
24
 
18
25
  def solve_scope_reference(application:, reference:)
19
- has_scope_reference_clue =
20
- reference.clues.one? && reference.clues.first.is_a?(Clue::ScopeReference)
21
-
22
- return unless has_scope_reference_clue
26
+ reference_to_scope_clue = reference.find_clue(Clue::ScopeReference)
23
27
 
24
- scope_reference_clue = reference.clues.first
28
+ return if reference_to_scope_clue.nil?
25
29
 
26
- referenced_scope = resolve_scope(
30
+ Resolver::Scope.resolve(
27
31
  application:,
28
- nesting: scope_reference_clue.nesting,
29
- resolution_possibilities: scope_reference_clue.resolution_possibilities
32
+ nesting: reference_to_scope_clue.nesting,
33
+ resolution_possibilities: reference_to_scope_clue.resolution_possibilities
30
34
  )
31
-
32
- if referenced_scope.present?
33
- return Conclusion.done(referenced_scope.fully_qualified_name)
34
- end
35
-
36
- nil
37
35
  end
38
36
 
39
37
  SolveMethodCallInCurrentScope = ->(application:, reference:, method_call_clue:) do
40
- referenced_method =
41
- if reference.scope.class_method?
42
- resolve_class_method(application:, scope: reference.scope.parent, method_name: method_call_clue.method_name)
43
- elsif reference.scope.instance_method? && reference.scope.parent.present?
44
- resolve_instance_method(application:, scope: reference.scope.parent, method_name: method_call_clue.method_name)
45
- end
46
-
47
- return if referenced_method.nil?
38
+ scope = reference.located_in_scope
48
39
 
49
- Conclusion.done(referenced_method.fully_qualified_name)
40
+ if scope.class_method?
41
+ Resolver::ClassMethod.resolve(scope: scope.lexical_parent, method_name: method_call_clue.method_name)
42
+ elsif scope.instance_method? && scope.lexical_parent.present?
43
+ Resolver::InstanceMethod.resolve(scope: scope.lexical_parent, method_name: method_call_clue.method_name)
44
+ end
50
45
  end
51
46
 
52
47
  SolveMethodCallInSpecifiedScope = ->(application:, reference:, method_call_clue:) do
53
- referenced_scope = resolve_scope(
48
+ referenced_scope = Resolver::Scope.resolve(
54
49
  application:,
55
50
  nesting: method_call_clue.nesting,
56
51
  resolution_possibilities: method_call_clue.resolution_possibilities
@@ -59,16 +54,8 @@ module Holistic::Ruby::TypeInference
59
54
  return if referenced_scope.nil?
60
55
 
61
56
  referenced_method = application.extensions.dispatch(:resolve_method_call_known_scope, { reference:, referenced_scope:, method_call_clue: })
62
- referenced_method ||= resolve_class_method(application:, scope: referenced_scope, method_name: method_call_clue.method_name)
63
-
64
- Conclusion.done(referenced_method.fully_qualified_name) if referenced_method.present?
65
- end
66
-
67
- SolveMethodCallInLocalVariable = ->(application:, reference:, method_call_clue:) do
68
- # local_variable_name = method_call_clue.nesting.to_s
69
- # referenced_scope = guess_scope_for_local_variable(scope: reference.scope, name: local_variable_name)
70
57
 
71
- nil
58
+ referenced_method || Resolver::ClassMethod.resolve(scope: referenced_scope, method_name: method_call_clue.method_name)
72
59
  end
73
60
 
74
61
  def solve_method_call(application:, reference:)
@@ -83,39 +70,8 @@ module Holistic::Ruby::TypeInference
83
70
  elsif method_call_clue.nesting.constant?
84
71
  SolveMethodCallInSpecifiedScope.call(application:, reference:, method_call_clue:)
85
72
  else
86
- SolveMethodCallInLocalVariable.call(application:, reference:, method_call_clue:)
73
+ nil # TODO
87
74
  end
88
75
  end
89
-
90
- def resolve_scope(application:, nesting:, resolution_possibilities:)
91
- resolution_possibilities = ["::"] if nesting.root_scope_resolution?
92
-
93
- resolution_possibilities.each do |resolution_candidate|
94
- fully_qualified_scope_name =
95
- if resolution_candidate == "::"
96
- "::#{nesting.to_s}"
97
- else
98
- "#{resolution_candidate}::#{nesting.to_s}"
99
- end
100
-
101
- scope = application.scopes.find_by_fully_qualified_name(fully_qualified_scope_name)
102
-
103
- return scope if scope.present?
104
- end
105
-
106
- nil
107
- end
108
-
109
- def resolve_instance_method(application:, scope:, method_name:)
110
- method_fully_qualified_name = "#{scope.fully_qualified_name}##{method_name}"
111
-
112
- application.scopes.find_by_fully_qualified_name(method_fully_qualified_name)
113
- end
114
-
115
- def resolve_class_method(application:, scope:, method_name:)
116
- method_fully_qualified_name = "#{scope.fully_qualified_name}.#{method_name}"
117
-
118
- application.scopes.find_by_fully_qualified_name(method_fully_qualified_name)
119
- end
120
76
  end
121
77
  end