Class: Puppet::Pops::Evaluator::Closure

Inherits:
CallableSignature show all
Defined in:
lib/puppet/pops/evaluator/closure.rb

Overview

A Closure represents logic bound to a particular scope. As long as the runtime (basically the scope implementation) has the behavior of Puppet 3x it is not safe to return and later use this closure.

The 3x scope is essentially a named scope with an additional internal local/ephemeral nested scope state. In 3x there is no way to directly refer to the nested scopes, instead, the named scope must be in a particular state. Specifically, closures that require a local/ephemeral scope to exist at a later point will fail. It is safe to call a closure (even with 3x scope) from the very same place it was defined, but not returning it and expecting the closure to reference the scope’s state at the point it was created.

Note that this class is a CallableSignature, and the methods defined there should be used as the API for obtaining information in a callable-implementation agnostic way.

Direct Known Subclasses

Named

Defined Under Namespace

Classes: Named

Constant Summary collapse

CLOSURE_NAME =
'lambda'.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from CallableSignature

#args_range, #block_range, #block_type, #infinity?

Constructor Details

#initialize(evaluator, model, scope) ⇒ Closure

Returns a new instance of Closure.



19
20
21
22
23
# File 'lib/puppet/pops/evaluator/closure.rb', line 19

def initialize(evaluator, model, scope)
  @evaluator = evaluator
  @model = model
  @enclosing_scope = scope
end

Instance Attribute Details

#enclosing_scopeObject (readonly)



17
18
19
# File 'lib/puppet/pops/evaluator/closure.rb', line 17

def enclosing_scope
  @enclosing_scope
end

#evaluatorObject (readonly)



15
16
17
# File 'lib/puppet/pops/evaluator/closure.rb', line 15

def evaluator
  @evaluator
end

#modelObject (readonly)



16
17
18
# File 'lib/puppet/pops/evaluator/closure.rb', line 16

def model
  @model
end

Instance Method Details

#block_nameObject



121
122
123
124
# File 'lib/puppet/pops/evaluator/closure.rb', line 121

def block_name
  # TODO: Lambda's does not support blocks yet. This is a placeholder
  'unsupported_block'
end

#call(*args) ⇒ Object

Evaluates a closure in its enclosing scope after having matched given arguments with parameters (from left to right)



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/puppet/pops/evaluator/closure.rb', line 27

def call(*args)
  variable_bindings = combine_values_with_parameters(args)

  tc = Puppet::Pops::Types::TypeCalculator
  final_args = tc.infer_set(parameters.inject([]) do |tmp_args, param|
    if param.captures_rest
      tmp_args.concat(variable_bindings[param.name])
    else
      tmp_args << variable_bindings[param.name]
    end
  end)

  if tc.callable?(type, final_args)
    @evaluator.evaluate_block_with_bindings(@enclosing_scope, variable_bindings, @model.body)
  else
    raise ArgumentError, "#{closure_name} called with mis-matched arguments\n#{Puppet::Pops::Evaluator::CallableMismatchDescriber.diff_string(closure_name, final_args, [self])}"
  end
end

#call_by_name(args_hash, enforce_parameters) ⇒ Object

Call closure with argument assignment by name



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
# File 'lib/puppet/pops/evaluator/closure.rb', line 55

def call_by_name(args_hash, enforce_parameters)
  if enforce_parameters
    if args_hash.size > parameters.size
      raise ArgumentError, "Too many arguments: #{args_hash.size} for #{parameters.size}"
    end

    # associate values with parameters
    scope_hash = {}
    parameters.each do |p|
      name = p.name
      if (arg_value = args_hash[name]).nil?
        # only set result of default expr if it is defined (it is otherwise not possible to differentiate
        # between explicit undef and no default expression
        unless p.value.nil?
          scope_hash[name] = @evaluator.evaluate(p.value, @enclosing_scope)
        end
      else
        scope_hash[name] = arg_value
      end
    end

    missing = parameters.select { |p| !scope_hash.include?(p.name) }
    if missing.any?
      raise ArgumentError, "Too few arguments; no value given for required parameters #{missing.collect(&:name).join(" ,")}"
    end

    tc = Puppet::Pops::Types::TypeCalculator
    final_args = tc.infer_set(parameter_names.collect { |param| scope_hash[param] })
    if !tc.callable?(type, final_args)
      raise ArgumentError, "#{closure_name} called with mis-matched arguments\n#{Puppet::Pops::Evaluator::CallableMismatchDescriber.diff_string(closure_name, final_args, [self])}"
    end
  else
    scope_hash = args_hash
  end

  @evaluator.evaluate_block_with_bindings(@enclosing_scope, scope_hash, @model.body)
end

#closure_nameObject



129
130
131
# File 'lib/puppet/pops/evaluator/closure.rb', line 129

def closure_name()
  CLOSURE_NAME
end

#invoke(instance, calling_scope, args, &block) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method makes a Closure compatible with a Dispatch. This is used when the closure is wrapped in a Function and the function is called. (Saves an extra Dispatch that just delegates to a Closure and avoids having two checks of the argument type/arity validity).



50
51
52
# File 'lib/puppet/pops/evaluator/closure.rb', line 50

def invoke(instance, calling_scope, args, &block)
  call(*args, &block)
end

#last_captures_rest?Boolean

Returns:

  • (Boolean)


115
116
117
118
# File 'lib/puppet/pops/evaluator/closure.rb', line 115

def last_captures_rest?
  last = @model.parameters[-1]
  last && last.captures_rest
end

#parameter_countInteger

Returns the number of parameters (required and optional)

Returns:

  • (Integer)

    the total number of accepted parameters



99
100
101
102
# File 'lib/puppet/pops/evaluator/closure.rb', line 99

def parameter_count
  # yes, this is duplication of code, but it saves a method call
  @model.parameters.size
end

#parameter_namesObject



105
106
107
# File 'lib/puppet/pops/evaluator/closure.rb', line 105

def parameter_names
  @model.parameters.collect(&:name)
end

#parametersObject



93
94
95
# File 'lib/puppet/pops/evaluator/closure.rb', line 93

def parameters
  @model.parameters
end

#typeObject



110
111
112
# File 'lib/puppet/pops/evaluator/closure.rb', line 110

def type
  @callable ||= create_callable_type
end