File: call_with.rb

package info (click to toggle)
ruby-contracts 0.17.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 544 kB
  • sloc: ruby: 3,807; makefile: 4; sh: 2
file content (130 lines) | stat: -rw-r--r-- 4,586 bytes parent folder | download
1
2
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
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
129
130
# frozen_string_literal: true

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

    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
  end
end