lib/tspec.rb in tspec-0.3.0 vs lib/tspec.rb in tspec-0.4.0

- old
+ new

@@ -1,125 +1,74 @@ +require 'tspec/version' require 'tspec/type_error' +require 'tspec/core' -class Symbol - def return(*types) - self - end - - def receive(*type, **types) - self - end -end - -class UnboundMethod - def return(*types) - self - end - - def receive(*type, **types) - self - end -end - -class Method - def return(*types) - self - end - - def receive(*type, **types) - self - end -end - module TSpec @method_return_type_table = {} @method_arguments_type_table = {} @before_trace = {} + @module_function_flags = {} + @module_function_mode = {} @type_error_flag = false + HOOK_EVENT = %i(call return c_call c_return line) DEFINE_METHOD_SYMBOLS = %i(method_added singleton_method_added define_method instance_method method) - def self.value_type_check(value, *types) - types.any? do |type| - if type.instance_of?(Array) - return false unless value.instance_of?(Array) - - value.all? do |v| - type.any? do |t| - value_type_check(v, t) - end - end - else - value.instance_of?(type) - end - end - end - - TracePoint.trace do |tp| - case tp.event - when :call - if %i(return receive).include?(tp.method_id) && DEFINE_METHOD_SYMBOLS.include?(@before_trace[:method_id]) - ctx = tp.binding - - if %i(instance_method method).include?(@before_trace[:method_id]) - key = "#{tp.self.owner}::#{tp.self.name}" + @trace = TracePoint.new(*HOOK_EVENT) do |tp| + @trace.disable do + case tp.event + when :call + if %i(return receive).include?(tp.method_id) && DEFINE_METHOD_SYMBOLS.include?(@before_trace[:method_id]) + ctx = tp.binding + keys = get_keys(tp, @before_trace) + regist_type(tp.method_id, ctx, keys) else - klass = (@before_trace[:method_id] == :singleton_method_added) ? @before_trace[:self].singleton_class : @before_trace[:self] - key = "#{klass}::#{ctx.receiver}" + check_type(tp) end + when :c_call + if tp.method_id == :module_function && tp.defined_class == Module + @module_function_mode[tp.self] = true - case tp.method_id - when :return - @method_return_type_table[key] = ctx.local_variable_get(:types) - when :receive - @method_arguments_type_table[key] = ctx.local_variable_get(:types) + line = File.readlines(tp.path)[tp.lineno-1] + names = line.scan(/:.+/) - if @method_arguments_type_table[key].empty? - @method_arguments_type_table[key] = {ctx.local_variable_get(:type).__id__ => ctx.local_variable_get(:type)} - end - end - else - key = "#{tp.defined_class}::#{tp.method_id}" + if names.empty? + @module_function_flags[tp.self] = true + else + names.each do |name| + key = "#{tp.self}:#{name}" - if types = @method_arguments_type_table[key] - arguments = tp.binding.eval("method(:#{tp.method_id}).parameters.map(&:last)") - - types.each do |name, type| - name = arguments.first if name == type.__id__ - - unless arguments.include?(name) - @type_error_flag = true - raise NotFoundArgumentNameError, "undefined arguments `#{name}' for #{key}" - end - - value = tp.binding.local_variable_get(name) - - unless value_type_check(value, *type) - @type_error_flag = true - if type.instance_of?(Array) - raise ArgumentTypeError, "##{tp.method_id} '#{name}' variable should be #{type.map(&:inspect).join(' or ')}, but actual '#{value.inspect}' - #{value.class}" - else - raise ArgumentTypeError, "##{tp.method_id} '#{name}' variable should be #{type.inspect}, but actual '#{value.inspect}' - #{value.class}" + if type = @method_return_type_table[key] + @method_return_type_table["#{tp.self.singleton_class}:#{name}"] = type end + if type = @method_arguments_type_table[key] + @method_arguments_type_table["#{tp.self.singleton_class}:#{name}"] = type + end end end end - end - when :return - if !@type_error_flag - key = "#{tp.defined_class}::#{tp.method_id}" + when :c_return + if tp.method_id == :module_function && tp.defined_class == Module + @module_function_mode[tp.self] = false + end + when :return + if !@type_error_flag + key = "#{tp.defined_class}::#{tp.method_id}" - if types = @method_return_type_table[key] - unless value_type_check(tp.return_value, *types) - @type_error_flag = true - raise ReturnValueTypeError, "`#{tp.method_id}' expected return #{types.map(&:inspect).join(' or ')}, but actual `#{tp.return_value.inspect}' - #{tp.return_value.class}" + if types = @method_return_type_table[key] + unless value_type_check(tp.return_value, *types) + @type_error_flag = true + raise ReturnValueTypeError, "`#{tp.method_id}' expected return #{types.map(&:inspect).join(' or ')}, but actual `#{tp.return_value.inspect}' - #{tp.return_value.class}" + end end end - end - end + end - @type_error_flag = false + @type_error_flag = false - if tp.defined_class != Symbol || !%i(return receive).include?(tp.method_id) - @before_trace = {self: tp.self, method_id: tp.method_id} + if tp.defined_class != Symbol || !%i(return receive).include?(tp.method_id) + @before_trace = { self: tp.self, method_id: tp.method_id } + end end end + + @trace.enable end