mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat!: Introduce structured destructuring plans for bindings (#485)
- add a dedicated `compiler/destructuring_planner` feature that precomputes binding plans for assignments, parameters, and `some in` expressions - enrich `ScopeContext` with same-scope tracking, local scheduling hints, and module globals so the planner enforces := shadowing rules without blocking parent scopes - wire the planner through compiler, hoist, interpreter, and engine paths while updating binding plan variants and adding query traversal helpers for dependency analysis - document the new planner architecture and ship interpreter regressions that exercise nested destructuring, shadowing, and error reporting Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
25a7ddad0a
commit
1e4ff952e6
@@ -0,0 +1,432 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: assignment-colonequals-nested-pattern
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := {"outer": outer, "inner": inner, "tag": tag} if {
|
||||
[outer, {"meta": {"inner": inner, "tag": tag}}] := ["alpha", {"meta": {"inner": "omega", "tag": "v1"}}]
|
||||
}
|
||||
query: data.test.result
|
||||
want_result:
|
||||
outer: "alpha"
|
||||
inner: "omega"
|
||||
tag: "v1"
|
||||
|
||||
- note: assignment-colonequals-cannot-rebind
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := true if {
|
||||
value := "initial"
|
||||
value := "shadowed"
|
||||
}
|
||||
query: data.test.result
|
||||
error: "ar `value` used before definition below"
|
||||
|
||||
- note: assignment-colonequals-requires-bindable-left
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := true if {
|
||||
1 := value
|
||||
value = 1
|
||||
}
|
||||
query: data.test.result
|
||||
error: "assignment operator := requires left-hand side to have bindable variables"
|
||||
|
||||
- note: equals-binds-left-nested-literal
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import future.keywords
|
||||
|
||||
result := {"first": first, "second": second, "deep": deep} if {
|
||||
[first, {"details": [second, deep]}] = ["foo", {"details": ["bar", "baz"]}]
|
||||
}
|
||||
query: data.test.result
|
||||
want_result:
|
||||
first: "foo"
|
||||
second: "bar"
|
||||
deep: "baz"
|
||||
|
||||
- note: equals-binds-right-nested-literal
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := {"first": first, "second": second, "deep": deep} if {
|
||||
payload := ["foo", {"details": ["bar", "baz"]}]
|
||||
payload = [first, {"details": [second, deep]}]
|
||||
}
|
||||
query: data.test.result
|
||||
want_result:
|
||||
first: "foo"
|
||||
second: "bar"
|
||||
deep: "baz"
|
||||
|
||||
- note: equals-both-sides-nested-dependent-order
|
||||
data:
|
||||
transitions:
|
||||
- [{"id": 1, "next": {"target": 2}}, {"id": 2, "payload": {"value": "beta"}}]
|
||||
- [{"id": 2, "next": {"target": 3}}, {"id": 3, "payload": {"value": "gamma"}}]
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := [[left_id, right_id, value] |
|
||||
some left, right, left_id, right_id, value
|
||||
data.transitions[_] = [left, right]
|
||||
[{"id": left_id, "next": {"target": right_id}}, {"id": right_id, "payload": {"value": value}}] = [left, right]
|
||||
]
|
||||
query: data.test.result
|
||||
want_result: [[1, 2, "beta"], [2, 3, "gamma"]]
|
||||
|
||||
- note: equals-non-shadowing-success
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
default result = false
|
||||
|
||||
result = true if {
|
||||
user_id := "user-1"
|
||||
[user_id, role] = ["user-1", "admin"]
|
||||
role = "admin"
|
||||
}
|
||||
query: data.test.result
|
||||
want_result: true
|
||||
|
||||
- note: equals-non-shadowing-mismatch
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
default result = false
|
||||
|
||||
result = true if {
|
||||
user_id := "user-1"
|
||||
[user_id, role] = ["user-2", "admin"]
|
||||
}
|
||||
query: data.test.result
|
||||
want_result: false
|
||||
|
||||
- note: colon-equals-shadowing-allowed
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
x := 10
|
||||
|
||||
y if {
|
||||
x := 5
|
||||
}
|
||||
query: data.test.y
|
||||
want_result: true
|
||||
|
||||
- note: equals-wildcard-both-sides
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := true if {
|
||||
[_, _] = [1, 2]
|
||||
}
|
||||
query: data.test.result
|
||||
want_result: true
|
||||
|
||||
- note: equals-incompatible-patterns-error
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := true if {
|
||||
[x] = {"key": 1}
|
||||
}
|
||||
query: data.test.result
|
||||
error: "incompatible destructuring patterns: both sides must be arrays or objects with matching structure"
|
||||
|
||||
- note: equals-array-size-mismatch-error
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := true if {
|
||||
some a, b, c
|
||||
[x, y] = [a, b, c]
|
||||
}
|
||||
query: data.test.result
|
||||
error: "mismatch in number of array elements"
|
||||
|
||||
- note: equals-array-literal-length-mismatch-error
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := true if {
|
||||
[x, y] = [1, 2, 3]
|
||||
}
|
||||
query: data.test.result
|
||||
error: "mismatch in number of array elements"
|
||||
|
||||
- note: equals-object-literal-keys-mismatch-error
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := true if{
|
||||
{"a": first, "b": second} = {"a": 1, "c": 2}
|
||||
}
|
||||
query: data.test.result
|
||||
error: "object literal keys mismatch. Expected keys [\"a\", \"b\"] got [\"a\", \"c\"]."
|
||||
|
||||
- note: equals-object-key-not-found-error
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := true if {
|
||||
some value
|
||||
{"a": first, "b": second} = {"a": 1, "c": value}
|
||||
}
|
||||
query: data.test.result
|
||||
error: "key \"c\" not found in left-hand side object during destructuring"
|
||||
|
||||
- note: equals-object-field-count-mismatch-error
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := true if {
|
||||
some a, b
|
||||
{"a": first} = {"a": a, "b": b}
|
||||
}
|
||||
query: data.test.result
|
||||
error: "object field count mismatch in assignment: left has 1 fields, right has 2 fields"
|
||||
|
||||
- note: dynamic-object-field-binding
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := {"target": target, "captured": captured} if {
|
||||
source := {"target": {"value": {"inner": 1}}, "alt": {"value": {"inner": 2}}}
|
||||
{"target": {"value": {"inner": target}}, chosen: {"value": {"inner": captured}}} = source
|
||||
chosen = "alt"
|
||||
}
|
||||
query: data.test.result
|
||||
want_result:
|
||||
target: 1
|
||||
captured: 2
|
||||
|
||||
- note: some-in-nested-binding
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := {
|
||||
[letter, code] |
|
||||
some letter, detail in {"a": {"info": {"code": 1}}, "b": {"info": {"code": 2}}}
|
||||
detail.info.code = code
|
||||
}
|
||||
query: data.test.result
|
||||
want_result:
|
||||
set!:
|
||||
- ["a", 1]
|
||||
- ["b", 2]
|
||||
|
||||
- note: some-in-array-length-mismatch-error
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := true if {
|
||||
some [x, y] in [[1, 2, 3]]
|
||||
}
|
||||
query: data.test.result
|
||||
error: "mismatch in number of array elements"
|
||||
|
||||
- note: some-in-shadowing
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := {"before": before, "after": value} if {
|
||||
value := 0
|
||||
before := value
|
||||
some value in [1, 2]
|
||||
value == 2
|
||||
}
|
||||
query: data.test.result
|
||||
error: "var `value` used before definition below"
|
||||
|
||||
- note: some-in-shadowing-equals
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := true if {
|
||||
value = 0
|
||||
some value in [1, 2]
|
||||
}
|
||||
query: data.test.result
|
||||
error: "var `value` used before definition below"
|
||||
|
||||
- note: some-in-after-shadows
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := true if {
|
||||
some value in [1, 2]
|
||||
value := 1
|
||||
}
|
||||
query: data.test.result
|
||||
error: "var `value` used before definition below"
|
||||
|
||||
- note: comprehension-nested-binding
|
||||
data:
|
||||
records:
|
||||
- {"type": "user", "profile": {"name": "alice", "roles": ["admin", "user"]}}
|
||||
- {"type": "user", "profile": {"name": "bob", "roles": ["user"]}}
|
||||
- {"type": "service", "profile": {"name": "svc"}}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := [[name, role] |
|
||||
some record, role
|
||||
data.records[_] = record
|
||||
record = {"type": "user", "profile": {"name": name, "roles": roles}}
|
||||
roles[_] = role
|
||||
role = "admin"
|
||||
]
|
||||
query: data.test.result
|
||||
want_result: [["alice", "admin"]]
|
||||
|
||||
- note: comprehension-scalar-filter
|
||||
data:
|
||||
scores:
|
||||
- {"value": 10}
|
||||
- {"value": 20}
|
||||
- {"value": 30}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := [score |
|
||||
some entry, score
|
||||
data.scores[_] = entry
|
||||
entry = {"value": score}
|
||||
score >= 20
|
||||
]
|
||||
query: data.test.result
|
||||
want_result: [20, 30]
|
||||
|
||||
|
||||
- note: parameter-nested-destructuring
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
pair([[name, {"primary": role}], {"meta": {"active": active}}]) := [
|
||||
[name, {"primary": role}],
|
||||
{"meta": {"active": active}},
|
||||
] if {
|
||||
name = "alice"
|
||||
role = "admin"
|
||||
active = true
|
||||
}
|
||||
|
||||
result := {"name": name, "role": role, "active": active} if {
|
||||
[[name, {"primary": role}], {"meta": {"active": active}}] := pair([
|
||||
["alice", {"primary": "admin"}],
|
||||
{"meta": {"active": true}},
|
||||
])
|
||||
}
|
||||
query: data.test.result
|
||||
want_result:
|
||||
name: "alice"
|
||||
role: "admin"
|
||||
active: true
|
||||
|
||||
- note: parameter-nested-destructuring-colonequals-error
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
pair([[name, {"primary": role}], {"meta": {"active": active}}]) if {
|
||||
name := "alice"
|
||||
role := "admin"
|
||||
active := true
|
||||
}
|
||||
query: data.test.pair
|
||||
error: "var `name` used before definition below"
|
||||
|
||||
- note: colon-equals-parameter-rebinding-error
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
pair([[name, {"primary": role}], {"meta": {"active": active}}]) if {
|
||||
name := "alice"
|
||||
role := "admin"
|
||||
active := true
|
||||
}
|
||||
|
||||
result := true if {
|
||||
pair([
|
||||
["alice", {"primary": "admin"}],
|
||||
{"meta": {"active": true}},
|
||||
])
|
||||
}
|
||||
query: data.test.result
|
||||
error: "var `name` used before definition below"
|
||||
|
||||
- note: parameter-shadowing
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
global_name := "global"
|
||||
|
||||
capture([name, {"role": role}]) if {
|
||||
name := "local"
|
||||
role := "admin"
|
||||
}
|
||||
|
||||
result := {"global": global_name, "param": name, "role": role} if {
|
||||
capture([name, {"role": role}])
|
||||
}
|
||||
query: data.test.result
|
||||
error: "var `name` used before definition below"
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
cases:
|
||||
- note: walk-loop-index-destructuring
|
||||
data:
|
||||
doc:
|
||||
team:
|
||||
ops:
|
||||
members: ["alice"]
|
||||
dev:
|
||||
members: ["bob"]
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
result := [[section, role, members] |
|
||||
walk(data.doc, [[section, role], {"members": members}])
|
||||
]
|
||||
query: data.test.result
|
||||
want_result: [["team", "dev", ["bob"]], ["team", "ops", ["alice"]]]
|
||||
@@ -27,7 +27,6 @@ cases:
|
||||
want_result:
|
||||
d: 5.1
|
||||
e: 3.25
|
||||
z: true
|
||||
|
||||
- note: non-numeric
|
||||
data: {}
|
||||
|
||||
@@ -76,7 +76,7 @@ cases:
|
||||
import future.keywords
|
||||
x { some [1] in [[1, 2]] }
|
||||
query: data.test
|
||||
error: "array length mismatch. Expected 1 got 2."
|
||||
error: "mismatch in number of array elements"
|
||||
|
||||
- note: array-length-mismatch-skipped
|
||||
data: {}
|
||||
|
||||
Reference in New Issue
Block a user