yadriggy 1.0.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.
@@ -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