typeprof 0.30.1 → 0.31.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +23 -4
  3. data/lib/typeprof/cli/cli.rb +27 -7
  4. data/lib/typeprof/code_range.rb +9 -7
  5. data/lib/typeprof/core/ast/base.rb +27 -10
  6. data/lib/typeprof/core/ast/call.rb +85 -24
  7. data/lib/typeprof/core/ast/const.rb +7 -12
  8. data/lib/typeprof/core/ast/control.rb +345 -71
  9. data/lib/typeprof/core/ast/meta.rb +5 -5
  10. data/lib/typeprof/core/ast/method.rb +15 -5
  11. data/lib/typeprof/core/ast/misc.rb +21 -2
  12. data/lib/typeprof/core/ast/module.rb +10 -7
  13. data/lib/typeprof/core/ast/pattern.rb +9 -1
  14. data/lib/typeprof/core/ast/sig_decl.rb +163 -42
  15. data/lib/typeprof/core/ast/sig_type.rb +394 -24
  16. data/lib/typeprof/core/ast/value.rb +10 -2
  17. data/lib/typeprof/core/ast/variable.rb +32 -2
  18. data/lib/typeprof/core/ast.rb +15 -4
  19. data/lib/typeprof/core/builtin.rb +15 -9
  20. data/lib/typeprof/core/env/method.rb +21 -16
  21. data/lib/typeprof/core/env/method_entity.rb +11 -2
  22. data/lib/typeprof/core/env/module_entity.rb +57 -0
  23. data/lib/typeprof/core/env/narrowing.rb +131 -0
  24. data/lib/typeprof/core/env/static_read.rb +9 -8
  25. data/lib/typeprof/core/env.rb +37 -12
  26. data/lib/typeprof/core/graph/box.rb +207 -97
  27. data/lib/typeprof/core/graph/change_set.rb +44 -37
  28. data/lib/typeprof/core/graph/vertex.rb +4 -21
  29. data/lib/typeprof/core/service.rb +30 -1
  30. data/lib/typeprof/core/type.rb +48 -123
  31. data/lib/typeprof/core.rb +1 -0
  32. data/lib/typeprof/diagnostic.rb +5 -6
  33. data/lib/typeprof/lsp/messages.rb +21 -15
  34. data/lib/typeprof/lsp/server.rb +132 -39
  35. data/lib/typeprof/lsp/text.rb +1 -0
  36. data/lib/typeprof/version.rb +1 -1
  37. data/typeprof.conf.jsonc +22 -0
  38. data/typeprof.gemspec +1 -0
  39. metadata +19 -6
@@ -20,27 +20,6 @@ module TypeProf::Core
20
20
  end
21
21
  end
22
22
 
23
- def check_match(genv, changes, vtx)
24
- vtx.each_type do |ty|
25
- if ty.is_a?(Type::Var)
26
- changes.add_edge(genv, self, ty.vtx) if self != ty.vtx
27
- return true
28
- end
29
- end
30
-
31
- return true if @types.empty?
32
- return true if vtx.types.empty?
33
-
34
- each_type do |ty|
35
- return true if vtx.types.include?(ty) # fast path
36
- if ty.check_match(genv, changes, vtx)
37
- return true
38
- end
39
- end
40
-
41
- return false
42
- end
43
-
44
23
  def show
45
24
  Fiber[:show_rec] ||= Set[]
46
25
  if Fiber[:show_rec].include?(self)
@@ -84,6 +63,10 @@ module TypeProf::Core
84
63
  end
85
64
  end
86
65
  end
66
+
67
+ def new_vertex(genv, origin)
68
+ raise NotImplementedError
69
+ end
87
70
  end
88
71
 
89
72
  class Source < BasicVertex
@@ -41,6 +41,9 @@ module TypeProf::Core
41
41
  Dir.glob(File.expand_path(rb_folder + "/**/*.{rb,rbs}")) do |path|
42
42
  update_file(path, nil)
43
43
  end
44
+ Dir.glob(File.expand_path(rbs_folder + "/**/*.{rb,rbs}")) do |path|
45
+ update_file(path, nil)
46
+ end
44
47
  end
45
48
 
46
49
  def update_file(path, code)
@@ -135,8 +138,12 @@ module TypeProf::Core
135
138
  true
136
139
  end
137
140
 
