Skip to content

fix(vm): respect expr struct tag in "in" operator - #991

Open
akashchamp wants to merge 1 commit into
expr-lang:masterfrom
akashchamp:fix/in-operator-respects-expr-tag
Open

akashchamp wants to merge 1 commit into
expr-lang:masterfrom
akashchamp:fix/in-operator-respects-expr-tag

Conversation

@akashchamp

Copy link
Copy Markdown

Fixes #990

Problem

The in operator on a struct checks membership using the struct's raw Go
field name via reflect.Type.FieldByName, ignoring any expr:"..." rename
tag. Plain field access (foo.bar) already resolves names through the tag,
so the two code paths for the same struct disagree:

type Outer struct {
	Inner map[string]struct{} `expr:"bar"`
}
type Env struct {
	Outer Outer `expr:"foo"`
}
expression before (buggy) after (fixed) field access (foo.bar/foo.Inner)
"bar" in foo false true foo.bar → map[] (valid)
"Inner" in foo true false foo.Inner → compile error

Fix

Added structFieldByExprName in vm/runtime/runtime.go, which resolves a
field name the same way the checker does at compile time: once a field
carries an expr tag, only that tag's value (or "-" to hide the field)
identifies it, and the original Go name no longer applies. runtime.In's
struct case now uses this helper instead of the raw FieldByName lookup.

This is scoped to the in operator only — Fetch/findStructField (used
for dynamic field access, e.g. through embedded interfaces) are unchanged,
since that's a separate code path not reported in the issue.

Testing

  • Added TestIn_StructRespectsFieldTag in vm/runtime/runtime_test.go,
    a table-driven unit test directly against In() covering: tag-renamed
    field found by tag name, original Go name no longer found, expr:"-"
    hidden field still excluded, and an untagged field still found by its Go
    name (no regression for the untagged case).
  • Added test/issues/990/issue_test.go (TestIssue990), an end-to-end
    regression test using the public expr.Eval API and the exact struct
    shape from the issue's playground repro, following this repo's existing
    test/issues/<N> convention.
  • Ran the full test suite (go test ./..., excluding the pre-existing,
    unrelated build failure in test/examples which fails identically on
    master before this change — confirmed by stashing the diff and
    rebuilding): all 57 testable packages pass.
  • go vet ./vm/... ./test/issues/990/... and gofmt -l are clean on the
    changed files.
  • Manually verified via go test -run TestIssue990 -v ./test/issues/990/...
    that both expressions from the issue's own repro now evaluate to the
    expected boolean, matching foo.bar/foo.Inner's existing behavior.

AI disclosure

This change was prepared with AI assistance (Claude Code). The repository
has no stated AI-contribution policy; noting this for transparency.

The `in` operator on a struct looked up the field by its raw Go name
via reflect.Type.FieldByName, ignoring any `expr:"..."` rename tag.
Plain field access (`foo.bar`) already resolves field names through
the tag, so the two paths disagreed: `"bar" in foo` returned false
and `"Inner" in foo` returned true for a field declared as
`Inner ... `expr:"bar"``, the opposite of what `foo.bar`/`foo.Inner`
do.

Add structFieldByExprName, matching the checker's compile-time name
resolution (a tag's value, or "-" to hide the field, replaces the Go
name entirely rather than being an alias for it), and use it in
runtime.In's struct case instead of the raw FieldByName lookup.

Fixes expr-lang#990

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

in operator on a struct doesn't respect expr tags

1 participant