orbacle 0.1.0

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 (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +4 -0
  6. data/Gemfile +2 -0
  7. data/Gemfile.lock +119 -0
  8. data/LICENSE +22 -0
  9. data/Makefile +57 -0
  10. data/README.md +53 -0
  11. data/circle.yml +82 -0
  12. data/exe/orbaclerun +6 -0
  13. data/index.html +106 -0
  14. data/lib/orbacle.rb +96 -0
  15. data/lib/orbacle/ast_utils.rb +35 -0
  16. data/lib/orbacle/bottom_type.rb +23 -0
  17. data/lib/orbacle/builder.rb +1414 -0
  18. data/lib/orbacle/builder/context.rb +71 -0
  19. data/lib/orbacle/builder/operator_assignment_processors.rb +80 -0
  20. data/lib/orbacle/class_type.rb +32 -0
  21. data/lib/orbacle/command_line_interface.rb +107 -0
  22. data/lib/orbacle/const_name.rb +33 -0
  23. data/lib/orbacle/const_ref.rb +53 -0
  24. data/lib/orbacle/constants_tree.rb +73 -0
  25. data/lib/orbacle/define_builtins.rb +139 -0
  26. data/lib/orbacle/engine.rb +74 -0
  27. data/lib/orbacle/find_definition_under_position.rb +76 -0
  28. data/lib/orbacle/generic_type.rb +35 -0
  29. data/lib/orbacle/global_tree.rb +280 -0
  30. data/lib/orbacle/graph.rb +126 -0
  31. data/lib/orbacle/indexer.rb +151 -0
  32. data/lib/orbacle/integer_id_generator.rb +13 -0
  33. data/lib/orbacle/lambda_type.rb +37 -0
  34. data/lib/orbacle/lang_server.rb +64 -0
  35. data/lib/orbacle/main_type.rb +23 -0
  36. data/lib/orbacle/nesting.rb +78 -0
  37. data/lib/orbacle/node.rb +23 -0
  38. data/lib/orbacle/nominal_type.rb +32 -0
  39. data/lib/orbacle/ruby_parser.rb +19 -0
  40. data/lib/orbacle/scope.rb +63 -0
  41. data/lib/orbacle/selfie.rb +41 -0
  42. data/lib/orbacle/type_pretty_printer.rb +24 -0
  43. data/lib/orbacle/typing_service.rb +816 -0
  44. data/lib/orbacle/union_type.rb +40 -0
  45. data/lib/orbacle/uuid_id_generator.rb +11 -0
  46. data/lib/orbacle/worklist.rb +51 -0
  47. data/orbacle.gemspec +33 -0
  48. metadata +258 -0
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orbacle
4
+ class DefineBuiltins
5
+ def initialize(graph, tree, id_generator)
6
+ @graph = graph
7
+ @tree = tree
8
+ @id_generator = id_generator
9
+ end
10
+
11
+ def call
12
+ add_object_klass
13
+ add_dir_klass
14
+ add_file_klass
15
+ add_integer_klass
16
+ end
17
+
18
+ private
19
+ attr_reader :id_generator
20
+
21
+ def add_object_klass
22
+ klass = @tree.add_klass(nil)
23
+ @tree.add_constant(
24
+ GlobalTree::Constant.new("Object", Scope.empty, nil, klass.id))
25
+
26
+ # BasicObject
27
+ template_just_bool(klass, "==")
28
+ template_just_bool(klass, "!")
29
+ template_just_bool(klass, "!=")
30
+ template_just_bool(klass, "equal?")
31
+ template_just_int(klass, "object_id")
32
+ template_just_int(klass, "__id__")
33
+
34
+ # Object
35
+ template_just_bool(klass, "!~")
36
+ template_maybe_int(klass, "<=>")
37
+ template_just_bool(klass, "===")
38
+ template_just_nil(klass, "display")
39
+ template_just_bool(klass, "eql?")
40
+ template_just_bool(klass, "frozen?")
41
+ template_just_bool(klass, "instance_of?")
42
+ template_just_bool(klass, "instance_variable_defined?")
43
+ template_just_bool(klass, "is_a?")
44
+ template_just_str(klass, "inspect")
45
+ template_just_bool(klass, "kind_of?")
46
+ template_just_bool(klass, "nil?")
47
+ template_just_bool(klass, "respond_to?")
48
+ template_just_bool(klass, "respond_to_missing?")
49
+ template_just_bool(klass, "tainted?")
50
+ template_just_bool(klass, "untrusted?")
51
+ template_just_str(klass, "to_s")
52
+ end
53
+
54
+ def add_integer_klass
55
+ klass = @tree.add_klass(nil)
56
+ @tree.add_constant(
57
+ GlobalTree::Constant.new("Integer", Scope.empty, nil, klass.id))
58
+
59
+ template_just_int(klass, "succ")
60
+ template_just_int(klass, "+")
61
+ template_just_int(klass, "-")
62
+ template_just_int(klass, "*")
63
+ end
64
+
65
+ def add_dir_klass
66
+ klass = @tree.add_klass(nil)
67
+ @tree.add_constant(
68
+ GlobalTree::Constant.new("Dir", Scope.empty, nil, klass.id))
69
+ eigenclass = @tree.get_eigenclass_of_definition(klass.id)
70
+
71
+ template_just_array_of_str(eigenclass, "glob")
72
+ end
73
+
74
+ def add_file_klass
75
+ klass = @tree.add_klass(nil)
76
+ @tree.add_constant(
77
+ GlobalTree::Constant.new("File", Scope.empty, nil, klass.id))
78
+ eigenclass = @tree.get_eigenclass_of_definition(klass.id)
79
+
80
+ template_just_str(eigenclass, "read")
81
+ end
82
+
83
+ def template_just_int(klass, name)
84
+ metod = template_args(klass, name)
85
+ int_node = Node.new(:int, {})
86
+ @graph.add_edge(int_node, @graph.get_metod_nodes(metod.id).result)
87
+ end
88
+
89
+ def template_maybe_int(klass, name)
90
+ metod = template_args(klass, name)
91
+ int_node = Node.new(:int, {})
92
+ nil_node = Node.new(:nil, {})
93
+ @graph.add_edge(int_node, @graph.get_metod_nodes(metod.id).result)
94
+ @graph.add_edge(nil_node, @graph.get_metod_nodes(metod.id).result)
95
+ end
96
+
97
+ def template_just_str(klass, name)
98
+ metod = template_args(klass, name)
99
+ str_node = Node.new(:str, {})
100
+ @graph.add_edge(str_node, @graph.get_metod_nodes(metod.id).result)
101
+ end
102
+
103
+ def template_just_bool(klass, name)
104
+ metod = template_args(klass, name)
105
+ str_node = Node.new(:bool, {})
106
+ @graph.add_edge(str_node, @graph.get_metod_nodes(metod.id).result)
107
+ end
108
+
109
+ def template_just_nil(klass, name)
110
+ metod = template_args(klass, name)
111
+ str_node = Node.new(:nil, {})
112
+ @graph.add_edge(str_node, @graph.get_metod_nodes(metod.id).result)
113
+ end
114
+
115
+ def template_just_array_of_str(klass, name)
116
+ metod = template_args(klass, name)
117
+ str_node = Node.new(:str, {})
118
+ array_node = Node.new(:array, {})
119
+ @graph.add_edge(str_node, array_node)
120
+ @graph.add_edge(array_node, @graph.get_metod_nodes(metod.id).result)
121
+ end
122
+
123
+ def template_args(klass, name)
124
+ metod = @tree.add_method(
125
+ generate_id,
126
+ klass.id,
127
+ name,
128
+ nil,
129
+ :public,
130
+ GlobalTree::ArgumentsTree.new([], []))
131
+ @graph.store_metod_nodes(metod.id, {})
132
+ metod
133
+ end
134
+
135
+ def generate_id
136
+ id_generator.call
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orbacle
4
+ class Engine
5
+ def initialize(logger)
6
+ @logger = logger
7
+ end
8
+
9
+ attr_reader :stats_recorder
10
+
11
+ def index(project_root)
12
+ @stats_recorder = Indexer::StatsRecorder.new
13
+ service = Indexer.new(logger, stats_recorder)
14
+ @state, @graph, @worklist = service.(project_root: project_root)
15
+ end
16
+
17
+ def get_type_information(filepath, searched_position)
18
+ relevant_nodes = @graph
19
+ .vertices
20
+ .select {|n| n.location && n.location.uri.eql?(filepath) && n.location.position_range.include_position?(searched_position) }
21
+ .sort_by {|n| n.location.span }
22
+
23
+ pretty_print_type(@state.type_of(relevant_nodes.at(0)))
24
+ end
25
+
26
+ def locations_for_definition_under_position(file_path, file_content, position)
27
+ result = find_definition_under_position(file_content, position.line, position.character)
28
+ case result
29
+ when FindDefinitionUnderPosition::ConstantResult
30
+ constants = @state.solve_reference2(result.const_ref)
31
+ constants.map(&:location)
32
+ when FindDefinitionUnderPosition::MessageResult
33
+ caller_type = get_type_of_caller_from_message_send(file_path, result.position_range)
34
+ methods_definitions = get_methods_definitions_for_type(caller_type, result.name)
35
+ methods_definitions = @state.get_methods(result.name) if methods_definitions.empty?
36
+ methods_definitions.map(&:location).compact
37
+ else
38
+ []
39
+ end
40
+ end
41
+
42
+ private
43
+ def get_type_of_caller_from_message_send(file_path, position_range)
44
+ message_send = @worklist
45
+ .message_sends
46
+ .find {|ms| ms.location && ms.location.uri.eql?(file_path) && ms.location.position_range.include_position?(position_range.start) }
47
+ @state.type_of(message_send.send_obj)
48
+ end
49
+
50
+ def get_methods_definitions_for_type(type, method_name)
51
+ case type
52
+ when NominalType
53
+ @state.get_instance_methods_from_class_name(type.name, method_name)
54
+ when ClassType
55
+ @state.get_class_methods_from_class_name(type.name, method_name)
56
+ when UnionType
57
+ type.types_set.flat_map {|t| get_methods_definitions_for_type(t, method_name) }
58
+ else
59
+ []
60
+ end
61
+ end
62
+
63
+ private
64
+ attr_reader :logger
65
+
66
+ def find_definition_under_position(content, line, character)
67
+ FindDefinitionUnderPosition.new(RubyParser.new).process_file(content, Position.new(line, character))
68
+ end
69
+
70
+ def pretty_print_type(type)
71
+ TypePrettyPrinter.new.(type)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orbacle
4
+ class FindDefinitionUnderPosition < Parser::AST::Processor
5
+ include AstUtils
6
+
7
+ ConstantResult = Struct.new(:const_ref)
8
+ MessageResult = Struct.new(:name, :position_range)
9
+
10
+ def initialize(parser)
11
+ @parser = parser
12
+ end
13
+
14
+ def process_file(file_content, searched_position)
15
+ ast = parser.parse(file_content)
16
+
17
+ @current_nesting = Nesting.empty
18
+ @searched_position = searched_position
19
+
20
+ process(ast)
21
+
22
+ @result
23
+ end
24
+
25
+ attr_reader :parser
26
+
27
+ def process(ast)
28
+ new_ast = super
29
+ raise unless ast.equal?(new_ast)
30
+ new_ast
31
+ end
32
+
33
+ def on_const(ast)
34
+ if build_position_range_from_ast(ast).include_position?(@searched_position)
35
+ @result = ConstantResult.new(ConstRef.from_ast(ast, @current_nesting))
36
+ end
37
+ nil
38
+ end
39
+
40
+ def on_class(ast)
41
+ klass_name_ast, _ = ast.children
42
+ klass_name_ref = ConstRef.from_ast(klass_name_ast, @current_nesting)
43
+ with_new_nesting(@current_nesting.increase_nesting_const(klass_name_ref)) do
44
+ super
45
+ end
46
+ nil
47
+ end
48
+
49
+ def on_module(ast)
50
+ module_name_ast, _ = ast.children
51
+ module_name_ref = ConstRef.from_ast(module_name_ast, @current_nesting)
52
+ with_new_nesting(@current_nesting.increase_nesting_const(module_name_ref)) do
53
+ super
54
+ end
55
+ nil
56
+ end
57
+
58
+ def on_send(ast)
59
+ if ast.loc.selector && build_position_range_from_parser_range(ast.loc.selector).include_position?(@searched_position)
60
+ message_name = ast.children.fetch(1).to_s
61
+ selector_position_range = build_position_range_from_parser_range(ast.loc.selector)
62
+ @result = MessageResult.new(message_name, selector_position_range)
63
+ else
64
+ super
65
+ end
66
+ nil
67
+ end
68
+
69
+ def with_new_nesting(new_nesting)
70
+ previous_nesting = @current_nesting
71
+ @current_nesting = new_nesting
72
+ yield
73
+ @current_nesting = previous_nesting
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orbacle
4
+ class GenericType
5
+ def initialize(name, parameters)
6
+ @name = name
7
+ @parameters = parameters
8
+ end
9
+
10
+ attr_reader :name, :parameters
11
+
12
+ def ==(other)
13
+ self.class == other.class &&
14
+ self.name == other.name &&
15
+ self.parameters == other.parameters
16
+ end
17
+
18
+ def hash
19
+ [
20
+ self.class,
21
+ self.name,
22
+ self.parameters,
23
+ ].hash ^ BIG_VALUE
24
+ end
25
+ alias eql? ==
26
+
27
+ def each_possible_type
28
+ yield self
29
+ end
30
+
31
+ def bottom?
32
+ false
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,280 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orbacle
4
+ class GlobalTree
5
+ class ArgumentsTree < Struct.new(:args, :kwargs, :blockarg)
6
+ Regular = Struct.new(:name)
7
+ Optional = Struct.new(:name)
8
+ Splat = Struct.new(:name)
9
+ Nested = Struct.new(:args)
10
+ end
11
+
12
+ class Method
13
+ def initialize(id, place_of_definition_id, name, location, visibility, args)
14
+ raise ArgumentError.new(visibility) if ![:public, :private, :protected].include?(visibility)
15
+
16
+ @id = id
17
+ @place_of_definition_id = place_of_definition_id
18
+ @name = name
19
+ @location = location
20
+ @visibility = visibility
21
+ @args = args
22
+ end
23
+
24
+ attr_reader :id, :name, :location, :args, :place_of_definition_id
25
+ attr_accessor :visibility
26
+ end
27
+
28
+ class Lambda
29
+ def initialize(id, args)
30
+ @id = id
31
+ @args = args
32
+ end
33
+
34
+ attr_reader :id, :args
35
+ end
36
+
37
+ class Klass
38
+ def initialize(id, parent_ref, eigenclass_id = nil)
39
+ @id = id
40
+ @parent_ref = parent_ref
41
+ @eigenclass_id = eigenclass_id
42
+ end
43
+
44
+ attr_reader :id, :parent_ref, :eigenclass_id
45
+ attr_writer :eigenclass_id
46
+
47
+ def ==(other)
48
+ @id == other.id &&
49
+ @parent_ref == other.parent_ref &&
50
+ @eigenclass_id == other.eigenclass_id
51
+ end
52
+ end
53
+
54
+ class Mod
55
+ def initialize(id, eigenclass_id = nil)
56
+ @id = id
57
+ @eigenclass_id = eigenclass_id
58
+ end
59
+
60
+ attr_reader :id, :eigenclass_id
61
+ attr_writer :eigenclass_id
62
+
63
+ def ==(other)
64
+ @id == other.id &&
65
+ @eigenclass_id == other.eigenclass_id
66
+ end
67
+ end
68
+
69
+ class Constant
70
+ def initialize(name, scope, location, definition_id = nil)
71
+ @name = name
72
+ @scope = scope
73
+ @location = location
74
+ @definition_id = definition_id
75
+ end
76
+
77
+ attr_reader :name, :scope, :location, :definition_id
78
+
79
+ def ==(other)
80
+ @name == other.name &&
81
+ @scope == other.scope &&
82
+ @location == other.location &&
83
+ @definition_id == other.definition_id
84
+ end
85
+
86
+ def full_name
87
+ [*scope.elems, @name].join("::")
88
+ end
89
+ end
90
+
91
+ def initialize(id_generator)
92
+ @id_generator = id_generator
93
+ @constants = ConstantsTree.new
94
+ @classes_by_id = {}
95
+ @modules_by_id = {}
96
+ @methods_by_class_id = Hash.new {|h,k| h[k] = Hash.new {|h2, k2| h2[k2] = [] } }
97
+ @methods_by_id = {}
98
+ @lambdas_by_id = {}
99
+ @type_mapping = Hash.new(BottomType.new)
100
+ end
101
+
102
+ ### Methods
103
+
104
+ def add_method(id, place_of_definition_id, name, location, visibility, args)
105
+ metod = Method.new(id, place_of_definition_id, name, location, visibility, args)
106
+ @methods_by_class_id[metod.place_of_definition_id][metod.name] << metod
107
+ @methods_by_id[metod.id] = metod
108
+ return metod
109
+ end
110
+
111
+ def find_instance_method_from_class_name(class_name, method_name)
112
+ get_instance_methods_from_class_name(class_name, method_name).first
113
+ end
114
+
115
+ def find_instance_method_from_class_id(class_id, method_name)
116
+ get_instance_methods_from_class_id(class_id, method_name).first
117
+ end
118
+
119
+ def get_instance_methods_from_class_id(class_id, method_name)
120
+ @methods_by_class_id[class_id][method_name]
121
+ end
122
+
123
+ def get_instance_methods_from_class_name(class_name, method_name)
124
+ klass = find_class_by_name(class_name)
125
+ return [] if klass.nil?
126
+ get_instance_methods_from_class_id(klass.id, method_name)
127
+ end
128
+
129
+ def get_class_methods_from_class_name(class_name, method_name)
130
+ klass = find_class_by_name(class_name)
131
+ return [] if klass.nil?
132
+ eigenclass = get_eigenclass_of_definition(klass.id)
133
+ get_instance_methods_from_class_id(eigenclass.id, method_name)
134
+ end
135
+
136
+ def find_class_method_from_class_name(class_name, method_name)
137
+ get_class_methods_from_class_name(class_name, method_name).first
138
+ end
139
+
140
+ def get_methods(method_name)
141
+ @methods_by_id.values.select do |m|
142
+ m.name.eql?(method_name)
143
+ end
144
+ end
145
+
146
+ def find_super_method(method_id)
147
+ analyzed_method = @methods_by_id.fetch(method_id)
148
+ klass_of_this_method = get_class(analyzed_method.place_of_definition_id)
149
+ return nil if klass_of_this_method.nil? || klass_of_this_method.parent_ref.nil?
150
+ parent_klass = solve_reference(klass_of_this_method.parent_ref)
151
+ return nil if parent_klass.nil?
152
+ find_instance_method_from_class_name(parent_klass.full_name, analyzed_method.name)
153
+ end
154
+
155
+ def change_method_visibility(klass_id, name, new_visibility)
156
+ @methods_by_class_id[klass_id][name].each do |m|
157
+ m.visibility = new_visibility
158
+ end
159
+ end
160
+
161
+ ### Definitions
162
+
163
+ def add_klass(parent_ref)
164
+ klass = Klass.new(id_generator.call, parent_ref)
165
+ @classes_by_id[klass.id] = klass
166
+ klass
167
+ end
168
+
169
+ def add_mod
170
+ mod = Mod.new(id_generator.call)
171
+ @modules_by_id[mod.id] = mod
172
+ mod
173
+ end
174
+
175
+ def get_class(class_id)
176
+ @classes_by_id[class_id]
177
+ end
178
+
179
+ def get_module(module_id)
180
+ @modules_by_id[module_id]
181
+ end
182
+
183
+ def get_definition(definition_id)
184
+ get_class(definition_id) || get_module(definition_id)
185
+ end
186
+
187
+ def get_eigenclass_of_definition(definition_id)
188
+ definition = get_definition(definition_id)
189
+ if definition.eigenclass_id
190
+ get_class(definition.eigenclass_id)
191
+ else
192
+ eigenclass = add_klass(nil)
193
+ definition.eigenclass_id = eigenclass.id
194
+ eigenclass
195
+ end
196
+ end
197
+
198
+ ### Constants
199
+
200
+ def add_constant(constant)
201
+ @constants.add_element(constant.scope, constant.name, constant)
202
+ return constant
203
+ end
204
+
205
+ def solve_reference(const_ref)
206
+ @constants.find_by_const_ref(const_ref)
207
+ end
208
+
209
+ def solve_reference2(const_ref)
210
+ @constants.select_by_const_ref(const_ref)
211
+ end
212
+
213
+ ### Lambdas
214
+
215
+ def add_lambda(args)
216
+ lamba = Lambda.new(id_generator.call, args)
217
+ @lambdas_by_id[lamba.id] = lamba
218
+ lamba
219
+ end
220
+
221
+ def get_lambda(lambda_id)
222
+ @lambdas_by_id[lambda_id]
223
+ end
224
+
225
+ ### Other
226
+
227
+ def get_parent_of(class_name)
228
+ return nil if class_name.eql?("Object")
229
+
230
+ const = find_constant_by_name(class_name)
231
+ return "Object" if const.nil?
232
+
233
+ klass = get_class(const.definition_id)
234
+ return "Object" if klass.nil?
235
+
236
+ return "Object" if klass.parent_ref.nil?
237
+ parent_const = solve_reference(klass.parent_ref)
238
+ if parent_const
239
+ parent_const.full_name
240
+ else
241
+ klass.parent_ref.relative_name
242
+ end
243
+ end
244
+
245
+ def find_class_by_name(full_name)
246
+ const = find_constant_by_name(full_name)
247
+ return nil if const.nil?
248
+ get_class(const.definition_id)
249
+ end
250
+
251
+ def find_module_by_name(full_name)
252
+ const = find_constant_by_name(full_name)
253
+ return nil if const.nil?
254
+ get_module(const.definition_id)
255
+ end
256
+
257
+ def find_constant_by_name(full_name)
258
+ @constants.find_by_const_name(ConstName.from_string(full_name))
259
+ end
260
+
261
+ def find_constant_for_definition(definition_id)
262
+ @constants.find do |constant|
263
+ constant.definition_id == definition_id
264
+ end
265
+ end
266
+
267
+ ### Types
268
+
269
+ def type_of(node)
270
+ @type_mapping[node]
271
+ end
272
+
273
+ def set_type_of(node, new_type)
274
+ @type_mapping[node] = new_type
275
+ end
276
+
277
+ private
278
+ attr_reader :id_generator
279
+ end
280
+ end