puppetlabs/puppet · error · Puppet::ParseError

%{member} attempts to override final %{label}

Error message

%{member} attempts to override final %{label}

What it means

Members declared final => true in an Object type are closed for extension: a child type that attempts to override one gets a Puppet::ParseError. The only exception in the source is a final constant overridden by another constant (constant? && member.constant?), since constants are resolved statically.

Source

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

        end
        self
      else
        parent_member.assert_can_be_overridden(self)
      end
    end

    # Checks if the given _member_ can override this member.
    #
    # @param member [PAnnotatedMember] the overriding member
    # @return [PAnnotatedMember] its argument
    # @raises [Puppet::ParseError] if the assertion fails
    # @api private
    def assert_can_be_overridden(member)
      unless instance_of?(member.class)
        raise Puppet::ParseError, _("%{member} attempts to override %{label}") % { member: member.label, label: label }
      end
      if @final && !(constant? && member.constant?)
        raise Puppet::ParseError, _("%{member} attempts to override final %{label}") % { member: member.label, label: label }
      end
      unless member.override?
        # TRANSLATOR 'override => true' is a puppet syntax and should not be translated
        raise Puppet::ParseError, _("%{member} attempts to override %{label} without having override => true") % { member: member.label, label: label }
      end
      unless @type.assignable?(member.type)
        raise Puppet::ParseError, _("%{member} attempts to override %{label} with a type that does not match") % { member: member.label, label: label }
      end

      member
    end

    def constant?
      false
    end

    # @return [Boolean] `true` if this feature cannot be overridden
    # @api public

View on GitHub (pinned to e227c27540)

Solutions

  1. Do not override the final member — add a differently named member in the child type instead.
  2. Use composition: wrap the parent type in a new Object type rather than inheriting from it.
  3. If you own the parent type, remove 'final' => true there (accepting that all subclasses see the contract change).

Example fix

# before — Base declares 'id' with final => true
type Sub = Object[{'parent' => 'Base', 'attributes' => {'id' => {'type' => String, 'override' => true}}}]

# after
type Sub = Object[{'parent' => 'Base', 'attributes' => {'sub_id' => String}}]
Defensive patterns

Strategy: validation

Validate before calling

# Ruby — before generating an override, check the inherited member is not final
inherited = parent_members[name]
fail("#{name} is final and cannot be overridden") if inherited&.final? && !(inherited.constant? && entry['kind'] == 'constant')

Type guard

def overridable_member?(member)
  !member.final? || (member.constant? && member.constant?) # constant-over-constant is the only exception
end

Try / catch

begin
  Puppet::Pops::Types::PObjectType.new(name, hash)
rescue Puppet::ParseError => e
  fail("cannot extend final member: #{e.message} — use a new attribute name instead")
end

Prevention

When it happens

Trigger: Parent attribute declared with 'final' => true; child type declares an attribute with the same name and 'override' => true. The final check fires before the override-flag and type checks.

Common situations: Extending third-party or core data types whose authors marked members final to freeze the contract; upstream version upgrades that newly mark a member final and break previously-working child types.

Related errors


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