rdl 1.1.1 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +6 -0
  3. data/README.md +211 -32
  4. data/gemfiles/Gemfile.travis +1 -1
  5. data/lib/rdl.rb +85 -18
  6. data/lib/rdl/info.rb +74 -0
  7. data/lib/rdl/query.rb +8 -9
  8. data/lib/rdl/typecheck.rb +1057 -0
  9. data/lib/rdl/types/annotated_arg.rb +5 -5
  10. data/lib/rdl/types/{nil.rb → bot.rb} +9 -13
  11. data/lib/rdl/types/dependent_arg.rb +5 -5
  12. data/lib/rdl/types/dots_query.rb +2 -0
  13. data/lib/rdl/types/finitehash.rb +67 -24
  14. data/lib/rdl/types/generic.rb +13 -21
  15. data/lib/rdl/types/intersection.rb +9 -8
  16. data/lib/rdl/types/method.rb +30 -32
  17. data/lib/rdl/types/nominal.rb +22 -16
  18. data/lib/rdl/types/optional.rb +8 -22
  19. data/lib/rdl/types/parser.racc +8 -3
  20. data/lib/rdl/types/parser.tab.rb +131 -118
  21. data/lib/rdl/types/singleton.rb +15 -10
  22. data/lib/rdl/types/structural.rb +6 -6
  23. data/lib/rdl/types/top.rb +6 -6
  24. data/lib/rdl/types/tuple.rb +56 -24
  25. data/lib/rdl/types/type.rb +9 -0
  26. data/lib/rdl/types/type_inferencer.rb +1 -1
  27. data/lib/rdl/types/union.rb +52 -26
  28. data/lib/rdl/types/var.rb +7 -6
  29. data/lib/rdl/types/vararg.rb +5 -6
  30. data/lib/rdl/types/wild_query.rb +9 -2
  31. data/lib/rdl/util.rb +9 -7
  32. data/lib/rdl/wrap.rb +90 -72
  33. data/lib/rdl_types.rb +2 -2
  34. data/rdl.gemspec +6 -8
  35. data/test/test_alias.rb +4 -3
  36. data/test/test_contract.rb +5 -4
  37. data/test/test_dsl.rb +2 -1
  38. data/test/test_generic.rb +30 -26
  39. data/test/test_intersection.rb +3 -3
  40. data/test/test_le.rb +129 -61
  41. data/test/test_lib_types.rb +3 -2
  42. data/test/test_member.rb +33 -46
  43. data/test/test_parser.rb +113 -116
  44. data/test/test_query.rb +2 -1
  45. data/test/test_rdl.rb +64 -6
  46. data/test/test_rdl_type.rb +3 -2
  47. data/test/test_type_contract.rb +30 -12
  48. data/test/test_typecheck.rb +893 -0
  49. data/test/test_types.rb +50 -54
  50. data/test/test_wrap.rb +2 -1
  51. data/types/ruby-2.x/_aliases.rb +13 -2
  52. data/types/ruby-2.x/bigdecimal.rb +60 -85
  53. data/types/ruby-2.x/bignum.rb +80 -119
  54. data/types/ruby-2.x/complex.rb +33 -40
  55. data/types/ruby-2.x/fixnum.rb +81 -120
  56. data/types/ruby-2.x/float.rb +79 -116
  57. data/types/ruby-2.x/integer.rb +187 -22
  58. data/types/ruby-2.x/nil.rb +12 -0
  59. data/types/ruby-2.x/numeric.rb +38 -38
  60. data/types/ruby-2.x/object.rb +3 -3
  61. data/types/ruby-2.x/random.rb +2 -0
  62. data/types/ruby-2.x/range.rb +20 -19
  63. data/types/ruby-2.x/rational.rb +40 -40
  64. data/types/ruby-2.x/regexp.rb +4 -4
  65. data/types/ruby-2.x/string.rb +15 -17
  66. metadata +17 -16
  67. data/lib/rdl/types/.#lexer.rex +0 -1
data/lib/rdl.rb CHANGED
@@ -1,20 +1,27 @@
1
- require 'set'
2
1
  require 'delegate'
3
- require 'require_all'
2
+ require 'digest'
3
+ require 'set'
4
+ require 'parser/current'
4
5
 
5
6
  module RDL
6
7
  end
7
8
 
8
- require_relative 'rdl/config.rb'
9
+ require 'rdl/config.rb'
9
10
  def RDL.config
10
11
  yield(RDL::Config.instance)
11
12
  end
13
+ require 'rdl/info.rb'
12
14
 
13
- # Hash from class name to method name to :pre/:post/:type to array of contracts
14
- # class names are strings (because they need to be manipulated in case they include ::)
15
- # (class names may have Util.add_singleton_marker applied to them to indicate they're singleton classes.)
16
- # method names are symbols
17
- $__rdl_contracts = Hash.new
15
+ # Method/variable info table with kinds:
16
+ # For methods
17
+ # :pre to array of precondition contracts
18
+ # :post to array of postcondition contracts
19
+ # :type to array of types
20
+ # :source_location to [filename, linenumber] location of most recent definition
21
+ # :typecheck - boolean that is true if method should be statically type checked
22
+ # For variables
23
+ # :type to type
24
+ $__rdl_info = RDL::Info.new
18
25
 
19
26
  # Map from full_method_name to number of times called when wrapped
20
27
  $__rdl_wrapped_calls = Hash.new 0
@@ -32,26 +39,86 @@ $__rdl_aliases = Hash.new
32
39
  # method is a symbol
33
40
  $__rdl_to_wrap = Set.new
34
41
 
42
+ # Same as $__rdl_to_wrap, but records [class, method] pairs to type check when they're defined
43
+ $__rdl_to_typecheck_now = Set.new
44
+
35
45
  # List of contracts that should be applied to the next method definition
36
46
  $__rdl_deferred = []
37
47
 
38
48
  # Create switches to control whether wrapping happens and whether
39
49
  # contracts are checked. These need to be created before rdl/wrap.rb
40
50
  # is loaded.
41
- require_rel 'rdl/switch.rb'
51
+ require 'rdl/switch.rb'
42
52
  $__rdl_wrap_switch = RDL::Switch.new
43
53
  $__rdl_contract_switch = RDL::Switch.new
44
54
 
45
- require_rel 'rdl/types/*.rb'
46
- require_rel 'rdl/contracts/*.rb'
47
- require_rel 'rdl/util.rb'
48
- require_rel 'rdl/wrap.rb'
49
- require_rel 'rdl/query.rb'
50
- #require_rel 'rdl/stats.rb'
55
+ require 'rdl/types/type.rb'
56
+ require 'rdl/types/annotated_arg.rb'
57
+ require 'rdl/types/bot.rb'
58
+ require 'rdl/types/dependent_arg.rb'
59
+ require 'rdl/types/dots_query.rb'
60
+ require 'rdl/types/finitehash.rb'
61
+ require 'rdl/types/generic.rb'
62
+ require 'rdl/types/intersection.rb'
63
+ require 'rdl/types/lexer.rex.rb'
64
+ require 'rdl/types/method.rb'
65
+ require 'rdl/types/singleton.rb'
66
+ require 'rdl/types/nominal.rb'
67
+ require 'rdl/types/optional.rb'
68
+ require 'rdl/types/parser.tab.rb'
69
+ require 'rdl/types/structural.rb'
70
+ require 'rdl/types/top.rb'
71
+ require 'rdl/types/tuple.rb'
72
+ require 'rdl/types/type_query.rb'
73
+ require 'rdl/types/union.rb'
74
+ require 'rdl/types/var.rb'
75
+ require 'rdl/types/vararg.rb'
76
+ require 'rdl/types/wild_query.rb'
77
+
78
+ require 'rdl/contracts/contract.rb'
79
+ require 'rdl/contracts/and.rb'
80
+ require 'rdl/contracts/flat.rb'
81
+ require 'rdl/contracts/or.rb'
82
+ require 'rdl/contracts/proc.rb'
83
+
84
+ require 'rdl/util.rb'
85
+ require 'rdl/wrap.rb'
86
+ require 'rdl/query.rb'
87
+ require 'rdl/typecheck.rb'
88
+ #require_relative 'rdl/stats.rb'
51
89
 
52
90
  $__rdl_parser = RDL::Type::Parser.new
53
91
 
