{"record":{"id":"e0f527d7890da7c6","repo":"OpenRA/OpenRA","slug":"empty-parenthesis-at-index-lasttoken-index","errorCode":null,"errorMessage":"Empty parenthesis at index {lastToken.Index}","messagePattern":"Empty parenthesis at index (.+?)","errorType":"exception","errorClass":"InvalidDataException","httpStatus":null,"severity":"error","filePath":"OpenRA.Game/Support/VariableExpression.cs","lineNumber":628,"sourceCode":"\t\t\t\t\t\tthrow new InvalidDataException($\"Unmatched closing parenthesis at index {token.Index}\");\n\n\t\t\t\t\tcurrentOpeners.Pop();\n\t\t\t\t}\n\n\t\t\t\tif (token.Opens != Grouping.None)\n\t\t\t\t\tcurrentOpeners.Push(token);\n\n\t\t\t\tif (lastToken == null)\n\t\t\t\t{\n\t\t\t\t\t// Expressions can't start with a binary or unary postfix operation or closer\n\t\t\t\t\tif (token.LeftOperand)\n\t\t\t\t\t\tthrow new InvalidDataException($\"Missing value or sub-expression at beginning for `{token.Symbol}` operator\");\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t// Disallow empty parentheses\n\t\t\t\t\tif (lastToken.Opens != Grouping.None && token.Closes != Grouping.None)\n\t\t\t\t\t\tthrow new InvalidDataException($\"Empty parenthesis at index {lastToken.Index}\");\n\n\t\t\t\t\t// Exactly one of two consecutive tokens must take the other's sub-expression evaluation as an operand\n\t\t\t\t\tif (lastToken.RightOperand == token.LeftOperand)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (lastToken.RightOperand)\n\t\t\t\t\t\t\tthrow new InvalidDataException(\"Missing value or sub-expression or there is an extra operator \" +\n\t\t\t\t\t\t\t\t$\"`{lastToken.Symbol}` at index {lastToken.Index} or `{token.Symbol}` at index {token.Index}\");\n\t\t\t\t\t\tthrow new InvalidDataException($\"Missing binary operation before `{token.Symbol}` at index {token.Index}\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (token.Type == TokenType.Variable)\n\t\t\t\t\tvariables.Add(token.Symbol);\n\n\t\t\t\ttokens.Add(token);\n\t\t\t\tlastToken = token;\n\t\t\t}\n","sourceCodeStart":610,"sourceCodeEnd":646,"githubUrl":"https://github.com/OpenRA/OpenRA/blob/a520984d91eda9de48a62b1d15c1e3bad0d4fb1a/OpenRA.Game/Support/VariableExpression.cs#L610-L646","documentation":"Thrown by VariableExpression.Build when an opening parenthesis is immediately followed by a closing parenthesis — i.e. '()' with no expression inside. Empty parenthetical grouping has no value. The message reports the index of the opening paren.","triggerScenarios":"An expression contains '()' as a sub-expression, e.g. '() + 1' or 'rank > ()'. The parser detects lastToken.Opens and token.Closes both set on consecutive tokens.","commonSituations":"A modder writes '()' as a placeholder or by mistake. A template leaves empty parentheses. Copy-paste drops the inner expression.","solutions":["Remove the empty '()' pair, or fill it with a valid sub-expression.","Check the index in the message and inspect the expression at that position.","Ensure every parenthetical group contains at least one value or operation."],"exampleFix":"# before — empty parentheses\nRequiresCondition: () && rank > 0\n\n# after — remove or fill\nRequiresCondition: rank > 0","handlingStrategy":"validation","validationCode":"// Reject empty parentheses.\nusing System.Text.RegularExpressions;\nif (Regex.IsMatch(expression, @\"\\(\\s*\\)\"))\n    throw new InvalidDataException(\"Empty parentheses are not valid in expressions.\");","typeGuard":null,"tryCatchPattern":"try\n{\n    var expr = new BooleanExpression(yamlValue);\n}\ncatch (System.IO.InvalidDataException ex)\n{\n    // Report: fill or remove the empty parentheses.\n}","preventionTips":["Ensure every parenthesized group contains at least one value or operation.","Remove placeholder '()' from template conditions.","Run mod lint to catch empty groups."],"tags":["expression","yaml","modding","parser","parentheses"],"backgroundTag":null,"analyzedSha":"a520984d91eda9de48a62b1d15c1e3bad0d4fb1a","analyzedAt":"2026-08-13T15:30:14.787Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}