typeprof 0.31.1 → 0.32.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.
- checksums.yaml +4 -4
- data/README.md +2 -1
- data/doc/report_guide.md +88 -0
- data/lib/typeprof/cli/cli.rb +9 -3
- data/lib/typeprof/code_range.rb +7 -5
- data/lib/typeprof/core/ast/base.rb +18 -6
- data/lib/typeprof/core/ast/call.rb +96 -32
- data/lib/typeprof/core/ast/const.rb +12 -9
- data/lib/typeprof/core/ast/control.rb +60 -30
- data/lib/typeprof/core/ast/meta.rb +194 -2
- data/lib/typeprof/core/ast/method.rb +74 -20
- data/lib/typeprof/core/ast/misc.rb +27 -7
- data/lib/typeprof/core/ast/module.rb +33 -3
- data/lib/typeprof/core/ast/sig_decl.rb +85 -24
- data/lib/typeprof/core/ast/sig_type.rb +77 -31
- data/lib/typeprof/core/ast/value.rb +14 -6
- data/lib/typeprof/core/ast/variable.rb +11 -4
- data/lib/typeprof/core/ast.rb +95 -14
- data/lib/typeprof/core/builtin.rb +184 -12
- data/lib/typeprof/core/env/method.rb +171 -6
- data/lib/typeprof/core/env/method_entity.rb +18 -15
- data/lib/typeprof/core/env/module_entity.rb +56 -18
- data/lib/typeprof/core/env/static_read.rb +4 -4
- data/lib/typeprof/core/env/type_alias_entity.rb +1 -1
- data/lib/typeprof/core/env/value_entity.rb +25 -3
- data/lib/typeprof/core/env.rb +79 -17
- data/lib/typeprof/core/graph/box.rb +379 -52
- data/lib/typeprof/core/graph/change_set.rb +59 -46
- data/lib/typeprof/core/graph/filter.rb +8 -5
- data/lib/typeprof/core/graph/vertex.rb +20 -19
- data/lib/typeprof/core/service.rb +317 -23
- data/lib/typeprof/core/type.rb +41 -7
- data/lib/typeprof/core/util.rb +6 -0
- data/lib/typeprof/lsp/messages.rb +5 -0
- data/lib/typeprof/lsp/server.rb +35 -4
- data/lib/typeprof/version.rb +1 -1
- metadata +3 -2
|
@@ -87,7 +87,7 @@ module TypeProf::Core
|
|
|
87
87
|
mod = genv.resolve_cpath(@node.cpath)
|
|
88
88
|
if mod.type_params && !mod.type_params.empty?
|
|
89
89
|
# Create a substitution map where each type parameter maps to a type variable vertex
|
|
90
|
-
subst = mod.type_params.to_h do |param|
|
|
90
|
+
subst = mod.type_params.to_h do |param, _default_ty|
|
|
91
91
|
type_var_vtx = Vertex.new(@node)
|
|
92
92
|
[param, type_var_vtx]
|
|
93
93
|
end
|
|
@@ -99,6 +99,141 @@ module TypeProf::Core
|
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
+
class OverloadSet
|
|
103
|
+
include Enumerable
|
|
104
|
+
|
|
105
|
+
def initialize(method_types)
|
|
106
|
+
@method_types = method_types
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def each(&blk) = @method_types.each(&blk)
|
|
110
|
+
def map(&blk) = @method_types.map(&blk)
|
|
111
|
+
def first = @method_types.first
|
|
112
|
+
def size = @method_types.size
|
|
113
|
+
def to_a = @method_types
|
|
114
|
+
|
|
115
|
+
# lazy cache: combination(2) for all-pair comparison
|
|
116
|
+
def overloads_differ_in_args?
|
|
117
|
+
return @overloads_differ_in_args if defined?(@overloads_differ_in_args)
|
|
118
|
+
@overloads_differ_in_args = !@method_types.combination(2).all? { |a, b|
|
|
119
|
+
positionals_match?(a, b) && keywords_match?(a, b)
|
|
120
|
+
}
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def overloads_differ_at_top_level?
|
|
124
|
+
return @overloads_differ_at_top_level if defined?(@overloads_differ_at_top_level)
|
|
125
|
+
@overloads_differ_at_top_level = !@method_types.combination(2).all? { |a, b|
|
|
126
|
+
positionals_match_shallow?(a, b) && keywords_match_shallow?(a, b)
|
|
127
|
+
}
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
# Check if two method types have structurally identical positional
|
|
133
|
+
# parameter types (req, opt, rest).
|
|
134
|
+
def positionals_match?(mt1, mt2)
|
|
135
|
+
return false unless mt1.req_positionals.size == mt2.req_positionals.size
|
|
136
|
+
return false unless mt1.opt_positionals.size == mt2.opt_positionals.size
|
|
137
|
+
return false unless mt1.rest_positionals.nil? == mt2.rest_positionals.nil?
|
|
138
|
+
mt1.req_positionals.zip(mt2.req_positionals).all? {|a, b| sig_types_match?(a, b) } &&
|
|
139
|
+
mt1.opt_positionals.zip(mt2.opt_positionals).all? {|a, b| sig_types_match?(a, b) } &&
|
|
140
|
+
(mt1.rest_positionals.nil? || sig_types_match?(mt1.rest_positionals, mt2.rest_positionals))
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Check if two method types have identical positional parameter
|
|
144
|
+
# types at the top level (ignoring type parameter contents).
|
|
145
|
+
def positionals_match_shallow?(mt1, mt2)
|
|
146
|
+
return false unless mt1.req_positionals.size == mt2.req_positionals.size
|
|
147
|
+
return false unless mt1.opt_positionals.size == mt2.opt_positionals.size
|
|
148
|
+
return false unless mt1.rest_positionals.nil? == mt2.rest_positionals.nil?
|
|
149
|
+
mt1.req_positionals.zip(mt2.req_positionals).all? {|a, b| sig_types_match_shallow?(a, b) } &&
|
|
150
|
+
mt1.opt_positionals.zip(mt2.opt_positionals).all? {|a, b| sig_types_match_shallow?(a, b) } &&
|
|
151
|
+
(mt1.rest_positionals.nil? || sig_types_match_shallow?(mt1.rest_positionals, mt2.rest_positionals))
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Check if two method types have structurally identical keyword
|
|
155
|
+
# parameter types (req, opt, rest).
|
|
156
|
+
def keywords_match?(mt1, mt2)
|
|
157
|
+
return false unless mt1.req_keyword_keys == mt2.req_keyword_keys
|
|
158
|
+
return false unless mt1.opt_keyword_keys == mt2.opt_keyword_keys
|
|
159
|
+
return false unless mt1.rest_keywords.nil? == mt2.rest_keywords.nil?
|
|
160
|
+
mt1.req_keyword_values.zip(mt2.req_keyword_values).all? {|a, b| sig_types_match?(a, b) } &&
|
|
161
|
+
mt1.opt_keyword_values.zip(mt2.opt_keyword_values).all? {|a, b| sig_types_match?(a, b) } &&
|
|
162
|
+
(mt1.rest_keywords.nil? || sig_types_match?(mt1.rest_keywords, mt2.rest_keywords))
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Shallow version: compare keyword keys and structure, but use
|
|
166
|
+
# shallow type comparison for values.
|
|
167
|
+
def keywords_match_shallow?(mt1, mt2)
|
|
168
|
+
return false unless mt1.req_keyword_keys == mt2.req_keyword_keys
|
|
169
|
+
return false unless mt1.opt_keyword_keys == mt2.opt_keyword_keys
|
|
170
|
+
return false unless mt1.rest_keywords.nil? == mt2.rest_keywords.nil?
|
|
171
|
+
mt1.req_keyword_values.zip(mt2.req_keyword_values).all? {|a, b| sig_types_match_shallow?(a, b) } &&
|
|
172
|
+
mt1.opt_keyword_values.zip(mt2.opt_keyword_values).all? {|a, b| sig_types_match_shallow?(a, b) } &&
|
|
173
|
+
(mt1.rest_keywords.nil? || sig_types_match_shallow?(mt1.rest_keywords, mt2.rest_keywords))
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Structural equality check for two SigTyNode objects.
|
|
177
|
+
def sig_types_match?(a, b)
|
|
178
|
+
return false unless a.class == b.class
|
|
179
|
+
case a
|
|
180
|
+
when AST::SigTyInstanceNode, AST::SigTyInterfaceNode
|
|
181
|
+
a.cpath == b.cpath &&
|
|
182
|
+
a.args.size == b.args.size &&
|
|
183
|
+
a.args.zip(b.args).all? {|x, y| sig_types_match?(x, y) }
|
|
184
|
+
when AST::SigTySingletonNode
|
|
185
|
+
a.cpath == b.cpath
|
|
186
|
+
when AST::SigTyTupleNode, AST::SigTyUnionNode, AST::SigTyIntersectionNode
|
|
187
|
+
a.types.size == b.types.size &&
|
|
188
|
+
a.types.zip(b.types).all? {|x, y| sig_types_match?(x, y) }
|
|
189
|
+
when AST::SigTyRecordNode
|
|
190
|
+
a.fields.size == b.fields.size &&
|
|
191
|
+
a.fields.all? {|k, v| b.fields[k] && sig_types_match?(v, b.fields[k]) }
|
|
192
|
+
when AST::SigTyOptionalNode, AST::SigTyProcNode
|
|
193
|
+
sig_types_match?(a.type, b.type)
|
|
194
|
+
when AST::SigTyVarNode
|
|
195
|
+
a.var == b.var
|
|
196
|
+
when AST::SigTyLiteralNode
|
|
197
|
+
a.lit == b.lit
|
|
198
|
+
when AST::SigTyAliasNode
|
|
199
|
+
a.cpath == b.cpath && a.name == b.name &&
|
|
200
|
+
a.args.size == b.args.size &&
|
|
201
|
+
a.args.zip(b.args).all? {|x, y| sig_types_match?(x, y) }
|
|
202
|
+
else
|
|
203
|
+
true # Leaf types (bool, nil, self, void, untyped, etc.)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Shallow structural equality: compare only the top-level type
|
|
208
|
+
# identity without recursing into type parameters.
|
|
209
|
+
def sig_types_match_shallow?(a, b)
|
|
210
|
+
return false unless a.class == b.class
|
|
211
|
+
case a
|
|
212
|
+
when AST::SigTyInstanceNode, AST::SigTyInterfaceNode
|
|
213
|
+
a.cpath == b.cpath
|
|
214
|
+
when AST::SigTySingletonNode
|
|
215
|
+
a.cpath == b.cpath
|
|
216
|
+
when AST::SigTyTupleNode
|
|
217
|
+
a.types.size == b.types.size
|
|
218
|
+
when AST::SigTyUnionNode, AST::SigTyIntersectionNode
|
|
219
|
+
a.types.size == b.types.size &&
|
|
220
|
+
a.types.zip(b.types).all? {|x, y| sig_types_match_shallow?(x, y) }
|
|
221
|
+
when AST::SigTyRecordNode
|
|
222
|
+
a.fields.keys.sort == b.fields.keys.sort
|
|
223
|
+
when AST::SigTyOptionalNode, AST::SigTyProcNode
|
|
224
|
+
true
|
|
225
|
+
when AST::SigTyVarNode
|
|
226
|
+
a.var == b.var
|
|
227
|
+
when AST::SigTyLiteralNode
|
|
228
|
+
a.lit == b.lit
|
|
229
|
+
when AST::SigTyAliasNode
|
|
230
|
+
a.cpath == b.cpath && a.name == b.name
|
|
231
|
+
else
|
|
232
|
+
true
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
102
237
|
class MethodDeclBox < Box
|
|
103
238
|
def initialize(node, genv, cpath, singleton, mid, method_types, overloading)
|
|
104
239
|
super(node)
|
|
@@ -179,14 +314,30 @@ module TypeProf::Core
|
|
|
179
314
|
end
|
|
180
315
|
end
|
|
181
316
|
|
|
317
|
+
# Check keyword arguments by inspecting the keywords vertex types
|
|
318
|
+
# directly. We avoid get_keyword_arg here because it creates a fresh
|
|
319
|
+
# Vertex each call, which would destabilize the change-set edges and
|
|
320
|
+
# cause oscillation when match_arguments? runs on every box re-eval.
|
|
321
|
+
if a_args.keywords
|
|
322
|
+
method_type.req_keyword_keys.zip(method_type.req_keyword_values) do |key, ty|
|
|
323
|
+
return false unless keyword_arg_typecheck?(genv, changes, a_args.keywords, key, ty, param_map)
|
|
324
|
+
end
|
|
325
|
+
method_type.opt_keyword_keys.zip(method_type.opt_keyword_values) do |key, ty|
|
|
326
|
+
return false unless keyword_arg_typecheck?(genv, changes, a_args.keywords, key, ty, param_map)
|
|
327
|
+
end
|
|
328
|
+
if method_type.rest_keywords
|
|
329
|
+
return false unless rest_keyword_args_typecheck?(genv, changes, a_args.keywords, method_type, param_map)
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
182
333
|
return true
|
|
183
334
|
end
|
|
184
335
|
|
|
185
336
|
def resolve_overload(changes, genv, method_type, node, param_map, a_args, ret, force)
|
|
186
337
|
param_map0 = param_map.dup
|
|
187
338
|
if method_type.type_params
|
|
188
|
-
method_type.type_params.zip(yield(method_type)) do |var, vtx|
|
|
189
|
-
param_map0[var] = vtx
|
|
339
|
+
method_type.type_params.zip(yield(method_type)) do |(var, _default_ty), vtx|
|
|
340
|
+
param_map0[var] = vtx # TODO: default_ty?
|
|
190
341
|
end
|
|
191
342
|
end
|
|
192
343
|
|
|
@@ -213,6 +364,7 @@ module TypeProf::Core
|
|
|
213
364
|
end
|
|
214
365
|
return false
|
|
215
366
|
end
|
|
367
|
+
|
|
216
368
|
if rbs_blk && a_args.block
|
|
217
369
|
# rbs_blk_func.optional_keywords, ...
|
|
218
370
|
blk_a_args = rbs_blk.req_positionals.map do |blk_a_arg|
|
|
@@ -251,6 +403,54 @@ module TypeProf::Core
|
|
|
251
403
|
return
|
|
252
404
|
end
|
|
253
405
|
|
|
406
|
+
# If any positional argument has no type information, we cannot
|
|
407
|
+
# determine which overload to select. Return silently (untyped)
|
|
408
|
+
# rather than attempting to match. This prevents oscillation in
|
|
409
|
+
# cyclic cases and avoids false "failed to resolve overloads"
|
|
410
|
+
# diagnostics for untyped arguments.
|
|
411
|
+
#
|
|
412
|
+
# We check at two levels:
|
|
413
|
+
# 1. Top-level empty vertices are always uninformative.
|
|
414
|
+
# 2. Empty type parameter vertices (e.g., Array[T] where T is
|
|
415
|
+
# empty) are only uninformative when overloads differ solely
|
|
416
|
+
# in their type parameters (e.g., Array[Integer] vs
|
|
417
|
+
# Array[String]). When overloads differ at the top level
|
|
418
|
+
# (e.g., Integer vs Float), the type parameter contents are
|
|
419
|
+
# irrelevant for overload selection and should not trigger
|
|
420
|
+
# bail-out.
|
|
421
|
+
has_uninformative_args = if @method_types.overloads_differ_in_args?
|
|
422
|
+
# Check whether overloads also differ at the top level (e.g.,
|
|
423
|
+
# Integer vs Float) or only in their type parameters (e.g.,
|
|
424
|
+
# Array[Integer] vs Array[String]).
|
|
425
|
+
if @method_types.overloads_differ_at_top_level?
|
|
426
|
+
# Overloads are distinguished by top-level types.
|
|
427
|
+
# Only top-level empty vertices matter; empty type parameters
|
|
428
|
+
# are irrelevant for overload selection.
|
|
429
|
+
# However, splatted arguments have their elements extracted
|
|
430
|
+
# during matching, so also check splat element vertices.
|
|
431
|
+
a_args.positionals.any? {|vtx| vtx.types.empty? } ||
|
|
432
|
+
splat_elements_uninformative?(genv, a_args) ||
|
|
433
|
+
(a_args.keywords && a_args.keywords.types.empty?)
|
|
434
|
+
else
|
|
435
|
+
# Overloads differ only in type parameters (e.g.,
|
|
436
|
+
# Array[Integer] vs Array[String]). Empty type parameter
|
|
437
|
+
# vertices can cause oscillation, so check recursively.
|
|
438
|
+
a_args.positionals.any? {|vtx| vertex_uninformative?(genv, vtx) } ||
|
|
439
|
+
(a_args.keywords && vertex_uninformative?(genv, a_args.keywords))
|
|
440
|
+
end
|
|
441
|
+
else
|
|
442
|
+
a_args.positionals.any? {|vtx| vtx.types.empty? } ||
|
|
443
|
+
(a_args.keywords && a_args.keywords.types.empty?)
|
|
444
|
+
end
|
|
445
|
+
if has_uninformative_args
|
|
446
|
+
a_args.positionals.each do |vtx|
|
|
447
|
+
changes.add_edge(genv, vtx, changes.target)
|
|
448
|
+
end
|
|
449
|
+
# Note: keywords already have a permanent edge to the box
|
|
450
|
+
# (established in MethodCallBox#initialize), so no extra edge needed.
|
|
451
|
+
return
|
|
452
|
+
end
|
|
453
|
+
|
|
254
454
|
match_any_overload = false
|
|
255
455
|
@method_types.each do |method_type|
|
|
256
456
|
if resolve_overload(changes, genv, method_type, node, param_map, a_args, ret, false, &blk)
|
|
@@ -263,6 +463,76 @@ module TypeProf::Core
|
|
|
263
463
|
end
|
|
264
464
|
end
|
|
265
465
|
|
|
466
|
+
# Check if any splatted argument has an Array element vertex
|
|
467
|
+
# that is empty. Splat expansion extracts elements during
|
|
468
|
+
# overload matching, so empty element types can cause oscillation
|
|
469
|
+
# even when the top-level Array type is present.
|
|
470
|
+
def splat_elements_uninformative?(genv, a_args)
|
|
471
|
+
a_args.positionals.each_with_index do |vtx, i|
|
|
472
|
+
next unless a_args.splat_flags[i]
|
|
473
|
+
vtx.each_type do |ty|
|
|
474
|
+
base = ty.base_type(genv)
|
|
475
|
+
if base.is_a?(Type::Instance) && base.mod == genv.mod_ary && base.args[0]
|
|
476
|
+
return true if base.args[0].types.empty?
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
false
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
def vertex_uninformative?(genv, vtx, depth = 0)
|
|
484
|
+
return true if vtx.types.empty?
|
|
485
|
+
return false if depth > 3
|
|
486
|
+
vtx.each_type do |ty|
|
|
487
|
+
base = ty.base_type(genv)
|
|
488
|
+
next unless base.is_a?(Type::Instance) && !base.args.empty?
|
|
489
|
+
base.args.each do |arg_vtx|
|
|
490
|
+
return true if arg_vtx && vertex_uninformative?(genv, arg_vtx, depth + 1)
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
false
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
# Typecheck a single keyword argument value against the expected type
|
|
497
|
+
# by directly inspecting the pre-existing value vertices in the
|
|
498
|
+
# keywords vertex's types (Record, Hash, Instance).
|
|
499
|
+
def keyword_arg_typecheck?(genv, changes, keywords_vtx, key, expected_ty, param_map)
|
|
500
|
+
keywords_vtx.each_type do |kw_ty|
|
|
501
|
+
val_vtx = case kw_ty
|
|
502
|
+
when Type::Hash then kw_ty.get_value(key)
|
|
503
|
+
when Type::Record then kw_ty.get_value(key)
|
|
504
|
+
when Type::Instance then kw_ty.mod == genv.mod_hash ? kw_ty.args[1] : nil
|
|
505
|
+
else nil
|
|
506
|
+
end
|
|
507
|
+
return false if val_vtx && !expected_ty.typecheck(genv, changes, val_vtx, param_map)
|
|
508
|
+
end
|
|
509
|
+
true
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
# Typecheck rest keyword argument values (those not consumed by named
|
|
513
|
+
# keywords) against the method type's rest_keywords type.
|
|
514
|
+
def rest_keyword_args_typecheck?(genv, changes, keywords_vtx, method_type, param_map)
|
|
515
|
+
named_keys = method_type.req_keyword_keys + method_type.opt_keyword_keys
|
|
516
|
+
rest_ty = method_type.rest_keywords
|
|
517
|
+
keywords_vtx.each_type do |kw_ty|
|
|
518
|
+
case kw_ty
|
|
519
|
+
when Type::Record
|
|
520
|
+
kw_ty.fields.each do |key, val_vtx|
|
|
521
|
+
next if named_keys.include?(key)
|
|
522
|
+
return false unless rest_ty.typecheck(genv, changes, val_vtx, param_map)
|
|
523
|
+
end
|
|
524
|
+
when Type::Hash
|
|
525
|
+
val_vtx = kw_ty.base_type(genv).args[1]
|
|
526
|
+
return false if val_vtx && !rest_ty.typecheck(genv, changes, val_vtx, param_map)
|
|
527
|
+
when Type::Instance
|
|
528
|
+
if kw_ty.mod == genv.mod_hash && kw_ty.args[1]
|
|
529
|
+
return false unless rest_ty.typecheck(genv, changes, kw_ty.args[1], param_map)
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
true
|
|
534
|
+
end
|
|
535
|
+
|
|
266
536
|
def show
|
|
267
537
|
@method_types.map do |method_type|
|
|
268
538
|
args = []
|
|
@@ -279,10 +549,10 @@ module TypeProf::Core
|
|
|
279
549
|
args << arg.show
|
|
280
550
|
end
|
|
281
551
|
|
|
282
|
-
method_type.
|
|
552
|
+
method_type.req_keyword_keys.zip(method_type.req_keyword_values) do |key, arg|
|
|
283
553
|
args << "#{ key }: #{arg.show}"
|
|
284
554
|
end
|
|
285
|
-
method_type.
|
|
555
|
+
method_type.opt_keyword_keys.zip(method_type.opt_keyword_values) do |key, arg|
|
|
286
556
|
args << "?#{ key }: #{arg.show}"
|
|
287
557
|
end
|
|
288
558
|
if method_type.rest_keywords
|
|
@@ -311,7 +581,6 @@ module TypeProf::Core
|
|
|
311
581
|
|
|
312
582
|
def wrong_return_type(f_ret_show, changes)
|
|
313
583
|
actual_ty = @a_ret.show
|
|
314
|
-
return if actual_ty == "untyped" # XXX: too ad-hoc?
|
|
315
584
|
msg = "expected: #{ f_ret_show }; actual: #{ actual_ty }"
|
|
316
585
|
case @node
|
|
317
586
|
when AST::ReturnNode
|
|
@@ -331,11 +600,13 @@ module TypeProf::Core
|
|
|
331
600
|
end
|
|
332
601
|
|
|
333
602
|
class SplatBox < Box
|
|
334
|
-
def initialize(node, genv, ary, idx)
|
|
603
|
+
def initialize(node, genv, ary, idx, orig = nil)
|
|
335
604
|
super(node)
|
|
336
605
|
@ary = ary
|
|
337
606
|
@idx = idx
|
|
607
|
+
@orig = orig
|
|
338
608
|
@ary.add_edge(genv, self)
|
|
609
|
+
@orig.add_edge(genv, self) if @orig
|
|
339
610
|
@ret = Vertex.new(node)
|
|
340
611
|
end
|
|
341
612
|
|
|
@@ -360,6 +631,12 @@ module TypeProf::Core
|
|
|
360
631
|
"???"
|
|
361
632
|
end
|
|
362
633
|
end
|
|
634
|
+
# For types where to_a is not defined, [*x] wraps x as [x]
|
|
635
|
+
if @orig && @ary.types.empty?
|
|
636
|
+
@orig.each_type do |ty|
|
|
637
|
+
changes.add_edge(genv, Source.new(ty), @ret)
|
|
638
|
+
end
|
|
639
|
+
end
|
|
363
640
|
end
|
|
364
641
|
end
|
|
365
642
|
|
|
@@ -418,7 +695,7 @@ module TypeProf::Core
|
|
|
418
695
|
|
|
419
696
|
attr_accessor :node
|
|
420
697
|
|
|
421
|
-
attr_reader :cpath, :singleton, :mid, :f_args, :ret
|
|
698
|
+
attr_reader :cpath, :singleton, :mid, :f_args, :ret, :record_block
|
|
422
699
|
|
|
423
700
|
def destroy(genv)
|
|
424
701
|
me = genv.resolve_method(@cpath, @singleton, @mid)
|
|
@@ -443,17 +720,17 @@ module TypeProf::Core
|
|
|
443
720
|
ty = Type::Singleton.new(genv, mod)
|
|
444
721
|
param_map0 = Type.default_param_map(genv, ty)
|
|
445
722
|
else
|
|
446
|
-
type_params = mod.type_params.map {|
|
|
723
|
+
type_params = mod.type_params.map {|(_name, _default_ty)| Source.new() } # TODO: better support
|
|
447
724
|
ty = Type::Instance.new(genv, mod, type_params)
|
|
448
725
|
param_map0 = Type.default_param_map(genv, ty)
|
|
449
726
|
if ty.is_a?(Type::Instance)
|
|
450
|
-
ty.mod.type_params.zip(ty.args) do |
|
|
451
|
-
param_map0[
|
|
727
|
+
ty.mod.type_params.zip(ty.args) do |(name, _default_ty), arg|
|
|
728
|
+
param_map0[name] = arg
|
|
452
729
|
end
|
|
453
730
|
end
|
|
454
731
|
end
|
|
455
|
-
method_type.type_params.each do |
|
|
456
|
-
param_map0[
|
|
732
|
+
method_type.type_params.each do |name, _default_ty|
|
|
733
|
+
param_map0[name] = Source.new()
|
|
457
734
|
end
|
|
458
735
|
|
|
459
736
|
positional_args = []
|
|
@@ -517,7 +794,7 @@ module TypeProf::Core
|
|
|
517
794
|
end
|
|
518
795
|
end
|
|
519
796
|
@f_args.opt_positionals.each_with_index do |f_vtx, i|
|
|
520
|
-
i += @f_args.
|
|
797
|
+
i += @f_args.req_positionals.size
|
|
521
798
|
if i < start_rest
|
|
522
799
|
changes.add_edge(genv, a_args.positionals[i], f_vtx)
|
|
523
800
|
else
|
|
@@ -599,16 +876,39 @@ module TypeProf::Core
|
|
|
599
876
|
end
|
|
600
877
|
|
|
601
878
|
if @node.rest_keywords
|
|
602
|
-
|
|
603
|
-
|
|
879
|
+
named_keys = @node.req_keywords + @node.opt_keywords
|
|
880
|
+
a_args.keywords.each_type do |kw_ty|
|
|
881
|
+
case kw_ty
|
|
882
|
+
when Type::Record
|
|
883
|
+
rest_fields = kw_ty.fields.reject {|key, _| named_keys.include?(key) }
|
|
884
|
+
base = kw_ty.base_type(genv)
|
|
885
|
+
rest_record = Type::Record.new(genv, rest_fields, base)
|
|
886
|
+
changes.add_edge(genv, Source.new(rest_record), @f_args.rest_keywords)
|
|
887
|
+
when Type::Hash, Type::Instance
|
|
888
|
+
changes.add_edge(genv, Source.new(kw_ty), @f_args.rest_keywords)
|
|
889
|
+
end
|
|
890
|
+
end
|
|
604
891
|
end
|
|
605
892
|
end
|
|
606
893
|
|
|
607
894
|
return true
|
|
608
895
|
end
|
|
609
896
|
|
|
897
|
+
def normalize_keyword_hash_argument_for_def(a_args)
|
|
898
|
+
return a_args unless a_args.keywords
|
|
899
|
+
return a_args if @node.no_keywords
|
|
900
|
+
return a_args if @node.rest_keywords
|
|
901
|
+
return a_args unless @node.req_keywords.empty? && @node.opt_keywords.empty?
|
|
902
|
+
|
|
903
|
+
a_args.with_keywords_as_last_positional_hash
|
|
904
|
+
end
|
|
905
|
+
|
|
610
906
|
def call(changes, genv, a_args, ret)
|
|
907
|
+
a_args = normalize_keyword_hash_argument_for_def(a_args)
|
|
611
908
|
if pass_arguments(changes, genv, a_args)
|
|
909
|
+
if @node.is_a?(AST::DefNode)
|
|
910
|
+
@node.body.lenv.forward_args&.accept_actual_arguments(genv, changes, a_args)
|
|
911
|
+
end
|
|
612
912
|
changes.add_edge(genv, a_args.block, @f_args.block) if @f_args.block && a_args.block
|
|
613
913
|
|
|
614
914
|
changes.add_edge(genv, @ret, ret)
|
|
@@ -635,7 +935,9 @@ module TypeProf::Core
|
|
|
635
935
|
@f_args.post_positionals.each do |var|
|
|
636
936
|
args << Type.strip_parens(var.show)
|
|
637
937
|
end
|
|
638
|
-
if @node.
|
|
938
|
+
if @node.respond_to?(:req_keywords) &&
|
|
939
|
+
@node.req_keywords.size == @f_args.req_keywords.size &&
|
|
940
|
+
@node.opt_keywords.size == @f_args.opt_keywords.size
|
|
639
941
|
@node.req_keywords.zip(@f_args.req_keywords) do |name, f_vtx|
|
|
640
942
|
args << "#{ name }: #{Type.strip_parens(f_vtx.show)}"
|
|
641
943
|
end
|
|
@@ -651,11 +953,11 @@ module TypeProf::Core
|
|
|
651
953
|
names = []
|
|
652
954
|
names.concat(@node.req_positionals)
|
|
653
955
|
names.concat(@node.opt_positionals)
|
|
654
|
-
names
|
|
956
|
+
names << @node.rest_positionals if @node.rest_positionals
|
|
655
957
|
names.concat(@node.post_positionals)
|
|
656
958
|
names.concat(@node.req_keywords)
|
|
657
959
|
names.concat(@node.opt_keywords)
|
|
658
|
-
names
|
|
960
|
+
names << @node.rest_keywords if @node.rest_keywords
|
|
659
961
|
args = args.zip(names).map do |arg, name|
|
|
660
962
|
name ? "#{ arg } #{ name }" : arg
|
|
661
963
|
end
|
|
@@ -702,7 +1004,7 @@ module TypeProf::Core
|
|
|
702
1004
|
end
|
|
703
1005
|
|
|
704
1006
|
class MethodCallBox < Box
|
|
705
|
-
def initialize(node, genv, recv, mid, a_args, subclasses)
|
|
1007
|
+
def initialize(node, genv, recv, mid, a_args, subclasses, suppress_errors: false)
|
|
706
1008
|
raise mid.to_s unless mid
|
|
707
1009
|
super(node)
|
|
708
1010
|
@recv = recv.new_vertex(genv, node)
|
|
@@ -713,21 +1015,23 @@ module TypeProf::Core
|
|
|
713
1015
|
@a_args.block.add_edge(genv, self) if @a_args.block
|
|
714
1016
|
@ret = Vertex.new(node)
|
|
715
1017
|
@subclasses = subclasses
|
|
1018
|
+
@suppress_errors = suppress_errors
|
|
716
1019
|
@generics = {}
|
|
717
1020
|
end
|
|
718
1021
|
|
|
719
1022
|
attr_reader :recv, :mid, :ret
|
|
720
1023
|
|
|
721
1024
|
def run0(genv, changes)
|
|
722
|
-
edges = Set
|
|
723
|
-
called_mdefs = Set
|
|
1025
|
+
edges = Set.empty
|
|
1026
|
+
called_mdefs = Set.empty
|
|
724
1027
|
error_count = 0
|
|
725
1028
|
resolve(genv, changes) do |me, ty, mid, orig_ty|
|
|
726
1029
|
if !me
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
1030
|
+
unless @suppress_errors
|
|
1031
|
+
if error_count < 3
|
|
1032
|
+
meth = @node.mid_code_range ? :mid_code_range : :code_range
|
|
1033
|
+
changes.add_diagnostic(meth, "undefined method: #{ orig_ty.show }##{ mid }")
|
|
1034
|
+
end
|
|
731
1035
|
end
|
|
732
1036
|
error_count += 1
|
|
733
1037
|
elsif me.builtin && me.builtin[changes, @node, orig_ty, @a_args, @ret]
|
|
@@ -739,12 +1043,12 @@ module TypeProf::Core
|
|
|
739
1043
|
# TODO: add_depended_method_entity for types used to resolve overloads
|
|
740
1044
|
ty_env = Type.default_param_map(genv, orig_ty)
|
|
741
1045
|
if ty.is_a?(Type::Instance)
|
|
742
|
-
ty.mod.type_params.zip(ty.args) do |param, arg|
|
|
743
|
-
ty_env[param] = arg
|
|
1046
|
+
ty.mod.type_params.zip(ty.args) do |(param, default_ty), arg|
|
|
1047
|
+
ty_env[param] = arg || (default_ty ? default_ty.covariant_vertex(genv, changes, ty_env) : Source.new)
|
|
744
1048
|
end
|
|
745
1049
|
end
|
|
746
1050
|
mdecl.resolve_overloads(changes, genv, @node, ty_env, @a_args, @ret) do |method_type|
|
|
747
|
-
@generics[method_type] ||= method_type.type_params.map {
|
|
1051
|
+
@generics[method_type] ||= method_type.type_params.map { Vertex.new(@node) }
|
|
748
1052
|
end
|
|
749
1053
|
end
|
|
750
1054
|
elsif !me.defs.empty?
|
|
@@ -772,7 +1076,7 @@ module TypeProf::Core
|
|
|
772
1076
|
edges.each do |src, dst|
|
|
773
1077
|
changes.add_edge(genv, src, dst)
|
|
774
1078
|
end
|
|
775
|
-
if error_count > 3
|
|
1079
|
+
if error_count > 3 && !@suppress_errors
|
|
776
1080
|
meth = @node.mid_code_range ? :mid_code_range : :code_range
|
|
777
1081
|
changes.add_diagnostic(meth, "... and other #{ error_count - 3 } errors")
|
|
778
1082
|
end
|
|
@@ -780,7 +1084,7 @@ module TypeProf::Core
|
|
|
780
1084
|
|
|
781
1085
|
def resolve(genv, changes, &blk)
|
|
782
1086
|
@recv.each_type do |orig_ty|
|
|
783
|
-
next if orig_ty ==
|
|
1087
|
+
next if orig_ty == genv.bot_type
|
|
784
1088
|
if @mid == :"*super"
|
|
785
1089
|
mid = @node.lenv.cref.mid
|
|
786
1090
|
skip = true
|
|
@@ -846,7 +1150,7 @@ module TypeProf::Core
|
|
|
846
1150
|
if prep_decl.is_a?(AST::SigPrependNode) && prep_mod.type_params
|
|
847
1151
|
prep_ty = genv.get_instance_type(prep_mod, prep_decl.args, changes, base_ty_env, ty)
|
|
848
1152
|
else
|
|
849
|
-
type_params = prep_mod.type_params.map {
|
|
1153
|
+
type_params = prep_mod.type_params.map { Source.new() } # TODO: better support
|
|
850
1154
|
prep_ty = Type::Instance.new(genv, prep_mod, type_params)
|
|
851
1155
|
end
|
|
852
1156
|
|
|
@@ -901,7 +1205,7 @@ module TypeProf::Core
|
|
|
901
1205
|
if inc_decl.is_a?(AST::SigIncludeNode) && inc_mod.type_params
|
|
902
1206
|
inc_ty = genv.get_instance_type(inc_mod, inc_decl.args, changes, base_ty_env, ty)
|
|
903
1207
|
else
|
|
904
|
-
type_params = inc_mod.type_params.map {
|
|
1208
|
+
type_params = inc_mod.type_params.map { Source.new() } # TODO: better support
|
|
905
1209
|
inc_ty = Type::Instance.new(genv, inc_mod, type_params)
|
|
906
1210
|
end
|
|
907
1211
|
|
|
@@ -925,7 +1229,7 @@ module TypeProf::Core
|
|
|
925
1229
|
def resolve_subclasses(genv, changes)
|
|
926
1230
|
# TODO: This does not follow new subclasses
|
|
927
1231
|
@recv.each_type do |ty|
|
|
928
|
-
next if ty ==
|
|
1232
|
+
next if ty == genv.bot_type
|
|
929
1233
|
base_ty = ty.base_type(genv)
|
|
930
1234
|
singleton = base_ty.is_a?(Type::Singleton)
|
|
931
1235
|
mod = base_ty.mod
|
|
@@ -980,23 +1284,44 @@ module TypeProf::Core
|
|
|
980
1284
|
singleton = @singleton
|
|
981
1285
|
cur_ive = mod.get_ivar(singleton, @name)
|
|
982
1286
|
target_vtx = nil
|
|
1287
|
+
target_decls = nil
|
|
983
1288
|
genv.each_direct_superclass(mod, singleton) do |mod, singleton|
|
|
984
1289
|
ive = mod.get_ivar(singleton, @name)
|
|
1290
|
+
# Subscribe to every visited ive so that, if one later acquires an
|
|
1291
|
+
# RBS declaration, this box is re-run and switches to the declared
|
|
1292
|
+
# type instead of the inferred one.
|
|
1293
|
+
changes.add_depended_value_entity(ive)
|
|
985
1294
|
if ive.exist?
|
|
986
1295
|
target_vtx = ive.vtx
|
|
1296
|
+
target_decls = ive.decls unless ive.decls.empty?
|
|
1297
|
+
break if target_decls
|
|
987
1298
|
end
|
|
988
1299
|
end
|
|
989
|
-
|
|
990
|
-
if
|
|
1300
|
+
|
|
1301
|
+
if target_decls
|
|
1302
|
+
# When declarations exist, return declared types instead of assigned types
|
|
1303
|
+
target_decls.each do |decl|
|
|
1304
|
+
subst = {}
|
|
1305
|
+
if decl.cpath
|
|
1306
|
+
decl_mod = genv.resolve_cpath(decl.cpath)
|
|
1307
|
+
if decl_mod.type_params && !decl_mod.type_params.empty?
|
|
1308
|
+
subst = decl_mod.type_params.to_h do |param, _default_ty|
|
|
1309
|
+
[param, Vertex.new(@node)]
|
|
1310
|
+
end
|
|
1311
|
+
end
|
|
1312
|
+
end
|
|
1313
|
+
vtx = decl.type.covariant_vertex(genv, changes, subst)
|
|
1314
|
+
changes.add_edge(genv, vtx, @ret)
|
|
1315
|
+
end
|
|
1316
|
+
elsif target_vtx
|
|
1317
|
+
edges = []
|
|
991
1318
|
if target_vtx != cur_ive.vtx
|
|
992
1319
|
edges << [cur_ive.vtx, @proxy] << [@proxy, target_vtx]
|
|
993
1320
|
end
|
|
994
1321
|
edges << [target_vtx, @ret]
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
edges.each do |src, dst|
|
|
999
|
-
changes.add_edge(genv, src, dst)
|
|
1322
|
+
edges.each do |src, dst|
|
|
1323
|
+
changes.add_edge(genv, src, dst)
|
|
1324
|
+
end
|
|
1000
1325
|
end
|
|
1001
1326
|
end
|
|
1002
1327
|
end
|
|
@@ -1064,25 +1389,27 @@ module TypeProf::Core
|
|
|
1064
1389
|
def ret = @rhs
|
|
1065
1390
|
|
|
1066
1391
|
def run0(genv, changes)
|
|
1067
|
-
edges = []
|
|
1068
1392
|
@value.each_type do |ty|
|
|
1069
1393
|
# TODO: call to_ary?
|
|
1070
1394
|
case ty
|
|
1071
1395
|
when Type::Array
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1396
|
+
ty.splat_assign(genv, @lefts, @rest_elem, @rights).each do |src, dst|
|
|
1397
|
+
changes.add_edge(genv, src, dst)
|
|
1398
|
+
end
|
|
1399
|
+
when Type::Instance
|
|
1400
|
+
if ty.mod == genv.mod_ary && (elem_vtx = ty.args[0])
|
|
1401
|
+
@lefts.each {|lhs| changes.add_edge(genv, elem_vtx, lhs) }
|
|
1402
|
+
changes.add_edge(genv, elem_vtx, @rest_elem) if @rest_elem
|
|
1403
|
+
@rights&.each {|rhs| changes.add_edge(genv, elem_vtx, rhs) }
|
|
1078
1404
|
else
|
|
1079
|
-
|
|
1405
|
+
lhs = @lefts[0] || (@rights && @rights[0]) || @rest_elem
|
|
1406
|
+
changes.add_edge(genv, Source.new(ty), lhs) if lhs
|
|
1080
1407
|
end
|
|
1408
|
+
else
|
|
1409
|
+
lhs = @lefts[0] || (@rights && @rights[0]) || @rest_elem
|
|
1410
|
+
changes.add_edge(genv, Source.new(ty), lhs) if lhs
|
|
1081
1411
|
end
|
|
1082
1412
|
end
|
|
1083
|
-
edges.each do |src, dst|
|
|
1084
|
-
changes.add_edge(genv, src, dst)
|
|
1085
|
-
end
|
|
1086
1413
|
end
|
|
1087
1414
|
end
|
|
1088
1415
|
|