rdl 1.1.1 → 2.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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))
|