141
+ def process_diagnostic_paths(&blk)
142
+ @genv.process_diagnostic_paths(&blk)
143
+ end
144
+
138
145
  def diagnostics(path, &blk)
139
- @rb_text_nodes[path]&.diagnostics(@genv, &blk)
146
+ @rb_text_nodes[path]&.each_diagnostic(@genv, &blk)
140
147
  end
141
148
 
142
149
  def definitions(path, pos)
@@ -161,6 +168,18 @@ module TypeProf::Core
161
168
  boxes.each do |box|
162
169
  box.resolve(genv, nil) do |me, _ty, mid, _orig_ty|
163
170
  next unless me
171
+ me.decls.each do |mdecl|
172
+ next unless mdecl.node.lenv.path
173
+
174
+ code_range =
175
+ if mdecl.node.respond_to?(:mname_code_range)
176
+ mdecl.node.mname_code_range(mid)
177
+ else
178
+ mdecl.node.code_range
179
+ end
180
+
181
+ defs << [mdecl.node.lenv.path, code_range]
182
+ end
164
183
  me.defs.each do |mdef|
165
184
  code_range =
166
185
  if mdef.node.respond_to?(:mname_code_range)
@@ -258,6 +277,13 @@ module TypeProf::Core
258
277
  end
259
278
  end
260
279
  end
280
+ if node.is_a?(AST::ConstantWriteNode)
281
+ if node.cname_code_range.include?(pos) && node.static_cpath
282
+ genv.resolve_const(node.static_cpath).defs.each do |cdef|
283
+ cdefs << cdef
284
+ end
285
+ end
286
+ end
261
287
  if node.is_a?(AST::DefNode) && node.mid_code_range.include?(pos)
262
288
  node.boxes(:mdef) do |mdef|
263
289
  mdefs << mdef
@@ -485,6 +511,9 @@ module TypeProf::Core
485
511
  output.puts "# failed to analyze: #{ file }"
486
512
  false
487
513
  end
514
+ rescue => e
515
+ output.puts "# error: #{ file }"
516
+ raise e
488
517
  end
489
518
  if @options[:display_indicator]
490
519
  $stderr << "\r\e[K"
@@ -15,6 +15,20 @@ module TypeProf::Core
15
15
  s.start_with?("Array[") && s.end_with?("]") ? s[6..-2] || raise : s
16
16
  end
17
17
 
18
+ def self.extract_hash_value_type(s)
19
+ if s.start_with?("Hash[") && s.end_with?("]")
20
+ type = RBS::Parser.parse_type(s)
21
+
22
+ if type.is_a?(RBS::Types::Union)
23
+ type.types.map {|t| t.args[1].to_s }.join(" | ")
24
+ else
25
+ type.args[1].to_s
26
+ end
27
+ else
28
+ s
29
+ end
30
+ end
31
+
18
32
  def self.default_param_map(genv, ty)
19
33
  ty = ty.base_type(genv)
20
34
  instance_ty = ty.is_a?(Type::Instance) ? ty : Type::Instance.new(genv, ty.mod, []) # TODO: type params
@@ -40,29 +54,6 @@ module TypeProf::Core
40
54
  self
41
55
  end
42
56
 
43
- def check_match(genv, changes, vtx)
44
- vtx.each_type do |other_ty|
45
- case other_ty
46
- when Singleton
47
- other_mod = other_ty.mod
48
- if other_mod.module?
49
- # TODO: implement
50
- else
51
- mod = @mod
52
- while mod
53
- return true if mod == other_mod
54
- changes.add_depended_superclass(mod)
55
- mod = mod.superclass
56
- end
57
- end
58
- when Instance
59
- base_ty = @mod.module? ? genv.mod_type : genv.cls_type
60
- return true if base_ty.check_match(genv, changes, Source.new(other_ty))
61
- end
62
- end
63
- return false
64
- end
65
-
66
57
  def show
67
58
  "singleton(#{ @mod.show_cpath })"
68
59
  end
@@ -88,60 +79,6 @@ module TypeProf::Core
88
79
  self
89
80
  end
90
81
 
