Skip to content

Commit 64ba729

Browse files
joshcooperluchihoratiu
authored andcommittedOct 12, 2021
(PUP-9561) Optimize parameter validation for blocks
Puppet used to infer types for arguments passed to a block. This was slow for several reasons: * Type inference is generally slow for large collections (hash/array) * Iterable functions like `reduce` pass an accumulator to the block for each iteration, so we inferred the accumulator's type multiple times. * When building a collection using `reduce`, the accumulator increases in size each time. So constructions like the following infer the type of an accumulator of size 0, 1, ..., N-1, which is equivalent to inferring the type of an accumulator of size N(N-1)/2: Integer[1, N].reduce([]) |$memo, $value| { $memo << $value} However, block arguments are often defined without a type, e.g. `$memo`, so the type inference was wasted effort. This commit switches from `callable?` to `callable_with?`. So rather than inferring the type of all arguments, and checking if those types are assignable to the parameter's type, we instead check if each argument is an instance of the parameter's type. For example, we check if the Ruby Hash {} is an instance of the `PAnyType`, which is always true[1]. The `callable_with?` method also checks required and optional parameters and return types, like the `callable?` method does. Note the `callable_with?` method was introduced in 2edd30e to address similar type inference slowness. [1] https://github.com/puppetlabs/puppet/blob/7.11.0/lib/puppet/pops/types/types.rb#L268
1 parent 7e1d805 commit 64ba729

File tree

1 file changed

+7
-5
lines changed

1 file changed

+7
-5
lines changed
 

‎lib/puppet/pops/evaluator/closure.rb

+7-5
Original file line numberDiff line numberDiff line change
@@ -219,24 +219,25 @@ def enclosing_scope
219219
def call_with_scope(scope, args)
220220
variable_bindings = combine_values_with_parameters(scope, args)
221221

222-
tc = Types::TypeCalculator.singleton
223-
final_args = tc.infer_set(parameters.reduce([]) do |tmp_args, param|
222+
final_args = parameters.reduce([]) do |tmp_args, param|
224223
if param.captures_rest
225224
tmp_args.concat(variable_bindings[param.name])
226225
else
227226
tmp_args << variable_bindings[param.name]
228227
end
229-
end)
228+
end
230229

231-
if type.callable?(final_args)
230+
if type.callable_with?(final_args, block_type)
232231
result = catch(:next) do
233232
@evaluator.evaluate_block_with_bindings(scope, variable_bindings, @model.body)
234233
end
235234
Types::TypeAsserter.assert_instance_of(nil, return_type, result) do
236235
"value returned from #{closure_name}"
237236
end
238237
else
239-
raise ArgumentError, Types::TypeMismatchDescriber.describe_signatures(closure_name, [self], final_args)
238+
tc = Types::TypeCalculator.singleton
239+
args_type = tc.infer_set(final_args)
240+
raise ArgumentError, Types::TypeMismatchDescriber.describe_signatures(closure_name, [self], args_type)
240241
end
241242
end
242243

@@ -309,6 +310,7 @@ def create_callable_type()
309310
to += param_range[1]
310311
end
311312
param_types = Types::PTupleType.new(types, Types::PIntegerType.new(from, to))
313+
# The block_type for a Closure is always nil for now, see comment in block_name above
312314
Types::PCallableType.new(param_types, nil, return_type)
313315
end
314316

0 commit comments

Comments
 (0)