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.
- checksums.yaml +4 -4
- data/CHANGES.md +6 -0
- data/README.md +211 -32
- data/gemfiles/Gemfile.travis +1 -1
- data/lib/rdl.rb +85 -18
- data/lib/rdl/info.rb +74 -0
- data/lib/rdl/query.rb +8 -9
- data/lib/rdl/typecheck.rb +1057 -0
- data/lib/rdl/types/annotated_arg.rb +5 -5
- data/lib/rdl/types/{nil.rb → bot.rb} +9 -13
- data/lib/rdl/types/dependent_arg.rb +5 -5
- data/lib/rdl/types/dots_query.rb +2 -0
- data/lib/rdl/types/finitehash.rb +67 -24
- data/lib/rdl/types/generic.rb +13 -21
- data/lib/rdl/types/intersection.rb +9 -8
- data/lib/rdl/types/method.rb +30 -32
- data/lib/rdl/types/nominal.rb +22 -16
- data/lib/rdl/types/optional.rb +8 -22
- data/lib/rdl/types/parser.racc +8 -3
- data/lib/rdl/types/parser.tab.rb +131 -118
- data/lib/rdl/types/singleton.rb +15 -10
- data/lib/rdl/types/structural.rb +6 -6
- data/lib/rdl/types/top.rb +6 -6
- data/lib/rdl/types/tuple.rb +56 -24
- data/lib/rdl/types/type.rb +9 -0
- data/lib/rdl/types/type_inferencer.rb +1 -1
- data/lib/rdl/types/union.rb +52 -26
- data/lib/rdl/types/var.rb +7 -6
- data/lib/rdl/types/vararg.rb +5 -6
- data/lib/rdl/types/wild_query.rb +9 -2
- data/lib/rdl/util.rb +9 -7
- data/lib/rdl/wrap.rb +90 -72
- data/lib/rdl_types.rb +2 -2
- data/rdl.gemspec +6 -8
- data/test/test_alias.rb +4 -3
- data/test/test_contract.rb +5 -4
- data/test/test_dsl.rb +2 -1
- data/test/test_generic.rb +30 -26
- data/test/test_intersection.rb +3 -3
- data/test/test_le.rb +129 -61
- data/test/test_lib_types.rb +3 -2
- data/test/test_member.rb +33 -46
- data/test/test_parser.rb +113 -116
- data/test/test_query.rb +2 -1
- data/test/test_rdl.rb +64 -6
- data/test/test_rdl_type.rb +3 -2
- data/test/test_type_contract.rb +30 -12
- data/test/test_typecheck.rb +893 -0
- data/test/test_types.rb +50 -54
- data/test/test_wrap.rb +2 -1
- data/types/ruby-2.x/_aliases.rb +13 -2
- data/types/ruby-2.x/bigdecimal.rb +60 -85
- data/types/ruby-2.x/bignum.rb +80 -119
- data/types/ruby-2.x/complex.rb +33 -40
- data/types/ruby-2.x/fixnum.rb +81 -120
- data/types/ruby-2.x/float.rb +79 -116
- data/types/ruby-2.x/integer.rb +187 -22
- data/types/ruby-2.x/nil.rb +12 -0
- data/types/ruby-2.x/numeric.rb +38 -38
- data/types/ruby-2.x/object.rb +3 -3
- data/types/ruby-2.x/random.rb +2 -0
- data/types/ruby-2.x/range.rb +20 -19
- data/types/ruby-2.x/rational.rb +40 -40
- data/types/ruby-2.x/regexp.rb +4 -4
- data/types/ruby-2.x/string.rb +15 -17
- metadata +17 -16
- 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 '
|
2
|
+
require 'digest'
|
3
|
+
require 'set'
|
4
|
+
require 'parser/current'
|
4
5
|
|
5
6
|
module RDL
|
6
7
|
end
|
7
8
|
|
8
|
-
|
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
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
|
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
|
-
|
51
|
+
require 'rdl/switch.rb'
|
42
52
|
$__rdl_wrap_switch = RDL::Switch.new
|
43
53
|
$__rdl_contract_switch = RDL::Switch.new
|
44
54
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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' =>
|
56
|
-
'%
|
57
|
-
|
122
|
+
$__rdl_special_types = {'%any' => $__rdl_top_type,
|
123
|
+
'%bot' => $__rdl_bot_type,
|
124
|
+
'%bool' => $__rdl_bool_type}
|
data/lib/rdl/info.rb
ADDED
@@ -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
|
data/lib/rdl/query.rb
CHANGED
@@ -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
|
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 $
|
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 $
|
31
|
-
$
|
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 $
|
39
|
-
$
|
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
|
-
$
|
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.
|
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))
|