91
- def check_match(genv, changes, vtx)
92
- vtx.each_type do |other_ty|
93
- case other_ty
94
- when Instance
95
- ty = self
96
- while ty
97
- if ty.mod == other_ty.mod
98
- args_all_match = true
99
- ty.args.zip(other_ty.args) do |arg, other_arg|
100
- unless arg.check_match(genv, changes, other_arg)
101
- args_all_match = false
102
- break
103
- end
104
- end
105
- return true if args_all_match
106
- end
107
- changes.add_depended_superclass(ty.mod)
108
-
109
- if other_ty.mod.module?
110
- return true if check_match_included_modules(genv, changes, ty, other_ty)
111
- end
112
-
113
- ty = genv.get_superclass_type(ty, changes, {})
114
- end
115
- end
116
- end
117
- return false
118
- end
119
-
120
- def check_match_included_modules(genv, changes, ty, other_ty)
121
- ty.mod.included_modules.each do |inc_decl, inc_mod|
122
- if inc_decl.is_a?(AST::SigIncludeNode) && inc_mod.type_params
123
- inc_ty = genv.get_instance_type(inc_mod, inc_decl.args, changes, {}, ty)
124
- else
125
- type_params = inc_mod.type_params.map {|ty_param| Source.new() } # TODO: better support
126
- inc_ty = Type::Instance.new(genv, inc_mod, type_params)
127
- end
128
- if inc_ty.mod == other_ty.mod
129
- args_all_match = true
130
- inc_ty.args.zip(other_ty.args) do |arg, other_arg|
131
- if other_arg && !arg.check_match(genv, changes, other_arg)
132
- args_all_match = false
133
- break
134
- end
135
- end
136
- return true if args_all_match
137
- end
138
- changes.add_depended_superclass(inc_ty.mod)
139
-
140
- return true if check_match_included_modules(genv, changes, inc_ty, other_ty)
141
- end
142
- return false
143
- end
144
-
145
82
  def show
146
83
  case @mod.cpath
147
84
  when [:NilClass] then "nil"
@@ -204,24 +141,6 @@ module TypeProf::Core
204
141
  @base_type
205
142
  end
206
143
 
207
- def check_match(genv, changes, vtx)
208
- vtx.each_type do |other_ty|
209
- if other_ty.is_a?(Array)
210
- if @elems.size == other_ty.elems.size
211
- match = true
212
- @elems.zip(other_ty.elems) do |elem, other_elem|
213
- unless elem.check_match(genv, changes, other_elem)
214
- match = false
215
- break
216
- end
217
- end
218
- return true if match
219
- end
220
- end
221
- end
222
- @base_type.check_match(genv, changes, vtx)
223
- end
224
-
225
144
  def show
226
145
  if @elems
227
146
  "[#{ @elems.map {|e| Type.strip_parens(e.show) }.join(", ") }]"
@@ -251,11 +170,6 @@ module TypeProf::Core
251
170
  @base_type
252
171
  end
253
172
 
254
- def check_match(genv, changes, vtx)
255
- # TODO: implement
256
- @base_type.check_match(genv, changes, vtx)
257
- end
258
-
259
173
  def show
260
174
  @base_type.show
261
175
  end
@@ -272,10 +186,6 @@ module TypeProf::Core
272
186
  genv.proc_type
273
187
  end
274
188
 
275
- def check_match(genv, changes, vtx)
276
- genv.proc_type.check_match(genv, changes, vtx)
277
- end
278
-
279
189
  def show
280
190
  "<Proc>"
281
191
  end
@@ -293,18 +203,6 @@ module TypeProf::Core
293
203
  genv.symbol_type
294
204
  end
295
205
 
296
- def check_match(genv, changes, vtx)
297
- vtx.each_type do |other_ty|
298
- case other_ty
299
- when Symbol
300
- return true if @sym == other_ty.sym
301
- when Instance
302
- return true if genv.symbol_type.check_match(genv, changes, Source.new(other_ty))
303
- end
304
- end
305
- return false
306
- end
307
-
308
206
  def show
309
207
  @sym.inspect
310
208
  end
@@ -318,10 +216,6 @@ module TypeProf::Core
318
216
  genv.obj_type
319
217
  end
320
218
 
321
- def check_match(genv, changes, vtx)
322
- return true
323
- end
324
-
325
219
  def show
326
220
  "bot"
327
221
  end
@@ -340,12 +234,43 @@ module TypeProf::Core
340
234
  genv.obj_type # Is this ok?
341
235
  end
342
236
 
