typeprof 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.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +26 -0
- data/.gitignore +7 -0
- data/.gitmodules +6 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +41 -0
- data/README.md +53 -0
- data/Rakefile +10 -0
- data/doc/doc.ja.md +415 -0
- data/doc/doc.md +429 -0
- data/doc/ppl2019.pdf +0 -0
- data/exe/typeprof +5 -0
- data/lib/typeprof.rb +13 -0
- data/lib/typeprof/analyzer.rb +1911 -0
- data/lib/typeprof/builtin.rb +554 -0
- data/lib/typeprof/cli.rb +110 -0
- data/lib/typeprof/container-type.rb +626 -0
- data/lib/typeprof/export.rb +203 -0
- data/lib/typeprof/import.rb +546 -0
- data/lib/typeprof/insns-def.rb +61 -0
- data/lib/typeprof/iseq.rb +387 -0
- data/lib/typeprof/method.rb +267 -0
- data/lib/typeprof/type.rb +1092 -0
- data/lib/typeprof/utils.rb +209 -0
- data/run.sh +3 -0
- data/smoke/alias.rb +30 -0
- data/smoke/alias2.rb +19 -0
- data/smoke/any-cbase.rb +5 -0
- data/smoke/any1.rb +15 -0
- data/smoke/any2.rb +17 -0
- data/smoke/arguments.rb +16 -0
- data/smoke/array-each.rb +14 -0
- data/smoke/array-each2.rb +15 -0
- data/smoke/array-each3.rb +15 -0
- data/smoke/array-ltlt.rb +13 -0
- data/smoke/array-ltlt2.rb +16 -0
- data/smoke/array-map.rb +11 -0
- data/smoke/array-map2.rb +10 -0
- data/smoke/array-map3.rb +22 -0
- data/smoke/array-mul.rb +17 -0
- data/smoke/array-plus1.rb +10 -0
- data/smoke/array-plus2.rb +15 -0
- data/smoke/array-pop.rb +11 -0
- data/smoke/array-replace.rb +12 -0
- data/smoke/array-s-aref.rb +11 -0
- data/smoke/array1.rb +26 -0
- data/smoke/array10.rb +14 -0
- data/smoke/array11.rb +13 -0
- data/smoke/array12.rb +24 -0
- data/smoke/array13.rb +30 -0
- data/smoke/array14.rb +13 -0
- data/smoke/array2.rb +27 -0
- data/smoke/array3.rb +25 -0
- data/smoke/array4.rb +14 -0
- data/smoke/array5.rb +13 -0
- data/smoke/array6.rb +14 -0
- data/smoke/array7.rb +13 -0
- data/smoke/array8.rb +13 -0
- data/smoke/array9.rb +12 -0
- data/smoke/attr.rb +28 -0
- data/smoke/backtrace.rb +32 -0
- data/smoke/block1.rb +22 -0
- data/smoke/block10.rb +14 -0
- data/smoke/block11.rb +39 -0
- data/smoke/block12.rb +22 -0
- data/smoke/block2.rb +14 -0
- data/smoke/block3.rb +38 -0
- data/smoke/block4.rb +18 -0
- data/smoke/block5.rb +18 -0
- data/smoke/block6.rb +20 -0
- data/smoke/block7.rb +20 -0
- data/smoke/block8.rb +27 -0
- data/smoke/block9.rb +12 -0
- data/smoke/blown.rb +12 -0
- data/smoke/break1.rb +18 -0
- data/smoke/break2.rb +15 -0
- data/smoke/case.rb +16 -0
- data/smoke/case2.rb +17 -0
- data/smoke/class.rb +5 -0
- data/smoke/class_instance_var.rb +9 -0
- data/smoke/class_method.rb +25 -0
- data/smoke/class_method2.rb +21 -0
- data/smoke/class_method3.rb +27 -0
- data/smoke/constant1.rb +38 -0
- data/smoke/constant2.rb +33 -0
- data/smoke/constant3.rb +9 -0
- data/smoke/constant4.rb +11 -0
- data/smoke/context-sensitive1.rb +12 -0
- data/smoke/cvar.rb +28 -0
- data/smoke/cvar2.rb +17 -0
- data/smoke/demo.rb +80 -0
- data/smoke/demo1.rb +16 -0
- data/smoke/demo10.rb +20 -0
- data/smoke/demo11.rb +11 -0
- data/smoke/demo2.rb +14 -0
- data/smoke/demo3.rb +16 -0
- data/smoke/demo4.rb +27 -0
- data/smoke/demo5.rb +13 -0
- data/smoke/demo6.rb +21 -0
- data/smoke/demo7.rb +14 -0
- data/smoke/demo8.rb +18 -0
- data/smoke/demo9.rb +18 -0
- data/smoke/dummy-execution1.rb +14 -0
- data/smoke/dummy-execution2.rb +16 -0
- data/smoke/ensure1.rb +20 -0
- data/smoke/enumerator.rb +15 -0
- data/smoke/expandarray1.rb +22 -0
- data/smoke/expandarray2.rb +23 -0
- data/smoke/fib.rb +28 -0
- data/smoke/flow1.rb +16 -0
- data/smoke/flow2.rb +14 -0
- data/smoke/flow3.rb +14 -0
- data/smoke/flow4.rb +5 -0
- data/smoke/flow5.rb +19 -0
- data/smoke/flow6.rb +19 -0
- data/smoke/flow7.rb +26 -0
- data/smoke/for.rb +9 -0
- data/smoke/freeze.rb +11 -0
- data/smoke/function.rb +16 -0
- data/smoke/gvar.rb +13 -0
- data/smoke/hash-fetch.rb +27 -0
- data/smoke/hash1.rb +18 -0
- data/smoke/hash2.rb +12 -0
- data/smoke/hash3.rb +13 -0
- data/smoke/hash4.rb +10 -0
- data/smoke/hash5.rb +14 -0
- data/smoke/inheritance.rb +34 -0
- data/smoke/inheritance2.rb +29 -0
- data/smoke/initialize.rb +26 -0
- data/smoke/instance_eval.rb +18 -0
- data/smoke/int_times.rb +14 -0
- data/smoke/integer.rb +10 -0
- data/smoke/ivar.rb +29 -0
- data/smoke/ivar2.rb +30 -0
- data/smoke/kernel-class.rb +12 -0
- data/smoke/keyword1.rb +11 -0
- data/smoke/keyword2.rb +11 -0
- data/smoke/keyword3.rb +12 -0
- data/smoke/keyword4.rb +11 -0
- data/smoke/keyword5.rb +15 -0
- data/smoke/kwsplat1.rb +42 -0
- data/smoke/kwsplat2.rb +12 -0
- data/smoke/manual-rbs.rb +27 -0
- data/smoke/manual-rbs.rbs +3 -0
- data/smoke/manual-rbs2.rb +20 -0
- data/smoke/manual-rbs2.rbs +8 -0
- data/smoke/masgn1.rb +13 -0
- data/smoke/masgn2.rb +17 -0
- data/smoke/masgn3.rb +12 -0
- data/smoke/method_in_branch.rb +22 -0
- data/smoke/module1.rb +29 -0
- data/smoke/module2.rb +28 -0
- data/smoke/module3.rb +33 -0
- data/smoke/module4.rb +29 -0
- data/smoke/module_function1.rb +28 -0
- data/smoke/module_function2.rb +28 -0
- data/smoke/multiple-include.rb +14 -0
- data/smoke/multiple-superclass.rb +16 -0
- data/smoke/next1.rb +20 -0
- data/smoke/next2.rb +16 -0
- data/smoke/object-send1.rb +22 -0
- data/smoke/once.rb +12 -0
- data/smoke/optional1.rb +13 -0
- data/smoke/optional2.rb +15 -0
- data/smoke/parameterizedd-self.rb +18 -0
- data/smoke/pathname1.rb +13 -0
- data/smoke/pathname2.rb +13 -0
- data/smoke/printf.rb +20 -0
- data/smoke/proc.rb +19 -0
- data/smoke/proc2.rb +16 -0
- data/smoke/proc3.rb +14 -0
- data/smoke/proc4.rb +11 -0
- data/smoke/range.rb +13 -0
- data/smoke/redo1.rb +21 -0
- data/smoke/redo2.rb +22 -0
- data/smoke/req-keyword.rb +12 -0
- data/smoke/rescue1.rb +20 -0
- data/smoke/rescue2.rb +22 -0
- data/smoke/respond_to.rb +22 -0
- data/smoke/rest-farg.rb +10 -0
- data/smoke/rest1.rb +25 -0
- data/smoke/rest2.rb +30 -0
- data/smoke/rest3.rb +36 -0
- data/smoke/rest4.rb +18 -0
- data/smoke/rest5.rb +10 -0
- data/smoke/rest6.rb +11 -0
- data/smoke/retry1.rb +20 -0
- data/smoke/return.rb +13 -0
- data/smoke/reveal.rb +13 -0
- data/smoke/singleton_class.rb +8 -0
- data/smoke/singleton_method.rb +9 -0
- data/smoke/step.rb +17 -0
- data/smoke/string-split.rb +11 -0
- data/smoke/struct.rb +9 -0
- data/smoke/struct2.rb +24 -0
- data/smoke/super1.rb +50 -0
- data/smoke/super2.rb +16 -0
- data/smoke/super3.rb +19 -0
- data/smoke/svar1.rb +12 -0
- data/smoke/tap1.rb +17 -0
- data/smoke/toplevel.rb +12 -0
- data/smoke/two-map.rb +17 -0
- data/smoke/type_var.rb +10 -0
- data/smoke/typed_method.rb +15 -0
- data/smoke/union-recv.rb +29 -0
- data/smoke/variadic1.rb.notyet +5 -0
- data/smoke/wrong-extend.rb +25 -0
- data/smoke/wrong-include.rb +26 -0
- data/smoke/wrong-rbs.rb +15 -0
- data/smoke/wrong-rbs.rbs +7 -0
- data/testbed/ao.rb +297 -0
- data/testbed/diff-lcs-entrypoint.rb +4 -0
- data/testbed/goodcheck-Gemfile.lock +51 -0
- data/tools/coverage.rb +14 -0
- data/tools/setup-insns-def.rb +30 -0
- data/tools/stackprof-wrapper.rb +10 -0
- data/typeprof.gemspec +24 -0
- metadata +262 -0
data/lib/typeprof/cli.rb
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
|
|
3
|
+
module TypeProf
|
|
4
|
+
class CLI
|
|
5
|
+
def initialize(argv)
|
|
6
|
+
opt = OptionParser.new
|
|
7
|
+
|
|
8
|
+
@output = nil
|
|
9
|
+
|
|
10
|
+
# Verbose level:
|
|
11
|
+
# * 0: no output
|
|
12
|
+
# * 1: show indicator
|
|
13
|
+
# * 2: debug print
|
|
14
|
+
@verbose = 1
|
|
15
|
+
|
|
16
|
+
@options = {
|
|
17
|
+
type_depth_limit: 5,
|
|
18
|
+
pedantic_output: false,
|
|
19
|
+
show_errors: false,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
opt.on("-o OUTFILE") {|v| @output = v }
|
|
23
|
+
opt.on("-q", "--quiet") {|v| @verbose = 0 }
|
|
24
|
+
opt.on("-v", "--verbose") {|v| @verbose = 2 }
|
|
25
|
+
opt.on("-f OPTION") do |v|
|
|
26
|
+
key, args = v.split("=", 2)
|
|
27
|
+
case key
|
|
28
|
+
when "type-depth-limit"
|
|
29
|
+
@options[:type_depth_limit] = Integer(args)
|
|
30
|
+
when "pedantic-output"
|
|
31
|
+
@options[:pedantic_output] = true
|
|
32
|
+
when "show-errors"
|
|
33
|
+
@options[:show_errors] = true
|
|
34
|
+
when "show-container-raw-elements"
|
|
35
|
+
@options[:show_container_raw_elements] = true
|
|
36
|
+
else
|
|
37
|
+
raise OptionParser::InvalidOption.new("unknown option: #{ key }")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
opt.parse!(argv)
|
|
42
|
+
|
|
43
|
+
@rb_files = []
|
|
44
|
+
@rbs_files = []
|
|
45
|
+
argv.each do |path|
|
|
46
|
+
if File.extname(path) == ".rbs"
|
|
47
|
+
@rbs_files << path
|
|
48
|
+
else
|
|
49
|
+
@rb_files << path
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
raise OptionParser::InvalidOption.new("no input files") if @rb_files.empty?
|
|
54
|
+
|
|
55
|
+
TypeProf.const_set(:Config, self)
|
|
56
|
+
|
|
57
|
+
rescue OptionParser::InvalidOption
|
|
58
|
+
puts $!
|
|
59
|
+
exit
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
attr_reader :verbose, :options, :files
|
|
63
|
+
attr_accessor :output
|
|
64
|
+
|
|
65
|
+
def run
|
|
66
|
+
scratch = Scratch.new
|
|
67
|
+
Builtin.setup_initial_global_env(scratch)
|
|
68
|
+
|
|
69
|
+
prologue_ctx = Context.new(nil, nil, nil)
|
|
70
|
+
prologue_ep = ExecutionPoint.new(prologue_ctx, -1, nil)
|
|
71
|
+
prologue_env = Env.new(StaticEnv.new(:top, Type.nil, false), [], [], Utils::HashWrapper.new({}))
|
|
72
|
+
|
|
73
|
+
@rb_files.each do |path|
|
|
74
|
+
if path == "-"
|
|
75
|
+
iseq = ISeq.compile_str($<.read)
|
|
76
|
+
else
|
|
77
|
+
iseq = ISeq.compile(path)
|
|
78
|
+
end
|
|
79
|
+
ep, env = CLI.starting_state(iseq)
|
|
80
|
+
scratch.merge_env(ep, env)
|
|
81
|
+
scratch.add_callsite!(ep.ctx, nil, prologue_ep, prologue_env) {|ty, ep| }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
@rbs_files.each do |path|
|
|
85
|
+
RubySignatureImporter.import_rbs_file(scratch, path)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
result = scratch.type_profile
|
|
89
|
+
|
|
90
|
+
if @output
|
|
91
|
+
open(@output, "w") do |output|
|
|
92
|
+
scratch.report(result, output)
|
|
93
|
+
end
|
|
94
|
+
else
|
|
95
|
+
scratch.report(result, $stdout)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def self.starting_state(iseq)
|
|
100
|
+
cref = CRef.new(:bottom, Type::Builtin[:obj], false) # object
|
|
101
|
+
recv = Type::Instance.new(Type::Builtin[:obj])
|
|
102
|
+
ctx = Context.new(iseq, cref, nil)
|
|
103
|
+
ep = ExecutionPoint.new(ctx, 0, nil)
|
|
104
|
+
locals = [Type.nil] * iseq.locals.size
|
|
105
|
+
env = Env.new(StaticEnv.new(recv, Type.nil, false), locals, [], Utils::HashWrapper.new({}))
|
|
106
|
+
|
|
107
|
+
return ep, env
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
module TypeProf
|
|
2
|
+
class AllocationSite
|
|
3
|
+
include Utils::StructuralEquality
|
|
4
|
+
|
|
5
|
+
def initialize(val, parent = nil)
|
|
6
|
+
raise if !val.is_a?(Utils::StructuralEquality) && !val.is_a?(Integer) && !val.is_a?(Symbol)
|
|
7
|
+
@val = val
|
|
8
|
+
@parent = parent
|
|
9
|
+
@_hash ||= (@val.hash ^ @parent.hash)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader :val, :parent
|
|
13
|
+
|
|
14
|
+
def hash
|
|
15
|
+
@_hash
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def add_id(val)
|
|
19
|
+
AllocationSite.new(val, self)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Type # or AbstractValue
|
|
24
|
+
# This is a type for global interface, e.g., TypedISeq.
|
|
25
|
+
# Do not insert Array type to local environment, stack, etc.
|
|
26
|
+
class Array < Type
|
|
27
|
+
def initialize(elems, base_type)
|
|
28
|
+
raise unless elems.is_a?(Array::Elements)
|
|
29
|
+
@elems = elems # Array::Elements
|
|
30
|
+
raise unless base_type
|
|
31
|
+
@base_type = base_type
|
|
32
|
+
# XXX: need infinite recursion
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
attr_reader :elems, :base_type
|
|
36
|
+
|
|
37
|
+
def inspect
|
|
38
|
+
"Type::Array[#{ @elems.inspect }, base_type: #{ @base_type.inspect }]"
|
|
39
|
+
#@base_type.inspect
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def screen_name(scratch)
|
|
43
|
+
str = @elems.screen_name(scratch)
|
|
44
|
+
if str.start_with?("*")
|
|
45
|
+
str = @base_type.screen_name(scratch) + str[1..]
|
|
46
|
+
end
|
|
47
|
+
str
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def globalize(env, visited, depth)
|
|
51
|
+
return Type.any if depth <= 0
|
|
52
|
+
elems = @elems.globalize(env, visited, depth - 1)
|
|
53
|
+
base_ty = @base_type.globalize(env, visited, depth - 1)
|
|
54
|
+
Array.new(elems, base_ty)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def localize(env, alloc_site, depth)
|
|
58
|
+
return env, Type.any if depth <= 0
|
|
59
|
+
alloc_site = alloc_site.add_id(:ary)
|
|
60
|
+
env, elems = @elems.localize(env, alloc_site, depth - 1)
|
|
61
|
+
env.deploy_array_type(alloc_site, elems, @base_type)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def limit_size(limit)
|
|
65
|
+
return Type.any if limit <= 0
|
|
66
|
+
Array.new(@elems.limit_size(limit - 1), @base_type)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def get_method(mid, scratch)
|
|
70
|
+
raise
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def consistent?(other, subst)
|
|
74
|
+
case other
|
|
75
|
+
when Type::Any then true
|
|
76
|
+
when Type::Var then other.add_subst!(self, subst)
|
|
77
|
+
when Type::Union
|
|
78
|
+
other.types.each do |ty2|
|
|
79
|
+
return true if consistent?(ty2, subst)
|
|
80
|
+
end
|
|
81
|
+
when Type::Array
|
|
82
|
+
@base_type.consistent?(other.base_type, subst) && @elems.consistent?(other.elems, subst)
|
|
83
|
+
else
|
|
84
|
+
self == other
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def substitute(subst, depth)
|
|
89
|
+
return Type.any if depth <= 0
|
|
90
|
+
elems = @elems.substitute(subst, depth - 1)
|
|
91
|
+
Array.new(elems, @base_type)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class Elements
|
|
95
|
+
include Utils::StructuralEquality
|
|
96
|
+
|
|
97
|
+
def initialize(lead_tys, rest_ty = Type.bot)
|
|
98
|
+
raise unless lead_tys.all? {|ty| ty.is_a?(Type) }
|
|
99
|
+
raise unless rest_ty.is_a?(Type)
|
|
100
|
+
@lead_tys, @rest_ty = lead_tys, rest_ty
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
attr_reader :lead_tys, :rest_ty
|
|
104
|
+
|
|
105
|
+
def to_local_type(id)
|
|
106
|
+
base_ty = Type::Instance.new(Type::Builtin[:ary])
|
|
107
|
+
Type::LocalArray.new(id, base_ty)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def globalize(env, visited, depth)
|
|
111
|
+
lead_tys = []
|
|
112
|
+
@lead_tys.each do |ty|
|
|
113
|
+
lead_tys << ty.globalize(env, visited, depth)
|
|
114
|
+
end
|
|
115
|
+
rest_ty = @rest_ty&.globalize(env, visited, depth)
|
|
116
|
+
Elements.new(lead_tys, rest_ty)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def localize(env, alloc_site, depth)
|
|
120
|
+
lead_tys = @lead_tys.map.with_index do |ty, i|
|
|
121
|
+
alloc_site2 = alloc_site.add_id(i)
|
|
122
|
+
env, ty = ty.localize(env, alloc_site2, depth)
|
|
123
|
+
ty
|
|
124
|
+
end
|
|
125
|
+
alloc_site_rest = alloc_site.add_id(:rest)
|
|
126
|
+
env, rest_ty = @rest_ty.localize(env, alloc_site_rest, depth)
|
|
127
|
+
return env, Elements.new(lead_tys, rest_ty)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def limit_size(limit)
|
|
131
|
+
Elements.new(@lead_tys.map {|ty| ty.limit_size(limit) }, @rest_ty.limit_size(limit))
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def screen_name(scratch)
|
|
135
|
+
if Config.options[:show_container_raw_elements] || @rest_ty == Type.bot
|
|
136
|
+
s = @lead_tys.map do |ty|
|
|
137
|
+
ty.screen_name(scratch)
|
|
138
|
+
end
|
|
139
|
+
s << "*" + @rest_ty.screen_name(scratch) if @rest_ty != Type.bot
|
|
140
|
+
return "[#{ s.join(", ") }]"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
"*[#{ squash.screen_name(scratch) }]"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def pretty_print(q)
|
|
147
|
+
q.group(9, "Elements[", "]") do
|
|
148
|
+
q.seplist(@lead_tys + [@rest_ty]) do |elem|
|
|
149
|
+
q.pp elem
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def consistent?(other, subst)
|
|
155
|
+
n = [@lead_tys.size, other.lead_tys.size].min
|
|
156
|
+
n.times do |i|
|
|
157
|
+
return false unless @lead_tys[i].consistent?(other.lead_tys[i], subst)
|
|
158
|
+
end
|
|
159
|
+
rest_ty1 = @lead_tys[n..].inject(@rest_ty) {|ty1, ty2| ty1.union(ty2) }
|
|
160
|
+
rest_ty2 = other.lead_tys[n..].inject(other.rest_ty) {|ty1, ty2| ty1.union(ty2) }
|
|
161
|
+
rest_ty1.consistent?(rest_ty2, subst)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def substitute(subst, depth)
|
|
165
|
+
lead_tys = @lead_tys.map {|ty| ty.substitute(subst, depth) }
|
|
166
|
+
rest_ty = @rest_ty.substitute(subst, depth)
|
|
167
|
+
Elements.new(lead_tys, rest_ty)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def squash
|
|
171
|
+
@lead_tys.inject(@rest_ty) {|ty1, ty2| ty1.union(ty2) } #.union(Type.nil) # is this needed?
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def [](idx)
|
|
175
|
+
if idx >= 0
|
|
176
|
+
if idx < @lead_tys.size
|
|
177
|
+
@lead_tys[idx]
|
|
178
|
+
elsif @rest_ty == Type.bot
|
|
179
|
+
Type.nil
|
|
180
|
+
else
|
|
181
|
+
@rest_ty
|
|
182
|
+
end
|
|
183
|
+
else
|
|
184
|
+
i = @lead_tys.size + idx
|
|
185
|
+
i = [i, 0].max
|
|
186
|
+
ty = @rest_ty
|
|
187
|
+
@lead_tys[i..].each do |ty2|
|
|
188
|
+
ty = ty.union(ty2)
|
|
189
|
+
end
|
|
190
|
+
ty
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def update(idx, ty)
|
|
195
|
+
if idx
|
|
196
|
+
if idx >= 0
|
|
197
|
+
if idx < @lead_tys.size
|
|
198
|
+
lead_tys = Utils.array_update(@lead_tys, idx, ty)
|
|
199
|
+
Elements.new(lead_tys, @rest_ty)
|
|
200
|
+
else
|
|
201
|
+
rest_ty = @rest_ty.union(ty)
|
|
202
|
+
Elements.new(@lead_tys, rest_ty)
|
|
203
|
+
end
|
|
204
|
+
else
|
|
205
|
+
i = @lead_tys.size + idx
|
|
206
|
+
if @rest_ty == Type.bot
|
|
207
|
+
if i >= 0
|
|
208
|
+
lead_tys = Utils.array_update(@lead_tys, i, ty)
|
|
209
|
+
Elements.new(lead_tys, Type.bot)
|
|
210
|
+
else
|
|
211
|
+
# TODO: out of bound? should we emit an error?
|
|
212
|
+
Elements.new(@lead_tys, Type.bot)
|
|
213
|
+
end
|
|
214
|
+
else
|
|
215
|
+
i = [i, 0].max
|
|
216
|
+
lead_tys = @lead_tys[0, i] + @lead_tys[i..].map {|ty2| ty2.union(ty) }
|
|
217
|
+
rest_ty = @rest_ty.union(ty)
|
|
218
|
+
Elements.new(@lead_tys, rest_ty)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
else
|
|
222
|
+
lead_tys = @lead_tys.map {|ty1| ty1.union(ty) }
|
|
223
|
+
rest_ty = @rest_ty.union(ty)
|
|
224
|
+
Elements.new(lead_tys, rest_ty)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def append(ty)
|
|
229
|
+
if @rest_ty == Type.bot
|
|
230
|
+
if @lead_tys.size < 5 # XXX: should be configurable, or ...?
|
|
231
|
+
lead_tys = @lead_tys + [ty]
|
|
232
|
+
Elements.new(lead_tys, @rest_ty)
|
|
233
|
+
else
|
|
234
|
+
Elements.new(@lead_tys, ty)
|
|
235
|
+
end
|
|
236
|
+
else
|
|
237
|
+
Elements.new(@lead_tys, @rest_ty.union(ty))
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def union(other)
|
|
242
|
+
return self if self == other
|
|
243
|
+
raise "Hash::Elements merge Array::Elements" if other.is_a?(Hash::Elements)
|
|
244
|
+
|
|
245
|
+
lead_count = [@lead_tys.size, other.lead_tys.size].min
|
|
246
|
+
lead_tys = (0...lead_count).map do |i|
|
|
247
|
+
@lead_tys[i].union(other.lead_tys[i])
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
rest_ty = @rest_ty.union(other.rest_ty)
|
|
251
|
+
(@lead_tys[lead_count..-1] + other.lead_tys[lead_count..-1]).each do |ty|
|
|
252
|
+
rest_ty = rest_ty.union(ty)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
Elements.new(lead_tys, rest_ty)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def take_first(num)
|
|
259
|
+
base_ty = Type::Instance.new(Type::Builtin[:ary])
|
|
260
|
+
if @lead_tys.size >= num
|
|
261
|
+
lead_tys = @lead_tys[0, num]
|
|
262
|
+
rest_ary_ty = Array.new(Elements.new(@lead_tys[num..-1], @rest_ty), base_ty)
|
|
263
|
+
return lead_tys, rest_ary_ty
|
|
264
|
+
else
|
|
265
|
+
lead_tys = @lead_tys.dup
|
|
266
|
+
until lead_tys.size == num
|
|
267
|
+
# .union(Type.nil) is needed for `a, b, c = [42]` to assign nil to b and c
|
|
268
|
+
lead_tys << @rest_ty.union(Type.nil)
|
|
269
|
+
end
|
|
270
|
+
rest_ary_ty = Array.new(Elements.new([], @rest_ty), base_ty)
|
|
271
|
+
return lead_tys, rest_ary_ty
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def take_last(num)
|
|
276
|
+
base_ty = Type::Instance.new(Type::Builtin[:ary])
|
|
277
|
+
if @rest_ty == Type.bot
|
|
278
|
+
if @lead_tys.size >= num
|
|
279
|
+
following_tys = @lead_tys[-num, num]
|
|
280
|
+
rest_ary_ty = Array.new(Elements.new(@lead_tys[0...-num], Type.bot), base_ty)
|
|
281
|
+
return rest_ary_ty, following_tys
|
|
282
|
+
else
|
|
283
|
+
following_tys = @lead_tys[-num, num] || []
|
|
284
|
+
until following_tys.size == num
|
|
285
|
+
following_tys.unshift(Type.nil)
|
|
286
|
+
end
|
|
287
|
+
rest_ary_ty = Array.new(Elements.new([], Type.bot), base_ty)
|
|
288
|
+
return rest_ary_ty, following_tys
|
|
289
|
+
end
|
|
290
|
+
else
|
|
291
|
+
lead_tys = @lead_tys.dup
|
|
292
|
+
last_ty = rest_ty
|
|
293
|
+
following_tys = []
|
|
294
|
+
until following_tys.size == num
|
|
295
|
+
last_ty = last_ty.union(lead_tys.pop) unless lead_tys.empty?
|
|
296
|
+
following_tys.unshift(last_ty)
|
|
297
|
+
end
|
|
298
|
+
rest_ty = lead_tys.inject(last_ty) {|ty1, ty2| ty1.union(ty2) }
|
|
299
|
+
rest_ary_ty = Array.new(Elements.new([], rest_ty), base_ty)
|
|
300
|
+
return rest_ary_ty, following_tys
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Do not insert Array type to local environment, stack, etc.
|
|
307
|
+
class LocalArray < Type
|
|
308
|
+
def initialize(id, base_type)
|
|
309
|
+
@id = id
|
|
310
|
+
raise unless base_type
|
|
311
|
+
@base_type = base_type
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
attr_reader :id, :base_type
|
|
315
|
+
|
|
316
|
+
def inspect
|
|
317
|
+
"Type::LocalArray[#{ @id }, base_type: #{ @base_type.inspect }]"
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def screen_name(scratch)
|
|
321
|
+
#raise "LocalArray must not be included in signature"
|
|
322
|
+
"LocalArray!"
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def globalize(env, visited, depth)
|
|
326
|
+
if visited[self] || depth <= 0
|
|
327
|
+
Type.any
|
|
328
|
+
else
|
|
329
|
+
visited[self] = true
|
|
330
|
+
elems = env.get_container_elem_types(@id)
|
|
331
|
+
if elems
|
|
332
|
+
elems = elems.globalize(env, visited, depth - 1)
|
|
333
|
+
else
|
|
334
|
+
# TODO: currently out-of-scope array cannot be accessed
|
|
335
|
+
elems = Array::Elements.new([], Type.any)
|
|
336
|
+
end
|
|
337
|
+
Array.new(elems, @base_type)
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def get_method(mid, scratch)
|
|
342
|
+
@base_type.get_method(mid, scratch)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def consistent?(other, subst)
|
|
346
|
+
raise "must not be used"
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class Hash < Type
|
|
352
|
+
def initialize(elems, base_type)
|
|
353
|
+
@elems = elems
|
|
354
|
+
raise unless elems
|
|
355
|
+
@base_type = base_type
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
attr_reader :elems, :base_type
|
|
359
|
+
|
|
360
|
+
def inspect
|
|
361
|
+
"Type::Hash#{ @elems.inspect }"
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def screen_name(scratch)
|
|
365
|
+
@elems.screen_name(scratch)
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def globalize(env, visited, depth)
|
|
369
|
+
return Type.any if depth <= 0
|
|
370
|
+
elems = @elems.globalize(env, visited, depth - 1)
|
|
371
|
+
base_ty = @base_type.globalize(env, visited, depth - 1)
|
|
372
|
+
Hash.new(elems, base_ty)
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def localize(env, alloc_site, depth)
|
|
376
|
+
return env, Type.any if depth <= 0
|
|
377
|
+
alloc_site = alloc_site.add_id(:hash)
|
|
378
|
+
env, elems = @elems.localize(env, alloc_site, depth - 1)
|
|
379
|
+
env.deploy_hash_type(alloc_site, elems, @base_type)
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def limit_size(limit)
|
|
383
|
+
return Type.any if limit <= 0
|
|
384
|
+
Hash.new(@elems.limit_size(limit - 1), @base_type)
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def get_method(mid, scratch)
|
|
388
|
+
raise
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def consistent?(other, subst)
|
|
392
|
+
case other
|
|
393
|
+
when Type::Any then true
|
|
394
|
+
when Type::Var then other.add_subst!(self, subst)
|
|
395
|
+
when Type::Union
|
|
396
|
+
other.types.each do |ty2|
|
|
397
|
+
return true if consistent?(ty2, subst)
|
|
398
|
+
end
|
|
399
|
+
when Type::Hash
|
|
400
|
+
@base_type.consistent?(other.base_type, subst) && @elems.consistent?(other.elems, subst)
|
|
401
|
+
else
|
|
402
|
+
self == other
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def substitute(subst, depth)
|
|
407
|
+
return Type.any if depth <= 0
|
|
408
|
+
elems = @elems.substitute(subst, depth - 1)
|
|
409
|
+
Hash.new(elems, @base_type)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
class Elements
|
|
413
|
+
include Utils::StructuralEquality
|
|
414
|
+
|
|
415
|
+
def initialize(map_tys)
|
|
416
|
+
map_tys.each do |k_ty, v_ty|
|
|
417
|
+
raise unless k_ty.is_a?(Type)
|
|
418
|
+
raise unless v_ty.is_a?(Type)
|
|
419
|
+
raise if k_ty.is_a?(Type::Union)
|
|
420
|
+
raise if k_ty.is_a?(Type::LocalArray)
|
|
421
|
+
raise if k_ty.is_a?(Type::LocalHash)
|
|
422
|
+
raise if k_ty.is_a?(Type::Array)
|
|
423
|
+
raise if k_ty.is_a?(Type::Hash)
|
|
424
|
+
end
|
|
425
|
+
@map_tys = map_tys
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
attr_reader :map_tys
|
|
429
|
+
|
|
430
|
+
def to_local_type(id)
|
|
431
|
+
base_ty = Type::Instance.new(Type::Builtin[:hash])
|
|
432
|
+
Type::LocalHash.new(id, base_ty)
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def globalize(env, visited, depth)
|
|
436
|
+
map_tys = {}
|
|
437
|
+
@map_tys.each do |k_ty, v_ty|
|
|
438
|
+
v_ty = v_ty.globalize(env, visited, depth)
|
|
439
|
+
if map_tys[k_ty]
|
|
440
|
+
map_tys[k_ty] = map_tys[k_ty].union(v_ty)
|
|
441
|
+
else
|
|
442
|
+
map_tys[k_ty] = v_ty
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
Elements.new(map_tys)
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def localize(env, alloc_site, depth)
|
|
449
|
+
map_tys = @map_tys.to_h do |k_ty, v_ty|
|
|
450
|
+
alloc_site2 = alloc_site.add_id(k_ty)
|
|
451
|
+
env, v_ty = v_ty.localize(env, alloc_site2, depth)
|
|
452
|
+
[k_ty, v_ty]
|
|
453
|
+
end
|
|
454
|
+
return env, Elements.new(map_tys)
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def limit_size(limit)
|
|
458
|
+
map_tys = {}
|
|
459
|
+
@map_tys.each do |k_ty, v_ty|
|
|
460
|
+
k_ty = k_ty.limit_size(limit)
|
|
461
|
+
v_ty = v_ty.limit_size(limit)
|
|
462
|
+
if map_tys[k_ty]
|
|
463
|
+
map_tys[k_ty] = map_tys[k_ty].union(v_ty)
|
|
464
|
+
else
|
|
465
|
+
map_tys[k_ty] = v_ty
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
Elements.new(map_tys)
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def screen_name(scratch)
|
|
472
|
+
s = @map_tys.map do |k_ty, v_ty|
|
|
473
|
+
k = k_ty.screen_name(scratch)
|
|
474
|
+
v = v_ty.screen_name(scratch)
|
|
475
|
+
"#{ k }=>#{ v }"
|
|
476
|
+
end.join(", ")
|
|
477
|
+
"{#{ s }}"
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def pretty_print(q)
|
|
481
|
+
q.group(9, "Elements[", "]") do
|
|
482
|
+
q.seplist(@map_tys) do |k_ty, v_ty|
|
|
483
|
+
q.group do
|
|
484
|
+
q.pp k_ty
|
|
485
|
+
q.text '=>'
|
|
486
|
+
q.group(1) do
|
|
487
|
+
q.breakable ''
|
|
488
|
+
q.pp v_ty
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
def consistent?(other, subst)
|
|
496
|
+
subst2 = subst.dup
|
|
497
|
+
other.map_tys.each do |k1, v1|
|
|
498
|
+
found = false
|
|
499
|
+
@map_tys.each do |k0, v0|
|
|
500
|
+
subst3 = subst2.dup
|
|
501
|
+
if k0.consistent?(k1, subst3) && v0.consistent?(v1, subst3)
|
|
502
|
+
subst2.replace(subst3)
|
|
503
|
+
found = true
|
|
504
|
+
break
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
return false unless found
|
|
508
|
+
end
|
|
509
|
+
subst.replace(subst2)
|
|
510
|
+
true
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
def substitute(subst, depth)
|
|
514
|
+
map_tys = {}
|
|
515
|
+
@map_tys.each do |k_ty_orig, v_ty_orig|
|
|
516
|
+
k_ty = k_ty_orig.substitute(subst, depth)
|
|
517
|
+
v_ty = v_ty_orig.substitute(subst, depth)
|
|
518
|
+
k_ty.each_child_global do |k_ty|
|
|
519
|
+
# This is a temporal hack to mitigate type explosion
|
|
520
|
+
k_ty = Type.any if k_ty.is_a?(Type::Array)
|
|
521
|
+
k_ty = Type.any if k_ty.is_a?(Type::Hash)
|
|
522
|
+
if map_tys[k_ty]
|
|
523
|
+
map_tys[k_ty] = map_tys[k_ty].union(v_ty)
|
|
524
|
+
else
|
|
525
|
+
map_tys[k_ty] = v_ty
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
Elements.new(map_tys)
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
def squash
|
|
533
|
+
all_k_ty, all_v_ty = Type.bot, Type.bot
|
|
534
|
+
@map_tys.each do |k_ty, v_ty|
|
|
535
|
+
all_k_ty = all_k_ty.union(k_ty)
|
|
536
|
+
all_v_ty = all_v_ty.union(v_ty)
|
|
537
|
+
end
|
|
538
|
+
return all_k_ty, all_v_ty
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
def [](key_ty)
|
|
542
|
+
val_ty = Type.bot
|
|
543
|
+
@map_tys.each do |k_ty, v_ty|
|
|
544
|
+
if k_ty.consistent?(key_ty, {})
|
|
545
|
+
val_ty = val_ty.union(v_ty)
|
|
546
|
+
end
|
|
547
|
+
end
|
|
548
|
+
val_ty
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
def update(idx, ty)
|
|
552
|
+
map_tys = @map_tys.dup
|
|
553
|
+
idx.each_child_global do |idx|
|
|
554
|
+
# This is a temporal hack to mitigate type explosion
|
|
555
|
+
idx = Type.any if idx.is_a?(Type::Array)
|
|
556
|
+
idx = Type.any if idx.is_a?(Type::Hash)
|
|
557
|
+
|
|
558
|
+
if map_tys[idx]
|
|
559
|
+
map_tys[idx] = map_tys[idx].union(ty)
|
|
560
|
+
else
|
|
561
|
+
map_tys[idx] = ty
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
Elements.new(map_tys)
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
def union(other)
|
|
568
|
+
return self if self == other
|
|
569
|
+
raise "Array::Elements merge Hash::Elements" if other.is_a?(Array::Elements)
|
|
570
|
+
|
|
571
|
+
map_tys = @map_tys.dup
|
|
572
|
+
other.map_tys.each do |k_ty, v_ty|
|
|
573
|
+
if map_tys[k_ty]
|
|
574
|
+
map_tys[k_ty] = map_tys[k_ty].union(v_ty)
|
|
575
|
+
else
|
|
576
|
+
map_tys[k_ty] = v_ty
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
Elements.new(map_tys)
|
|
581
|
+
end
|
|
582
|
+
end
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
class LocalHash < Type
|
|
586
|
+
def initialize(id, base_type)
|
|
587
|
+
@id = id
|
|
588
|
+
@base_type = base_type
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
attr_reader :id, :base_type
|
|
592
|
+
|
|
593
|
+
def inspect
|
|
594
|
+
"Type::LocalHash[#{ @id }]"
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
def screen_name(scratch)
|
|
598
|
+
#raise "LocalHash must not be included in signature"
|
|
599
|
+
"LocalHash!"
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
def globalize(env, visited, depth)
|
|
603
|
+
if visited[self] || depth <= 0
|
|
604
|
+
Type.any
|
|
605
|
+
else
|
|
606
|
+
visited[self] = true
|
|
607
|
+
elems = env.get_container_elem_types(@id)
|
|
608
|
+
if elems
|
|
609
|
+
elems = elems.globalize(env, visited, depth - 1)
|
|
610
|
+
else
|
|
611
|
+
elems = Hash::Elements.new({Type.any => Type.any})
|
|
612
|
+
end
|
|
613
|
+
Hash.new(elems, @base_type)
|
|
614
|
+
end
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
def get_method(mid, scratch)
|
|
618
|
+
@base_type.get_method(mid, scratch)
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
def consistent?(other, subst)
|
|
622
|
+
raise "must not be used"
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
end
|
|
626
|
+
end
|