typed.rb 0.0.11 → 0.0.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/typed.rb +42 -4
- data/lib/typed/language.rb +62 -14
- data/lib/typed/model/tm_array_literal.rb +1 -1
- data/lib/typed/model/tm_boolean_operator.rb +1 -1
- data/lib/typed/model/tm_fun.rb +4 -4
- data/lib/typed/model/tm_hash_literal.rb +2 -2
- data/lib/typed/model/tm_range_literal.rb +1 -1
- data/lib/typed/model/tm_rescue.rb +1 -1
- data/lib/typed/model/tm_send.rb +3 -0
- data/lib/typed/prelude_registry.bin +0 -0
- data/lib/typed/types/ty_object.rb +8 -0
- data/lib/typed/version.rb +1 -1
- data/lib/typed.rb +37 -0
- data/spec/lib/language_spec.rb +19 -3
- data/spec/lib/typing/instance_vars_spec.rb +1 -1
- data/spec/lib/typing/tm_raise_spec.rb +1 -1
- data/spec/lib/typing/tm_range_literal_spec.rb +4 -4
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c111e1f20ab0b156bcead6d988eae88c0aabf660
|
4
|
+
data.tar.gz: 8103b73f4455913b46606a7e81d2c64bcac2f1e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb9195b5d6411a04ae2fd223ce61db7b314ea4cb2a38ad661b48827fc8f50fdcec2827af10d0d09131dfae81fc438f51ea354f1ce1bab883edb46d49105f424c
|
7
|
+
data.tar.gz: b038edcf95e5551c70c544cf3e7e089d365b8069b5d6785d11d8dbe6de7c9a835ccca41bc323fa8ff96b452053f882e4cb72dfbbd9b15baa0d6a1ec1d8004ad9
|
data/bin/typed.rb
CHANGED
@@ -3,8 +3,34 @@
|
|
3
3
|
require 'pry'
|
4
4
|
require_relative '../lib/typed'
|
5
5
|
require 'benchmark'
|
6
|
-
|
7
6
|
require 'set'
|
7
|
+
require 'optparse'
|
8
|
+
|
9
|
+
class OptionsParser
|
10
|
+
class << self
|
11
|
+
def parse(options)
|
12
|
+
args = { :dynamic_warnings => false }
|
13
|
+
|
14
|
+
|
15
|
+
opt_parser = OptionParser.new do |opts|
|
16
|
+
opts.banner = 'Usage: typed.rb [options] [path]'
|
17
|
+
|
18
|
+
opts.on('-m', '--missing-type', 'Produce warnings when a missing type annotation has been detected') do |m|
|
19
|
+
args[:dynamic_warnings] = true
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on('-h', '--help', 'Prints this help') do
|
23
|
+
puts opts
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
opt_parser.parse!(options)
|
29
|
+
|
30
|
+
args
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
8
34
|
|
9
35
|
class TargetFinder
|
10
36
|
# Generate a list of target files by expanding globbing patterns
|
@@ -102,9 +128,21 @@ class TargetFinder
|
|
102
128
|
end
|
103
129
|
end
|
104
130
|
|
131
|
+
TypedRb.options = OptionsParser.parse(ARGV)
|
132
|
+
|
133
|
+
success = true
|
105
134
|
time = Benchmark.realtime do
|
106
|
-
|
107
|
-
|
135
|
+
begin
|
136
|
+
files_to_check = TargetFinder.new.find(ARGV).reject { |f| f == File.expand_path(__FILE__) }
|
137
|
+
TypedRb::Language.new.check_files(files_to_check, true)
|
138
|
+
rescue TypedRb::TypeCheckError => e
|
139
|
+
success = false
|
140
|
+
end
|
108
141
|
end
|
109
142
|
|
110
|
-
puts "
|
143
|
+
puts "\nFinished in #{time} seconds"
|
144
|
+
if success == false
|
145
|
+
exit(1)
|
146
|
+
else
|
147
|
+
exit(0)
|
148
|
+
end
|
data/lib/typed/language.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require_relative './runtime/ast_parser'
|
2
|
+
require 'colorize'
|
2
3
|
|
3
4
|
module TypedRb
|
4
5
|
class Language
|
@@ -67,7 +68,7 @@ module TypedRb
|
|
67
68
|
end
|
68
69
|
end
|
69
70
|
|
70
|
-
def check_files(files)
|
71
|
+
def check_files(files, raise_errors = false)
|
71
72
|
::BasicObject::TypeRegistry.clear
|
72
73
|
$TYPECHECK = true
|
73
74
|
prelude_path = File.join(File.dirname(__FILE__), 'prelude.rb')
|
@@ -85,27 +86,74 @@ module TypedRb
|
|
85
86
|
::BasicObject::TypeRegistry.normalize_types!
|
86
87
|
TypingContext.clear(:top_level)
|
87
88
|
check_result = nil
|
89
|
+
errors = {}
|
88
90
|
ordered_files.each do |file|
|
89
|
-
puts "*** FILE #{file}"
|
90
91
|
$TYPECHECK_FILE = file
|
91
92
|
expr = File.open(file, 'r').read
|
92
|
-
|
93
|
+
begin
|
93
94
|
check_result = check_type(parse(expr))
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
95
|
+
print '.'.green
|
96
|
+
rescue TypedRb::Types::UncomparableTypes, TypedRb::TypeCheckError => e
|
97
|
+
print 'E'.red
|
98
|
+
errors_for_file = errors[file] || []
|
99
|
+
errors_for_file << e
|
100
|
+
errors[file] = errors_for_file
|
101
|
+
end
|
101
102
|
end
|
102
|
-
|
103
|
-
|
103
|
+
top_level_errors = []
|
104
|
+
begin
|
105
|
+
::BasicObject::TypeRegistry.check_super_type_annotations
|
106
|
+
@unification_result = run_unification
|
107
|
+
rescue TypedRb::Types::UncomparableTypes, TypedRb::TypeCheckError => e
|
108
|
+
print 'E'.red
|
109
|
+
top_level_errors << e
|
110
|
+
end
|
111
|
+
puts "\n"
|
112
|
+
total_errors = all_errors(top_level_errors, errors)
|
113
|
+
dynamic_warnings = TypedRb.dynamic_warnings
|
114
|
+
if total_errors.count > 0
|
115
|
+
puts "\nErrors:"
|
116
|
+
report_errors(top_level_errors, errors, {})
|
117
|
+
end
|
118
|
+
if dynamic_warnings.count > 0
|
119
|
+
puts "\nWarnings:"
|
120
|
+
report_errors([], {}, dynamic_warnings)
|
121
|
+
end
|
122
|
+
puts "\nProcessed #{files.size} files, #{total_errors.count} errors, #{dynamic_warnings.count} warnings\n"
|
123
|
+
check_errors(total_errors) if raise_errors
|
104
124
|
check_result
|
105
125
|
end
|
106
126
|
|
107
|
-
def
|
108
|
-
|
127
|
+
def report_errors(top_level_errors, errors, warnings)
|
128
|
+
files = (errors.keys||[]) + (warnings.keys||[])
|
129
|
+
files.each do |file|
|
130
|
+
errors_for_file = errors[file] || []
|
131
|
+
warnings_for_file = warnings[file] || []
|
132
|
+
errors_for_file.each do |error|
|
133
|
+
puts "\n"
|
134
|
+
puts error.message.red
|
135
|
+
end
|
136
|
+
warnings_for_file.each do |warning|
|
137
|
+
puts "\n"
|
138
|
+
puts warning.message.yellow
|
139
|
+
end
|
140
|
+
end
|
141
|
+
top_level_errors.each do |error|
|
142
|
+
puts "\n"
|
143
|
+
puts error.message.red
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def all_errors(top_level_errors, errors)
|
148
|
+
(top_level_errors||[]) + ((errors||{}).values.reduce(&:concat) || [])
|
149
|
+
end
|
150
|
+
|
151
|
+
def check_errors(total_errors)
|
152
|
+
raise total_errors.first if total_errors.count > 0
|
153
|
+
end
|
154
|
+
|
155
|
+
def check_file(path, raise_errors = false)
|
156
|
+
check_files([path], raise_errors)
|
109
157
|
end
|
110
158
|
|
111
159
|
def parse(expr)
|
@@ -11,7 +11,7 @@ module TypedRb
|
|
11
11
|
|
12
12
|
def check_type(context)
|
13
13
|
element_types = elements.map { |element| element.check_type(context) }
|
14
|
-
max_type = element_types.max
|
14
|
+
max_type = element_types.reduce(&:max)
|
15
15
|
type_var = Types::Polymorphism::TypeVariable.new('Array:T',
|
16
16
|
:node => node,
|
17
17
|
:gen_name => false,
|
@@ -23,7 +23,7 @@ module TypedRb
|
|
23
23
|
var
|
24
24
|
else
|
25
25
|
types = [lhs_type, rhs_type].reject { |type| type.is_a?(Types::TyUnit) || type.is_a?(Types::TyError) }
|
26
|
-
type = types.max
|
26
|
+
type = types.reduce(&:max)
|
27
27
|
type = Types::TyUnit.new if type.nil?
|
28
28
|
type.node = node
|
29
29
|
type
|
data/lib/typed/model/tm_fun.rb
CHANGED
@@ -24,10 +24,10 @@ module TypedRb
|
|
24
24
|
|
25
25
|
function_klass_type, function_type = owner_type.find_function_type(name, @arg_count, @has_block)
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
if function_type.nil? || function_type.dynamic?
|
28
|
+
TypedRb.log_dynamic_warning(node, owner_type, name)
|
29
|
+
return Types::TyUnit.new(node)
|
30
|
+
end
|
31
31
|
|
32
32
|
context = setup_context(context, function_type)
|
33
33
|
# check the body with the new bindings for the args
|
@@ -11,8 +11,8 @@ module TypedRb
|
|
11
11
|
|
12
12
|
def check_type(context)
|
13
13
|
pair_types = pairs.map { |key, element| [key.check_type(context), element.check_type(context)] }
|
14
|
-
max_key_type = pair_types.map(&:first).max
|
15
|
-
max_value_type = pair_types.map(&:last).max
|
14
|
+
max_key_type = pair_types.map(&:first).reduce(&:max)
|
15
|
+
max_value_type = pair_types.map(&:last).reduce(&:max)
|
16
16
|
type_var_key = Types::Polymorphism::TypeVariable.new('Hash:T',
|
17
17
|
:node => node,
|
18
18
|
:gen_name => false,
|
@@ -15,7 +15,7 @@ module TypedRb
|
|
15
15
|
def check_type(context)
|
16
16
|
start_range_type = start_range.check_type(context)
|
17
17
|
end_range_type = end_range.check_type(context)
|
18
|
-
max_type =
|
18
|
+
max_type = start_range_type.max(end_range_type)
|
19
19
|
|
20
20
|
type_var = Types::Polymorphism::TypeVariable.new('Range:T',
|
21
21
|
:node => node,
|
@@ -13,7 +13,7 @@ module TypedRb
|
|
13
13
|
|
14
14
|
def check_type(context)
|
15
15
|
if catch_var
|
16
|
-
exception_type = exceptions.map{|e| e.check_type(context) }.max
|
16
|
+
exception_type = exceptions.map{|e| e.check_type(context) }.reduce(&:max)
|
17
17
|
context.add_binding!(catch_var, exception_type)
|
18
18
|
end
|
19
19
|
if rescue_body
|
data/lib/typed/model/tm_send.rb
CHANGED
@@ -52,6 +52,8 @@ module TypedRb
|
|
52
52
|
def check_instantiation(context)
|
53
53
|
self_type = singleton_object_type(receiver, context).as_object_type
|
54
54
|
function_klass_type, function_type = self_type.find_function_type(:initialize, args.size, @block)
|
55
|
+
TypedRb.log_dynamic_warning(node, self_type, :initialize) if function_type.dynamic?
|
56
|
+
|
55
57
|
# function application
|
56
58
|
@message = :initialize
|
57
59
|
begin
|
@@ -94,6 +96,7 @@ module TypedRb
|
|
94
96
|
check_lambda_application(receiver_type, context)
|
95
97
|
else
|
96
98
|
function_klass_type, function_type = receiver_type.find_function_type(message, args.size, @block)
|
99
|
+
TypedRb.log_dynamic_warning(node, receiver_type, message) if function_type.dynamic?
|
97
100
|
# begin
|
98
101
|
if function_type.nil?
|
99
102
|
error_message = "Error type checking message sent '#{message}': Type information for #{receiver_type}:#{message} not found."
|
Binary file
|
@@ -137,6 +137,14 @@ module TypedRb
|
|
137
137
|
TyObject.new(smaller_common_class, (node || self.node || other_type.node))
|
138
138
|
end
|
139
139
|
|
140
|
+
def max(other)
|
141
|
+
if other.is_a?(TyObject)
|
142
|
+
[self, other].max rescue TyObject.new((self.classes & other.classes).first, (node || other.node))
|
143
|
+
else
|
144
|
+
fail TypedRb::UncomparableTypes(self, other).new
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
140
148
|
def <=>(other)
|
141
149
|
if other.is_a?(TyObject)
|
142
150
|
if other.with_ruby_type
|
data/lib/typed/version.rb
CHANGED
data/lib/typed.rb
CHANGED
@@ -100,6 +100,25 @@ end
|
|
100
100
|
Kernel.reset_dependencies
|
101
101
|
|
102
102
|
module TypedRb
|
103
|
+
|
104
|
+
def options
|
105
|
+
@options || {}
|
106
|
+
end
|
107
|
+
|
108
|
+
def options=(options)
|
109
|
+
@options = options
|
110
|
+
end
|
111
|
+
|
112
|
+
def dynamic_warnings
|
113
|
+
@dynamic_warnings ||= {}
|
114
|
+
end
|
115
|
+
|
116
|
+
def register_dynamic_warning(warning)
|
117
|
+
file_warnings = dynamic_warnings[$TYPECHECK_FILE] || []
|
118
|
+
file_warnings << warning
|
119
|
+
dynamic_warnings[$TYPECHECK_FILE] = file_warnings
|
120
|
+
end
|
121
|
+
|
103
122
|
def log(client_binding, level, message)
|
104
123
|
client = client_binding.receiver
|
105
124
|
client_id = if client.instance_of?(Class)
|
@@ -121,6 +140,14 @@ module TypedRb
|
|
121
140
|
logger('[' + client_id.gsub('::', '/') + ']').send(level, message)
|
122
141
|
end
|
123
142
|
|
143
|
+
def log_dynamic_warning(node, owner, name)
|
144
|
+
if options[:dynamic_warnings]
|
145
|
+
error = TypeCheckError.new("Error type checking function #{owner}##{name}: Cannot find function type information for owner.", node)
|
146
|
+
print '.'.yellow
|
147
|
+
register_dynamic_warning(error)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
124
151
|
def logger(client)
|
125
152
|
logger = Log4r::Logger[client]
|
126
153
|
logger = Log4r::Logger.new(client) if logger.nil?
|
@@ -154,6 +181,16 @@ TypedRb.module_eval do
|
|
154
181
|
public :logger
|
155
182
|
module_function(:set_level)
|
156
183
|
public :set_level
|
184
|
+
module_function(:log_dynamic_warning)
|
185
|
+
public :log_dynamic_warning
|
186
|
+
module_function(:options=)
|
187
|
+
public :options=
|
188
|
+
module_function(:options)
|
189
|
+
public :options
|
190
|
+
module_function(:dynamic_warnings)
|
191
|
+
public :dynamic_warnings
|
192
|
+
module_function(:register_dynamic_warning)
|
193
|
+
public :register_dynamic_warning
|
157
194
|
end
|
158
195
|
|
159
196
|
Dir[File.join(File.dirname(__FILE__), '**/*.rb')].each do |file|
|
data/spec/lib/language_spec.rb
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
require_relative '../spec_helper'
|
2
2
|
|
3
|
+
def silence_stream(stream)
|
4
|
+
old_stream = stream.dup
|
5
|
+
stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null')
|
6
|
+
stream.sync = true
|
7
|
+
yield
|
8
|
+
ensure
|
9
|
+
stream.reopen(old_stream)
|
10
|
+
old_stream.close
|
11
|
+
end
|
12
|
+
|
3
13
|
describe TypedRb::Language do
|
4
14
|
let(:language) { described_class.new }
|
5
15
|
let(:file) { File.join(File.dirname(__FILE__), 'examples', example) }
|
@@ -9,7 +19,9 @@ describe TypedRb::Language do
|
|
9
19
|
let(:example) { 'counter.rb' }
|
10
20
|
|
11
21
|
it 'should be possible to type check the code' do
|
12
|
-
|
22
|
+
silence_stream(STDOUT) do
|
23
|
+
language.check_file(file, true)
|
24
|
+
end
|
13
25
|
expect_binding(language, Counter, '@counter', Integer)
|
14
26
|
end
|
15
27
|
end
|
@@ -18,7 +30,9 @@ describe TypedRb::Language do
|
|
18
30
|
let(:example) { 'if.rb' }
|
19
31
|
|
20
32
|
it 'should be possible to type check the code' do
|
21
|
-
|
33
|
+
silence_stream(STDOUT) do
|
34
|
+
language.check_file(file, true)
|
35
|
+
end
|
22
36
|
expect_binding(language, TestIf, '@state', String)
|
23
37
|
end
|
24
38
|
end
|
@@ -28,7 +42,9 @@ describe TypedRb::Language do
|
|
28
42
|
|
29
43
|
it 'should be possible to type check errors about array invariance' do
|
30
44
|
expect {
|
31
|
-
|
45
|
+
silence_stream(STDOUT) do
|
46
|
+
language.check_file(file, true)
|
47
|
+
end
|
32
48
|
}.to raise_error(TypedRb::TypeCheckError,
|
33
49
|
/Error type checking message sent 'mindless_func': Array\[Animal1\] expected, Array\[Cat1\] found/)
|
34
50
|
end
|
@@ -16,10 +16,10 @@ describe TypedRb::Model::TmRangeLiteral do
|
|
16
16
|
expect(result.type_vars.first.bound.ruby_type).to eq(String)
|
17
17
|
end
|
18
18
|
|
19
|
-
it '
|
19
|
+
it 'defaults to the super type for uncomparable types' do
|
20
20
|
code = '0..10.0'
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
result = language.check(code)
|
22
|
+
expect(result.ruby_type).to eq(Range)
|
23
|
+
expect(result.type_vars.first.bound.ruby_type).to eq(Numeric)
|
24
24
|
end
|
25
25
|
end
|