class Puppet::Pops::Evaluator::Closure

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.

Constants

ANY_NUMBER_RANGE
CLOSURE_NAME
OPTIONAL_SINGLE_RANGE
REQUIRED_SINGLE_RANGE

Attributes

enclosing_scope[R]
evaluator[R]
model[R]

Public Class Methods

new(evaluator, model) click to toggle source
   # File lib/puppet/pops/evaluator/closure.rb
61 def initialize(evaluator, model)
62   @evaluator = evaluator
63   @model = model
64 end

Public Instance Methods

block_name() click to toggle source

@api public

    # File lib/puppet/pops/evaluator/closure.rb
162 def block_name
163   # TODO: Lambda's does not support blocks yet. This is a placeholder
164   'unsupported_block'
165 end
call(*args) click to toggle source

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

   # File lib/puppet/pops/evaluator/closure.rb
68 def call(*args)
69   call_with_scope(enclosing_scope, args)
70 end
call_by_name(args_hash, enforce_parameters) click to toggle source
   # File lib/puppet/pops/evaluator/closure.rb
86 def call_by_name(args_hash, enforce_parameters)
87   call_by_name_internal(enclosing_scope, args_hash, enforce_parameters)
88 end
call_by_name_with_scope(scope, args_hash, enforce_parameters) click to toggle source
   # File lib/puppet/pops/evaluator/closure.rb
82 def call_by_name_with_scope(scope, args_hash, enforce_parameters)
83   call_by_name_internal(scope, args_hash, enforce_parameters)
84 end
closure_name() click to toggle source

@api public

    # File lib/puppet/pops/evaluator/closure.rb
170 def closure_name()
171   CLOSURE_NAME
172 end
invoke(instance, calling_scope, args, &block) click to toggle source

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). @api private

   # File lib/puppet/pops/evaluator/closure.rb
76 def invoke(instance, calling_scope, args, &block)
77   enclosing_scope.with_global_scope do |global_scope|
78     call_with_scope(global_scope, args, &block)
79   end
80 end
last_captures_rest?() click to toggle source

@api public

    # File lib/puppet/pops/evaluator/closure.rb
156 def last_captures_rest?
157   last = @model.parameters[-1]
158   last && last.captures_rest
159 end
parameter_count() click to toggle source

Returns the number of parameters (required and optional) @return [Integer] the total number of accepted parameters

    # File lib/puppet/pops/evaluator/closure.rb
131 def parameter_count
132   # yes, this is duplication of code, but it saves a method call
133   @model.parameters.size
134 end
parameter_names() click to toggle source

@api public

    # File lib/puppet/pops/evaluator/closure.rb
137 def parameter_names
138   @model.parameters.collect(&:name)
139 end
parameters() click to toggle source
    # File lib/puppet/pops/evaluator/closure.rb
125 def parameters
126   @model.parameters
127 end
params_struct() click to toggle source

@api public

    # File lib/puppet/pops/evaluator/closure.rb
151 def params_struct
152   @params_struct ||= create_params_struct
153 end
return_type() click to toggle source
    # File lib/puppet/pops/evaluator/closure.rb
141 def return_type
142   @return_type ||= create_return_type
143 end
type() click to toggle source

@api public

    # File lib/puppet/pops/evaluator/closure.rb
146 def type
147   @callable ||= create_callable_type
148 end

Private Instance Methods

call_by_name_internal(closure_scope, args_hash, enforce_parameters) click to toggle source

Call closure with argument assignment by name

    # File lib/puppet/pops/evaluator/closure.rb
 91 def call_by_name_internal(closure_scope, args_hash, enforce_parameters)
 92   if enforce_parameters
 93     # Push a temporary parameter scope used while resolving the parameter defaults
 94     closure_scope.with_parameter_scope(closure_name, parameter_names) do |param_scope|
 95       # Assign all non-nil values, even those that represent non-existent parameters.
 96       args_hash.each { |k, v| param_scope[k] = v unless v.nil? }
 97       parameters.each do |p|
 98         name = p.name
 99         arg = args_hash[name]
100         if arg.nil?
101           # Arg either wasn't given, or it was undef
102           if p.value.nil?
103             # No default. Assign nil if the args_hash included it
104             param_scope[name] = nil if args_hash.include?(name)
105           else
106             param_scope[name] = param_scope.evaluate(name, p.value, closure_scope, @evaluator)
107           end
108         end
109       end
110       args_hash = param_scope.to_hash
111     end
112     Types::TypeMismatchDescriber.validate_parameters(closure_name, params_struct, args_hash)
113     result = catch(:next) do
114       @evaluator.evaluate_block_with_bindings(closure_scope, args_hash, @model.body)
115     end
116     Types::TypeAsserter.assert_instance_of(nil, return_type, result) do
117       "value returned from #{closure_name}"
118     end
119   else
120     @evaluator.evaluate_block_with_bindings(closure_scope, args_hash, @model.body)
121   end
122 end
call_with_scope(scope, args) click to toggle source
    # File lib/puppet/pops/evaluator/closure.rb
