rdl 1.1.1 → 2.0.0.rc1

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.
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))