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
Public Class Methods
# File lib/puppet/pops/evaluator/closure.rb 61 def initialize(evaluator, model) 62 @evaluator = evaluator 63 @model = model 64 end
Public Instance Methods
@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
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
# 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
# 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
@api public
# File lib/puppet/pops/evaluator/closure.rb 170 def closure_name() 171 CLOSURE_NAME 172 end
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
@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
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
@api public
# File lib/puppet/pops/evaluator/closure.rb 137 def parameter_names 138 @model.parameters.collect(&:name) 139 end
# File lib/puppet/pops/evaluator/closure.rb 125 def parameters 126 @model.parameters 127 end
@api public
# File lib/puppet/pops/evaluator/closure.rb 151 def params_struct 152 @params_struct ||= create_params_struct 153 end
# File lib/puppet/pops/evaluator/closure.rb 141 def return_type 142 @return_type ||= create_return_type 143 end
@api public
# File lib/puppet/pops/evaluator/closure.rb 146 def type 147 @callable ||= create_callable_type 148 end
Private Instance Methods
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
# 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
# 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
# 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
# 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
# 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
# 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
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