92
+ # Map from file names to [digest, cache] where 2nd elt maps
93
+ # :ast to the AST
94
+ # :line_defs maps linenumber to AST for def at that line
95
+ $__rdl_ruby_parser_cache = Hash.new
96
+
97
+ # Some generally useful types; not really a big deal to do this since
98
+ # NominalTypes are cached, but these names are shorter to type
99
+ $__rdl_nil_type = RDL::Type::NominalType.new NilClass # actually creates singleton type
100
+ $__rdl_top_type = RDL::Type::TopType.new
101
+ $__rdl_bot_type = RDL::Type::BotType.new
102
+ $__rdl_object_type = RDL::Type::NominalType.new Object
103
+ $__rdl_true_type = RDL::Type::NominalType.new TrueClass # actually creates singleton type
104
+ $__rdl_false_type = RDL::Type::NominalType.new FalseClass # also singleton type
105
+ $__rdl_bool_type = RDL::Type::UnionType.new($__rdl_true_type, $__rdl_false_type)
106
+ $__rdl_fixnum_type = RDL::Type::NominalType.new Fixnum
107
+ $__rdl_bignum_type = RDL::Type::NominalType.new Bignum
108
+ $__rdl_float_type = RDL::Type::NominalType.new Float
109
+ $__rdl_complex_type = RDL::Type::NominalType.new Complex
110
+ $__rdl_rational_type = RDL::Type::NominalType.new Rational
111
+ $__rdl_integer_type = RDL::Type::UnionType.new($__rdl_fixnum_type, $__rdl_bignum_type)
112
+ $__rdl_numeric_type = RDL::Type::NominalType.new Numeric
113
+ $__rdl_string_type = RDL::Type::NominalType.new String
114
+ $__rdl_array_type = RDL::Type::NominalType.new Array
115
+ $__rdl_hash_type = RDL::Type::NominalType.new Hash
116
+ $__rdl_symbol_type = RDL::Type::NominalType.new Symbol
117
+ $__rdl_range_type = RDL::Type::NominalType.new Range
118
+ $__rdl_regexp_type = RDL::Type::NominalType.new Regexp
119
+ $__rdl_standard_error_type = RDL::Type::NominalType.new StandardError
120
+
54
121
  # Hash from special type names to their values
55
- $__rdl_special_types = {'%any' => RDL::Type::TopType.new,
56
- '%bool' => RDL::Type::UnionType.new(RDL::Type::NominalType.new(TrueClass),
57
- RDL::Type::NominalType.new(FalseClass)) }
122
+ $__rdl_special_types = {'%any' => $__rdl_top_type,
123
+ '%bot' => $__rdl_bot_type,
124
+ '%bool' => $__rdl_bool_type}
@@ -0,0 +1,74 @@
1
+ class RDL::Info
2
+ # map from klass (String) to label (Symbol) to kind (Symbol) to either array or some other value
3
+ #
4
+ # class names are strings because they need to be manipulated in case they include ::
5
+ # (class names may have Util.add_singleton_marker applied to them to indicate they're singleton classes.)
6
+
7
+ attr_accessor :info
8
+
9
+ def initialize
10
+ @info = Hash.new
11
+ end
12
+
13
+ # [+kind+] must map to an array
14
+ def add(klass, label, kind, val)
15
+ klass = klass.to_s
16
+ label = label.to_sym
17
+ @info[klass] = {} unless @info[klass]
18
+ @info[klass][label] = {} unless @info[klass][label]
19
+ @info[klass][label][kind] = [] unless @info[klass][label][kind]
20
+ @info[klass][label][kind] << val
21
+ end
22
+
23
+ # if no prev info for kind, set to val and return true
24
+ # if prev info for kind, return true if prev == val and false otherwise
25
+ def set(klass, label, kind, val)
26
+ klass = klass.to_s
27
+ label = label.to_sym
28
+ @info[klass] = {} unless @info[klass]
29
+ @info[klass][label] = {} unless @info[klass][label]
30
+ if @info[klass][label].has_key? kind
31
+ return (val == @info[klass][label][kind])
32
+ else
33
+ @info[klass][label][kind] = val
34
+ return true
35
+ end
36
+ end
37
+
38
+ # replace info for kind
39
+ def set!(klass, label, kind, val)
40
+ klass = klass.to_s
41
+ label = label.to_sym
42
+ @info[klass] = {} unless @info[klass]
43
+ @info[klass][label] = {} unless @info[klass][label]
44
+ @info[klass][label][kind] = val
45
+ end
46
+
47
+ def has?(klass, label, kind)
48
+ klass = klass.to_s
49
+ label = label.to_sym
50
+ return (@info.has_key? klass) &&
51
+ (@info[klass].has_key? label) &&
52
+ (@info[klass][label].has_key? kind)
53
+ end
54
+
55
+ def has_any?(klass, label, kinds)
56
+ klass = klass.to_s
57
+ label = label.to_sym
58
+ return (@info.has_key? klass) &&
59
+ (@info[klass].has_key? label) &&
60
+ (kinds.any? { |k| @info[klass][label].has_key? k })
61
+ end
62
+
63
+ # eventually replace with Hash#dig
64
+ def get(klass, label, kind)
65
+ klass = klass.to_s
66
+ label = label.to_sym
67
+ t1 = @info[klass]
68
+ return t1 if t1.nil?
69
+ t2 = t1[label]
70
+ return t2 if t2.nil?
71
+ return t2[kind]
72
+ # return @info[klass][label][kind]
73
+ end
74
+ end
@@ -17,26 +17,25 @@ class RDL::Query
17
17
  # klass = self.class.to_s
18
18
  # meth = q.to_sym
19
19
  end
20
- return nil unless RDL::Wrap.has_contracts?(klass, meth, :type)
21
- return RDL::Wrap.get_contracts(klass, meth, :type)
20
+ return $__rdl_info.get(klass, meth, :type)
22
21
  end
23
22
 
24
23
  # Return an ordered list of all method types of a class. The query should be a class name.
25
24
  def self.class_query(q)
26
25
  klass = q.to_s
27
- return nil unless $__rdl_contracts.has_key? klass
26
+ return nil unless $__rdl_info.info.has_key? klass
28
27
  cls_meths = []
29
28
  cls_klass = RDL::Util.add_singleton_marker(klass)
