From 6bc165b32683e36ac694f02e298d4b9479fd28f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorben=20Schro=CC=88der?= Date: Fri, 10 Apr 2015 16:56:51 +0200 Subject: [PATCH] Idea for some syntactic sugar --- README.md | 9 ++++++++- lib/rubype/syntactic_sugar.rb | 20 ++++++++++++++++++++ rubype.gemspec | 2 ++ test/minitest_helper.rb | 1 + test/test_rubype.rb | 33 ++++++++++++++++++++++++--------- 5 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 lib/rubype/syntactic_sugar.rb diff --git a/README.md b/README.md index 6d02f1b..baf789f 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,14 @@ def sum(x, y) end typesig :sum, [Numeric, Numeric] => String +# This type definition can also be written like this: + +require 'rubype/syntactic_sugar' +[Numeric, Numeric, String] > def sum(x, y) + (x + y).to_s +end + + # Assert first arg has method #to_i def sum(x, y) x.to_i + y @@ -17,7 +25,6 @@ end typesig :sum, [:to_i, Numeric] => Numeric ``` - This gem brings you advantage of type without changing existing code's behavior. ## Good point: diff --git a/lib/rubype/syntactic_sugar.rb b/lib/rubype/syntactic_sugar.rb new file mode 100644 index 0000000..b357774 --- /dev/null +++ b/lib/rubype/syntactic_sugar.rb @@ -0,0 +1,20 @@ +require 'binding_of_caller' + +class Array + def >(*args) + args_match = args.length == 1 && args.first.kind_of?(Symbol) + self_matches = length > 0 && all? { |e| e.kind_of?(Class) } + + if args_match && self_matches + return_type = self.last + call_string = "typesig :#{args.first}, [#{self[0...-1].map(&:name).join(', ')}] => #{return_type}" + binding.of_caller(1).eval(call_string) + else + if !args_match + raise "You must pass a Symbol as the only argument to a type definition!" + else + raise "Wrong type definition #{self}" + end + end + end +end \ No newline at end of file diff --git a/rubype.gemspec b/rubype.gemspec index 018a0e7..73bb796 100644 --- a/rubype.gemspec +++ b/rubype.gemspec @@ -20,6 +20,8 @@ Gem::Specification.new do |spec| spec.required_ruby_version = ">= 2.0.0" + spec.add_runtime_dependency "binding_of_caller", "~> 0.7.2" + spec.add_development_dependency "bundler", "~> 1.3" spec.add_development_dependency "rake" spec.add_development_dependency "minitest" diff --git a/test/minitest_helper.rb b/test/minitest_helper.rb index a214977..07c9cc1 100644 --- a/test/minitest_helper.rb +++ b/test/minitest_helper.rb @@ -1,4 +1,5 @@ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'rubype' +require 'rubype/syntactic_sugar' require 'pry' require 'minitest/autorun' diff --git a/test/test_rubype.rb b/test/test_rubype.rb index 9ef0a30..6ca33de 100644 --- a/test/test_rubype.rb +++ b/test/test_rubype.rb @@ -95,18 +95,17 @@ def test_mth(n1, n2) end typesig :test_mth, [Numeric, Numeric] => String RUBY_CODE - Object.const_set('MyClass', klass) - meth = klass.new.method(:test_mth) - assert_equal meth.type_info, { [Numeric, Numeric] => String } - assert_equal meth.arg_types, [Numeric, Numeric] - assert_equal meth.return_type, String + assert_correct_checks_on_class(klass) + end - err = assert_raises(Rubype::ReturnTypeError) { meth.(1,2) } - assert_equal err.message, %|Expected MyClass#test_mth to return String but got nil instead| + def test_type_info_syntactic_sugar + klass = Class.new.class_eval <<-RUBY_CODE + [Numeric, Numeric, String] > def test_mth(n1, n2) + end + RUBY_CODE - err = assert_raises(Rubype::ArgumentTypeError) { meth.(1,'2') } - assert_equal err.message, %|Expected MyClass#test_mth's 2nd argument to be Numeric but got "2" instead| + assert_correct_checks_on_class(klass) end private @@ -126,6 +125,22 @@ def assert_wrong_rtn(type_list, args, val) assert_raises(Rubype::ReturnTypeError) { define_test_method(type_list, args, val).call(*args) } end + def assert_correct_checks_on_class(klass) + Object.send(:remove_const, 'MyClass') if defined? MyClass + Object.const_set('MyClass', klass) + + meth = klass.new.method(:test_mth) + assert_equal meth.type_info, { [Numeric, Numeric] => String } + assert_equal meth.arg_types, [Numeric, Numeric] + assert_equal meth.return_type, String + + err = assert_raises(Rubype::ReturnTypeError) { meth.(1,2) } + assert_equal err.message, %|Expected MyClass#test_mth to return String but got nil instead| + + err = assert_raises(Rubype::ArgumentTypeError) { meth.(1,'2') } + assert_equal err.message, %|Expected MyClass#test_mth's 2nd argument to be Numeric but got "2" instead| + end + def define_test_method(type_list, args, val) klass = Class.new.class_eval <<-RUBY_CODE def call(#{arg_literal(args.count)})