Module: Contracts::ClassMethods

Defined in:
lib/contracts.rb

Instance Method Summary collapse

Instance Method Details

#contract(name = nil, **options, &block) ⇒ Object



917
918
919
920
921
922
# File 'lib/contracts.rb', line 917

def contract(name = nil, **options, &block)
  if name.nil? then @__contracts_pending = [options, block, caller_locations(1, 1).first]
                    return
  end
  declare_contract(name, :instance, options, &block)
end

#declare_contract(name, type, options, source_location: caller_locations(2, 1).first, &block) ⇒ Object



956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
# File 'lib/contracts.rb', line 956

def declare_contract(name, type, options, source_location: caller_locations(2, 1).first, &block)
  contract = Contract.new(owner: self, method_name: name, method_type: type, source_location: source_location,
                          options: options)
  ContractBuilder.new(contract).instance_eval(&block) if block
  merge_parent_contract!(contract) unless Contracts.configuration.inheritance_mode == :independent
  if contract.snapshot_block.nil? && @__contracts_snapshot
    contract.instance_variable_set(:@snapshot_block,
                                   @__contracts_snapshot)
  end
  Contracts.registry.register(contract)

  wrap_contract(name, contract) if method_defined?(name,
                                                   false) || private_method_defined?(name,
                                                                                     false) || protected_method_defined?(
                                                                                       name, false
                                                                                     )
  contract
end

#invariant(description = "invariant", &block) ⇒ Object



924
925
926
927
928
929
930
931
932
933
934
935
936
# File 'lib/contracts.rb', line 924

def invariant(description = "invariant", &block)
  location = caller_locations(1, 1).first
  contract = Contracts.registry.find(self,
                                     :__invariant__) || Contract.new(owner: self, method_name: :__invariant__,
                                                                     source_location: location)
  contract.invariants << Invariant.new(id: "#{name || object_id}:#{caller_locations(1, 1).first.lineno}",
                                       owner: self, description: description, predicate: block, source_location: caller_locations(1, 1).first, options: {})

  Contracts.registry.register(contract)
  @__contracts_has_invariants = true
  wrap_initialize_for_invariants if method_defined?(:initialize,
                                                    false) || private_method_defined?(:initialize, false)
end

#merge_parent_contract!(contract) ⇒ Object



975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
# File 'lib/contracts.rb', line 975

def merge_parent_contract!(contract)
  parent = ancestors.drop(1).lazy.map do |ancestor|
    Contracts.registry.for_class(ancestor).find do |candidate|
      candidate.method_name == contract.method_name && candidate.method_type == contract.method_type
    end
  end.find(&:itself)
  return unless parent

  if Contracts.configuration.inheritance_mode == :strict
    parent.parameters.each do |name, constraint|
      child = contract.parameters[name]
      if child && child.description != constraint.description
        raise InheritanceViolation.new(owner: self, method_name: contract.method_name, contract_type: :inheritance,
                                       description: "parameter #{name} changes parent constraint #{constraint.description}")
      end
    end
  end
  contract.parameters = parent.parameters.merge(contract.parameters) unless parent.parameters.empty?
  contract.positionals = parent.positionals if contract.positionals.empty?
  contract.preconditions.unshift(*parent.preconditions)
  contract.postconditions.unshift(*parent.postconditions)
  contract.return_constraint = parent.return_constraint unless contract.return_constraint
  contract.allowed_exceptions.unshift(*parent.allowed_exceptions)
  contract.observed.unshift(*parent.observed.reject do |observation|
    contract.observed.any? do |own|
      own.name == observation.name
    end
  end)
end

#method_added(name) ⇒ Object



940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
# File 'lib/contracts.rb', line 940

def method_added(name)
  return if @__contracts_hook

  if (pending = @__contracts_pending)
    @__contracts_pending = nil

    declare_contract(name, :instance, pending[0], source_location: pending[2], &pending[1])
  elsif (contract = Contracts.registry.find(self, name, method_type: :instance)) && contract.owner == self
    wrap_contract(name, contract)
  elsif (contract = Contracts.registry.find(self, name, method_type: :singleton)) && contract.owner == self
    wrap_contract(name, contract)
  end
  wrap_initialize_for_invariants if name == :initialize && @__contracts_has_invariants
  super
end

#snapshot(&block) ⇒ Object



938
# File 'lib/contracts.rb', line 938

def snapshot(&block) = (@__contracts_snapshot = block)

#wrap_contract(name, contract) ⇒ Object



1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
# File 'lib/contracts.rb', line 1005

def wrap_contract(name, contract)
  return if contract.instance_variable_defined?(:@wrapped)

  original = instance_method(name)

  contract.method_source_location = original.source_location
  visibility = if private_method_defined?(name)
                 :private
               else
                 protected_method_defined?(name) ? :protected : :public
               end
  @__contracts_hook = true
  define_method(name) do |*args, **kwargs, &block|
    Contracts.invoke(self, contract, args, kwargs, block) do
      original.bind_call(self, *args, **kwargs, &block)
    end
  end
  send(visibility, name)
  contract.instance_variable_set(:@wrapped, true)
ensure
  @__contracts_hook = false
end

#wrap_initialize_for_invariantsObject



1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
# File 'lib/contracts.rb', line 1028

def wrap_initialize_for_invariants
  return if @__contracts_initialize_wrapped

  original = instance_method(:initialize)
  @__contracts_hook = true
  define_method(:initialize) do |*args, **kwargs, &block|
    original.bind_call(self, *args, **kwargs, &block).tap do
      Contracts.check_invariants!(self) if Contracts.configuration.check_invariants_after_initialize && Contracts.configuration.invariant_checking != :disabled
    end
  end
  private :initialize
  @__contracts_initialize_wrapped = true
ensure
  @__contracts_hook = false
end