Module: Contracts::CallWith

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

Instance Method Summary collapse

Instance Method Details

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



5
6
7
# File 'lib/contracts/call_with.rb', line 5

def call_with(this, *args, **kargs, &blk)
  call_with_inner(false, this, *args, **kargs, &blk)
end

#call_with_inner(returns, this, *args, **kargs, &blk) ⇒ Object



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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/contracts/call_with.rb', line 9

def call_with_inner(returns, this, *args, **kargs, &blk)
  args << blk if blk

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

  if @kargs_validator && !@kargs_validator[kargs]
    data = {
      arg:          kargs,
      contract:     kargs_contract,
      class:        klass,
      method:       method,
      contracts:    self,
      arg_pos:      :keyword,
      total_args:   args.size,
      return_value: false,
    }
    return ParamContractError.new("as return value", data) if returns
    return unless Contract.failure_callback(data)
  end

  # 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]
      data = {
        arg:          arg,
        contract:     contract,
        class:        klass,
        method:       method,
        contracts:    self,
        arg_pos:      i+1,
        total_args:   args.size,
        return_value: false,
      }
      return ParamContractError.new("as return value", data) if returns
      return unless Contract.failure_callback(data)
    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]
        # rubocop:disable Style/SoleNestedConditional
        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,
        })
        # rubocop:enable Style/SoleNestedConditional
      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, **kargs, &blk)
           else
             # original method name reference
             # Don't reassign blk, else Travis CI shows "stack level too deep".
             target_blk = blk
             target_blk = lambda { |*params| blk.call(*params) } if blk.is_a?(Contract)
             method.send_to(this, *args, **kargs, &target_blk)
           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