apple/pkl · error · VmException

propertyMustBeConst

propertyMustBeConst

Error message

Cannot reference property `{0}` from here because it is not `const`.

What it means

ReadSuperPropertyNode resolves `super.prop` by walking up the owner chain from the current owner. When the read site requires a const value (needsConst), it rejects any resolved member that is not declared `const`, throwing propertyMustBeConst. This guarantees `super` references used in const contexts cannot observe values that differ per amending object.

Source

Thrown at pkl-core/src/main/java/org/pkl/core/ast/expression/member/ReadSuperPropertyNode.java:60

    var receiver = VmUtils.getObjectReceiver(frame);

    // start from the parent of the owner of the `super.<propertyName>` expression
    // skip any function object owners (same as when resolving `this`)
    // `receiver` must be passed on unchanged to make sure that overridden properties still take
    // effect
    var initialOwner = VmUtils.getOwner(frame);
    while (initialOwner instanceof VmFunction) {
      initialOwner = initialOwner.getEnclosingOwner();
    }
    assert initialOwner != null : "VmFunction always has a parent";
    initialOwner = initialOwner.getParent();

    for (var owner = initialOwner; owner != null; owner = owner.getParent()) {
      var property = owner.getMember(propertyName);
      if (property == null) continue;
      if (needsConst && !property.isConst()) {
        CompilerDirectives.transferToInterpreter();
        throw exceptionBuilder().evalError("propertyMustBeConst", propertyName.toString()).build();
      }

      var constantValue = property.getConstantValue();
      if (constantValue != null) return constantValue; // TODO: type check

      // caching the result of a super call is tricky (function of both receiver and owner)
      return callNode.call(
          property.getCallTarget(),
          // TODO: should the marker only turn off constraint checking, not overall type checking?
          receiver,
          owner,
          propertyName,
          VmUtils.SKIP_TYPECHECK_MARKER);
    }

    // TODO: refine when to return VmDynamic.empty() and when to fail
    return VmDynamic.empty();
  }

View on GitHub (pinned to f3efcbfc9b)

Solutions

  1. Declare the parent's property `const` (e.g. `const baseValue: Int = 10` in the superclass)
  2. Copy the needed value into a local const or literal instead of reading it via `super`
  3. Change the reading member so it does not require const (drop the `const`/`fixed` qualifier)
  4. Restructure inheritance so the const value lives in a shared const definition both classes reference

Example fix

// before
open class Base {
  port: Int = 8080
}
class Derived extends Base {
  const defaultPort: Int = super.port // ERROR
}
// after
open class Base {
  const port: Int = 8080
}
class Derived extends Base {
  const defaultPort: Int = super.port
}
Defensive patterns

Strategy: validation

Validate before calling

// before writing `super.prop` in a const member, confirm the superclass declares it const:
// open class Base { const prop: Int = ... }

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: Writing `super.someProp` inside a `const` (or other needsConst) member where `someProp` is defined non-const in a parent class or enclosing object; executeGeneric walks owner.getParent() and finds the member but its isConst() is false.

Common situations: `const x: Int = super.baseValue` where the superclass declares `baseValue` as a normal property; migrating code that changed `super.foo` context to `fixed`/`const` after a refactor.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08). Data as JSON: /api/errors/f68b964de69fbd65. Report an issue: GitHub.