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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 12f994fd40b7d71db8aa86c0875aa723347a8edd
4
- data.tar.gz: 13a8daf59012cddba4a106476f9609c901cd6f9b
3
+ metadata.gz: c111e1f20ab0b156bcead6d988eae88c0aabf660
4
+ data.tar.gz: 8103b73f4455913b46606a7e81d2c64bcac2f1e5
5
5
  SHA512:
6
- metadata.gz: c9f9d208d55df978a5e1fa6f6014f955ffd3c10922970ce9d9801c9ed56bfd0775840f43978283b6ebd9139dfc0a3b595b81473b16823342a413c1e4fd0d37ea
7
- data.tar.gz: aaa9b40826ca304563e4e5eb34e60f74efd3a5a47985bac003f2bd77c60f7ea25405bec24e4e3cffe5fa261a338370592cad42b68ebd25f52818a2192bc01abd
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
- files_to_check = TargetFinder.new.find(ARGV).reject { |f| f == File.expand_path(__FILE__) }
107
- TypedRb::Language.new.check_files(files_to_check)
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 "Finished in #{time} seconds"
143
+ puts "\nFinished in #{time} seconds"
144
+ if success == false
145
+ exit(1)
146
+ else
147
+ exit(0)
148
+ end
@@ -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
- #begin
93
+ begin
93
94
  check_result = check_type(parse(expr))
94
- #rescue TypedRb::Types::UncomparableTypes => e
95
- # puts e.backtrace.join("\n")
96
- # puts e.message
97
- # exit(-1)
98
- #rescue TypedRb::TypeCheckError => e
99
- # puts e.message
100
- #end
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
- ::BasicObject::TypeRegistry.check_super_type_annotations
103
- @unification_result = run_unification
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 check_file(path)
108
- check_files([path])
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 rescue Types::TyObject.new(Object, node)
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 rescue Types::TyObject.new(Object, node)
26
+ type = types.reduce(&:max)
27
27
  type = Types::TyUnit.new if type.nil?
28
28
  type.node = node
29
29
  type
@@ -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
- # fail TypeCheckError.new("Error type checking function #{owner}##{name}: Cannot find function type information for owner.", node)
28
- # Missing type information stops the type checking process
29
- # TODO: raise a warning here about the previous fact
30
- return Types::TyUnit.new(node) if function_type.nil? || function_type.dynamic?
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 rescue Types::TyObject.new(Object, node)
15
- max_value_type = pair_types.map(&:last).max rescue Types::TyObject.new(Object, node)
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 = [start_range_type, end_range_type].max
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module TypedRb
2
- VERSION = '0.0.11'
2
+ VERSION = '0.0.12'
3
3
  end
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|
@@ -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
- language.check_file(file)
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
- language.check_file(file)
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
- language.check_file(file)
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
@@ -98,6 +98,6 @@ __END
98
98
 
99
99
  expect {
100
100
  language.check(code)
101
- }.to raise_error
101
+ }.to raise_error(TypedRb::TypeCheckError)
102
102
  end
103
103
  end
@@ -24,7 +24,7 @@ __END
24
24
  __END
25
25
  expect {
26
26
  language.check(code)
27
- }.not_to raise_error(TypedRb::Types::UncomparableTypes)
27
+ }.not_to raise_error
28
28
  end
29
29
  end
30
30
 
@@ -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 'fails for incompatible types' do
19
+ it 'defaults to the super type for uncomparable types' do
20
20
  code = '0..10.0'
21
- expect {
22
- language.check(code)
23
- }.to raise_error
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typed.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.11
4
+ version: 0.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Antonio Garrote