{"record":{"id":"c255b50d0ed5f19b","repo":"apple/pkl","slug":"commandoptionstypeabstractclass","errorCode":"commandOptionsTypeAbstractClass","errorMessage":"commandOptionsTypeAbstractClass","messagePattern":"commandOptionsTypeAbstractClass","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkl-core/src/main/java/org/pkl/core/runtime/CommandSpecParser.java","lineNumber":185,"sourceCode":"      // at this point we've asserted the options property exists and that it is neither amended nor\n      // assigned\n      // the only possibility here is that it has a type annotation, otherwise this wouldn't parse\n      throw PklBugException.unreachableCode();\n    }\n    var optionsTypeNode = optionsPropertyTypeNode.getTypeNode();\n    if (optionsTypeNode instanceof TypeNode.TypedTypeNode) {\n      return BaseModule.getTypedClass();\n    }\n    if (!(optionsTypeNode instanceof TypeNode.UserClassTypeNode node)) {\n      throw exceptionBuilder()\n          .withSourceSection(optionsTypeNode.getSourceSection())\n          .evalError(\n              \"commandOptionsTypeNotClass\", optionsTypeNode.getSourceSection().getCharacters())\n          .build();\n    }\n    var clazz = node.getVmClass();\n    if (clazz.isAbstract()) {\n      throw exceptionBuilder()\n          .withSourceSection(clazz.getHeaderSection())\n          .evalError(\"commandOptionsTypeAbstractClass\", clazz.getQualifiedName())\n          .build();\n    }\n    return clazz;\n  }\n\n  private Iterable<CommandSpec.Option> collectOptions(VmClass optionsClass) {\n    CommandSpec.Argument lastRepeatedArg = null;\n    EconomicMap<String, CommandSpec.Option> opts = EconomicMap.create();\n\n    var clazz = optionsClass;\n    while (clazz != null) {\n      for (var prop : clazz.getDeclaredProperties()) {\n        var name = prop.getName().toString();\n        if (VmModifier.isLocalOrExternalOrAbstractOrFixedOrConst(prop.getModifiers())\n            || opts.containsKey(name)) continue;\n","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/apple/pkl/blob/f3efcbfc9b60d30053b0536d664948d7aa1b8673/pkl-core/src/main/java/org/pkl/core/runtime/CommandSpecParser.java#L167-L203","documentation":"getOptionsClass throws commandOptionsTypeAbstractClass when the class given as a command's `options` type is abstract. Abstract classes have no instantiable properties to parse flags from, so the CLI spec parser rejects them at the class header section.","triggerScenarios":"Declaring `abstract class Options { ... }` and using it as the `options` type of a CLI command.","commonSituations":"Factoring shared options into an abstract base class and forgetting to make the concrete per-command options class; converting an options class to abstract during refactoring.","solutions":["Remove `abstract` from the options class","Create a concrete subclass of the abstract base and use that as the `options` type","Move shared fields into the abstract base but declare concrete options classes per command"],"exampleFix":"// before\nabstract class Options { verbose: Boolean }\noptions: Options\n// after\nclass Options extends BaseOptions { ... }\noptions: Options","handlingStrategy":"type-guard","validationCode":"// ensure the options class is instantiable\n// abstract class Options { ... }  <-- invalid\n// class Options extends BaseOpts { ... }  <-- valid","typeGuard":"function isConcreteClass(c) { return c && !c.isAbstract; }","tryCatchPattern":null,"preventionTips":["Keep options classes concrete; put shared fields in an abstract base","Search for `abstract class` before assigning an options type","Make one concrete subclass per command"],"tags":["pkl","cli","command-spec","abstract-class"],"backgroundTag":"type-mismatch","analyzedSha":"f3efcbfc9b60d30053b0536d664948d7aa1b8673","analyzedAt":"2026-09-08T13:10:45.570Z","contentChangedAt":"2026-09-08T13:10:45.570Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}