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

  1. Make Puppet::Parser::Functions.newfunction(...) the last expression in the file
  2. Move requires and other statements above the newfunction call
  3. 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

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


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/9a47bb86a654426f. Report an issue: GitHub.