Module: Contracts::CallWith

Included in:
Contract
Defined in:
lib/contracts/call_with.rb

Instance Method Summary collapse

Instance Method Details

#call_with(this, *args, &blk) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/contracts/call_with.rb', line 3

def call_with(this, *args, &blk)
  args << blk if blk

  # Explicitly append blk=nil if nil != Proc contract violation anticipated
  nil_block_appended = maybe_append_block!(args, blk)

  # Explicitly append options={} if Hash contract is present
  maybe_append_options!(args, blk)

  # Loop forward validating the arguments up to the splat (if there is one)
  (@args_contract_index || args.size).times do |i|
    contract = args_contracts[i]
    arg = args[i]
    validator = @args_validators[i]

    unless validator && validator[arg]
      return unless Contract.failure_callback(:arg => arg,
                                              :contract => contract,
                                              :class => klass,
                                              :method => method,
                                              :contracts => self,
                                              :arg_pos => i+1,
                                              :total_args => args.size,
                                              :return_value => false)
    end

    if contract.is_a?(Contracts::Func) && blk && !nil_block_appended
      blk = Contract.new(klass, arg, *contract.contracts)
    elsif contract.is_a?(Contracts::Func)
      args[i] = Contract.new(klass, arg, *contract.contracts)
    end
  end

  # If there is a splat loop backwards to the lower index of the splat
  # Once we hit the splat in this direction set its upper index
  # Keep validating but use this upper index to get the splat validator.
  if @args_contract_index
    splat_upper_index = @args_contract_index
    (args.size - @args_contract_index).times do |i|
      arg = args[args.size - 1 - i]

      if args_contracts[args_contracts.size - 1 - i].is_a?(Contracts::Args)
        splat_upper_index = i
      end

      # Each arg after the spat is found must use the splat validator
      j = i < splat_upper_index ? i : splat_upper_index
      contract = args_contracts[args_contracts.size - 1 - j]
      validator = @args_validators[args_contracts.size - 1 - j]

      unless validator && validator[arg]
        return unless Contract.failure_callback(:arg => arg,
                                                :contract => contract,
                                                :class => klass,
                                                :method => method,
                                                :contracts => self,
                                                :arg_pos => i-1,
                                                :total_args => args.size,
                                                :return_value => false)
      end

      if contract.is_a?(Contracts::Func)
        args[args.size - 1 - i] = Contract.new(klass, arg, *contract.contracts)
      end
    end
  end

  # If we put the block into args for validating, restore the args
  # OR if we added a fake nil at the end because a block wasn't passed in.
  args.slice!(-1) if blk || nil_block_appended
  result = if method.respond_to?(:call)
             # proc, block, lambda, etc
             method.call(*args, &blk)
           else
             # original method name referrence
             added_block = blk ? lambda { |*params| blk.call(*params) } : nil
             method.send_to(this, *args, &added_block)
           end

  unless @ret_validator[result]
    Contract.failure_callback(:arg => result,
                              :contract => ret_contract,
                              :class => klass,
                              :method => method,
                              :contracts => self,
                              :return_value => true)
  end

  this.verify_invariants!(method) if this.respond_to?(:verify_invariants!)

  if ret_contract.is_a?(Contracts::Func)
    result = Contract.new(klass, result, *ret_contract.contracts)
  end

  result
end