Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions doc/langdef.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,21 @@ name which resolves in the current lexical scope is used. For example, if
possible field selection, then `a.b.c` takes priority over the interpretation
`(a.b).c`.

Note: Comprehensions (.exists, .all, etc) introduce new scopes that add
variables as simple identifiers. When resolving a name within a comprehension
body, the name is first compared against the variables declared by the
comprehension. If there is a match, the name resolves to that variable, taking
precedence over the package-based resolution rules above. If not,
resolution proceeds checking for variable matches in parent
comprehension scopes recursively. Finally the name follows the package
resolution rules above against declarations in the environment. A name with a
leading '.' always resolves in the root scope, bypassing local scopes from
comprehensions.

For example, in `[1].exists(x, x == 1)`, `x` is a local variable which shadows
any identifier named 'x' in ancestor scopes or the package namespace. In
`[1].exists(x, .x == 1)`, x is the global variable `x`.

## Values

Values in CEL represent any of the following:
Expand Down
59 changes: 59 additions & 0 deletions tests/simple/testdata/bindings_ext.textproto
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,63 @@ section: {
bool_value: true
}
}
test: {
name: "shadowing"
expr: "cel.bind(x, 0, x == 0)"
type_env: {
name: "x"
ident: {
type: { primitive: INT64 }
}
}
bindings: {
key: "x"
value: {
value: { int64_value: 1 }
}
}
value: {
bool_value: true
}
}
test: {
name: "shadowing_namespace_resolution"
expr: "cel.bind(x, 0, x == 0)"
container: "com.example"
type_env: {
name: "com.example.x"
ident: {
type: { primitive: INT64 }
}
}
bindings: {
key: "com.example.x"
value: {
value: { int64_value: 1 }
}
}
value: {
bool_value: true
}
}
test: {
name: "shadowing_namespace_resolution_selector"
expr: "cel.bind(x, {'y': 0}, x.y == 0)"
container: "com.example"
type_env: {
name: "com.example.x.y"
ident: {
type: { primitive: INT64 }
}
}
bindings: {
key: "com.example.x.y"
value: {
value: { int64_value: 1 }
}
}
value: {
bool_value: true
}
}
}
203 changes: 197 additions & 6 deletions tests/simple/testdata/namespace.textproto
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ section {
expr: "x.y"
value: { bool_value: true }
type_env: {
name: "x.y",
name: "x.y"
ident: { type: { primitive: BOOL } }
}
bindings: {
Expand All @@ -28,11 +28,11 @@ section {
expr: "y"
container: "x"
type_env: {
name: "x.y",
name: "x.y"
ident: { type: { primitive: BOOL } }
}
type_env: {
name: "y",
name: "y"
ident: { type: { primitive: STRING } }
}
bindings: {
Expand All @@ -50,11 +50,11 @@ section {
expr: "y"
container: "x"
type_env: {
name: "x.y",
name: "x.y"
ident: { type: { primitive: BOOL } }
}
type_env: {
name: "y",
name: "y"
ident: { type: { primitive: BOOL } }
}
bindings: {
Expand All @@ -65,7 +65,198 @@ section {
key: "y"
value: { value: { bool_value: false } }
}
disable_check: true ## ensure unchecked ASTs resolve the same as checked ASTs
disable_check: true ## ensure unchecked ASTs resolve the same as checked ASTs
value: { bool_value: true }
}
}
section {
name: "namespace_shadowing"
description: "Variable shadowing in comprehensions"
test {
name: "basic"
expr: "y"
container: "com.example"
type_env: {
name: "com.example.y"
ident: { type: { primitive: BOOL } }
}
type_env: {
name: "y"
ident: { type: { primitive: STRING } }
}
bindings: {
key: "com.example.y"
value: { value: { bool_value: true } }
}
bindings: {
key: "y"
value: { value: { string_value: "string" } }
}
value: { bool_value: true }
}
test {
name: "disambiguation"
expr: ".y"
container: "com.example"
type_env: {
name: "com.example.y"
ident: { type: { primitive: STRING } }
}
type_env: {
name: "y"
ident: { type: { primitive: STRING } }
}
bindings: {
key: "com.example.y"
value: { value: { string_value: "com.example.y" } }
}
bindings: {
key: "y"
value: { value: { string_value: "y" } }
}
value: { string_value: "y" }
}
test {
name: "comprehension_shadowing"
expr: "[0].exists(y, y == 0)"
container: "com.example"
type_env: {
name: "com.example.y"
ident: { type: { primitive: INT64 } }
}
bindings: {
key: "com.example.y"
value: { value: { int64_value: 42 } }
}
value: { bool_value: true }
}
test {
name: "comprehension_shadowing_disambiguation"
expr: "['compre'].exists(y, .y == 'y')"
container: "com.example"
type_env: {
name: "y"
ident: { type: { primitive: STRING } }
}
bindings: {
key: "y"
value: { value: { string_value: "y" } }
}
value: { bool_value: true }
}
test {
name: "comprehension_shadowing_parse_only"
expr: "[0].exists(y, y == 0)"
container: "com.example"
type_env: {
name: "com.example.y"
ident: { type: { primitive: INT64 } }
}
bindings: {
key: "com.example.y"
value: { value: { int64_value: 42 } }
}
disable_check: true
value: { bool_value: true }
}
test {
name: "comprehension_shadowing_selector"
expr: "[{'z': 0}].exists(y, y.z == 0)"
type_env: {
name: "y.z"
ident: { type: { primitive: INT64 } }
}
bindings: {
key: "y.z"
value: { value: { int64_value: 42 } }
}
value: { bool_value: true }
}
test {
name: "comprehension_shadowing_selector_parse_only"
expr: "[{'z': 0}].exists(y, y.z == 0)"
type_env: {
name: "y.z"
ident: { type: { primitive: INT64 } }
}
bindings: {
key: "y.z"
value: { value: { int64_value: 42 } }
}
disable_check: true
value: { bool_value: true }
}
test {
name: "comprehension_shadowing_namespaced_selector"
expr: "[{'z': 0}].exists(y, y.z == 0)"
container: "com.example"
type_env: {
name: "com.example.y.z"
ident: { type: { primitive: INT64 } }
}
bindings: {
key: "com.example.y.z"
value: { value: { int64_value: 42 } }
}
value: { bool_value: true }
}
test {
name: "comprehension_shadowing_namespaced_selector_parse_only"
expr: "[{'z': 0}].exists(y, y.z == 0)"
container: "com.example"
type_env: {
name: "com.example.y.z"
ident: { type: { primitive: INT64 } }
}
bindings: {
key: "com.example.y.z"
value: { value: { int64_value: 42 } }
}
disable_check: true
value: { bool_value: true }
}
test {
name: "comprehension_shadowing_namespaced_selector_disambiguation"
expr: "[{'z': 'compre'}].exists(y, .y.z == 'y.z')"
container: "com.example"
type_env: {
name: "com.example.y.z"
ident: { type: { primitive: STRING } }
}
type_env: {
name: "y.z"
ident: { type: { primitive: STRING } }
}
bindings: {
key: "com.example.y.z"
value: { value: { string_value: "com.example.y.z" } }
}
bindings: {
key: "y.z"
value: { value: { string_value: "y.z" } }
}
value: { bool_value: true }
}
test {
name: "comprehension_shadowing_nesting"
expr: "[1].exists(y, [0].exists(y, y == 0))"
container: "com.example"
type_env: {
name: "com.example.y"
ident: { type: { primitive: INT64 } }
}
type_env: {
name: "y"
ident: { type: { primitive: INT64 } }
}
bindings: {
key: "com.example.y"
value: { value: { int64_value: 42 } }
}
bindings: {
key: "y"
value: { value: { int64_value: 42 } }
}
value: { bool_value: true }
}
}
Loading