{"record":{"id":"6be1c991721b1ae4","repo":"python/cpython","slug":"unexpected-node-inside-joinedstr-node-r","errorCode":null,"errorMessage":"Unexpected node inside JoinedStr, {node!r}","messagePattern":"Unexpected node inside JoinedStr, (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_ast_unparse.py","lineNumber":654,"sourceCode":"        if isinstance(node, JoinedStr):\n            # for both the f-string itself, and format_spec\n            for value in node.values:\n                self._write_ftstring_inner(value, is_format_spec=is_format_spec)\n        elif isinstance(node, Constant) and isinstance(node.value, str):\n            value = node.value.replace(\"{\", \"{{\").replace(\"}\", \"}}\")\n\n            if is_format_spec:\n                value = value.replace(\"\\\\\", \"\\\\\\\\\")\n                value = value.replace(\"'\", \"\\\\'\")\n                value = value.replace('\"', '\\\\\"')\n                value = value.replace(\"\\n\", \"\\\\n\")\n            self.write(value)\n        elif isinstance(node, FormattedValue):\n            self.visit_FormattedValue(node)\n        elif isinstance(node, Interpolation):\n            self.visit_Interpolation(node)\n        else:\n            raise ValueError(f\"Unexpected node inside JoinedStr, {node!r}\")\n\n    def _unparse_interpolation_value(self, inner):\n        unparser = type(self)()\n        unparser.set_precedence(_Precedence.TEST.next(), inner)\n        return unparser.visit(inner)\n\n    def _write_interpolation(self, node, use_str_attr=False):\n        with self.delimit(\"{\", \"}\"):\n            if use_str_attr:\n                expr = node.str\n            else:\n                expr = self._unparse_interpolation_value(node.value)\n            if expr.startswith(\"{\"):\n                # Separate pair of opening brackets as \"{ {\"\n                self.write(\" \")\n            self.write(expr)\n            if node.conversion != -1:\n                self.write(f\"!{chr(node.conversion)}\")","sourceCodeStart":636,"sourceCodeEnd":672,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_ast_unparse.py#L636-L672","documentation":"Raised by ast.unparse() when traversing a JoinedStr (f-string) node whose values sequence contains an element that is neither a plain str Constant, a FormattedValue, nor a template-string Interpolation node. Real f-strings produced by ast.parse never contain such children, so this indicates a malformed hand-built or mutated JoinedStr.","triggerScenarios":"Constructing ast.JoinedStr(values=[...]) with an invalid element (e.g. a non-string Constant like ast.Constant(value=1), an ast.Name, or an arbitrary object) and calling ast.unparse on it. Also triggered by transform passes that splice non-interpolation nodes into an f-string's values list.","commonSituations":"Code-generation libraries that assemble f-strings node-by-node; naive attempts to embed an expression into an f-string by appending it directly to values; version differences where code targets FormattedValue but runs on a build using template-string Interpolation nodes.","solutions":["Wrap every dynamic piece in ast.FormattedValue(value=expr, conversion=-1, format_spec=None) instead of putting the raw expression in values.","Keep literal text as ast.Constant(value='<str>') with an actual str value; convert non-str constants (e.g. 1) to FormattedValue.","If targeting 3.14+ template strings, use the Interpolation node shape the parser produces (copy it from ast.parse of a t-string) rather than inventing one.","Prefer generating source text or using ast.parse(f'f{...!r}') to obtain a correct JoinedStr instead of building it manually."],"exampleFix":"// before\nnode = ast.JoinedStr(values=[\n    ast.Constant(value='n='),\n    ast.Name(id='n', ctx=ast.Load()),  # invalid child\n])\nast.unparse(node)  # ValueError: Unexpected node inside JoinedStr\n\n// after\nnode = ast.JoinedStr(values=[\n    ast.Constant(value='n='),\n    ast.FormattedValue(value=ast.Name(id='n', ctx=ast.Load()), conversion=-1, format_spec=None),\n])\nast.unparse(node)  # \"f'n={n}'\"","handlingStrategy":"validation","validationCode":"import ast\n\ndef joinedstr_ok(node: ast.JoinedStr) -> bool:\n    return all(\n        (isinstance(v, ast.Constant) and isinstance(v.value, str))\n        or isinstance(v, (ast.FormattedValue, getattr(ast, 'Interpolation', ())))\n        for v in node.values\n    )\n\nassert joinedstr_ok(node), 'bad JoinedStr children'","typeGuard":"import ast\n_Interp = getattr(ast, 'Interpolation', None)\n\ndef is_valid_joinedstr(n: ast.AST) -> bool:\n    return isinstance(n, ast.JoinedStr) and all(\n        (isinstance(v, ast.Constant) and isinstance(v.value, str))\n        or isinstance(v, ast.FormattedValue)\n        or (_Interp is not None and isinstance(v, _Interp))\n        for v in n.values\n    )","tryCatchPattern":"try:\n    ast.unparse(node)\nexcept ValueError as e:\n    if 'Unexpected node inside JoinedStr' in str(e):\n        node.values = [v for v in node.values if isinstance(v, ast.Constant) and isinstance(v.value, str)]\n    else:\n        raise","preventionTips":["Wrap dynamic expressions in FormattedValue; keep only str Constants as literals.","Generate f-strings via ast.parse(repr of source) instead of assembling nodes by hand.","Test on the exact Python version you ship (Interpolation nodes appear in 3.14+ template strings)."],"tags":["ast","f-string","codegen","ast-unparse"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}