Skip to content

fix: support braceless predicates matching Go expr-lang behavior#23

Merged
jdx merged 3 commits intomainfrom
fix/predicate-parsing
Feb 10, 2026
Merged

fix: support braceless predicates matching Go expr-lang behavior#23
jdx merged 3 commits intomainfrom
fix/predicate-parsing

Conversation

@jdx
Copy link
Owner

@jdx jdx commented Feb 10, 2026

Summary

  • In Go expr-lang, predicates like filter(arr, # > 2) work without braces. Our PEG parser requires braces because # gets consumed as an expr argument before the predicate rule can match.
  • This fix detects when the last argument to a function contains # references and promotes it to the predicate position at the AST level.
  • The promotion only triggers when there are 2+ args to avoid incorrectly affecting nested calls like len(#) inside sortBy(arr, {len(#)}).

Test plan

  • Added tests for braceless predicates: filter(0..9, # % 2 == 0), filter([1, 2, 3], # > 1), map([1, 2, 3], # * 2)
  • Verified existing sortBy tests with {len(#)} still pass (no regression)
  • All 94 tests pass

🤖 Generated with Claude Code


Note

Medium Risk
Changes how function-call ASTs are constructed by reclassifying certain trailing arguments as predicates, which could subtly alter parsing/semantics for edge-case expressions. Scope is limited to a hardcoded set of predicate-accepting functions and is covered by new regression tests.

Overview
Enables Go expr-lang–compatible braceless predicates like filter(arr, # > 2) and pipe forms like arr | filter(# > 2) by promoting the last argument to a function predicate when it references # and no explicit {...} predicate was parsed.

Adds contains_hash_ident traversal on Node/PostfixOperator and gates promotion behind a hardcoded allowlist of predicate-capable functions to avoid changing behavior for non-predicate calls and nested # usage; expands tests to cover braceless, piped, and nested-predicate regression cases.

Written by Cursor Bugbot for commit 2f34560. This will update automatically on new commits. Configure here.

In Go expr-lang, predicates like `filter(arr, # > 2)` work without
braces around the predicate expression. Our PEG parser requires braces
because `#` gets consumed as an `expr` argument before the `predicate`
rule has a chance to match.

This fix detects when the last argument to a function contains `#` and
promotes it to the predicate position at the AST level. The promotion
only triggers when there are 2+ args to avoid incorrectly affecting
nested calls like `len(#)` inside `sortBy(arr, {len(#)})`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link

Summary of Changes

Hello @jdx, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the expression language parser to support a more flexible syntax for function predicates. By automatically detecting and promoting braceless predicates containing the # variable, it improves user experience and ensures consistency with the behavior of Go expr-lang, making expressions like filter(arr, # > 2) valid without requiring explicit braces.

Highlights

  • Braceless Predicate Support: Implemented support for braceless predicates in function calls, such as filter(arr, # > 2), aligning the behavior with Go expr-lang.
  • Hash Identifier Detection: Introduced a contains_hash_ident method on the Node enum to recursively detect # variable references within an Abstract Syntax Tree (AST) node.
  • Predicate Promotion Logic: Modified the AST construction logic to automatically promote the last argument of a function call to a predicate if it contains a # reference, no explicit predicate was provided, and there are at least two arguments.
Changelog
  • src/ast/node.rs
    • Added contains_hash_ident method to Node for recursive checking of # identifier presence.
    • Modified From<Pair<'_, Rule>> for Node to implement predicate promotion logic for function calls.
  • src/test.rs
    • Added new test cases to validate the parsing and evaluation of braceless predicates in filter and map functions.
Activity
  • Added tests for braceless predicates: filter(0..9, # % 2 == 0), filter([1, 2, 3], # > 1), map([1, 2, 3], # * 2).
  • Verified existing sortBy tests with {len(#)} still pass, ensuring no regressions.
  • Confirmed that all 94 tests pass after the changes.
  • The pull request was generated with Claude Code.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support for braceless predicates to align with Go expr-lang behavior, which is a great enhancement for usability. The implementation correctly identifies when to promote an argument to a predicate by checking for the # reference. The logic is sound and well-tested. I have one suggestion to make the code for promoting the argument more concise and readable.

src/ast/node.rs Outdated
Comment on lines +107 to +117
if predicate.is_none() && args.len() >= 2 {
if let Some(last) = args.last() {
if last.contains_hash_ident() {
let last = args.pop().unwrap();
predicate = Some(Box::new(Program {
lines: Vec::new(),
expr: last,
}));
}
}
}

Choose a reason for hiding this comment

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

medium

This block can be made more concise by combining the conditions into a single if statement. The if let is redundant since args.len() >= 2 guarantees args.last() is Some. Using unwrap() is safe here due to the length check.

                if predicate.is_none() && args.len() >= 2 && args.last().unwrap().contains_hash_ident() {
                    let last = args.pop().unwrap();
                    predicate = Some(Box::new(Program {
                        lines: Vec::new(),
                        expr: last,
                    }));
                }

- Use a whitelist of predicate-accepting functions (filter, map, sortBy,
  etc.) instead of relying on arg count heuristic. This prevents the
  promotion from incorrectly firing on nested multi-arg functions like
  indexOf("abc", #) inside braced predicates.
- Lower threshold from args.len() >= 2 to >= 1 so pipe syntax works:
  `arr | filter(# > 2)` where the array arrives at runtime via pipe.
- Fix contains_hash_ident to recurse into PostfixOperator inner nodes
  (Index, Default, Ternary, Pipe) which can contain # references.
- Add tests for pipe + braceless predicates and nested func in predicate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jdx jdx force-pushed the fix/predicate-parsing branch from 0c01dc4 to a6bebf4 Compare February 10, 2026 12:13
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is ON, but a Cloud Agent failed to start.

contains_hash_ident was recursing into Node::Func predicates, but #
inside a function's predicate is bound to that function's iteration —
not a free reference for outer promotion. For example, in
map(arr1, any(arr2, {# > 0})), the inner any's # is bound, so it
should not cause any(...) to be promoted as map's predicate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jdx added a commit to jdx/mise that referenced this pull request Feb 10, 2026
…8081)

## Summary
- Flutter's Google API returns releases in newest-first order, which
causes `mise install flutter` to resolve to an old version (3.22.1)
instead of the latest (3.38.x)
- Switch from `version_json_path` to `version_expr` with
`sortVersions()` to ensure proper ascending semver order
- Adds a unit test for the Flutter version_expr parsing

Fixes
#7863 (comment)

Also submitted jdx/expr.rs#23 to fix braceless
predicate parsing in expr.rs (Go expr-lang compatibility), which is why
this PR uses `{#.channel == "stable"}` with braces.

## Test plan
- [x] Unit test `test_parse_flutter_with_version_expr` verifies correct
filtering and sorting
- [x] All existing tests pass
- [x] Linting passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: registry config change and a new unit test; behavior change
is limited to how Flutter versions are extracted/sorted from the
upstream JSON.
> 
> **Overview**
> Fixes Flutter version resolution by switching the registry backend
from `version_json_path` to a `version_expr` that filters `stable`
releases and applies `sortVersions()` so installs pick the latest stable
version.
> 
> Adds a unit test (`test_parse_flutter_with_version_expr`) to validate
the expr-based filtering/sorting behavior against Flutter-style JSON.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
9af283d. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@jdx jdx merged commit a85e62e into main Feb 10, 2026
4 checks passed
@jdx jdx deleted the fix/predicate-parsing branch February 10, 2026 12:42
lucasew pushed a commit to lucasew/CONTRIB-mise that referenced this pull request Feb 18, 2026
…dx#8081)

## Summary
- Flutter's Google API returns releases in newest-first order, which
causes `mise install flutter` to resolve to an old version (3.22.1)
instead of the latest (3.38.x)
- Switch from `version_json_path` to `version_expr` with
`sortVersions()` to ensure proper ascending semver order
- Adds a unit test for the Flutter version_expr parsing

Fixes
jdx#7863 (comment)

Also submitted jdx/expr.rs#23 to fix braceless
predicate parsing in expr.rs (Go expr-lang compatibility), which is why
this PR uses `{#.channel == "stable"}` with braces.

## Test plan
- [x] Unit test `test_parse_flutter_with_version_expr` verifies correct
filtering and sorting
- [x] All existing tests pass
- [x] Linting passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: registry config change and a new unit test; behavior change
is limited to how Flutter versions are extracted/sorted from the
upstream JSON.
> 
> **Overview**
> Fixes Flutter version resolution by switching the registry backend
from `version_json_path` to a `version_expr` that filters `stable`
releases and applies `sortVersions()` so installs pick the latest stable
version.
> 
> Adds a unit test (`test_parse_flutter_with_version_expr`) to validate
the expr-based filtering/sorting behavior against Flutter-style JSON.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
9af283d. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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.

1 participant