puppetlabs/puppet · error · Puppet::ParseError

%{label} of kind 'constant' cannot be combined with final =>

Error message

%{label} of kind 'constant' cannot be combined with final => false

What it means

In a PObject type attribute, kind 'constant' implies final — a constant is fixed by definition. Declaring {'kind' => 'constant', 'final' => false} is contradictory, and PAttribute#initialize raises this Puppet::ParseError before final is force-set to true.

Source

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

    # @param name [String] The name of the attribute
    # @param container [PObjectType] The containing object type
    # @param init_hash [Hash{String=>Object}] Hash containing attribute options
    # @option init_hash [PAnyType] 'type' The attribute type (required)
    # @option init_hash [Object] 'value' The default value, must be an instanceof the given `type` (optional)
    # @option init_hash [String] 'kind' The attribute kind, matching #TYPE_ATTRIBUTE_KIND
    # @api public
    def initialize(name, container, init_hash)
      super(name, container, TypeAsserter.assert_instance_of(nil, TYPE_ATTRIBUTE, init_hash) { "initializer for #{self.class.label(container, name)}" })
      if name == Serialization::PCORE_TYPE_KEY || name == Serialization::PCORE_VALUE_KEY
        raise Puppet::ParseError, _("The attribute '%{name}' is reserved and cannot be used") % { name: name }
      end

      @kind = init_hash[KEY_KIND]
      if @kind == ATTRIBUTE_KIND_CONSTANT # final is implied
        if init_hash.include?(KEY_FINAL) && !@final
          # TRANSLATOR 'final => false' is puppet syntax and should not be translated
          raise Puppet::ParseError, _("%{label} of kind 'constant' cannot be combined with final => false") % { label: label }
        end

        @final = true
      end

      if init_hash.include?(KEY_VALUE)
        if @kind == ATTRIBUTE_KIND_DERIVED || @kind == ATTRIBUTE_KIND_GIVEN_OR_DERIVED
          raise Puppet::ParseError, _("%{label} of kind '%{kind}' cannot be combined with an attribute value") % { label: label, kind: @kind }
        end

        v = init_hash[KEY_VALUE]
        @value = v == :default ? v : TypeAsserter.assert_instance_of(nil, type, v) { "#{label} #{KEY_VALUE}" }
      else
        raise Puppet::ParseError, _("%{label} of kind 'constant' requires a value") % { label: label } if @kind == ATTRIBUTE_KIND_CONSTANT

        @value = :undef # Not to be confused with nil or :default
      end
    end

View on GitHub (pinned to e227c27540)

Solutions

  1. Remove 'final' => false — a constant is always final.
  2. If the member must stay overridable/non-final, use a regular attribute with a 'value' instead of kind 'constant'.
  3. Validate generated type hashes against the documented option set per kind before declaring them.

Example fix

# before
{'type' => Float, 'kind' => 'constant', 'value' => 3.14, 'final' => false}

# after
{'type' => Float, 'kind' => 'constant', 'value' => 3.14}
Defensive patterns

Strategy: validation

Validate before calling

# Ruby — validate an attribute hash before declaration
if h['kind'] == 'constant' && h.key?('final') && !h['final']
  fail("constant implies final; remove final => false")
end

Type guard

def consistent_finality?(h)
  h['kind'] != 'constant' || !h.key?('final') || h['final'] == true
end

Prevention

When it happens

Trigger: An attribute init hash that contains both 'kind' => 'constant' and 'final' => false, e.g. type T = Object[{'attributes' => {'pi' => {'type' => Float, 'kind' => 'constant', 'value' => 3.14, 'final' => false}}}].

Common situations: Hand-written or template-generated type hashes that merge defaults (final => false) into every attribute; converting a plain attribute to a constant while leftover option keys remain.

Related errors


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