30
- if $__rdl_contracts.has_key? cls_klass then
31
- $__rdl_contracts[cls_klass].each { |meth, kinds|
29
+ if $__rdl_info.info.has_key? cls_klass then
30
+ $__rdl_info.info[cls_klass].each { |meth, kinds|
32
31
  if kinds.has_key? :type then
33
32
  kinds[:type].each { |t| cls_meths << [meth.to_s, t] }
34
33
  end
35
34
  }
36
35
  end
37
36
  inst_meths = []
38
- if $__rdl_contracts.has_key? klass then
39
- $__rdl_contracts[klass].each { |meth, kinds|
37
+ if $__rdl_info.info.has_key? klass then
38
+ $__rdl_info.info[klass].each { |meth, kinds|
40
39
  if kinds.has_key? :type then
41
40
  kinds[:type].each { |t| inst_meths << [meth.to_s, t] }
42
41
  end
@@ -52,12 +51,12 @@ class RDL::Query
52
51
  def self.method_type_query(q)
53
52
  q = $__rdl_parser.scan_str "#Q #{q}"
54
53
  result = []
55
- $__rdl_contracts.each { |klass, meths|
54
+ $__rdl_info.info.each { |klass, meths|
56
55
  meths.each { |meth, kinds|
57
56
  if kinds.has_key? :type then
58
57
  kinds[:type].each { |t|
59
58
  if q.match(t)
60
- result << [RDL::Util.pretty_name(klass, meth), t]
59
+ result << [RDL::Util.pp_klass_method(klass, meth), t]
61
60
  end
62
61
  }
63
62
  end
@@ -0,0 +1,1057 @@
1
+ module RDL::Typecheck
2
+
3
+ class StaticTypeError < StandardError; end
4
+
5
+ @@empty_hash_type = RDL::Type::FiniteHashType.new(Hash.new)
6
+ @@asgn_to_var = { lvasgn: :lvar, ivasgn: :ivar, cvasgn: :cvar, gvasgn: :gvar }
7
+
8
+ # Create mapping from file/line numbers to the def that appears at that location
9
+ class ASTMapper < AST::Processor
10
+ attr_accessor :line_defs
11
+
12
+ def initialize(file)
13
+ @file = file
14
+ @line_defs = Hash.new # map from line numbers to defs
15
+ end
16
+
17
+ def handler_missing(node)
18
+ node.children.each { |n| process n if n.is_a?(AST::Node) }
19
+ end
20
+
21
+ def on_def(node)
22
+ # (def name args body)
23
+ name, _, body = *node
24
+ if @line_defs[node.loc.line]
25
+ raise RuntimeError, "Multiple defs per line (#{name} and #{@line_defs[node.loc.line].children[1]} in #{@file}) currently not allowed"
26
+ end
27
+ @line_defs[node.loc.line] = node
28
+ process body
29
+ node.updated(nil, nil)
30
+ end
31
+
32
+ def on_defs(node)
33
+ # (defs (self) name args body)
34
+ _, name, _, body = *node
35
+ if @line_defs[node.loc.line]
36
+ raise RuntimeError, "Multiple defs per line (#{name} and #{@line_defs[node.loc.line].children[1]} in #{@file}) currently not allowed"
37
+ end
38
+ @line_defs[node.loc.line] = node
39
+ process body
40
+ node.updated(nil, nil)
41
+ end
42
+
43
+ end
44
+
45
+ # Local variable environment
46
+ # tracks the types of local variables, and whether they're "fixed," i.e.,
47
+ # whether they should be treated flow-insensitively
48
+ class Env
49
+ attr_accessor :env
50
+
51
+ # [+ params +] is a map from Symbols to Types. This initial variable mapping
52
+ # is added to the Env, and these initial mappings are fixed. If params
53
+ # is nil, initial Env is empty.
54
+ def initialize(params = nil)
55
+ @env = Hash.new
56
+ unless params.nil?
57
+ params.each_pair { |var, typ|
58
+ @env[var] = {type: typ, fixed: true}
59
+ }
60
+ end
61
+ end
62
+
63
+ # [+ var +] is a Symbol
64
+ def [](var)
65
+ return @env[var][:type]
66
+ end
67
+
68
+ def bind(var, typ)
69
+ raise RuntimeError, "Can't update variable with fixed type" if @env[var] && @env[var][:fixed]
70
+ result = Env.new
71
+ result.env = @env.merge(var => {type: typ, fixed: false})
72
+ return result
73
+ end
74
+
75
+ def has_key?(var)
76
+ return @env.has_key?(var)
77
+ end
78
+
79
+ def fix(var, typ)
80
+ raise RuntimeError, "Can't fix type of already-bound variable" if @env[var]
81
+ result = Env.new
82
+ result.env = @env.merge(var => {type: typ, fixed: true})
83
+ return result
84
+ end
85
+
86
+ def fixed?(var)
87
+ return @env[var] && @env[var][:fixed]
88
+ end
89
+
90
+ def ==(other)
91
+ return false unless other.is_a? Env
92
+ return @env == other.env
93
+ end
94
+
95
+ # merges bindings in self with bindings in other, preferring bindings in other if there is a common key
96
+ def merge(other)
97
+ result = Env.new
98
+ result.env = @env.merge(other.env)
99
+ return result
100
+ end
101
+
102
+ # [+ envs +] is Array<Env>
103
+ # any elts of envs that are nil are discarded
104
+ # returns new Env where every key is mapped to the union of its bindings in the envs
105
+ # any fixed binding in any env must be fixed in all envs and at the same type
106
+ def self.join(e, *envs)
107
+ raise RuntimeError, "Expecting AST, got #{e.class}" unless e.is_a? AST::Node
108
+ env = Env.new
109
+ envs.delete(nil)
110
+ return env if envs.empty?
111
+ return envs[0] if envs.size == 1
112
+ first = envs[0]
113
+ rest = envs[1..-1]
114
+ first.env.each_pair { |var, h|
115
+ first_typ = h[:type]
116
+ if h[:fixed]
117
+ error :inconsistent_var_type, [var.to_s], e unless rest.all? { |other| (other.fixed? var) || (not (other.has_key? var)) }
118
+ neq = []
119
+ rest.each { |other|
120
+ other_typ = other[var]
121
+ neq << other_typ unless first_typ == other_typ
122
+ }
123
+ error :inconsistent_var_type_type, [var.to_s, (first_typ + neq).map { |t| t.to_s }.join(' and ')], e unless neq.empty?
124
+ env.env[var] = {type: h[:type], fixed: true}
125
+ else
126
+ typ = RDL::Type::UnionType.new(first_typ, *rest.map { |other| ((other.has_key? var) && other[var]) || $__rdl_nil_type })
127
+ typ = typ.canonical
128
+ env.env[var] = {type: typ, fixed: false}
129
+ end
130
+ }
131
+ return env
132
+ end
133
+ end
134
+
135
+
136
+ # Call block with new Hash that is the same as Hash [+ scope +] except mappings in [+ elts +] have been merged.
137
+ # When block returns, copy out mappings in the new Hash to [+ scope +] except keys in [+ elts +].
138
+ def self.scope_merge(scope, **elts)
139
+ new_scope = scope.merge(**elts)
140
+ r = yield(new_scope)
141
+ new_scope.each_pair { |k,v|
142
+ scope[k] = v unless elts.has_key? k
143
+ }
144
+ return r
145
+ end
146
+
147
+ # report msg at ast's loc
148
+ def self.error(reason, args, ast)
149
+ raise StaticTypeError, ("\n" + (Parser::Diagnostic.new :error, reason, args, ast.loc.expression).render.join("\n"))
150
+ end
151
+
152
+ def self.typecheck(klass, meth)
153
+ file, line = $__rdl_info.get(klass, meth, :source_location)
154
+ raise RuntimeError, "static type checking in irb not supported" if file == "(irb)"
155
+ digest = Digest::MD5.file file
156
+ cache_hit = (($__rdl_ruby_parser_cache.has_key? file) &&
157
+ ($__rdl_ruby_parser_cache[file][0] == digest))
158
+ unless cache_hit
159
+ file_ast = Parser::CurrentRuby.parse_file file
160
+ mapper = ASTMapper.new(file)
161
+ mapper.process(file_ast)
162
+ cache = {ast: file_ast, line_defs: mapper.line_defs}
163
+ $__rdl_ruby_parser_cache[file] = [digest, cache]
164
+ end
165
+ ast = $__rdl_ruby_parser_cache[file][1][:line_defs][line]
166
+ raise RuntimeError, "Can't find source for class #{RDL::Util.pp_klass_method(klass, meth)}" if ast.nil?
167
+ types = $__rdl_info.get(klass, meth, :type)
168
+ raise RuntimeError, "Can't typecheck method with no types?!" if types.nil? or types == []
169
+
170
+ if ast.type == :def
171
+ name, args, body = *ast
172
+ elsif ast.type == :defs
173
+ _, name, args, body = *ast
174
+ else
175
+ raise RuntimeError, "Unexpected ast type #{ast.type}"
176
+ end
177
+ raise RuntimeError, "Method #{name} defined where method #{meth} expected" if name.to_sym != meth
178
+ types.each { |type|
179
+ # TODO will need fancier logic here for matching up more complex arg lists
180
+ if RDL::Util.has_singleton_marker(klass)
181
+ # to_class gets the class object itself, so remove singleton marker to get class rather than singleton class
182
+ self_type = RDL::Type::SingletonType.new(RDL::Util.to_class(RDL::Util.remove_singleton_marker(klass)))
183
+ else
184
+ self_type = RDL::Type::NominalType.new(klass)
185
+ end
186
+ inst = {self: self_type}
187
+ type = type.instantiate inst
188
+ error :arg_count_mismatch, ['method', type.args.length, 'method', args.children.length], args unless type.args.length == args.children.length
189
+ a = args.children.map { |arg| arg.children[0] }.zip(type.args).to_h
190
+ a[:self] = self_type
191
+ scope = {tret: type.ret, tblock: type.block }
192
+ _, body_type = if body.nil? then [nil, $__rdl_nil_type] else tc(scope, Env.new(a), body) end
193
+ error :bad_return_type, [body_type.to_s, type.ret.to_s], body unless body.nil? || body_type <= type.ret
194
+ }
195
+ end
196
+
197
+ # The actual type checking logic.
198
+ # [+ scope +] tracks flow-insensitive information about the current scope, excluding local variables
199
+ # [+ env +] is the (local variable) Env
200
+ # [+ e +] is the expression to type check
201
+ # Returns [env', t], where env' is the type environment at the end of the expression
202
+ # and t is the type of the expression. t is always canonical.
203
+ def self.tc(scope, env, e)
204
+ case e.type
205
+ when :nil
206
+ [env, $__rdl_nil_type]
207
+ when :true
208
+ [env, $__rdl_true_type]
209
+ when :false
210
+ [env, $__rdl_false_type]
211
+ when :complex, :rational, :str, :string # constants
212
+ [env, RDL::Type::NominalType.new(e.children[0].class)]
213
+ when :int, :float, :sym # singletons
214
+ [env, RDL::Type::SingletonType.new(e.children[0])]
215
+ when :dstr, :xstr # string (or execute-string) with interpolation
216
+ envi = env
217
+ e.children.each { |ei| envi, _ = tc(scope, envi, ei) }
218
+ [envi, $__rdl_string_type]
219
+ when :dsym # symbol with interpolation
220
+ envi = env
221
+ e.children.each { |ei| envi, _ = tc(scope, envi, ei) }
222
+ [envi, $__rdl_symbol_type]
223
+ when :regexp
224
+ envi = env
225
+ e.children.each { |ei| envi, _ = tc(scope, envi, ei) unless ei.type == :regopt }
226
+ [envi, $__rdl_regexp_type]
227
+ when :array
228
+ envi = env
229
+ tis = []
230
+ is_array = false
231
+ e.children.each { |ei|
232
+ if ei.type == :splat
233
+ envi, ti = tc(scope, envi, ei.children[0]);
234
+ if ti.is_a? RDL::Type::TupleType
235
+ ti.cant_promote! # must remain a tuple
236
+ tis.concat(ti.params)
237
+ elsif ti.is_a? RDL::Type::FiniteHashType
238
+ ti.cant_promote! # must remain a finite hash
239
+ ti.elts.each_pair { |k, t|
240
+ tis << RDL::Type::TupleType.new(RDL::Type::SingletonType.new(k), t)
241
+ }
242
+ elsif ti.is_a?(RDL::Type::GenericType) && ti.base == $__rdl_array_type
243
+ is_array = true
244
+ tis << ti.params[0]
245
+ elsif ti.is_a?(RDL::Type::GenericType) && ti.base == $__rdl_hash_type
246
+ is_array = true
247
+ tis << RDL::Type::TupleType.new(*ti.params)
248
+ elsif ti.is_a?(RDL::Type::SingletonType) && ti.val.nil?
249
+ # nil gets thrown out
250
+ elsif ($__rdl_array_type <= ti) || (ti <= $__rdl_array_type) ||
251
+ ($__rdl_hash_type <= ti) || (ti <= $__rdl_hash_type)
252
+ # might or might not be array...can't splat...
253
+ error :cant_splat, [ti], ei
254
+ else
255
+ tis << ti # splat does nothing
256
+ end
257
+ else
258
+ envi, ti = tc(scope, envi, ei);
259
+ tis << ti
260
+ end
261
+ }
262
+ if is_array
263
+ [envi, RDL::Type::GenericType.new($__rdl_array_type, RDL::Type::UnionType.new(*tis).canonical)]
264
+ else
265
+ [envi, RDL::Type::TupleType.new(*tis)]
266
+ end
267
+ when :hash
268
+ envi = env
269
+ tlefts = []
270
+ trights = []
271
+ is_fh = true
272
+ e.children.each { |p|
273
+ # each child is a pair
274
+ if p.type == :pair
275
+ envi, tleft = tc(scope, envi, p.children[0])
276
+ tlefts << tleft
277
+ envi, tright = tc(scope, envi, p.children[1])
278
+ trights << tright
279
+ is_fh = false unless tleft.is_a?(RDL::Type::SingletonType)
280
+ elsif p.type == :kwsplat
281
+ envi, tkwsplat = tc(scope, envi, p.children[0])
282
+ if tkwsplat.is_a? RDL::Type::FiniteHashType
283
+ tkwsplat.cant_promote! # must remain finite hash
284
+ tlefts.concat(tkwsplat.elts.keys.map { |k| RDL::Type::SingletonType.new(k) })
285
+ trights.concat(tkwsplat.elts.values)
286
+ elsif tkwsplat.is_a?(RDL::Type::GenericType) && tkwsplat.base == $__rdl_hash_type
287
+ is_fh = false
288
+ tlefts << tkwsplat.params[0]
289
+ trights << tkwsplat.params[1]
290
+ else
291
+ error :cant_splat, [tkwsplat], p
292
+ end
293
+ else
294
+ raise "Don't know what to do with #{p.type}"
295
+ end
296
+ }
297
+ if is_fh
298
+ # keys are all symbols
299
+ fh = tlefts.map { |t| t.val }.zip(trights).to_h
300
+ [envi, RDL::Type::FiniteHashType.new(fh)]
301
+ else
302
+ tleft = RDL::Type::UnionType.new(*tlefts)
303
+ tright = RDL::Type::UnionType.new(*trights)
304
+ [envi, RDL::Type::GenericType.new($__rdl_hash_type, tleft, tright)]
305
+ end
306
+ #TODO test!
307
+ # when :kwsplat # TODO!
308
+ when :irange, :erange
309
+ env1, t1 = tc(scope, env, e.children[0])
310
+ env2, t2 = tc(scope, env1, e.children[1])
311
+ # promote singleton types to nominal types; safe since Ranges are immutable
312
+ t1 = RDL::Type::NominalType.new(t1.val.class) if t1.is_a? RDL::Type::SingletonType
313
+ t2 = RDL::Type::NominalType.new(t2.val.class) if t2.is_a? RDL::Type::SingletonType
314
+ error :nonmatching_range_type, [t1, t2], e if t1 != t2
315
+ [env2, RDL::Type::GenericType.new($__rdl_range_type, t1)]
316
+ when :self
317
+ [env, env[:self]]
318
+ when :lvar, :ivar, :cvar, :gvar
319
+ tc_var(scope, env, e.type, e.children[0], e)
320
+ when :lvasgn, :ivasgn, :cvasgn, :gvasgn
321
+ x = e.children[0]
322
+ # if local var, lhs is bound to nil before assignment is executed! only matters in type checking for locals
323
+ env = env.bind(x, $__rdl_nil_type) if ((e.type == :lvasgn) && (not (env.has_key? x)))
324
+ envright, tright = tc(scope, env, e.children[1])
325
+ tc_vasgn(scope, envright, e.type, x, tright, e)
326
+ when :masgn
327
+ # (masgn (mlhs (Xvasgn var-name) ... (Xvasgn var-name)) rhs)
328
+ e.children[0].children.each { |asgn|
329
+ next unless asgn.type == :lvasgn
330
+ x = e.children[0]
331
+ env = env.bind(x, $__rdl_nil_type) if (not (env.has_key? x)) # see lvasgn
332
+ # Note don't need to check outer_env here because will be checked by tc_vasgn below
333
+ }
334
+ envi, tright = tc(scope, env, e.children[1])
335
+ lhs = e.children[0].children
336
+ if tright.is_a? RDL::Type::TupleType
337
+ tright.cant_promote! # must always remain a tuple because of the way type checking currently works
338
+ rhs = tright.params
339
+ splat_ind = lhs.index { |lhs_elt| lhs_elt.type == :splat }
340
+ if splat_ind
341
+ if splat_ind > 0
342
+ lhs[0..splat_ind-1].each { |left|
343
+ # before splat
344
+ error :masgn_bad_lhs, [], left if rhs.empty?
345
+ envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], rhs.shift, left)
346
+ }
347
+ end
348
+ lhs[splat_ind+1..-1].reverse_each { |left|
349
+ # after splat
350
+ error :masgn_bad_lhs, [], left if rhs.empty?
351
+ envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], rhs.pop, left)
352
+ }
353
+ splat = lhs[splat_ind]
354
+ envi, _ = tc_vasgn(scope, envi, splat.children[0].type, splat.children[0].children[0], RDL::Type::TupleType.new(*rhs), splat)
355
+ [envi, tright]
356
+ else
357
+ error :masgn_num, [rhs.length, lhs.length], e unless lhs.length == rhs.length
358
+ lhs.zip(rhs).each { |left, right|
359
+ envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], right, left)
360
+ }
361
+ [envi, tright]
362
+ end
363
+ elsif (tright.is_a? RDL::Type::GenericType) && (tright.base == $__rdl_array_type)
364
+ tasgn = tright.params[0]
365
+ lhs.each { |asgn|
366
+ if asgn.type == :splat
367
+ envi, _ = tc_vasgn(scope, envi, asgn.children[0].type, asgn.children[0].children[0], tright, asgn)
368
+ else
369
+ envi, _ = tc_vasgn(scope, envi, asgn.type, asgn.children[0], tasgn, asgn)
370
+ end
371
+ }
372
+ [envi, tright]
373
+ else
374
+ error :masgn_bad_rhs, [tright], e.children[1]
375
+ end
376
+ when :op_asgn
377
+ if e.children[0].type == :send
378
+ # (op-asgn (send recv meth) :op operand)
379
+ meth = e.children[0].children[1]
380
+ envleft, trecv = tc(scope, env, e.children[0].children[0]) # recv
381
+ tloperand = tc_send(scope, envleft, trecv, meth, [], nil, e.children[0]) # call recv.meth()
382
+ envoperand, troperand = tc(scope, envleft, e.children[2]) # operand
383
+ tright = tc_send(scope, envoperand, tloperand, e.children[1], [troperand], nil, e) # recv.meth().op(operand)
384
+ mutation_meth = (meth.to_s + '=').to_sym
385
+ tres = tc_send(scope, envoperand, trecv, mutation_meth, [tright], nil, e) # call recv.meth=(recv.meth().op(operand))
386
+ [envoperand, tres]
387
+ else
388
+ # (op-asgn (Xvasgn var-name) :op operand)
389
+ x = e.children[0].children[0] # Note don't need to check outer_env here because will be checked by tc_vasgn below
390
+ env = env.bind(x, $__rdl_nil_type) if ((e.children[0].type == :lvasgn) && (not (env.has_key? x))) # see :lvasgn
391
+ envi, trecv = tc_var(scope, env, @@asgn_to_var[e.children[0].type], x, e.children[0]) # var being assigned to
392
+ envright, tright = tc(scope, envi, e.children[2]) # operand
393
+ trhs = tc_send(scope, envright, trecv, e.children[1], [tright], nil, e)
394
+ tc_vasgn(scope, envright, e.children[0].type, x, trhs, e)
395
+ end
396
+ when :and_asgn, :or_asgn
397
+ # very similar logic to op_asgn
398
+ if e.children[0].type == :send
399
+ meth = e.children[0].children[1]
400
+ envleft, trecv = tc(scope, env, e.children[0].children[0]) # recv
401
+ tleft = tc_send(scope, envleft, trecv, meth, [], nil, e.children[0]) # call recv.meth()
402
+ envright, tright = tc(scope, envleft, e.children[1]) # operand
403
+ else
404
+ x = e.children[0].children[0] # Note don't need to check outer_env here because will be checked by tc_var below
405
+ env = env.bind(x, $__rdl_nil_type) if ((e.children[0].type == :lvasgn) && (not (env.has_key? x))) # see :lvasgn
406
+ envleft, tleft = tc_var(scope, env, @@asgn_to_var[e.children[0].type], x, e.children[0]) # var being assigned to
407
+ envright, tright = tc(scope, envleft, e.children[1])
408
+ end
409
+ envi, trhs = (if tleft.is_a? RDL::Type::SingletonType
410
+ if e.type == :and_asgn
411
+ if tleft.val then [envright, tright] else [envleft, tleft] end
412
+ else # e.type == :or_asgn
413
+ if tleft.val then [envleft, tleft] else [envright, tright] end
414
+ end
415
+ else
416
+ [Env.join(e, envleft, envright), RDL::Type::UnionType.new(tleft, tright).canonical]
417
+ end)
418
+ if e.children[0].type == :send
419
+ mutation_meth = (meth.to_s + '=').to_sym
420
+ tres = tc_send(scope, envi, trecv, mutation_meth, [trhs], nil, e)
421
+ [envi, tres]
422
+ else
423
+ tc_vasgn(scope, envi, e.children[0].type, x, trhs, e)
424
+ end
425
+ when :nth_ref, :back_ref
426
+ [env, $__rdl_string_type]
427
+ when :const
428
+ c = nil
429
+ if e.children[0].nil?
430
+ c = env[:self].klass.const_get(e.children[1])
431
+ elsif e.children[0].type == :cbase
432
+ raise "const cbase not implemented yet" # TODO!
433
+ elsif e.children[0].type == :lvar
434
+ raise "const lvar not implemented yet" # TODO!
435
+ else
436
+ raise "const other not implemented yet"
437
+ end
438
+ case c
439
+ when TrueClass, FalseClass, Complex, Rational, Fixnum, Bignum, Float, Symbol, Class
440
+ [env, RDL::Type::SingletonType.new(c)]
441
+ else
442
+ [env, RDL::Type::NominalType.new(const_get(e.children[1]).class)]
443
+ end
444
+ when :defined?
445
+ # do not type check subexpression, since it may not be type correct, e.g., undefined variable
446
+ [env, $__rdl_string_type]
447
+ when :send, :csend
448
+ # children[0] = receiver; if nil, receiver is self
449
+ # children[1] = method name, a symbol
450
+ # children [2..] = actual args
451
+ return tc_var_type(scope, env, e) if e.children[1] == :var_type && e.children[0].nil? && scope[:block].nil?
452
+ return tc_type_cast(scope, env, e) if e.children[1] == :type_cast && scope[:block].nil?
453
+ envi = env
454
+ tactuals = []
455
+ block = scope[:block]
456
+ scope_merge(scope, block: nil) { |sscope|
457
+ e.children[2..-1].each { |ei|
458
+ if ei.type == :splat
459
+ envi, ti = tc(sscope, envi, ei.children[0])
460
+ if ti.is_a? RDL::Type::TupleType
461
+ tactuals.concat ti.params
462
+ elsif ti.is_a?(RDL::Type::GenericType) && ti.base == $__rdl_array_type
463
+ tactuals << RDL::Type::VarargType.new(ti)
464
+ else
465
+ error :cant_splat, [ti], ei.children[0]
466
+ end
467
+ else
468
+ envi, ti = tc(sscope, envi, ei)
469
+ tactuals << ti
470
+ end
471
+ }
472
+ envi, trecv = if e.children[0].nil? then [envi, envi[:self]] else tc(sscope, envi, e.children[0]) end # if no receiver, self is receiver
473
+ [envi, tc_send(sscope, envi, trecv, e.children[1], tactuals, block, e).canonical]
474
+ }
475
+ when :yield
476
+ # very similar to send except the callee is the method's block
477
+ error :no_block, [], e unless scope[:tblock]
478
+ error :block_block, [], e if scope[:tblock].block
479
+ scope[:exn] = Env.join(e, scope[:exn], env) if scope.has_key? :exn # assume this call might raise an exception
480
+ envi = env
481
+ tactuals = []
482
+ e.children[0..-1].each { |ei| envi, ti = tc(scope, envi, ei); tactuals << ti }
483
+ unless tc_arg_types(scope[:tblock], tactuals)
484
+ msg = <<RUBY
485
+ Block type: #{scope[:tblock]}
486
+ Actual arg types: (#{tactuals.map { |ti| ti.to_s }.join(', ')})
487
+ RUBY
488
+ msg.chomp! # remove trailing newline
489
+ error :block_type_error, [msg], e
490
+ end
491
+ [envi, scope[:tblock].ret]
492
+ # tblock
493
+ when :block
494
+ # (block send block-args block-body)
495
+ scope_merge(scope, block: [e.children[1], e.children[2]]) { |bscope|
496
+ tc(bscope, env, e.children[0])
497
+ }
498
+ when :and, :or
499
+ envleft, tleft = tc(scope, env, e.children[0])
500
+ envright, tright = tc(scope, envleft, e.children[1])
501
+ if tleft.is_a? RDL::Type::SingletonType
502
+ if e.type == :and
503
+ if tleft.val then [envright, tright] else [envleft, tleft] end
504
+ else # e.type == :or
505
+ if tleft.val then [envleft, tleft] else [envright, tright] end
506
+ end
507
+ else
508
+ [Env.join(e, envleft, envright), RDL::Type::UnionType.new(tleft, tright).canonical]
509
+ end
510
+ # when :not # in latest Ruby, not is a method call that could be redefined, so can't count on its behavior
511
+ # a1, t1 = tc(scope, a, e.children[0])
512
+ # if t1.is_a? RDL::Type::SingletonType
513
+ # if t1.val then [a1, $__rdl_false_type] else [a1, $__rdl_true_type] end
514
+ # else
515
+ # [a1, $__rdl_bool_type]
516
+ # end
517
+ when :if
518
+ envi, tguard = tc(scope, env, e.children[0]) # guard; any type allowed
519
+ # always type check both sides
520
+ envleft, tleft = if e.children[1].nil? then [envi, $__rdl_nil_type] else tc(scope, envi, e.children[1]) end # then
521
+ envright, tright = if e.children[2].nil? then [envi, $__rdl_nil_type] else tc(scope, envi, e.children[2]) end # else
522
+ if tguard.is_a? RDL::Type::SingletonType
523
+ if tguard.val then [envleft, tleft] else [envright, tright] end
524
+ else
525
+ [Env.join(e, envleft, envright), RDL::Type::UnionType.new(tleft, tright).canonical]
526
+ end
527
+ when :case
528
+ envi = env
529
+ envi, tcontrol = tc(scope, envi, e.children[0]) unless e.children[0].nil? # the control expression, which make be nil
530
+ # for each guard, invoke guard === control expr, then possibly do body, possibly short-circuiting arbitrary later stuff
531
+ tbodies = []
532
+ envbodies = []
533
+ e.children[1..-2].each { |wclause|
534
+ raise RuntimeError, "Don't know what to do with case clause #{wclause.type}" unless wclause.type == :when
535
+ envguards = []
536
+ wclause.children[0..-2].each { |guard| # first wclause.length-1 children are the guards
537
+ envi, tguard = tc(scope, envi, guard) # guard type can be anything
538
+ tc_send(scope, envi, tguard, :===, [tcontrol], nil, guard) unless tcontrol.nil?
539
+ envguards << envi
540
+ }
541
+ envbody, tbody = tc(scope, Env.join(e, *envguards), wclause.children[-1]) # last wclause child is body
542
+ tbodies << tbody
543
+ envbodies << envbody
544
+ }
545
+ if e.children[-1].nil?
546
+ # no else clause, might fall through having missed all cases
547
+ envbodies << envi
548
+ else
549
+ # there is an else clause
550
+ envelse, telse = tc(scope, envi, e.children[-1])
551
+ tbodies << telse
552
+ envbodies << envelse
553
+ end
554
+ return [Env.join(e, *envbodies), RDL::Type::UnionType.new(*tbodies).canonical]
555
+ when :while, :until
556
+ # break: loop exit, i.e., right after loop guard; may take argument
557
+ # next: before loop guard; argument not allowed
558
+ # retry: not allowed
559
+ # redo: after loop guard, which is same as break
560
+ env_break, _ = tc(scope, env, e.children[0]) # guard can have any type, may exit after checking guard
561
+ scope_merge(scope, break: env_break, tbreak: $__rdl_nil_type, next: env, redo: env_break) { |lscope|
562
+ begin
563
+ old_break = lscope[:break]
564
+ old_next = lscope[:next]
565
+ old_tbreak = lscope[:tbreak]
566
+ if e.children[1]
567
+ env_body, _ = tc(lscope, lscope[:break], e.children[1]) # loop runs
568
+ lscope[:next] = Env.join(e, lscope[:next], env_body)
569
+ end
570
+ env_guard, _ = tc(lscope, lscope[:next], e.children[0]) # then guard runs
571
+ lscope[:break] = lscope[:redo] = Env.join(e, lscope[:break], lscope[:redo], env_guard)
572
+ end until old_break == lscope[:break] && old_next == lscope[:next] && old_tbreak == lscope[:tbreak]
573
+ [lscope[:break], lscope[:tbreak].canonical]
574
+ }
575
+ when :while_post, :until_post
576
+ # break: loop exit; note may exit loop before hitting guard once; maybe take argument
577
+ # next: before loop guard; argument not allowed
578
+ # retry: not allowed
579
+ # redo: beginning of body, which is same as after guard, i.e., same as break
580
+ scope_merge(scope, break: nil, tbreak: $__rdl_nil_type, next: nil, redo: nil) { |lscope|
581
+ if e.children[1]
582
+ env_body, _ = tc(lscope, env, e.children[1])
583
+ lscope[:next] = Env.join(e, lscope[:next], env_body)
584
+ end
585
+ begin
586
+ old_break = lscope[:break]
587
+ old_next = lscope[:next]
588
+ old_tbreak = lscope[:tbreak]
589
+ env_guard, _ = tc(lscope, lscope[:next], e.children[0])
590
+ lscope[:break] = lscope[:redo] = Env.join(e, lscope[:break], lscope[:redo], env_guard)
591
+ if e.children[1]
592
+ env_body, _ = tc(lscope, lscope[:break], e.children[1])
593
+ lscope[:next] = Env.join(e, lscope[:next], env_body)
594
+ end
595
+ end until old_break == lscope[:break] && old_next == lscope[:next] && old_tbreak == lscope[:tbreak]
596
+ [lscope[:break], lscope[:tbreak].canonical]
597
+ }
598
+ when :for
599
+ # break: loop exit, which is same as top of body, arg allowed
600
+ # next: top of body, arg allowed
601
+ # retry: not allowed
602
+ # redo: top of body
603
+ raise RuntimeError, "Loop variable #{e.children[0]} in for unsupported" unless e.children[0].type == :lvasgn
604
+ # TODO: mlhs in e.children[0]
605
+ x = e.children[0].children[0] # loop variable
606
+ if scope[:outer_env] && (scope[:outer_env].has_key? x) && (not (scope[:outer_env].fixed? x))
607
+ error :nonlocal_access, [x], e.children[0]
608
+ end
609
+ envi, tcollect = tc(scope, env, e.children[1]) # collection to iterate through
610
+ teaches = nil
611
+ tcollect = tcollect.canonical
612
+ case tcollect
613
+ when RDL::Type::NominalType
614
+ teaches = lookup(tcollect.name, :each, e.children[1])
615
+ when RDL::Type::GenericType, RDL::Type::TupleType, RDL::Type::FiniteHashType
616
+ unless tcollect.is_a? RDL::Type::GenericType
617
+ error :tuple_finite_hash_promote, (if tcollect.is_a? RDL::Type::TupleType then ['tuple', 'Array'] else ['finite hash', 'Hash'] end), e.children[1] unless tcollect.promote!
618
+ tcollect = tcollect.canonical
619
+ end
620
+ teaches = lookup(tcollect.base.name, :each, e.children[1])
621
+ inst = tcollect.to_inst.merge(self: tcollect)
622
+ teaches = teaches.map { |t| t.instantiate(inst) }
623
+ else
624
+ error :for_collection, [tcollect], e.children[1]
625
+ end
626
+ teach = nil
627
+ teaches.each { |t|
628
+ # find `each` method with right type signature:
629
+ # () { (t1) -> t2 } -> t3
630
+ next unless t.args.empty?
631
+ next if t.block.nil?
632
+ next unless t.block.args.size == 1
633
+ next unless t.block.block.nil?
634
+ teach = t
635
+ break
636
+ }
637
+ error :no_each_type, [tcollect.name], e.children[1] if teach.nil?
638
+ envi = envi.bind(x, teach.block.args[0])
639
+ scope_merge(scope, break: envi, next: envi, redo: envi, tbreak: teach.ret, tnext: envi[x]) { |lscope|
640
+ # could exit here
641
+ # if the loop always exits via break, then return type will come only from break, and otherwise the
642
+ # collection is returned. But it's hard to tell statically if there are only exits via break, so
643
+ # conservatively assume that at least the collection is returned.
644
+ begin
645
+ old_break = lscope[:break]
646
+ old_tbreak = lscope[:tbreak]
647
+ old_tnext = lscope[:tnext]
648
+ if e.children[2]
649
+ lscope[:break] = lscope[:break].bind(x, lscope[:tnext])
650
+ env_body, _ = tc(lscope, lscope[:break], e.children[2])
651
+ lscope[:break] = lscope[:next] = lscope[:redo] = Env.join(e, lscope[:break], lscope[:next], lscope[:redo], env_body)
652
+ end
653
+ end until old_break == lscope[:break] && old_tbreak == lscope[:tbreak] && old_tnext == lscope[:tnext]
654
+ [lscope[:break], lscope[:tbreak].canonical]
655
+ }
656
+ when :break, :redo, :next, :retry
657
+ error :kw_not_allowed, [e.type], e unless scope.has_key? e.type
658
+ if e.children[0]
659
+ tkw_name = ('t' + e.type.to_s).to_sym
660
+ error :kw_arg_not_allowed, [e.type], e unless scope.has_key? tkw_name
661
+ env, tkw = tc(scope, env, e.children[0])
662
+ scope[tkw_name] = RDL::Type::UnionType.new(scope[tkw_name], tkw)
663
+ end
664
+ scope[e.type] = Env.join(e, scope[e.type], env)
665
+ [env, $__rdl_bot_type]
666
+ when :return
667
+ # TODO return in lambda returns from lambda and not outer scope
668
+ env1, t1 = tc(scope, env, e.children[0])
669
+ error :bad_return_type, [t1.to_s, scope[:tret]], e unless t1 <= scope[:tret]
670
+ [env1, $__rdl_bot_type] # return is a void value expression
671
+ when :begin, :kwbegin # sequencing
672
+ envi = env
673
+ ti = nil
674
+ e.children.each { |ei| envi, ti = tc(scope, envi, ei) }
675
+ [envi, ti]
676
+ when :ensure
677
+ # (ensure main-body ensure-body)
678
+ # TODO exception control flow from main-body, vars initialized to nil
679
+ env_body, tbody = tc(scope, env, e.children[0])
680
+ env_ensure, _ = tc(scope, env_body, e.children[1])
681
+ [env_ensure, tbody] # value of ensure not returned
682
+ when :rescue
683
+ # (rescue main-body resbody1 resbody2 ... (else else-body))
684
+ # resbodyi, else optional
685
+ # local variables assigned to in main-body will all be initialized to nil even if an exception
686
+ # is raised during main-body's execution before those varibles are assigned to.
687
+ # similarly, local variables assigned in resbody will be initialized to nil even if the resbody
688
+ # is never triggered
689
+ scope_merge(scope, retry: env, exn: nil) { |rscope|
690
+ begin
691
+ old_retry = rscope[:retry]
692
+ env_body, tbody = tc(rscope, rscope[:retry], e.children[0])
693
+ tres = [tbody] # note throw away inferred types from previous iterations---should be okay since should be monotonic
694
+ env_res = [env_body]
695
+ if rscope[:exn]
696
+ e.children[1..-2].each { |resbody|
697
+ env_resbody, tresbody = tc(rscope, rscope[:exn], resbody)
698
+ tres << tresbody
699
+ env_res << env_resbody
700
+ }
701
+ if e.children[-1]
702
+ env_else, telse = tc(rscope, rscope[:exn], e.children[-1])
703
+ tres << telse
704
+ env_res << env_else
705
+ end
706
+ end
707
+ end until old_retry == rscope[:retry]
708
+ # TODO: variables newly bound in *env_res should be unioned with nil
709
+ [Env.join(e, *env_res), RDL::Type::UnionType.new(*tres).canonical]
710
+ }
711
+ when :resbody
712
+ # (resbody (array exns) (lvasgn var) rescue-body)
713
+ envi = env
714
+ texns = []
715
+ if e.children[0]
716
+ e.children[0].children.each { |exn|
717
+ envi, texn = tc(scope, envi, exn)
718
+ error :exn_type, [], exn unless texn.is_a?(RDL::Type::SingletonType) && texn.val.is_a?(Class)
719
+ texns << RDL::Type::NominalType.new(texn.val)
720
+ }
721
+ else
722
+ texns = [$__rdl_standard_error_type]
723
+ end
724
+ if e.children[1]
725
+ envi, _ = tc_vasgn(scope, envi, :lvasgn, e.children[1].children[0], RDL::Type::UnionType.new(*texns), e.children[1])
726
+ end
727
+ tc(scope, envi, e.children[2])
728
+ else
729
+ raise RuntimeError, "Expression kind #{e.type} unsupported"
730
+ end
731
+ end
732
+
733
+ # [+ kind +] is :lvar, :ivar, :cvar, or :gvar
734
+ # [+ name +] is the variable name, which should be a symbol
735
+ # [+ e +] is the expression for which errors should be reported
736
+ def self.tc_var(scope, env, kind, name, e)
737
+ case kind
738
+ when :lvar # local variable
739
+ error :undefined_local_or_method, [name], e unless env.has_key? name
740
+ if scope[:outer_env] && (scope[:outer_env].has_key? name) && (not (scope[:outer_env].fixed? name))
741
+ error :nonlocal_access, [name], e
742
+ end
743
+ [env, env[name].canonical]
744
+ when :ivar, :cvar, :gvar
745
+ klass = (if kind == :gvar then RDL::Util::GLOBAL_NAME else env[:self] end)
746
+ unless $__rdl_info.has?(klass, name, :type)
747
+ kind_text = (if kind == :ivar then "instance"
748
+ elsif kind == :cvar then "class"
749
+ else "global" end)
750
+ error :untyped_var, [kind_text, name], e
751
+ end
752
+ [env, $__rdl_info.get(klass, name, :type).canonical]
753
+ else
754
+ raise RuntimeError, "unknown kind #{kind}"
755
+ end
756
+ end
757
+
758
+ # Same arguments as tc_var except
759
+ # [+ tright +] is type of right-hand side
760
+ def self.tc_vasgn(scope, env, kind, name, tright, e)
761
+ case kind
762
+ when :lvasgn
763
+ if scope[:outer_env] && (scope[:outer_env].has_key? name) && (not (scope[:outer_env].fixed? name))
764
+ error :nonlocal_access, [name], e
765
+ end
766
+ if env.fixed? name
767
+ error :vasgn_incompat, [tright, env[name]], e unless tright <= env[name]
768
+ [env, tright.canonical]
769
+ else
770
+ [env.bind(name, tright), tright.canonical]
771
+ end
772
+ when :ivasgn, :cvasgn, :gvasgn
773
+ klass = (if kind == :gvasgn then RDL::Util::GLOBAL_NAME else env[:self] end)
774
+ unless $__rdl_info.has?(klass, name, :type)
775
+ kind_text = (if kind == :ivasgn then "instance"
776
+ elsif kind == :cvasgn then "class"
777
+ else "global" end)
778
+ error :untyped_var, [kind_text, name], e
779
+ end
780
+ tleft = $__rdl_info.get(klass, name, :type)
781
+ error :vasgn_incompat, [tright.to_s, tleft.to_s], e unless tright <= tleft
782
+ [env, tright.canonical]
783
+ when :send
784
+ meth = e.children[1] # note method name include =!
785
+ envi, trecv = tc(scope, env, e.children[0]) # receiver
786
+ # name is not useful here
787
+ [envi, tc_send(scope, envi, trecv, meth, [tright], nil, e)] # call receiver.meth(tright)
788
+ else
789
+ raise RuntimeError, "unknown kind #{kind}"
790
+ end
791
+ end
792
+
793
+ # [+ e +] is the method call
794
+ def self.tc_var_type(scope, env, e)
795
+ error :var_type_format, [], e unless e.children.length == 4
796
+ var = e.children[2].children[0] if e.children[2].type == :sym
797
+ error :var_type_format, [], e.children[2] if var.nil? || (not (var =~ /^[a-z]/))
798
+ typ_str = e.children[3].children[0] if (e.children[3].type == :str) || (e.children[3].type == :string)
799
+ error :var_type_format, [], e.children[3] if typ_str.nil?
800
+ begin
801
+ typ = $__rdl_parser.scan_str("#T " + typ_str)
802
+ rescue Racc::ParseError => err
803
+ error :generic_error, [err.to_s[1..-1]], e.children[3] # remove initial newline
804
+ end
805
+ [env.fix(var, typ), $__rdl_nil_type]
806
+ end
807
+
808
+ def self.tc_type_cast(scope, env, e)
809
+ error :type_cast_format, [], e unless e.children.length <= 4
810
+ typ_str = e.children[2].children[0] if (e.children[2].type == :str) || (e.children[2].type == :string)
811
+ error :type_cast_format, [], e.children[2] if typ_str.nil?
812
+ begin
813
+ typ = $__rdl_parser.scan_str("#T " + typ_str)
814
+ rescue Racc::ParseError => err
815
+ error :generic_error, [err.to_s[1..-1]], e.children[2] # remove initial newline
816
+ end
817
+ if e.children[3]
818
+ fh = e.children[3]
819
+ error :type_cast_format, [], fh unless fh.type == :hash && fh.children.length == 1
820
+ pair = fh.children[0]
821
+ error :type_cast_format, [], fh unless pair.type == :pair && pair.children[0].type == :sym && pair.children[0].children[0] == :force
822
+ force_arg = pair.children[1]
823
+ env1, _ = tc(scope, env, force_arg)
824
+ end
825
+ [env1, typ]
826
+ end
827
+
828
+ # Type check a send
829
+ # [+ scope +] is the scope; used only for checking block arguments
830
+ # [+ env +] is the environment; used only for checking block arguments.
831
+ # Note locals from blocks args don't escape, so no env is returned.
832
+ # [+ trecvs +] is the type of the recevier
833
+ # [+ meth +] is a symbol with the method name
834
+ # [+ tactuals +] are the actual arguments
835
+ # [+ block +] is a pair of expressions [block-args, block-body], from the block AST node
836
+ # [+ e +] is the expression at which location to report an error
837
+ def self.tc_send(scope, env, trecvs, meth, tactuals, block, e)
838
+ scope[:exn] = Env.join(e, scope[:exn], env) if scope.has_key? :exn # assume this call might raise an exception
839
+
840
+ # convert trecvs to array containing all receiver types
841
+ trecvs = trecvs.canonical
842
+ trecvs = if trecvs.is_a? RDL::Type::UnionType then trecvs.types else [trecvs] end
843
+
844
+ trets = []
845
+ trecvs.each { |trecv|
846
+ trets.concat(tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e))
847
+ }
848
+ return RDL::Type::UnionType.new(*trets)
849
+ end
850
+
851
+ # Like tc_send but trecv should never be a union type
852
+ # Returns array of possible return types, or throws exception if there are none
853
+ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e)
854
+ tmeth_inter = [] # Array<MethodType>, i.e., an intersection types
855
+ case trecv
856
+ when RDL::Type::SingletonType
857
+ if trecv.val.is_a? Class
858
+ if meth == :new then name = :initialize else name = meth end
859
+ ts = lookup(RDL::Util.add_singleton_marker(trecv.val.to_s), name, e)
860
+ ts = [RDL::Type::MethodType.new([], nil, RDL::Type::NominalType.new(trecv.val))] if (meth == :new) && (ts.nil?) # there's always a nullary new if initialize is undefined
861
+ error :no_singleton_method_type, [trecv.val, meth], e unless ts
862
+ inst = {self: trecv}
863
+ tmeth_inter = ts.map { |t| t.instantiate(inst) }
864
+ else
865
+ klass = trecv.val.class.to_s
866
+ ts = lookup(klass, meth, e)
867
+ error :no_instance_method_type, [klass, meth], e unless ts
868
+ inst = {self: trecv}
869
+ tmeth_inter = ts.map { |t| t.instantiate(inst) }
870
+ end
871
+ when RDL::Type::NominalType
872
+ ts = lookup(trecv.name, meth, e)
873
+ error :no_instance_method_type, [trecv.name, meth], e unless ts
874
+ inst = {self: trecv}
875
+ tmeth_inter = ts.map { |t| t.instantiate(inst) }
876
+ when RDL::Type::GenericType, RDL::Type::TupleType, RDL::Type::FiniteHashType
877
+ unless trecv.is_a? RDL::Type::GenericType
878
+ error :tuple_finite_hash_promote, (if trecv.is_a? RDL::Type::TupleType then ['tuple', 'Array'] else ['finite hash', 'Hash'] end), e unless trecv.promote!
879
+ trecv = trecv.canonical
880
+ end
881
+ ts = lookup(trecv.base.name, meth, e)
882
+ error :no_instance_method_type, [trecv.base.name, meth], e unless ts
883
+ inst = trecv.to_inst.merge(self: trecv)
884
+ tmeth_inter = ts.map { |t| t.instantiate(inst) }
885
+ else
886
+ raise RuntimeError, "receiver type #{t} not supported yet"
887
+ end
888
+
889
+ trets = [] # all possible return types
890
+ # there might be more than one return type because multiple cases of an intersection type might match
891
+ tmeth_inter.each { |tmeth| # MethodType
892
+ if ((tmeth.block && block) || (tmeth.block.nil? && block.nil?)) && tc_arg_types(tmeth, tactuals)
893
+ tc_block(scope, env, tmeth.block, block) if block
894
+ trets << tmeth.ret
895
+ end
896
+ }
897
+ if trets.empty? # no possible matching call
898
+ msg = <<RUBY
899
+ Method type:
900
+ #{ tmeth_inter.map { |ti| " " + ti.to_s }.join("\n") }
901
+ Actual arg type#{tactuals.size > 1 ? "s" : ""}:
902
+ (#{tactuals.map { |ti| ti.to_s }.join(', ')}) #{if block then '{ block }' end}
903
+ RUBY
904
+ msg.chomp! # remove trailing newline
905
+ name = if (trecv.is_a? RDL::Type::SingletonType) && (trecv.val.is_a? Class) && (meth == :new) then
906
+ :initialize
907
+ elsif trecv.is_a? RDL::Type::SingletonType
908
+ trecv.val.class.to_s
909
+ elsif trecv.is_a? RDL::Type::NominalType
910
+ trecv.name
911
+ else
912
+ raise RutimeError, "impossible"
913
+ end
914
+ error :arg_type_single_receiver_error, [name, meth, msg], e
915
+ end
916
+ # TODO: issue warning if trets.size > 1 ?
917
+ return trets
918
+ end
919
+
920
+ # [+ tmeth +] is MethodType
921
+ # [+ actuals +] is Array<Type> containing the actual argument types
922
+ # return true if actuals match method type, false otherwise
923
+ # Very similar to MethodType#pre_cond?
924
+ def self.tc_arg_types(tmeth, tactuals)
925
+ states = [[0, 0]] # position in tmeth, position in tactuals
926
+ tformals = tmeth.args
927
+ until states.empty?
928
+ formal, actual = states.pop
929
+ if formal == tformals.size && actual == tactuals.size # Matched everything
930
+ return true
931
+ end
932
+ next if formal >= tformals.size # Too many actuals to match
933
+ t = tformals[formal]
934
+ if t.instance_of? RDL::Type::AnnotatedArgType
935
+ t = t.type
936
+ end
937
+ case t
938
+ when RDL::Type::OptionalType
939
+ t = t.type #TODO .instantiate(inst)
940
+ if actual == tactuals.size
941
+ states << [formal+1, actual] # skip over optinal formal
942
+ elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && tactuals[actual] <= t
943
+ states << [formal+1, actual+1] # match
944
+ states << [formal+1, actual] # skip
945
+ else
946
+ states << [formal+1, actual] # types don't match; must skip this formal
947
+ end
948
+ when RDL::Type::VarargType
949
+ # t = t.type #TODO .instantiate(inst)
950
+ if actual == tactuals.size
951
+ states << [formal+1, actual] # skip to allow empty vararg at end
952
+ elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && tactuals[actual] <= t.type
953
+ states << [formal, actual+1] # match, more varargs coming
954
+ states << [formal+1, actual+1] # match, no more varargs
955
+ states << [formal+1, actual] # skip over even though matches
956
+ elsif tactuals[actual].is_a?(RDL::Type::VarargType) && tactuals[actual] = t
957
+ states << [formal+1, actual+1] # match, no more varargs; no other choices!
958
+ else
959
+ states << [formal+1, actual] # doesn't match, must skip
960
+ end
961
+ else
962
+ if actual == tactuals.size
963
+ next unless t.instance_of? RDL::Type::FiniteHashType
964
+ if @@empty_hash_type <= t
965
+ states << [formal+1, actual]
966
+ end
967
+ elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && tactuals[actual] <= t
968
+ states << [formal+1, actual+1] # match!
969
+ # no else case; if there is no match, this is a dead end
970
+ end
971
+ end
972
+ end
973
+ false
974
+ end
975
+
976
+ # [+ tblock +] is the type of the block (a MethodType)
977
+ # [+ block +] is a pair [block-args, block-body] from the block AST node
978
+ # returns if the block matches type tblock
979
+ # otherwise throws an exception with a type error
980
+ def self.tc_block(scope, env, tblock, block)
981
+ # TODO more complex arg lists (same as self.typecheck?); also for
982
+ # TODO self is the same *except* instance_exec or instance_eval
983
+ raise RuntimeError, "block with block arg?" unless tblock.block.nil?
984
+ args, body = block
985
+ error :arg_count_mismatch, ['block', tblock.args.length, 'block', args.children.length], block[0] unless tblock.args.length == args.children.length
986
+ a = args.children.map { |arg| arg.children[0] }.zip(tblock.args).to_h
987
+
988
+ scope_merge(scope, outer_env: env) { |bscope|
989
+ # note: okay if outer_env shadows, since nested scope will include outer scope by next line
990
+ env = env.merge(Env.new(a))
991
+ _, body_type = if body.nil? then [nil, $__rdl_nil_type] else tc(bscope, env.merge(Env.new(a)), body) end
992
+ error :bad_return_type, [body_type, tblock.ret], body unless body.nil? || body_type <= tblock.ret
993
+ }
994
+ end
995
+
996
+ # [+ klass +] is a string containing the class name
997
+ # [+ name +] is a symbol naming the thing to look up (either a method or field)
998
+ # returns klass#name's type, walking up the inheritance hierarchy if appropriate
999
+ # returns nil if no type found
1000
+ def self.lookup(klass, name, e)
1001
+ name = $__rdl_aliases[klass][name] if $__rdl_aliases[klass] && $__rdl_aliases[klass][name]
1002
+ t = $__rdl_info.get(klass, name, :type)
1003
+ return t if t # simplest case, no need to walk inheritance hierarchy
1004
+ the_klass = RDL::Util.to_class(klass)
1005
+ included = the_klass.included_modules
1006
+ the_klass.ancestors[1..-1].each { |ancestor|
1007
+ # assumes ancestors is proper order to walk hierarchy
1008
+ if (ancestor.is_a? Module) && (included.member? ancestor)
1009
+ ancestor_name = RDL::Util.add_singleton_marker(ancestor.to_s)
1010
+ else
1011
+ ancestor_name = ancestor.to_s
1012
+ end
1013
+ tancestor = $__rdl_info.get(ancestor_name, name, :type)
1014
+ return tancestor if tancestor
1015
+ if (if RDL::Util.has_singleton_marker(ancestor_name) then ancestor.singleton_methods.member?(name) else ancestor.instance_methods.member?(name) end)
1016
+ error :missing_ancestor_type, [ancestor_name, klass, name], e
1017
+ end
1018
+ }
1019
+ return nil
1020
+ end
1021
+ end
1022
+
1023
+ # Modify Parser::MESSAGES so can use the awesome parser diagnostics printing!
1024
+ type_error_messages = {
1025
+ bad_return_type: "got type `%s' where return type `%s' expected",
1026
+ undefined_local_or_method: "undefined local variable or method `%s'",
1027
+ nonmatching_range_type: "attempt to construct range with non-matching types `%s' and `%s'",
1028
+ no_instance_method_type: "no type information for instance method `%s#%s'",
1029
+ no_singleton_method_type: "no type information for class/singleton method `%s.%s'",
1030
+ arg_type_single_receiver_error: "argument type error for instance method `%s#%s'\n%s",
1031
+ untyped_var: "no type for %s variable `%s'",
1032
+ vasgn_incompat: "incompatible types: `%s' can't be assigned to variable of type `%s'",
1033
+ inconsistent_var_type: "local variable `%s' has declared type on some paths but not all",
1034
+ inconsistent_var_type_type: "local variable `%s' declared with inconsistent types %s",
1035
+ no_each_type: "can't find `each' method with signature `() { (t1) -> t2 } -> t3' in class `%s'",
1036
+ tuple_finite_hash_promote: "can't promote %s to %s",
1037
+ masgn_bad_rhs: "multiple assignment has right-hand side of type `%s' where tuple or array expected",
1038
+ masgn_num: "can't multiple-assign %d values to %d variables",
1039
+ masgn_bad_lhs: "no corresponding right-hand side elemnt for left-hand side assignee",
1040
+ kw_not_allowed: "can't use %s in current scope",
1041
+ kw_arg_not_allowed: "argument to %s not allowed in current scope",
1042
+ arg_count_mismatch: "%s signature expects %d arguments, actual %s has %d arguments",
1043
+ nonlocal_access: "variable %s from outer scope must have type declared with var_type",
1044
+ no_block: "attempt to call yield in method not declared to take a block argument",
1045
+ block_block: "can't call yield on a block expecting another block argument",
1046
+ block_type_error: "argument type error for block\n%s",
1047
+ missing_ancestor_type: "ancestor %s of %s has method %s but no type for it",
1048
+ type_cast_format: "type_cast must be called as type_cast('type-string') or type_cast('type-string', force: expr)",
1049
+ var_type_format: "var_type must be called as var_type(:var-name, 'type-string')",
1050
+ generic_error: "%s",
1051
+ exn_type: "can't determine exception type",
1052
+ cant_splat: "can't type splat with element of type `%s'",
1053
+ for_collection: "can't type for with collection of type `%s'",
1054
+ }
1055
+ old_messages = Parser::MESSAGES
1056
+ Parser.send(:remove_const, :MESSAGES)
1057
+ Parser.const_set :MESSAGES, (old_messages.merge(type_error_messages))