yadriggy 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,335 @@
1
+ # Copyright (C) 2017- Shigeru Chiba. All rights reserved.
2
+
3
+ require 'set'
4
+ require 'yadriggy/ruby_typecheck'
5
+
6
+ module Yadriggy
7
+
8
+ # Type checker for Ruby with type inference.
9
+ #
10
+ class RubyTypeInferer < RubyTypeChecker
11
+
12
+ # Binds a local variable name to a type.
13
+ # @param [TypeEnv] env a type environment.
14
+ # @param [ASTnode] ast a local variable name.
15
+ # @param [Type] var_type a type.
16
+ # @param [Boolean] is_def true if the variable is initialized there.
17
+ def bind_local_var(env, ast, var_type, is_def=true)
18
+ unless var_type.nil?
19
+ t = if UnionType.role(var_type)
20
+ ts = UnionType.role(var_type).types
21
+ UnionType.new(ts.map {|t| to_non_instance_type(t) })
22
+ else
23
+ ins_t = InstanceType.role(var_type)
24
+ to_non_instance_type(var_type)
25
+ end
26
+ lvt = LocalVarType.new(t.copy(OptionalRole), is_def ? ast : nil)
27
+ env.bind_name(ast, lvt)
28
+ @typetable[ast] = lvt
29
+ end
30
+ end
31
+
32
+ # @private
33
+ # When the initial value of a variable is an InstanceType,
34
+ # the type of the variable has to be a RubyCass type
35
+ # corresponding to that instance type.
36
+ # The variable type can be set to that InstanceType only when
37
+ # it is guaranteed that the value of the variable is never changed
38
+ # later.
39
+ def to_non_instance_type(t)
40
+ ins_t = InstanceType.role(t)
41
+ if ins_t.nil?
42
+ t
43
+ else
44
+ ins_t.supertype
45
+ end
46
+ end
47
+
48
+ # Note that obj.name += ... is regarded as obj.name=(obj.name + ...).
49
+ #
50
+ rule(Assign) do
51
+ rtype = type(ast.right)
52
+ left_expr = ast.left
53
+ if ast.op != :'=' # if op is += etc.
54
+ ltype = type(left_expr)
55
+ LocalVarType.role(ltype)&.definition = left_expr
56
+ ltype
57
+ elsif left_expr.is_a?(IdentifierOrCall)
58
+ vtype = type_env.bound_name?(left_expr)
59
+ if vtype.nil? # if a new name is found,
60
+ method_name = left_expr.name + '='
61
+ if is_attr_accessor?(ast, type_env, method_name.to_sym) # self.name=()?
62
+ call_expr = Call.make(name: method_name, args: [ ast.right ])
63
+ get_call_expr_type(call_expr, type_env, method_name)
64
+ else
65
+ bind_local_var(type_env, left_expr, rtype)
66
+ end
67
+ else
68
+ type_assert(rtype <= vtype, 'incompatible assignment type')
69
+ LocalVarType.role(vtype)&.definition = left_expr
70
+ vtype
71
+ end
72
+ elsif left_expr.is_a?(Call) && left_expr.op == :'.' # obj.name=()?
73
+ method_name = left_expr.name.name + '='
74
+ call_expr = Call.make(receiver: left_expr.receiver,
75
+ name: method_name, args: [ ast.right ],
76
+ parent: ast.parent)
77
+ get_call_expr_type(call_expr, type_env, method_name)
78
+ elsif left_expr.is_a?(InstanceVariable) # @var = ..., @@cvar = ..., @var += ...
79
+ get_instance_variable_type(type_env.context, left_expr, true,
80
+ InstanceType.role(rtype)&.supertype || rtype)
81
+ elsif left_expr.is_a?(GlobalVariable)
82
+ get_instance_variable_type(:global_variables, left_expr, true,
83
+ InstanceType.role(rtype)&.supertype || rtype)
84
+ else
85
+ type(left_expr) # a[i] = ..., <expr> = ... or <expr> += ...
86
+ end
87
+ end
88
+
89
+ # @private
90
+ def is_attr_accessor?(expr, tenv, name)
91
+ self_t = type_env.context
92
+ !self_t.nil? &&
93
+ (self_t.method_defined?(name) ||
94
+ self_t.private_method_defined?(name))
95
+ end
96
+
97
+ rule(Name) do
98
+ tenv = type_env
99
+ name_ast = ast
100
+ type = tenv.bound_name?(name_ast)
101
+ if type
102
+ type
103
+ else
104
+ method_name = name_ast.to_sym
105
+ if is_attr_accessor?(name_ast, tenv, method_name.to_sym)
106
+ call_expr = Call.make(name: method_name, parent: name_ast.parent)
107
+ get_call_expr_type(call_expr, tenv, method_name)
108
+ else
109
+ # this name is a free variable.
110
+ v = name_ast.value
111
+ if v == Undef
112
+ DynType
113
+ else
114
+ InstanceType.new(v)
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ rule(Const) do
121
+ v = ast.value
122
+ if v == Undef
123
+ DynType
124
+ else
125
+ InstanceType.new(v)
126
+ end
127
+ end
128
+
129
+ rule(GlobalVariable) do
130
+ v = ast.value
131
+ if v == Undef
132
+ get_instance_variable_type(:global_variables, ast, false, DynType)
133
+ else
134
+ get_instance_variable_type(:global_variables, ast, true, RubyClass[v.class])
135
+ end
136
+ end
137
+
138
+ rule(InstanceVariable) do
139
+ v = ast.value
140
+ key = type_env.context
141
+ if v == Undef
142
+ get_instance_variable_type(key, ast, false, DynType)
143
+ else
144
+ get_instance_variable_type(key, ast, true, RubyClass[v.class])
145
+ end
146
+ end
147
+
148
+ # Obtains the type of the given instance variable `ivar` declared
149
+ # in the given class (i.e. module) or the instance object `key`.
150
+ # If the type of `ivar` is not defined, `value_type` is recorded
151
+ # as its type.
152
+ #
153
+ # @param [Module|Object] key the key when looking into the typedef table.
154
+ # @param [InstanceVariable] ivar an instance variable.
155
+ # @param [Boolean] is_valid_type true if `value_type` is valid.
156
+ # @param [Type] value_type the type suggested for `ivar`.
157
+ # @return [Type] the type of `ivar`.
158
+ def get_instance_variable_type(key, ivar, is_valid_type, value_type)
159
+ td = add_typedef(key)
160
+ ivar_t = td[ivar]
161
+ if ivar_t.nil?
162
+ td[ivar] = value_type
163
+ else
164
+ type_assert_subsume(ivar_t, value_type,
165
+ "bad type value for #{ivar.name}") if is_valid_type
166
+ ivar_t
167
+ end
168
+ end
169
+
170
+ # +@, -@, !, ~, not
171
+ rule(Unary) do
172
+ expr_t = type(ast.expr)
173
+ op = ast.op
174
+ if op == :! || op == :not
175
+ RubyClass::Boolean
176
+ else
177
+ if (op == :~ && expr_t <= RubyClass::Integer) ||
178
+ ((op == :+@ || op == :-@) && expr_t <= RubyClass::Numeric)
179
+ expr_t
180
+ else
181
+ call_expr = Call.make(receiver: ast.expr, name: op,
182
+ parent: ast.parent)
183
+ get_call_expr_type(call_expr, type_env, op)
184
+ end
185
+ end
186
+ end
187
+
188
+ rule(Binary) do
189
+ right_t = type(ast.right)
190
+ left_t = type(ast.left)
191
+ binary_type(ast, right_t, left_t)
192
+ end
193
+
194
+ # @private
195
+ def binary_type(bin_expr, right_t, left_t)
196
+ op = bin_expr.op
197
+ case op
198
+ when :'&&', :'||', :and, :or # not overridable
199
+ return UnionType.new([right_t, left_t])
200
+ when :>, :>=, :<, :<=, :==, :===, :!=
201
+ if left_t <= RubyClass::Numeric
202
+ return RubyClass::Boolean
203
+ end
204
+ when :**, :*, :/, :%, :+, :-
205
+ if left_t <= RubyClass::Numeric
206
+ if left_t <= RubyClass::Float || right_t <= RubyClass::Float
207
+ return RubyClass::Float
208
+ else
209
+ return RubyClass::Integer
210
+ end
211
+ end
212
+ when :<<, :>>, :&, :|, :^
213
+ return RubyClass::Integer if left_t <= RubyClass::Integer
214
+ # when :=~, :!~, :<=>
215
+ end
216
+
217
+ if left_t <= RubyClass::String
218
+ if op == :% || op == :+ || op == :<<
219
+ return RubyClass::String
220
+ elsif op == :=~ || op == :<=>
221
+ return UnionType.new(RubyClass::Integer, RubyClass::NilClass)
222
+ elsif op == :!~
223
+ return RubyClass::Boolean
224
+ end
225
+ end
226
+
227
+ call_expr = Call.make(receiver: bin_expr.left, name: op,
228
+ args: [bin_expr.right], parent: bin_expr.parent)
229
+ return get_call_expr_type(call_expr, type_env, op)
230
+ end
231
+
232
+ rule(Dots) do
233
+ CompositeType.new(RubyClass::Range, type(ast.left))
234
+ end
235
+
236
+ rule(ArrayLiteral) do
237
+ ele = ast.elements
238
+ if 0 < ele.size && ele.size < 17
239
+ t = type(ele[0])
240
+ et = InstanceType.role(t)&.supertype || t
241
+ if ele.all? {|e| type(e) <= et }
242
+ CompositeType.new(RubyClass::Array, et)
243
+ else
244
+ RubyClass::Array
245
+ end
246
+ else
247
+ RubyClass::Array
248
+ end
249
+ end
250
+
251
+ # Variable access or a method call without arguments.
252
+ # This implementation invokes the method if the expression is
253
+ # a method call. It returns a InstanceType containing the
254
+ # resulting value.
255
+ #
256
+ rule(VariableCall) do
257
+ type = type_env.bound_name?(ast)
258
+ if type
259
+ type
260
+ else
261
+ call_expr = Call.make(name: ast.name, parent: ast.parent)
262
+ get_call_expr_type(call_expr, type_env, call_expr.name.to_sym)
263
+ end
264
+ end
265
+
266
+ # @private
267
+ # Overrides {RubyTypeChecker#get_return_type}.
268
+ #
269
+ def get_return_type(an_ast, mthd, new_tenv, arg_types)
270
+ m_ast = an_ast.root.reify(mthd)
271
+ type_assert_false(m_ast.nil?, "no source code: for #{mthd}")
272
+ (@syntax.check(m_ast.tree) || @syntax.raise_error) if @syntax
273
+
274
+ m_ast.tree.params.each_with_index do |p, i|
275
+ bind_local_var(new_tenv, p, arg_types[i])
276
+ end
277
+
278
+ nparams = m_ast.tree.params.length
279
+ m_ast.tree.optionals.each_with_index do |p, i|
280
+ bind_local_var(new_tenv, p, arg_types[nparams + i])
281
+ end
282
+
283
+ mtype = MethodType.role(type(m_ast.tree, new_tenv))
284
+ type_assert(mtype, 'not a method type')
285
+ type_assert_params(mtype.params, arg_types, 'argument type mismatch')
286
+ mtype.result
287
+ end
288
+
289
+ rule(ArrayRef) do
290
+ array_t = CompositeType.role(type(ast.array))
291
+ if array_t&.ruby_class == Array
292
+ array_t.first_arg
293
+ else
294
+ DynType
295
+ end
296
+ end
297
+
298
+ rule(ForLoop) do
299
+ set_type = type(ast.set)
300
+ var_type = CompositeType.role(set_type)&.first_arg
301
+ ast.vars.each do |v|
302
+ bind_local_var(type_env, v, var_type.nil? ? DynType : var_type)
303
+ end
304
+ type(ast.body)
305
+ DynType
306
+ end
307
+
308
+ rule(Def) do
309
+ ptypes = ast.params.map do |v|
310
+ t = type_env.bound_name?(v.name)
311
+ t || DynType
312
+ end
313
+
314
+ ptypes = ptypes + ast.optionals.map do |v|
315
+ t = type_env.bound_name?(v[0].name)
316
+ t || type(v[1])
317
+ end
318
+
319
+ mtype = MethodType.new(ast, ptypes, DynType)
320
+ type_assert_later do
321
+ s = type_env.new_tenv
322
+ type_parameters(ast, s) # ignore parameter types
323
+ body_t = type(ast.body, s)
324
+ res_t = if ast.rescue.nil?
325
+ body_t
326
+ else
327
+ UnionType.make(body_t, type(ast.rescue, s))
328
+ end
329
+ type_assert_subsume(mtype.result, res_t, 'bad result type')
330
+ end
331
+ mtype
332
+ end
333
+
334
+ end
335
+ end
@@ -0,0 +1,168 @@
1
+ # Copyright (C) 2017- Shigeru Chiba. All rights reserved.
2
+
3
+ require 'ripper'
4
+ require 'pry'
5
+
6
+ module Yadriggy
7
+ # @private
8
+ # Retrieves source code in the S-expression style.
9
+ class SourceCode
10
+
11
+ # Gets an S-expression.
12
+ #
13
+ def self.get_sexp(proc)
14
+ return nil unless proc.is_a?(Proc) || proc.is_a?(Method) ||
15
+ proc.is_a?(UnboundMethod)
16
+
17
+ file_name, line = proc.source_location
18
+ return nil if file_name.nil?
19
+ src = if file_name == "(pry)" then read_pry_history
20
+ else File.read(file_name) end
21
+ prog = Ripper.sexp(src)
22
+ prog && [file_name, find_sexp(prog, line)]
23
+ end
24
+
25
+ def self.read_pry_history
26
+ cmds = Pry.commands
27
+
28
+ # The line number seems wrong if the source code is in pry_history.
29
+ # To correct the line number, a blank line is added to source.
30
+ source = "\n"
31
+
32
+ lineno = 0
33
+ lineno1 = Pry.history.original_lines
34
+ File.foreach(Pry.config.history.file) do |line|
35
+ lineno += 1
36
+ if lineno > lineno1
37
+ if cmds.select {|k,v| v.matches?(line) }.empty?
38
+ source << line
39
+ else
40
+ # source << "\n"
41
+ end
42
+ end
43
+ end
44
+ source
45
+ end
46
+
47
+ def self.min(a, b)
48
+ if a < b then a else b end
49
+ end
50
+
51
+ def self.max(a, b)
52
+ if a > b then a else b end
53
+ end
54
+
55
+ def self.find_sexp(prog, line)
56
+ find_sexp2(prog, line, [1, nil])
57
+ end
58
+
59
+ # @param [Array] current the current location `[line, block]`
60
+ def self.find_sexp2(prog, line, current)
61
+ if prog.nil? || !prog.is_a?(Array)
62
+ return nil
63
+ else
64
+ t = prog[0]
65
+ if t == :@ident || t == :@tstring_content || t == :@const ||
66
+ t == :@int || t == :@float || t == :@kw || t == :@label ||
67
+ t == :@gvar || t == :@CHAR
68
+ #current[0] = prog[2][0]
69
+ current_line = prog[2][0]
70
+ if line < current_line && !current[1].nil?
71
+ return current[1]
72
+ else
73
+ current[0] = current_line
74
+ return nil
75
+ end
76
+ else
77
+ is_block = (t == :brace_block || t == :do_block ||
78
+ t == :def || t == :defs || t == :lambda)
79
+ if is_block && line == current[0] || def_at?(line, t, prog)
80
+ return prog
81
+ else
82
+ current[1] = nil
83
+ prog.each do |e|
84
+ r = find_sexp2(e, line, current)
85
+ return r unless r.nil?
86
+ end
87
+ if is_block
88
+ if line <= current[0]
89
+ return prog
90
+ else
91
+ current[1] = prog
92
+ return nil
93
+ end
94
+ else
95
+ nil
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ def self.def_at?(line, t, prog)
103
+ (t == :def || t == :defs) &&
104
+ prog[1].is_a?(Array) && prog[1][0] == :@ident &&
105
+ prog[1][2][0] == line
106
+ end
107
+
108
+ class Cons
109
+ include Enumerable
110
+ attr_accessor :head, :tail
111
+
112
+ def initialize(head, tail=nil)
113
+ @head = head
114
+ @tail = tail
115
+ end
116
+
117
+ def self.list(*elements)
118
+ list = nil
119
+ elements.reverse_each do |e|
120
+ list = Cons.new(e, list)
121
+ end
122
+ list
123
+ end
124
+
125
+ def self.append!(lst1, lst2)
126
+ if lst1 == nil
127
+ lst2
128
+ elsif lst2 == nil
129
+ lst1
130
+ else
131
+ p = lst1
132
+ while p.tail != nil
133
+ p = p.tail
134
+ end
135
+ p.tail = lst2
136
+ lst1
137
+ end
138
+ end
139
+
140
+ def size()
141
+ size = 0
142
+ list = self
143
+ while list != nil
144
+ list = list.tail
145
+ size += 1
146
+ end
147
+ size
148
+ end
149
+
150
+ def each()
151
+ list = self
152
+ while list != nil
153
+ yield list.head
154
+ list = list.tail
155
+ end
156
+ end
157
+
158
+ def fold(acc)
159
+ list = self
160
+ while list != nil
161
+ acc = yield acc, list.head
162
+ list = list.tail
163
+ end
164
+ acc
165
+ end
166
+ end
167
+ end
168
+ end