lib/rubype.rb in rubype-0.2.5 vs lib/rubype.rb in rubype-0.2.6

- old
+ new

@@ -1,122 +1,104 @@ require 'rubype/ordinal' - -# Builtin Contracts -class Any; end -module Boolean; end -TrueClass.send(:include, Boolean) -FalseClass.send(:include, Boolean) - +# === Rubype core === # class Module private - # @return [Module] def __rubype__ prepend (@__rubype__ = Module.new) unless @__rubype__ @__rubype__ end + # typesig :__rubype__, [] => Rubype - # @param meth [Symbol] - # @param type_info_hash [Hash] { [ArgInfo_1, ArgInfo_2, ... ArgInfo_n] => RtnInfo } - # @return self def typesig(meth, type_info_hash) - ::Rubype.send(:define_typed_method, self, meth, type_info_hash, __rubype__) + ::Rubype.define_typed_method(self, meth, type_info_hash, __rubype__) self end + # typesig :typesig, [Symbol, Hash] => Module end -class Method - # @return [Hash]: { [ArgInfo_1, ArgInfo_2, ... ArgInfo_n] => RtnInfo } - def type_info - if methods_hash = Rubype.typed_method_info[owner] - methods_hash[name] - end - end - - # @return [Array<Class, Symbol>]: [ArgInfo_1, ArgInfo_2, ... ArgInfo_n] - def arg_types - type_info.first.first if type_info - end - - # @return [Class, Symbol]: RtnInfo - def return_type - type_info.first.last if type_info - end -end - module Rubype + @@typed_method_info = Hash.new({}) class ArgumentTypeError < ::TypeError; end class ReturnTypeError < ::TypeError; end - @@typed_method_info = Hash.new({}) - class << self - def typed_method_info - @@typed_method_info - end + def define_typed_method(owner, meth, type_info_hash, __rubype__) + arg_types, rtn_type = *type_info_hash.first - private - # @param caller [Object] - # @param type_info_hash [Hash] { [ArgInfo_1, ArgInfo_2, ... ArgInfo_n] => RtnInfo } - # @param module [Module] - def define_typed_method(owner, meth, type_info_hash, __rubype__) - arg_types, rtn_type = *strip_type_info(type_info_hash) - @@typed_method_info[owner][meth] = { - arg_types => rtn_type - } - __rubype__.send(:define_method, meth) do |*args, &block| - ::Rubype.send(:assert_arg_type, self, meth, args, arg_types) - rtn = super(*args, &block) - ::Rubype.send(:assert_trn_type, self, meth, rtn, rtn_type) - rtn + @@typed_method_info[owner][meth] = { arg_types => rtn_type } + + __rubype__.send(:define_method, meth) do |*args, &block| + ::Rubype.assert_arg_type(self, meth, args, arg_types, caller) + super(*args, &block).tap do |rtn| + ::Rubype.assert_rtn_type(self, meth, rtn, rtn_type, caller) end end + end - # @param type_info_hash [Hash] { [ArgInfo_1, ArgInfo_2, ... ArgInfo_n] => RtnInfo } - # @return arg_types [Array<Class, Symbol>], rtn_type [Class, Symbol] - def strip_type_info(type_info_hash) - arg_types, rtn_type = type_info_hash.first - [arg_types, rtn_type] + def assert_arg_type(meth_caller, meth, args, type_infos, caller_trace) + args.zip(type_infos).each.with_index(1) do |(arg, type_info), i| + unless match_type?(arg, type_info) + raise ArgumentTypeError, + error_mes("#{meth_caller.class}##{meth}'s #{i}#{ordinal(i)} argument", type_info, arg, caller_trace) + end end + end - # @param caller [Module] - # @param meth [Symbol] - # @param args [Array<Object>] - # @param type_infos [Array<Class, Symbol>] - def assert_arg_type(meth_caller, meth, args, type_infos) - args.zip(type_infos).each.with_index(1) do |(arg, type_info), i| - case type_check(arg, type_info) - when :need_correct_class - raise ArgumentTypeError, - "Expected #{meth_caller.class}##{meth}'s #{i}#{ordinal(i)} argument to be #{type_info} but got #{arg.inspect} instead" - when :need_correct_method - raise ArgumentTypeError, - "Expected #{meth_caller.class}##{meth}'s #{i}#{ordinal(i)} argument to have method ##{type_info} but got #{arg.inspect} instead" - end - end + def assert_rtn_type(meth_caller, meth, rtn, type_info, caller_trace) + unless match_type?(rtn, type_info) + raise ReturnTypeError, + error_mes("#{meth_caller.class}##{meth}'s return", type_info, rtn, caller_trace) end + end - # @param caller [Module] - # @param rtn [Object] - # @param type_info [Class, Symbol] - def assert_trn_type(meth_caller, meth, rtn, type_info) - case type_check(rtn, type_info) - when :need_correct_class - raise ReturnTypeError, - "Expected #{meth_caller.class}##{meth} to return #{type_info} but got #{rtn.inspect} instead" - when :need_correct_method - raise ReturnTypeError, - "Expected #{meth_caller.class}##{meth} to return object which has method ##{type_info} but got #{rtn.inspect} instead" + def typed_method_info + @@typed_method_info + end + + private + def match_type?(obj, type_info) + case type_info + when Module; (obj.is_a?(type_info) || type_info == Any) + when Symbol; (obj.respond_to?(type_info)) end end - # @param obj [Object] - # @param type_info [Class, Symbol] - # @return [Symbol] - def type_check(obj, type_info) - case type_info - when Module - :need_correct_class unless (obj.is_a?(type_info) || type_info == Any) - when Symbol - :need_correct_method unless (obj.respond_to?(type_info)) + def error_mes(target, expected, actual, caller_trace) + expected_mes = case expected + when Module; expected + when Symbol; "respond to :#{expected}" end + <<-ERROR_MES +for #{target} +Expected: #{expected_mes}, +Actual: #{actual.inspect} + +#{caller_trace.join("\n")} + ERROR_MES end end +end +# === end (only 79 lines :D)=== # + +# Builtin Contracts +class Any; end +module Boolean; end +TrueClass.send(:include, Boolean) +FalseClass.send(:include, Boolean) + +class Method + def type_info + if methods_hash = Rubype.typed_method_info[owner] + methods_hash[name] + end + end + typesig :type_info, [] => Hash + + def arg_types + type_info.first.first if type_info + end + typesig :arg_types, [] => Array + + def return_type + type_info.first.last if type_info + end + typesig :arg_types, [] => Any end