xai-org/x-algorithm · error · SemanticCheckFailure

Recursive invocation of derived feature function %s is prohi

Error message

Recursive invocation of derived feature function %s is prohibited

What it means

Derived feature functions cannot invoke themselves, directly or via the current in-progress macro expansion. The compiler tracks macros being expanded (containsMacro) and rejects recursion to prevent infinite expansion.

Source

Thrown at botmaker/src/java/com/twitter/botmaker/compiler/Compiler.java:1149

  private Pair<String, ASTNode> toAssignment(CompilerContext context, Tree root)
      throws ParseFailure, SemanticCheckFailure {
    final String variableName = getVariableName(root.getChild(0));
    final Tree variableRhsNode = root.getChild(1);
    final ASTNode varExprASTNode = createASTNodeTree(context, variableRhsNode);

    if (varExprASTNode.getReturnType() == Type.SYMBOL) {
      throw new SemanticCheckFailure("Cannot assign SYMBOL type to variable");
    }

    return Pair.of(variableName, varExprASTNode);
  }

  private ASTNode toDerivedFeatureFunction(
      CompilerContext context, String funcName, int lineNo, List<ASTNode> children
  ) throws ParseFailure, SemanticCheckFailure {
    if (context.containsMacro(funcName)) {
      throw new SemanticCheckFailure(String.format(
          "Recursive invocation of derived feature function %s is prohibited", funcName));
    }

    Optional<DerivedFeature> dfOpt = context.getDerivedFeature(funcName);
    if (!dfOpt.isPresent()) {
      throw new SemanticCheckFailure(String.format(
          "%s is not a derived feature function", funcName));
    }

    DerivedFeature derivedFeature = dfOpt.get();
    context.trackUsage(derivedFeature);

    List<Tuple3<Tree, String, Optional<Tree>>> params = derivedFeature.getParams();
    boolean isLogged = derivedFeature.isLogged();

    List<ASTNode> args = children;
    if (params.size() != children.size()) {

View on GitHub (pinned to 24c60942c5)

Solutions

  1. Rewrite the recursion as an iteration over a collection (Foldl/Foldl over a list)
  2. Extract the recursive step into a separate, differently named derived feature and restructure to avoid cycles
  3. Use a builtin like Exists/Forall over collections instead of self-recursion

Example fix

// before
derived feature depth(t) = If(t.child == null, 0L, depth(t.child) + 1L)
// after
derived feature depth(t) = Foldl(t.children, 0L, (acc, c) -> acc + 1L)
Defensive patterns

Strategy: validation

Validate before calling

Set<String> inProgress = new HashSet<>();
if (inProgress.contains(funcName)) throw new IllegalArgumentException("Recursive derived feature: " + funcName);

Try / catch

catch (SemanticCheckFailure e) { if (e.getMessage().contains("Recursive invocation")) reportRecursion(funcName); else throw e; }

Prevention

When it happens

Trigger: A derived feature `f` whose body calls `f(...)`, including mutual recursion routed through the same name during expansion.

Common situations: Porting recursive helper logic into a derived feature, or renaming a feature so a helper call now resolves to the feature itself.

Related errors


AI-assisted analysis of xai-org/x-algorithm@24c60942c5 (2026-08-28). Data as JSON: /api/errors/89288500cf0fa318. Report an issue: GitHub.