343
- def check_match(genv, changes, vtx)
344
- true # should implement a better support...
237
+ def show
238
+ "var[#{ @name }]"
239
+ end
240
+ end
241
+
242
+ class Record < Type
243
+ #: (GlobalEnv, ::Hash[Symbol, Vertex], Instance) -> void
244
+ def initialize(genv, fields, base_type)
245
+ @fields = fields
246
+ @base_type = base_type
247
+ raise unless base_type.is_a?(Instance)
248
+ end
249
+
250
+ attr_reader :fields
251
+
252
+ def get_value(key = nil)
253
+ if key
254
+ # Return specific field value if it exists
255
+ @fields[key]
256
+ elsif @fields.empty?
257
+ # Empty record has no values
258
+ nil
259
+ else
260
+ # Return union of all field values if no specific key
261
+ @base_type.args[1]
262
+ end
263
+ end
264
+
265
+ def base_type(genv)
266
+ @base_type
345
267
  end
346
268
 
347
269
  def show
348
- "var[#{ @name }]"
270
+ field_strs = @fields.map do |key, val_vtx|
271
+ "#{ key }: #{ Type.strip_parens(val_vtx.show) }"
272
+ end
273
+ "{ #{ field_strs.join(", ") } }"
349
274
  end
350
275
  end
351
276
  end
data/lib/typeprof/core.rb CHANGED
@@ -23,6 +23,7 @@ require_relative "core/env/type_alias_entity"
23
23
  require_relative "core/env/value_entity"
24
24
  require_relative "core/env/method"
25
25
  require_relative "core/env/static_read"
26
+ require_relative "core/env/narrowing"
26
27
  require_relative "core/graph/change_set"
27
28
  require_relative "core/graph/vertex"
28
29
  require_relative "core/graph/filter"
@@ -1,18 +1,17 @@
1
1
  module TypeProf
2
2
  class Diagnostic
3
- def initialize(node, meth, msg)
3
+ def initialize(node, meth, msg, tags: nil)
4
4
  @node = node
5
5
  @meth = meth
6
6
  @msg = msg
7
- @severity = :error # TODO: keyword argument
8
- @tags = nil # TODO: keyword argument
7
+ @tags = tags
9
8
  end
10
9
 
11
10
  def reuse(new_node)
12
11
  @node = new_node
13
12
  end
14
13
 
15
- attr_reader :msg, :severity
14
+ attr_reader :node, :msg, :tags
16
15
 
17
16
  def code_range
18
17
  @node.send(@meth)
@@ -21,13 +20,13 @@ module TypeProf
21
20
  SEVERITY = { error: 1, warning: 2, info: 3, hint: 4 }
22
21
  TAG = { unnecessary: 1, deprecated: 2 }
23
22
 
24
- def to_lsp
23
+ def to_lsp(severity: :error)
25
24
  json = {
26
25
  range: code_range.to_lsp,
27
26
  source: "TypeProf",
28
27
  message: @msg,
29
28
  }
30
- json[:severity] = SEVERITY[@severity] if @severity
29
+ json[:severity] = SEVERITY[severity]
31
30
  json[:tags] = @tags.map {|tag| TAG[tag] } if @tags
32
31
  json
33
32
  end
@@ -1,5 +1,6 @@
1
1
  module TypeProf::LSP
2
2
  class Message
3
+ #: (Server, untyped) -> void
3
4
  def initialize(server, json)
4
5
  @server = server
5
6
  @id = json[:id]
@@ -90,7 +91,6 @@ module TypeProf::LSP
90
91
  "typeprof.disableSignature",
91
92
  ],
92
93
  },
93
- #typeDefinitionProvider: true,
94
94
  referencesProvider: true,
95
95
  },
96
96
  serverInfo: {
@@ -136,9 +136,10 @@ module TypeProf::LSP
136
136
 
137
137
  text = Text.new(path, text, version)
138
138
  @server.open_texts[uri] = text
139
- @server.core.update_file(text.path, text.string)
139
+ @server.update_file(text.path, text.string)
140
+ notify("typeprof.enableToggleButton")
140
141
  @server.send_request("workspace/codeLens/refresh")
141
- @server.publish_diagnostics(uri)
142
+ @server.publish_updated_diagnostics
142
143
  end
143
144
  end
144
145
 
@@ -146,12 +147,14 @@ module TypeProf::LSP
146
147
  METHOD = "textDocument/didChange" # notification
147
148
  def run
148
149
  @params => { textDocument: { uri:, version: }, contentChanges: changes }
150
+
149
151
  text = @server.open_texts[uri]
150
152
  return unless text
153
+
151
154
  text.apply_changes(changes, version)
152
- @server.core.update_file(text.path, text.string)
155
+ @server.update_file(text.path, text.string)
153
156
  @server.send_request("workspace/codeLens/refresh")
154
- @server.publish_diagnostics(uri)
157
+ @server.publish_updated_diagnostics
155
158
  end
156
159
  end
157
160
 
@@ -163,9 +166,11 @@ module TypeProf::LSP
163
166
  METHOD = "textDocument/didClose" # notification
164
167
  def run
165
168
  @params => { textDocument: { uri: } }
169
+
166
170
  text = @server.open_texts.delete(uri)
167
171
  return unless text
168
- @server.core.update_file(text.path, nil)
172
+
173
+ @server.update_file(text.path, nil)
169
174
  end
170
175
  end
171
176
 
@@ -178,12 +183,13 @@ module TypeProf::LSP
178
183
  textDocument: { uri: },
179
184
  position: pos,
180
185
  }
