holistic-ruby 0.1.4 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
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