bazelbuild/bazel · error · OptionProcessorException

Option is an expansion flag with a static expansion, but…

Error message

Option is an expansion flag with a static expansion, but does not have Void type.

What it means

Bazel's options annotation processor requires that an option with a static expansion (a non-empty expansion attribute) have return type Void. Expansion options exist purely to rewrite the command line; they carry no value of their own, so a non-Void return type signals a modeling mistake.

Solutions

  1. Change the option method's return type to Void (declared as void in the options class).
  2. If the option genuinely needs a value, remove the expansion attribute — a valued option cannot also be a static expansion.
  3. Recompile to confirm.

Example fix

// before
@Option(
  name = "fast_build",
  defaultValue = "false",
  expansion = {"--compilation_mode=fastbuild"}
)
public static boolean fastBuild;
// after
@Option(
  name = "fast_build",
  defaultValue = "null",
  expansion = {"--compilation_mode=fastbuild"}
)
public static void fastBuild;
Defensive patterns

Strategy: type-guard

Validate before calling

static void checkExpansionIsVoid(String[] expansion, Class<?> optionType) {
  if (expansion.length > 0) {
    Preconditions.checkState(optionType == Void.class,
        "Static expansion options must be Void, got %s", optionType);
  }
}

Type guard

// Ensure expansion options are declared Void before registration
static boolean isValidExpansionOption(String[] expansion, java.lang.reflect.Type type) {
  return expansion.length == 0 || type == Void.class;
}

Prevention

When it happens

Trigger: @Option(..., expansion = {"--foo=1"}) on a method whose return type is anything other than Void (e.g. Boolean, String, Integer) or void (primitive) — checked with Types.isSameType against java.lang.Void.

Common situations: Turning a valued flag into an alias for other flags while keeping its old type; writing a new expansion option by copying a Boolean-valued option; forgetting that expansion options are conventionally defined as 'public static void' accessor methods with Void in the abstract-option paradigm.

Related errors


AI-assisted analysis of bazelbuild/bazel@e6e199d060 (2026-08-14). Data as JSON: /api/errors/1f132567c75a9f76. Report an issue: GitHub.

Appendix: source

Thrown at src/main/java/com/google/devtools/common/options/processor/OptionsClassProcessor.java:546

          method, "Options with metadata tag DEPRECATED must be annotated with @Deprecated.");
    }
    if (hasDeprecatedAnnotation && !hasDeprecatedMetadataTag) {
      throw new OptionProcessorException(
          method, "Options annotated with @Deprecated must have metadata tag DEPRECATED.");
    }
  }

  private void checkConverter(ExecutableElement method) throws OptionProcessorException {
    TypeMirror optionType = method.getReturnType();
    Option annotation = method.getAnnotation(Option.class);
    ImmutableList<TypeMirror> acceptedConverterReturnTypes =
        getAcceptedConverterReturnTypes(method);

    // For simple, static expansions, don't accept non-Void types.
    if (annotation.expansion().length != 0
        && !typeUtils.isSameType(
            optionType, elementUtils.getTypeElement(Void.class.getCanonicalName()).asType())) {
      throw new OptionProcessorException(
          method,
          "Option is an expansion flag with a static expansion, but does not have Void type.");
    }

    // Obtain the converter for this option.
    AnnotationMirror optionMirror =
        ProcessorUtils.getAnnotation(elementUtils, typeUtils, method, Option.class);
    TypeElement defaultConverterElement =
        elementUtils.getTypeElement(Converter.class.getCanonicalName());
    TypeElement converterElement =
        ProcessorUtils.getClassTypeFromAnnotationField(elementUtils, optionMirror, "converter");

    if (typeUtils.isSameType(converterElement.asType(), defaultConverterElement.asType())) {
      // Find a matching converter in the default converter list, and check that it successfully
      // parses the default value for this option.
      checkForDefaultConverter(method, acceptedConverterReturnTypes, annotation.defaultValue());
    } else {
      // Check that the provided converter has an accepted return type.

View on GitHub (pinned to e6e199d060)