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 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