apple/pkl · error · VmException

cannotRenderSubtypeForProtobuf

cannotRenderSubtypeForProtobuf

Error message

cannotRenderSubtypeForProtobuf

What it means

When rendering Pkl values to Protobuf, the renderer walks a class hierarchy and refuses if it encounters a value whose concrete class is a non-final subclass of the declared class being rendered. Protobuf rendering requires the exact class, so a value of a subtype that differs from the target class cannot be represented. The error names both the offending subtype and the expected class.

Source

Thrown at pkl-core/src/main/java/org/pkl/core/stdlib/protobuf/RendererNodes.java:406

            writePropertyName();
            expectedName = Identifier.get("it_" + computedName);
            propertyPath.push(expectedName);
            startMessage();
          } else if (type instanceof ListingOrMappingTypeNode listingOrMappingType) {
            hasCollection = true;
            collectionType = listingOrMappingType.getValueTypeNode();
          } else if (type instanceof ListTypeNode listType) {
            hasCollection = true;
            collectionType = listType.getElementTypeNode();
          } else if (type instanceof MapTypeNode mapType) {
            hasCollection = true;
            collectionType = mapType.getValueTypeNode();
          } else if (type instanceof SetTypeNode setType) {
            hasCollection = true;
            collectionType = setType.getElementTypeNode();
          } else if (type instanceof NonFinalClassTypeNode) {
            if (type.getVmClass() != clazz) {
              throw new VmExceptionBuilder()
                  .evalError("cannotRenderSubtypeForProtobuf", type.getVmClass(), clazz)
                  .build();
            }
          }
        }
      }
      visit(value);
      if (hasWrapper) {
        endMessage();
        var popped = propertyPath.pop();
        assert popped == expectedName : "Corrupted traversal stack.";
      } else if (hasCollection) {
        collectionType = prevCollectionType;
      }
      var popped = propertyPath.pop();
      assert name == popped : "Corrupted traversal stack.";
    }

View on GitHub (pinned to f3efcbfc9b)

Solutions

  1. Change the property's declared type to the exact concrete class of the value so subtype and rendered class match
  2. Use Protobuf `oneof`/separate properties per subtype instead of a polymorphic base-typed property
  3. Choose a different output format (e.g. JSON/YAML/PBF) that supports subtyping via a converter

Example fix

// before
worker: Worker = new SshWorker { host = "h" }  // SshWorker != Worker for Protobuf
// after
worker: SshWorker = new SshWorker { host = "h" }
Defensive patterns

Strategy: type-guard

Validate before calling

// before rendering to protobuf, check the value's class equals the declared property type
function isExactClass(value: Any, expected: Class): Boolean = value.getClass() == expected
if (!isExactModulePolymorphicValues(module)) throw "polymorphic values cannot be protobuf-rendered"

Type guard

function rendersAsExactClass(value: Any, expected: Class): Boolean = value.getClass() == expected

Prevention

When it happens

Trigger: Rendering (Protobuf output) a property whose declared type is a non-final class but whose runtime value is an instance of a different subclass of that class, e.g. a property typed as a base class holding a derived-class instance.

Common situations: Config modules using class hierarchies (e.g. a `Worker` base class with `SshWorker`/`K8sWorker` subtypes) rendered to Protobuf; polymorphic collections where elements have mixed concrete types.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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