puppetlabs/puppet · error · Puppet::Error

%{callee}: default expression for $%{from} tries to illegall

Error message

%{callee}: default expression for $%{from} tries to illegally access not yet evaluated $%{to}

What it means

Raised by the same ParameterScope#parameter_reference_failure path, but for the opposite ordering case: the default expression of parameter $from reads parameter $to that is declared LATER in the parameter list. Because Puppet evaluates parameters in declaration order, $to has not been evaluated yet when $from's default runs, and forward references between parameter defaults are illegal. The message names both the referencing parameter ($from) and the not-yet-evaluated parameter ($to).

Source

Thrown at lib/puppet/parser/scope.rb:214

      end
    end

    def evaluate(name, expression, scope, evaluator)
      scope.with_guarded_scope do
        bad = catch(:unevaluated_parameter) do
          scope.new_match_scope(nil)
          return as_read_only { evaluator.evaluate(expression, scope) }
        end
        parameter_reference_failure(name, bad)
      end
    end

    def parameter_reference_failure(from, to)
      # Parameters are evaluated in the order they have in the @params hash.
      keys = @params.keys
      raise Puppet::Error, _("%{callee}: expects a value for parameter $%{to}") % { callee: @callee_name, to: to } if keys.index(to) < keys.index(from)

      raise Puppet::Error, _("%{callee}: default expression for $%{from} tries to illegally access not yet evaluated $%{to}") % { callee: @callee_name, from: from, to: to }
    end
    private :parameter_reference_failure

    def initialize(parent, callee_name, param_names)
      super(parent)
      @callee_name = callee_name
      @params = {}
      param_names.each { |name| @params[name] = Access.new }
    end

    def [](name)
      access = @params[name]
      return super if access.nil?

      throw(:unevaluated_parameter, name) unless access.assigned?
      access.value
    end

View on GitHub (pinned to e227c27540)

Solutions

  1. Reorder the parameter list so every referenced parameter is declared before the parameter whose default references it: `class example($b = 3, $a = $b)`.
  2. Pass explicit values for both parameters so neither default needs to run: `class { 'example': a => 1, b => 3 }`.
  3. Replace the cross-reference with a single expression or helper function both defaults can call independently.

Example fix

# before
class example(
  $a = $b,      # references later parameter
  $b = 'default',
) { }

# after
class example(
  $b = 'default',
  $a = $b,
) { }
Defensive patterns

Strategy: validation

Validate before calling

# Quick grep-based lint for forward references in signatures:
#   rg -U 'class\s+\w+\s*\(([^)]*)\)' manifests/ then, per param list,
#   assert every $ref inside a default refers to a param declared earlier.
# Or run a catalog preview in CI: puppet apply --noop site.pp

Prevention

When it happens

Trigger: `class example($a = $b, $b = 3) {}` in any form: `include example`, resource-style declaration, or a define `define x($a = $b, $b = 3)`. Evaluating $a's default calls ParameterScope#[] on unassigned $b, which throws :unevaluated_parameter('b'); since keys.index('b') > keys.index('a'), the 'default expression for $a tries to illegally access not yet evaluated $b' branch is taken. Also triggered via lambdas with ordered parameters referencing each other's defaults.

Common situations: Adding a new parameter at the top of an existing signature whose default depends on a parameter declared below it; merging two branches of a module where parameter order differs; converting a manifest function into a lambda and preserving a wrong parameter order.

Related errors


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