{"record":{"id":"56848f3911f8f5f9","repo":"jax-ml/jax","slug":"equation-must-contain-exactly-one","errorCode":null,"errorMessage":"Equation must contain exactly one '->'","messagePattern":"Equation must contain exactly one '->'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"jax/_src/pallas/einshape.py","lineNumber":160,"sourceCode":"      j = s.find(\")\", i)\n      if j == -1:\n        raise ValueError(f\"Unmatched parenthesis in {s!r}\")\n      group = list(s[i + 1 : j])\n      groups.append(group)\n      i = j + 1\n    elif s[i] == \")\":\n      raise ValueError(f\"Unmatched parenthesis in {s!r}\")\n    else:\n      # distinct dimension\n      groups.append([s[i]])\n      i += 1\n  return groups\n\n\ndef _parse_equation(equation: str) -> tuple[list[list[str]], list[list[str]]]:\n  \"\"\"Parses an einshape equation.\"\"\"\n  if equation.count(\"->\") != 1:\n    raise ValueError(\"Equation must contain exactly one '->'\")\n  lhs_str, rhs_str = equation.split(\"->\")\n  return _parse_side(lhs_str), _parse_side(rhs_str)\n\n\ndef _get_einshape_dims(\n    parsed_side: list[list[str]],\n    shape: tuple[int, ...],\n    sizes: dict[str, int],\n) -> dict[str, int]:\n  \"\"\"Parses an einshape equation into a dictionary of dimension sizes.\"\"\"\n  dim_sizes: dict[str, int] = {}\n\n  # Populate known sizes from input\n  for i, group in enumerate(parsed_side):\n    shape_val = shape[i]\n    if len(group) == 1:\n      name = group[0]\n      if name in dim_sizes and dim_sizes[name] != shape_val:","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/jax-ml/jax/blob/1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb/jax/_src/pallas/einshape.py#L142-L178","documentation":"einshape requires a single explicit '->' mapping an input-side layout to an output-side layout (unlike general einsum, which allows implicit mode). _parse_equation counts occurrences of '->' and raises ValueError if there isn't exactly one — both zero occurrences and two or more fail.","triggerScenarios":"Calling get_einshape_transforms with an equation like 'abc' (no arrow) or 'a->b->c' (two arrows). e.g. jax._src.pallas.einshape.get_einshape_transforms('abc', shape).","commonSituations":"Assuming einsum-style implicit output ordering works in einshape; string concatenation accidentally duplicating the arrow; typos like '-' or '=>'.","solutions":["Add exactly one '->' specifying the output layout, e.g. 'abc -> a(bc)'","If you intended implicit mode, compute the output side explicitly instead — einshape has no implicit form","Sanity-check equation.count('->') == 1 before calling einshape helpers"],"exampleFix":"# before\nt = get_einshape_transforms('abc', x.shape)  # no '->'\n\n# after\nt = get_einshape_transforms('abc -> a(bc)', x.shape)","handlingStrategy":"validation","validationCode":"assert equation.count('->') == 1, 'einshape equation needs exactly one \"->\"'","typeGuard":"def is_valid_einshape_equation(eq: str) -> bool:\n    return eq.count('->') == 1 and all(eq_side_ok(s) for s in eq.split('->'))","tryCatchPattern":"try:\n    t = get_einshape_transforms(eq, shape)\nexcept ValueError as e:\n    if 'exactly one' in str(e):\n        eq = eq if '->' in eq else eq + ' -> ' + ''.join(c for c in eq if c not in '()->')\n        t = get_einshape_transforms(eq, shape)\n    else:\n        raise","preventionTips":["Never rely on einsum implicit output mode with einshape — always write 'lhs -> rhs'","Pre-validate with equation.count('->') == 1","Encode equations in one place with tests"],"tags":["jax","einshape","einsum-notation","equation-format","parse-error"],"backgroundTag":"equation-string-parse-error","analyzedSha":"1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb","analyzedAt":"2026-08-27T09:53:25.647Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}