puppetlabs/puppet · error · ArgumentError

attempt to redefine implementation class for #{label}

Error message

attempt to redefine implementation class for #{label}

What it means

PObjectType#implementation_class= may be assigned exactly once; a second assignment raises ArgumentError with the type's label. The one-shot setter prevents silently swapping the Ruby class behind a live data type after instances or generated methods already exist.

Source

Thrown at lib/puppet/pops/types/p_object_type.rb:574

        # Use generator to create a default implementation
        @implementation_class = RubyGenerator.new.create_class(self)
        @implementation_class.class_eval(&@implementation_override) if instance_variable_defined?(:@implementation_override)
      else
        # Can the mapping be loaded?
        @implementation_class = ClassLoader.provide(class_name)

        raise Puppet::Error, "Unable to load class #{class_name}" if @implementation_class.nil?
        unless @implementation_class < PuppetObject || @implementation_class.respond_to?(:ecore)
          raise Puppet::Error, "Unable to create an instance of #{name}. #{class_name} does not include module #{PuppetObject.name}"
        end
      end
    end
    @implementation_class
  end

  # @api private
  def implementation_class=(cls)
    raise ArgumentError, "attempt to redefine implementation class for #{label}" unless @implementation_class.nil?

    @implementation_class = cls
  end

  # The block passed to this method will be passed in a call to `#class_eval` on the dynamically generated
  # class for this data type. It's indended use is to complement or redefine the generated methods and
  # attribute readers.
  #
  # The method is normally called with the block passed to `#implementation` when a data type is defined using
  # {Puppet::DataTypes::create_type}.
  #
  # @api private
  def implementation_override=(block)
    if !@implementation_class.nil? || instance_variable_defined?(:@implementation_override)
      raise ArgumentError, "attempt to redefine implementation override for #{label}"
    end

    @implementation_override = block

View on GitHub (pinned to e227c27540)

Solutions

  1. Define each data type name exactly once across all loaded modules; rename colliding types.
  2. Guard the assignment: `type.implementation_class = cls if type.implementation_class.nil?` in code that may run twice.
  3. In long-lived custom hosts, memoize type registration so files are processed once per process.

Example fix

# before
type.implementation_class = cls   # raises if already assigned

# after
type.implementation_class = cls if type.implementation_class.nil?
Defensive patterns

Strategy: validation

Validate before calling

# Ruby — make registration idempotent
type.implementation_class = cls if type.implementation_class.nil?

Type guard

def implementation_assignable?(type)
  type.implementation_class.nil?
end

Try / catch

begin
  type.implementation_class = cls
rescue ArgumentError => e
  raise unless type.implementation_class == cls # tolerate benign re-registration of the same class
end

Prevention

When it happens

Trigger: Ruby code calling type.implementation_class = cls when an implementation is already set — most commonly Puppet::DataTypes.create_type executed twice for the same type name in one process (two modules declaring the same data type, or test suites that load type definitions repeatedly), or custom registration code running on every request.

Common situations: Duplicate data type definitions with the same name across modules; rspec/task runs that re-evaluate type files without process isolation; frameworks that re-register types on reload without tracking what was already loaded.

Related errors


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