Skip to content

fix(workflows): evaluate parenthesised expressions - #4417

Open
NgoQuocViet2001 wants to merge 1 commit into
github:mainfrom
NgoQuocViet2001:fix-parenthesised-grouping
Open

fix(workflows): evaluate parenthesised expressions#4417
NgoQuocViet2001 wants to merge 1 commit into
github:mainfrom
NgoQuocViet2001:fix-parenthesised-grouping

Conversation

@NgoQuocViet2001

Copy link
Copy Markdown
Contributor

Problem

Adding parentheses to a workflow expression silently inverts its result:

ctx = StepContext(inputs={"a": True, "b": False, "c": True})

evaluate_expression("{{ inputs.a or inputs.b and inputs.c }}",     ctx)  # True
evaluate_expression("{{ (inputs.a or inputs.b) and inputs.c }}",   ctx)  # False  ← same logic
evaluate_expression("{{ (inputs.n) }}",                            ctx)  # None   ← not 5

_find_top_level deliberately skips bracketed text so an operator inside a nested operand is not split on. Nothing then unwraps a group that spans the whole expression, so:

  1. (a or b) and c splits at the top-level and
  2. the left side (a or b) reaches the dot-path fallback
  3. _resolve_dot_path looks up a key literally named (a or b), finds nothing, returns None

The or is never evaluated and the expression reads false. Parentheses are added precisely to make precedence explicit, so this fires on the expressions an author was being careful with.

Nothing catches it either: the syntax is valid, so condition_has_malformed_expression_block and condition_is_never_evaluated both pass it. An if: step gated on such a condition takes the wrong branch, and a while step never starts, with no diagnostic.

Fix

Unwrap a group that spans the whole expression, before the operator scans run. The check is quote-aware and requires the opening paren to close at the very last character, so it does not touch:

  • (a) and (b) — the first group closes early, so the top-level and still splits
  • (inputs.n) | default(9) — same reason; the filter still applies
  • 'a(b' and ('(') — a paren inside a string literal is not a group

Scope

One helper and one early return in src/specify_cli/workflows/expressions.py. Unparenthesised expressions take exactly the path they did before.

Test plan

  • Ran: pytest tests/test_workflows.py -k "parenthesised or indexing or Expressions" → 54 passed.
  • Ran: pytest tests/test_workflows.py → 942 passed. The 20 failures are the TestWorkflowCliAlignment symlink cases, which fail identically on an unmodified checkout here (Windows, no symlink privilege).
  • Checked: reverting only expressions.py fails the new test with assert False is True.

The operator scans in _evaluate_simple_expression skip over bracketed
text, so an operator inside a nested operand is never split on. Nothing
then unwrapped a group spanning the whole expression: `(a or b) and c`
split at the top-level `and`, evaluated `(a or b)` as a dot path, found
no such key, and got None. The `or` was never evaluated and the
expression read false.

So adding parentheses to make precedence explicit — the usual reason to
add them — silently inverted the result: `inputs.a or inputs.b and
inputs.c` was true while `(inputs.a or inputs.b) and inputs.c` was
false. A step gated on such a condition is skipped with nothing reported;
the malformed-condition validators do not flag it, because the syntax is
valid.

Unwrap a group that spans the whole expression, quote-aware and only when
the opening paren closes at the very end, so `(a) and (b)` and a literal
paren inside a string are untouched.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Parenthesis handling must also be mirrored in _unresolvable_term() with remediation coverage.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds correct evaluation of fully parenthesized workflow expressions.

Changes:

  • Unwraps complete parenthesized groups before evaluation.
  • Adds grouping, nesting, filtering, and quote-awareness tests.
File summaries
File Review
tests/test_workflows.py Adds regression coverage for grouped expressions and edge cases.
src/specify_cli/workflows/expressions.py Implements parenthesis unwrapping. The remediation parser remains inconsistent with evaluator behavior for grouped bare conditions (moderate; 2 votes).
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +536 to +537
if _is_wrapped_in_parens(expr):
return _evaluate_simple_expression(expr[1:-1], namespace)

@mnriem mnriem left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address Copilot feedback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants