puppetlabs/puppet · error · ArgumentError

Functions must be based on Puppet::Pops::Functions::Function

Error message

Functions must be based on Puppet::Pops::Functions::Function. Got %{function_base}

What it means

create_loaded_function validates that the base class passed as function_base has Puppet::Pops::Functions::Function among its ancestors. Any class that skips that chain, or a Module passed instead of a Class, raises this ArgumentError at definition time. The base class is the second argument to Puppet::Functions.create_function.

Source

Thrown at lib/puppet/functions.rb:213

    raise ArgumentError, _("Function Load Error for function '%{function_name}': %{message}") % { function_name: func_name, message: e.message }
  end

  # Creates a function in, or in a local loader under the given loader.
  # This method should only be used when manually creating functions
  # for the sake of testing. Functions that are autoloaded should
  # always use the `create_function` method and the autoloader will supply
  # the correct loader.
  #
  # @param func_name [String, Symbol] a simple or qualified function name
  # @param loader [Puppet::Pops::Loaders::Loader] the loader loading the function
  # @param block [Proc] the block that defines the methods and dispatch of the
  #   Function to create
  # @return [Class<Function>] the newly created Function class
  #
  # @api public
  def self.create_loaded_function(func_name, loader, function_base = Function, &block)
    if function_base.ancestors.none? { |s| s == Puppet::Pops::Functions::Function }
      raise ArgumentError, _("Functions must be based on Puppet::Pops::Functions::Function. Got %{function_base}") % { function_base: function_base }
    end

    func_name = func_name.to_s
    # Creates an anonymous class to represent the function
    # The idea being that it is garbage collected when there are no more
    # references to it.
    #
    # (Do not give the class the block here, as instance variables should be set first)
    the_class = Class.new(function_base)

    unless loader.nil?
      the_class.instance_variable_set(:'@loader', loader.private_loader)
    end

    # Make the anonymous class appear to have the class-name <func_name>
    # Even if this class is not bound to such a symbol in a global ruby scope and
    # must be resolved via the loader.
    # This also overrides any attempt to define a name method in the given block

View on GitHub (pinned to e227c27540)

Solutions

  1. Derive the custom base from Puppet::Functions::Function so the ancestor check passes.
  2. If you only need shared helpers, drop the custom base and include ordinary Ruby modules inside the create_function block instead.
  3. Confirm you passed a Class, not a Module, as the second argument.

Example fix

# before: base class outside the Function hierarchy
class MyCoolBase; end
Puppet::Functions.create_function(:f, MyCoolBase) { ... }

# after: derive from Puppet::Functions::Function
class MyCoolBase < Puppet::Functions::Function; end
Puppet::Functions.create_function(:f, MyCoolBase) { ... }
Defensive patterns

Strategy: type-guard

Type guard

# Guard the base class before calling create_function
def valid_function_base?(klass)
  klass.is_a?(Class) && klass.ancestors.include?(Puppet::Pops::Functions::Function)
end

raise ArgumentError, 'bad base' unless valid_function_base?(MyCoolBase)

Prevention

When it happens

Trigger: Puppet::Functions.create_function(:foo, MyCustomBase) where MyCustomBase does not derive from Puppet::Functions::Function (which itself derives from Puppet::Pops::Functions::Function). Also triggered by passing a Module used as a mixin, or a typo'd constant that resolves to an unrelated class.

Common situations: Authors adding shared behavior through a custom function base; refactors that reparent the base class; scaffolding copied between Puppet versions where the base hierarchy changed.

Related errors


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