apple/pkl · error

commandOptionNoTypeAnnotation

commandOptionNoTypeAnnotation

Error message

commandOptionNoTypeAnnotation

What it means

Thrown when a CLI option, flag, or argument property has no type annotation at all (e.g. just `verbosity:`, relying on amending). The command framework derives value parsing and validation from the declared type, so a property collected as a CLI parameter must carry an explicit type annotation.

Solutions

  1. Add an explicit type annotation to the property (e.g. `verbose: Boolean`)
  2. If inheriting from a base class, redeclare the property with its type in the command spec class
  3. Use a supported type (String, Int, Boolean, List, Listing, Map, etc.)

Example fix

// before
@Option verbose:

// after
@Option verbose: Boolean
Defensive patterns

Strategy: validation

Validate before calling

// ensure every CLI parameter property has a type annotation before spec parse
for (prop in commandClass.properties) {
  if (prop.hasAnnotation("Option") && prop.type == null) throw "missing type annotation"
}

Prevention

When it happens

Trigger: Declaring a @Option/@Flag/@CountedFlag/@Argument property without a type, e.g. `@Option verbose:` — resolveType finds prop.getTypeNode() == null and throws.

Common situations: Amending-style shorthand Pkl habits carried into a command spec class; deleting a type during refactoring; relying on base-class property types without redeclaring.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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

Appendix: source

Thrown at pkl-core/src/main/java/org/pkl/core/runtime/CommandSpecParser.java:393

          .build();
    }

    return new CommandSpec.Argument(
        prop.getName().toString(),
        VmUtils.exportDocComment(prop.getDocComment()),
        behavior.getEach(),
        behavior.getAll(),
        behavior.getCompletionCandidates(),
        behavior.getMultiple());
  }

  /** Unwrap nullables, constraints, and aliases and return Pair(underlying type, is nullable) */
  private Pair<TypeNode, Boolean> resolveType(ClassProperty prop) {
    var propertyTypeNode = prop.getTypeNode();
    if (propertyTypeNode != null) {
      return resolveType(propertyTypeNode.getTypeNode());
    }
    throw exceptionBuilder()
        .withSourceSection(prop.getHeaderSection())
        .evalError("commandOptionNoTypeAnnotation", prop.getName())
        .build();
  }

  /** Unwrap nullables, constraints, and aliases and return Pair(underlying type, is nullable) */
  private Pair<TypeNode, Boolean> resolveType(TypeNode typeNode) {
    var isNullable = false;
    while (true) {
      if (typeNode instanceof TypeNode.NullableTypeNode nullableTypeNode) {
        isNullable = true;
        typeNode = nullableTypeNode.getElementTypeNode();
      } else if (typeNode instanceof TypeNode.ConstrainedTypeNode constrainedTypeNode) {
        typeNode = constrainedTypeNode.getChildTypeNode();
      } else if (typeNode instanceof TypeNode.TypeAliasTypeNode typeAliasTypeNode) {
        if (typeAliasTypeNode.getVmTypeAlias() == BaseModule.getCharTypeAlias()) break;
        typeNode = typeAliasTypeNode.getAliasedTypeNode();
      } else {

View on GitHub (pinned to f3efcbfc9b)