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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +5 -5
- data/exe/holistic-ruby +0 -1
- data/lib/holistic/application.rb +12 -4
- data/lib/holistic/database/migrations.rb +23 -0
- data/lib/holistic/database/node.rb +37 -0
- data/lib/holistic/database/relation.rb +21 -0
- data/lib/holistic/database.rb +57 -0
- data/lib/holistic/document/file/record.rb +10 -0
- data/lib/holistic/document/file/repository.rb +24 -0
- data/lib/holistic/document/file/store.rb +13 -0
- data/lib/holistic/document/location.rb +4 -6
- data/lib/holistic/document/unsaved/record.rb +12 -3
- data/lib/holistic/extensions/ruby/stdlib.rb +8 -8
- data/lib/holistic/language_server/requests/lifecycle/initialize.rb +5 -10
- data/lib/holistic/language_server/requests/text_document/completion.rb +16 -5
- data/lib/holistic/language_server/requests/text_document/did_close.rb +5 -9
- data/lib/holistic/language_server/requests/text_document/did_open.rb +1 -0
- data/lib/holistic/language_server/requests/text_document/did_save.rb +5 -9
- data/lib/holistic/language_server/requests/text_document/find_references.rb +7 -4
- data/lib/holistic/language_server/requests/text_document/go_to_definition.rb +3 -2
- data/lib/holistic/language_server/stdio/parser.rb +2 -2
- data/lib/holistic/language_server/stdio/start.rb +1 -1
- data/lib/holistic/ruby/autocompletion/suggest.rb +45 -25
- data/lib/holistic/ruby/parser/constant_resolution.rb +11 -11
- data/lib/holistic/ruby/parser/live_editing/process_file_changed.rb +23 -23
- data/lib/holistic/ruby/parser/program_visitor.rb +62 -29
- data/lib/holistic/ruby/parser.rb +51 -11
- data/lib/holistic/ruby/reference/delete.rb +18 -0
- data/lib/holistic/ruby/reference/find_referenced_scope.rb +2 -2
- data/lib/holistic/ruby/reference/record.rb +15 -8
- data/lib/holistic/ruby/reference/repository.rb +19 -41
- data/lib/holistic/ruby/reference/store.rb +29 -0
- data/lib/holistic/ruby/scope/delete.rb +29 -0
- data/lib/holistic/ruby/scope/lexical.rb +11 -0
- data/lib/holistic/ruby/scope/list_class_methods.rb +19 -0
- data/lib/holistic/ruby/scope/list_instance_methods.rb +19 -0
- data/lib/holistic/ruby/scope/list_references.rb +3 -3
- data/lib/holistic/ruby/scope/location.rb +9 -9
- data/lib/holistic/ruby/scope/outline.rb +10 -10
- data/lib/holistic/ruby/scope/record.rb +20 -50
- data/lib/holistic/ruby/scope/repository.rb +29 -26
- data/lib/holistic/ruby/scope/store.rb +45 -0
- data/lib/holistic/ruby/type_inference/clue/method_call.rb +1 -0
- data/lib/holistic/ruby/type_inference/clue/reference_to_superclass.rb +9 -0
- data/lib/holistic/ruby/type_inference/clue/scope_reference.rb +1 -0
- data/lib/holistic/ruby/type_inference/processing_queue.rb +28 -0
- data/lib/holistic/ruby/type_inference/resolver/class_method.rb +9 -0
- data/lib/holistic/ruby/type_inference/resolver/instance_method.rb +9 -0
- data/lib/holistic/ruby/type_inference/resolver/scope.rb +24 -0
- data/lib/holistic/ruby/type_inference/solve.rb +25 -69
- data/lib/holistic/ruby/type_inference/solve_pending_references.rb +3 -1
- data/lib/holistic/version.rb +1 -1
- metadata +21 -10
- data/lib/holistic/database/table.rb +0 -78
- data/lib/holistic/document/file.rb +0 -36
- data/lib/holistic/ruby/parser/table_of_contents.rb +0 -17
- data/lib/holistic/ruby/reference/register.rb +0 -15
- data/lib/holistic/ruby/reference/unregister.rb +0 -11
- data/lib/holistic/ruby/scope/register.rb +0 -31
- data/lib/holistic/ruby/scope/unregister.rb +0 -27
- 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 =
|
8
|
+
references_to_scope = scope.referenced_by.to_a
|
9
9
|
|
10
|
-
references_to_child_scopes = scope.
|
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.
|
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 :
|
6
|
+
attr_reader :scope_name, :items
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
|
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 :
|
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 =
|
28
|
+
scope_name_in_snake_case = scope_name.underscore
|
29
29
|
|
30
30
|
items.find do |location|
|
31
|
-
::File.basename(location.declaration.
|
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.
|
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 =
|
21
|
+
scope = reference.referenced_scope
|
22
22
|
|
23
|
-
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.
|
31
|
-
.filter { |reference| reference.
|
32
|
-
.filter { |reference| reference.
|
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.
|
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
|
43
|
+
dependencies = QueryDependenciesRecursively.call(application, scope, scope).uniq
|
44
44
|
|
45
|
-
references =
|
45
|
+
references = scope.referenced_by
|
46
46
|
|
47
|
-
dependants = references.map { |reference| reference.
|
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
|
-
|
6
|
-
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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 :
|
6
|
-
|
7
|
-
def initialize
|
8
|
-
@
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
20
|
-
|
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
|
-
|
25
|
-
|
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
|
-
|
33
|
+
file = database.find(cursor.file_path)
|
34
|
+
|
35
|
+
return nil if file.nil?
|
33
36
|
|
34
|
-
matching_scopes =
|
35
|
-
scope.locations.
|
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
|
-
|
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
|
-
|
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
|
@@ -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,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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
28
|
+
return if reference_to_scope_clue.nil?
|
25
29
|
|
26
|
-
|
30
|
+
Resolver::Scope.resolve(
|
27
31
|
application:,
|
28
|
-
nesting:
|
29
|
-
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
|
-
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|