{"record":{"id":"65df8370f52aaf15","repo":"cocoindex-io/cocoindex","slug":"memo-key-transform-for-args-must-return-tuple-go","errorCode":null,"errorMessage":"memo_key transform for *args must return tuple, got {type(varargs).__name__}","messagePattern":"memo_key transform for \\*args must return tuple, got (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"python/cocoindex/_internal/function.py","lineNumber":481,"sourceCode":"    for i, arg in enumerate(fixed_args):\n        key_fn = memo_key_plan.positional_specs[i]\n        if is_not_set(key_fn):\n            new_fixed_args.append(arg)\n        elif key_fn is None:\n            continue  # Exclude this positional arg\n        else:\n            new_fixed_args.append(key_fn(arg))\n\n    # Apply varargs override if present (whole *args parameter)\n    if not is_not_set(memo_key_plan.varargs_override):\n        if memo_key_plan.varargs_override is None:\n            # Exclude entire *args\n            varargs = ()\n        else:\n            # Transform entire *args tuple\n            varargs = memo_key_plan.varargs_override(varargs)\n            if not isinstance(varargs, tuple):\n                raise TypeError(\n                    f\"memo_key transform for *args must return tuple, \"\n                    f\"got {type(varargs).__name__}\"\n                )\n\n    # Combine fixed args and varargs\n    final_args = tuple(new_fixed_args) + varargs\n\n    # Process kwargs: separate matched (keyword-only/POSITIONAL_OR_KEYWORD passed as kwarg)\n    # from unmatched (extra **kwargs)\n    new_kwargs: dict[str, Any] = {}\n    unmatched_kwargs: dict[str, Any] = {}\n\n    for key, value in kwargs.items():\n        if key in memo_key_plan.keyword_specs:\n            key_fn = memo_key_plan.keyword_specs[key]\n            if key_fn is None:\n                continue  # Exclude this kwarg\n            new_kwargs[key] = key_fn(value)","sourceCodeStart":463,"sourceCodeEnd":499,"githubUrl":"https://github.com/cocoindex-io/cocoindex/blob/e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b/python/cocoindex/_internal/function.py#L463-L499","documentation":"memo_key specifications can supply a transform (varargs_override) applied to a function's *args tuple before fingerprinting. The transform must return a tuple; any other return type raises TypeError because the result is concatenated into the final memo-key args.","triggerScenarios":"@coco.fn(memo_key={\"*\": transform}) (or MemoKeySpec with varargs override) where transform returns a list, generator, or single value instead of a tuple, for a function accepting *args.","commonSituations":"Writing `return [x for x in args]` in a transform; forgetting to wrap a single normalized value in a tuple; converting args to a string for logging and returning it.","solutions":["Return a tuple from the varargs transform: tuple(transformed)","Wrap single values: (value,)","Materialize generators/lists into a tuple before returning"],"exampleFix":"# before\ndef key(*args):\n    return [normalize(a) for a in args]\n\n# after\ndef key(*args):\n    return tuple(normalize(a) for a in args)","handlingStrategy":"validation","validationCode":"def as_tuple(result):\n    if not isinstance(result, tuple):\n        raise TypeError(\"varargs transform must return tuple\")\n    return result","typeGuard":"def is_tuple(x: object) -> TypeGuard[tuple]:\n    return isinstance(x, tuple)","tryCatchPattern":null,"preventionTips":["Always wrap transform output in tuple(...)","Unit-test memo_key transforms directly (return types are enforced lazily at call time)","Add return type annotations: def t(*args) -> tuple: ..."],"tags":["memoization","type-error","decorator-config"],"backgroundTag":"type-mismatch","analyzedSha":"e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b","analyzedAt":"2026-09-08T15:59:19.997Z","contentChangedAt":"2026-09-08T15:59:19.997Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}