{"record":{"id":"89288500cf0fa318","repo":"xai-org/x-algorithm","slug":"recursive-invocation-of-derived-feature-function","errorCode":null,"errorMessage":"Recursive invocation of derived feature function %s is prohibited","messagePattern":"Recursive invocation of derived feature function (.+?) is prohibited","errorType":"validation","errorClass":"SemanticCheckFailure","httpStatus":null,"severity":"error","filePath":"botmaker/src/java/com/twitter/botmaker/compiler/Compiler.java","lineNumber":1149,"sourceCode":"\n  private Pair<String, ASTNode> toAssignment(CompilerContext context, Tree root)\n      throws ParseFailure, SemanticCheckFailure {\n    final String variableName = getVariableName(root.getChild(0));\n    final Tree variableRhsNode = root.getChild(1);\n    final ASTNode varExprASTNode = createASTNodeTree(context, variableRhsNode);\n\n    if (varExprASTNode.getReturnType() == Type.SYMBOL) {\n      throw new SemanticCheckFailure(\"Cannot assign SYMBOL type to variable\");\n    }\n\n    return Pair.of(variableName, varExprASTNode);\n  }\n\n  private ASTNode toDerivedFeatureFunction(\n      CompilerContext context, String funcName, int lineNo, List<ASTNode> children\n  ) throws ParseFailure, SemanticCheckFailure {\n    if (context.containsMacro(funcName)) {\n      throw new SemanticCheckFailure(String.format(\n          \"Recursive invocation of derived feature function %s is prohibited\", funcName));\n    }\n\n    Optional<DerivedFeature> dfOpt = context.getDerivedFeature(funcName);\n    if (!dfOpt.isPresent()) {\n      throw new SemanticCheckFailure(String.format(\n          \"%s is not a derived feature function\", funcName));\n    }\n\n    DerivedFeature derivedFeature = dfOpt.get();\n    context.trackUsage(derivedFeature);\n\n    List<Tuple3<Tree, String, Optional<Tree>>> params = derivedFeature.getParams();\n    boolean isLogged = derivedFeature.isLogged();\n\n    List<ASTNode> args = children;\n    if (params.size() != children.size()) {\n","sourceCodeStart":1131,"sourceCodeEnd":1167,"githubUrl":"https://github.com/xai-org/x-algorithm/blob/24c60942c5c5fdad3a6addffb4c6e6d2f228f04f/botmaker/src/java/com/twitter/botmaker/compiler/Compiler.java#L1131-L1167","documentation":"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.","triggerScenarios":"A derived feature `f` whose body calls `f(...)`, including mutual recursion routed through the same name during expansion.","commonSituations":"Porting recursive helper logic into a derived feature, or renaming a feature so a helper call now resolves to the feature itself.","solutions":["Rewrite the recursion as an iteration over a collection (Foldl/Foldl over a list)","Extract the recursive step into a separate, differently named derived feature and restructure to avoid cycles","Use a builtin like Exists/Forall over collections instead of self-recursion"],"exampleFix":"// before\nderived feature depth(t) = If(t.child == null, 0L, depth(t.child) + 1L)\n// after\nderived feature depth(t) = Foldl(t.children, 0L, (acc, c) -> acc + 1L)","handlingStrategy":"validation","validationCode":"Set<String> inProgress = new HashSet<>();\nif (inProgress.contains(funcName)) throw new IllegalArgumentException(\"Recursive derived feature: \" + funcName);","typeGuard":null,"tryCatchPattern":"catch (SemanticCheckFailure e) { if (e.getMessage().contains(\"Recursive invocation\")) reportRecursion(funcName); else throw e; }","preventionTips":["Avoid self-calls in derived feature bodies","Express recursion as folds over collections"],"tags":["botmaker","derived-feature","recursion"],"backgroundTag":"recursive-macro-expansion","analyzedSha":"24c60942c5c5fdad3a6addffb4c6e6d2f228f04f","analyzedAt":"2026-08-28T11:40:14.686Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}