puppetlabs/puppet · error · ArgumentError
Illegal legacy function definition! The code loaded from %{s
Error message
Illegal legacy function definition! The code loaded from %{source_ref} did not return the result of calling 'newfunction'. Got '%{klass}' What it means
Raised by RubyLegacyFunctionInstantiator after eval'ing legacy function code when the eval result is not a Hash. newfunction returns a Hash of function info (name, arity, type, block), so the file's last expression must be the newfunction call itself; any other final value fails here. This runs only when the function was not already loaded through another 3x API (func_info was nil).
Source
Thrown at lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb:47
# make the private loader available in a binding to allow it to be passed on
loader_for_function = loader.private_loader
here = get_binding(loader_for_function)
# Avoid reloading the function if already loaded via one of the APIs that trigger 3x function loading
# Check if function is already loaded the 3x way (and obviously not the 4x way since we would not be here in the
# first place.
environment = Puppet.lookup(:current_environment)
func_info = Puppet::Parser::Functions.environment_module(environment).get_function_info(typed_name.name.to_sym)
if func_info.nil?
# This will do the 3x loading and define the "function_<name>" and "real_function_<name>" methods
# in the anonymous module used to hold function definitions.
#
func_info = eval(ruby_code_string, here, source_ref, 1) # rubocop:disable Security/Eval
# Validate what was loaded
unless func_info.is_a?(Hash)
# TRANSLATORS - the word 'newfunction' should not be translated as it is a method name.
raise ArgumentError, _("Illegal legacy function definition! The code loaded from %{source_ref} did not return the result of calling 'newfunction'. Got '%{klass}'") % { source_ref: source_ref, klass: func_info.class }
end
unless func_info[:name] == "function_#{typed_name.name()}"
raise ArgumentError, _("The code loaded from %{source_ref} produced mis-matched name, expected 'function_%{type_name}', got '%{created_name}'") % {
source_ref: source_ref, type_name: typed_name.name, created_name: func_info[:name]
}
end
end
created = Puppet::Functions::Function3x.create_function(typed_name.name(), func_info, loader_for_function)
# create the function instance - it needs closure (scope), and loader (i.e. where it should start searching for things
# when calling functions etc.
# It should be bound to global scope
# Sets closure scope to nil, to let it be picked up at runtime from Puppet.lookup(:global_scope)
# If function definition used the loader from the binding to create a new loader, that loader wins
created.new(nil, loader_for_function)View on GitHub (pinned to e227c27540)
Solutions
- Make Puppet::Parser::Functions.newfunction(...) the last expression in the file
- Move requires and other statements above the newfunction call
- Use the reported 'Got' class to identify what the file now returns
Example fix
# before
module Puppet::Parser::Functions
newfunction(:double, :type => :rvalue) { |a| a[0] * 2 }
end
require 'English'
# after
require 'English'
module Puppet::Parser::Functions
newfunction(:double, :type => :rvalue) { |a| a[0] * 2 }
end Defensive patterns
Strategy: try-catch
Type guard
def legacy_function_info?(obj) obj.is_a?(Hash) && obj.key?(:name) && obj.key?(:arity) end
Try / catch
begin
fn = loader.load_typed(typed_name)
rescue ArgumentError => e
raise unless e.message.include?("did not return the result of calling 'newfunction'")
warn "legacy file does not end with newfunction call: #{e.message}"
end Prevention
- Make newfunction the final expression of the file
- Move requires and logging above the definition block
- Re-test legacy functions after any edit, since they are eval'd once per process
When it happens
Trigger: `eval(ruby_code_string, ...)` does not return a Hash: statements after the newfunction call (requires, logging, assignments) change the file's value, or the newfunction call is inside a conditional.
Common situations: Trailing require/load statements or debug output after the definition; refactors that leave a different final expression; partially converted files.
Related errors
- The code loaded from %{source_ref} does not seem to be a Pup
- The code loaded from %{source_ref} does not seem to be a Pup
- The code loaded from %{source_ref} did not produce a data ty
- The code loaded from %{source_ref} does not seem to be a Pup
- The code loaded from %{source_ref} did not produce a Functio
AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21).
Data as JSON: /api/errors/9a47bb86a654426f.
Report an issue: GitHub.