186
+
181
187
  text = @server.open_texts[uri]
182
188
  unless text
183
189
  respond(nil)
184
190
  return
185
191
  end
186
- defs = @server.core.definitions(text.path, TypeProf::CodePosition.from_lsp(pos))
192
+ defs = @server.definitions(text.path, TypeProf::CodePosition.from_lsp(pos))
187
193
  if defs.empty?
188
194
  respond(nil)
189
195
  else
@@ -209,7 +215,7 @@ module TypeProf::LSP
209
215
  respond(nil)
210
216
  return
211
217
  end
212
- defs = @server.core.type_definitions(text.path, TypeProf::CodePosition.from_lsp(pos))
218
+ defs = @server.type_definitions(text.path, TypeProf::CodePosition.from_lsp(pos))
213
219
  if defs.empty?
214
220
  respond(nil)
215
221
  else
@@ -235,7 +241,7 @@ module TypeProf::LSP
235
241
  respond(nil)
236
242
  return
237
243
  end
238
- callsites = @server.core.references(text.path, TypeProf::CodePosition.from_lsp(pos))
244
+ callsites = @server.references(text.path, TypeProf::CodePosition.from_lsp(pos))
239
245
  if callsites
240
246
  respond(callsites.map do |path, code_range|
241
247
  {
@@ -261,7 +267,7 @@ module TypeProf::LSP
261
267
  respond(nil)
262
268
  return
263
269
  end
264
- str = @server.core.hover(text.path, TypeProf::CodePosition.from_lsp(pos))
270
+ str = @server.hover(text.path, TypeProf::CodePosition.from_lsp(pos))
265
271
  if str
266
272
  respond(contents: { language: "ruby", value: str })
267
273
  else
@@ -280,7 +286,7 @@ module TypeProf::LSP
280
286
  return
281
287
  end
282
288
  ret = []
283
- @server.core.code_lens(text.path) do |code_range, title|
289
+ @server.code_lens(text.path) do |code_range, title|
284
290
  pos = code_range.first
285
291
  ret << {
286
292
  range: TypeProf::CodeRange.new(pos, pos.right).to_lsp,
@@ -317,9 +323,9 @@ module TypeProf::LSP
317
323
  items = []
318
324
  sort = "aaaa"
319
325
  text.modify_for_completion(text, pos) do |string, trigger, pos|
320
- @server.core.update_file(text.path, string)
326
+ @server.update_file(text.path, string)
321
327
  pos = TypeProf::CodePosition.from_lsp(pos)
322
- @server.core.completion(text.path, trigger, pos) do |mid, hint|
328
+ @server.completion(text.path, trigger, pos) do |mid, hint|
323
329
  items << {
324
330
  label: mid,
325
331
  kind: 2, # Method
@@ -333,7 +339,7 @@ module TypeProf::LSP
333
339
  isIncomplete: false,
334
340
  items: items,
335
341
  )
336
- @server.core.update_file(text.path, text.string)
342
+ @server.update_file(text.path, text.string)
337
343
  end
338
344
  end
339
345
 
@@ -354,7 +360,7 @@ module TypeProf::LSP
354
360
  respond(nil)
355
361
  return
356
362
  end
357
- renames = @server.core.rename(text.path, TypeProf::CodePosition.from_lsp(pos))
363
+ renames = @server.rename(text.path, TypeProf::CodePosition.from_lsp(pos))
358
364
  if renames
359
365
  changes = {}
360
366
  renames.each do |path, cr|