typed.rb 0.0.11 → 0.0.12
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/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
|