220 def call_with_scope(scope, args)
221   variable_bindings = combine_values_with_parameters(scope, args)
222 
223   final_args = parameters.reduce([]) do |tmp_args, param|
224     if param.captures_rest
225       tmp_args.concat(variable_bindings[param.name])
226     else
227       tmp_args << variable_bindings[param.name]
228     end
229   end
230 
231   if type.callable_with?(final_args, block_type)
232     result = catch(:next) do
233       @evaluator.evaluate_block_with_bindings(scope, variable_bindings, @model.body)
234     end
235     Types::TypeAsserter.assert_instance_of(nil, return_type, result) do
236       "value returned from #{closure_name}"
237     end
238   else
239     tc = Types::TypeCalculator.singleton
240     args_type = tc.infer_set(final_args)
241     raise ArgumentError, Types::TypeMismatchDescriber.describe_signatures(closure_name, [self], args_type)
242   end
243 end
combine_values_with_parameters(scope, args) click to toggle source
    # File lib/puppet/pops/evaluator/closure.rb
245 def combine_values_with_parameters(scope, args)
246   scope.with_parameter_scope(closure_name, parameter_names) do |param_scope|
247     parameters.each_with_index do |parameter, index|
248       param_captures     = parameter.captures_rest
249       default_expression = parameter.value
250 
251       if index >= args.size
252         if default_expression
253           # not given, has default
254           value = param_scope.evaluate(parameter.name, default_expression, scope, @evaluator)
255 
256           if param_captures && !value.is_a?(Array)
257             # correct non array default value
258             value = [value]
259           end
260         else
261           # not given, does not have default
262           if param_captures
263             # default for captures rest is an empty array
264             value = []
265           else
266             @evaluator.fail(Issues::MISSING_REQUIRED_PARAMETER, parameter, { :param_name => parameter.name })
267           end
268         end
269       else
270         given_argument = args[index]
271         if param_captures
272           # get excess arguments
273           value = args[(parameter_count-1)..-1]
274           # If the input was a single nil, or undef, and there is a default, use the default
275           # This supports :undef in case it was used in a 3x data structure and it is passed as an arg
276           #
277           if value.size == 1 && (given_argument.nil? || given_argument == :undef) && default_expression
278             value = param_scope.evaluate(parameter.name, default_expression, scope, @evaluator)
279             # and ensure it is an array
280             value = [value] unless value.is_a?(Array)
281           end
282         else
283           value = given_argument
284         end
285       end
286       param_scope[parameter.name] = value
287     end
288     param_scope.to_hash
289   end
290 end
create_callable_type() click to toggle source
    # File lib/puppet/pops/evaluator/closure.rb
292 def create_callable_type()
293   types = []
294   from = 0
295   to = 0
296   in_optional_parameters = false
297   closure_scope = enclosing_scope
298 
299   parameters.each do |param|
300     type, param_range = create_param_type(param, closure_scope)
301 
302     types << type
303 
304     if param_range[0] == 0
305       in_optional_parameters = true
306     elsif param_range[0] != 0 && in_optional_parameters
307       @evaluator.fail(Issues::REQUIRED_PARAMETER_AFTER_OPTIONAL, param, { :param_name => param.name })
308     end
309 
310     from += param_range[0]
311     to += param_range[1]
312   end
313   param_types = Types::PTupleType.new(types, Types::PIntegerType.new(from, to))
314   # The block_type for a Closure is always nil for now, see comment in block_name above
315   Types::PCallableType.new(param_types, nil, return_type)
316 end
create_param_type(param, closure_scope) click to toggle source
    # File lib/puppet/pops/evaluator/closure.rb
340 def create_param_type(param, closure_scope)
341   type = if param.type_expr
342            @evaluator.evaluate(param.type_expr, closure_scope)
343          else
344            Types::PAnyType::DEFAULT
345          end
346 
347   if param.captures_rest && type.is_a?(Types::PArrayType)
348     # An array on a slurp parameter is how a size range is defined for a
349     # slurp (Array[Integer, 1, 3] *$param). However, the callable that is
350     # created can't have the array in that position or else type checking
351     # will require the parameters to be arrays, which isn't what is
352     # intended. The array type contains the intended information and needs
353     # to be unpacked.
354     param_range = type.size_range
355     type = type.element_type
356   elsif param.captures_rest && !type.is_a?(Types::PArrayType)
357     param_range = ANY_NUMBER_RANGE
358   elsif param.value
359     param_range = OPTIONAL_SINGLE_RANGE
360   else
361     param_range = REQUIRED_SINGLE_RANGE
362   end
363   [type, param_range]
364 end
create_params_struct() click to toggle source
    # File lib/puppet/pops/evaluator/closure.rb
318 def create_params_struct
319   type_factory = Types::TypeFactory
320   members = {}
321   closure_scope = enclosing_scope
322 
323   parameters.each do |param|
324     arg_type, _ = create_param_type(param, closure_scope)
325     key_type = type_factory.string(param.name.to_s)
326     key_type = type_factory.optional(key_type) unless param.value.nil?
327     members[key_type] = arg_type
328   end
329   type_factory.struct(members)
330 end
create_return_type() click to toggle source
    # File lib/puppet/pops/evaluator/closure.rb
332 def create_return_type
333   if @model.return_type
334     @evaluator.evaluate(@model.return_type, @enclosing_scope)
335   else
336     Types::PAnyType::DEFAULT
337   end
338 end
signatures() click to toggle source

Produces information about parameters compatible with a 4x Function (which can have multiple signatures)

    # File lib/puppet/pops/evaluator/closure.rb
367 def signatures
368   [ self ]
369 end