Files
zen/core/expression/tests/data/analysis/diagnostics.toml
T
stefan-gorules ffdfd807d1 feat: implement slots instead of natural language (#527)
* feat: implement slots instead of natural language

Replace the natural-language projection (`nl`) with engine-owned slot
completion: for a cursor position the engine classifies the slot being
edited (operand, operator, literal, member access, ...) and returns typed
options, literal facts and the expected type, for policies and graphs.

- zen-expression: new `slot` module (classification, literals, operators)
  and a lenient lexer mode for incomplete input; `nl` removed
- Intellisense: typed diagnostic codes with arguments, richer inspect for
  hover, dedicated parser error variants (messages unchanged)
- Engine: `Workspace::cursor_scope`, `slot`, `facts`; `rename_from` and
  `references_from`; editor spans in UTF-16 units; renames reach imported
  policies, `$nodes` reads, child graphs and output column heads; no `$root`
  completion in policies
- Node bindings: `slot`, `facts`, `cursorScope`, `slotBatch` replace
  `nl`, `nlTokenize`, `nlEncodeString`, `nlTokenizeBatch`

No change to expression evaluation: the VM, compiler, functions and the
default lexer path are untouched, and parser error messages keep their text.

* fix: harden slot classification, scopes and graph expectations

- restore master execution order; slot scopes hide writes of the edited
  block and its dependents instead of relying on block ranking
- token-start editing gated by slot state; operator, list and operand
  spans no longer splice into neighbouring tokens
- interval-aware bracket pairing for half-open and reversed intervals
- lenient lexer recovers from unknown characters (slots only)
- graph expected type follows switch and pass-through nodes and merges
  compatible output schemas
- sibling inference caps distinct values and analyses lazily
- deterministic overload return type display

* fix: order policy blocks by entity reads through closures and relationships

- record reads for `#.field` in closures so entity fields read through
  lists become dependencies
- link reads through derived lists (filter/map results) to the entity
  fields they carry and order the list after those fields
- evaluate entity blocks on single relationships, and demand entity
  paths for plain reads through relationships
- slot scopes keep the top-level variable when hiding written fields
2026-09-28 21:47:40 +02:00

368 lines
7.1 KiB
TOML

# Error and warning diagnostics from all pipeline stages
# Type errors
[[test]]
name = "type error: string minus number"
expression = "a - b"
input = '{"a": "hello", "b": 5}'
reads = [
{ type = "direct", path = ["a"] },
{ type = "direct", path = ["b"] },
]
[test.loose]
return_type = '"Any"'
diagnostics = [
{ source = "type_check", severity = "error" },
]
[test.strict]
return_type = '"Any"'
diagnostics = [
{ source = "type_check", severity = "error" },
]
[[test]]
name = "type error: not on number"
expression = "not a"
input = '{"a": 5}'
reads = [
{ type = "direct", path = ["a"] },
]
[test.loose]
return_type = '"Bool"'
diagnostics = [
{ source = "type_check", severity = "error" },
]
[test.strict]
return_type = '"Bool"'
diagnostics = [
{ source = "type_check", severity = "error" },
]
# Type warnings
[[test]]
name = "type warning: always false comparison"
expression = "a == b"
input = '{"a": "hello", "b": 5}'
reads = [
{ type = "direct", path = ["a"] },
{ type = "direct", path = ["b"] },
]
[test.loose]
return_type = '"Bool"'
diagnostics = [
{ source = "type_check", severity = "warning" },
]
[test.strict]
return_type = '"Bool"'
diagnostics = [
{ source = "type_check", severity = "warning" },
]
[[test]]
name = "type warning: always true not-equal"
expression = "a != b"
input = '{"a": "hello", "b": 5}'
reads = [
{ type = "direct", path = ["a"] },
{ type = "direct", path = ["b"] },
]
[test.loose]
return_type = '"Bool"'
diagnostics = [
{ source = "type_check", severity = "warning" },
]
[test.strict]
return_type = '"Bool"'
diagnostics = [
{ source = "type_check", severity = "warning" },
]
# Lexer errors
[[test]]
name = "lexer error: invalid symbol"
expression = "@invalid"
[test.loose]
diagnostics = [
{ source = "lexer", severity = "error" },
]
[test.strict]
diagnostics = [
{ source = "lexer", severity = "error" },
]
# Parser errors
[[test]]
name = "parser error: incomplete expression"
expression = "a +"
[test.loose]
diagnostics = [
{ source = "parser", severity = "error" },
]
[test.strict]
diagnostics = [
{ source = "parser", severity = "error" },
]
[[test]]
name = "parser error: unmatched paren"
expression = "(a + b"
[test.loose]
diagnostics = [
{ source = "parser", severity = "error" },
]
[test.strict]
diagnostics = [
{ source = "parser", severity = "error" },
]
[[test]]
name = "unknown method call causes parse error"
expression = "name.upper()"
input = '{"name": "John"}'
[test.loose]
diagnostics = [
{ source = "parser", severity = "error" },
]
[test.strict]
diagnostics = [
{ source = "parser", severity = "error" },
]
# Invalid member access on typed objects
[[test]]
name = "invalid member on typed object"
expression = "customer.nonExistent"
input = '{"customer": {"name": "John", "age": 30}}'
[test.loose]
return_type = '"Any"'
[test.strict]
return_type = '"Any"'
diagnostics = [
{ source = "type_check", severity = "error" },
]
[[test]]
name = "valid member on typed object no error"
expression = "customer.name"
input = '{"customer": {"name": "John", "age": 30}}'
[test.loose]
return_type = '"String"'
[test.strict]
return_type = '"String"'
[[test]]
name = "invalid nested member"
expression = "order.customer.nonExistent"
input = '{"order": {"customer": {"name": "John"}}}'
[test.loose]
return_type = '"Any"'
[test.strict]
return_type = '"Any"'
diagnostics = [
{ source = "type_check", severity = "error" },
]
[[test]]
name = "closure alias invalid member"
expression = "map(items as item, item.nonExistent)"
input = '{"items": [{"price": 10, "name": "a"}]}'
[test.loose]
return_type = '{"Array":"Any"}'
[test.strict]
return_type = '{"Array":"Any"}'
diagnostics = [
{ source = "type_check", severity = "error" },
]
[[test]]
name = "closure alias valid member no error"
expression = "map(items as item, item.price)"
input = '{"items": [{"price": 10, "name": "a"}]}'
[test.loose]
return_type = '{"Array":"Number"}'
[test.strict]
return_type = '{"Array":"Number"}'
[[test]]
name = "closure callback ref invalid member"
expression = "map(items, #.nonExistent)"
input = '{"items": [{"price": 10, "name": "a"}]}'
[test.loose]
return_type = '{"Array":"Any"}'
[test.strict]
return_type = '{"Array":"Any"}'
diagnostics = [
{ source = "type_check", severity = "error" },
]
[[test]]
name = "nested closure invalid inner member"
expression = "map(groups as g, sum(map(g.items as i, i.nonExistent)))"
input = '{"groups": [{"items": [{"value": 1}]}]}'
[test.loose]
[test.strict]
diagnostics = [
{ source = "type_check", severity = "error" },
]
[[test]]
name = "empty object allows any member even in strict"
expression = "data.anything"
input = '{"data": {}}'
[test.loose]
return_type = '"Any"'
[test.strict]
return_type = '"Any"'
[[test]]
name = "conditional string vs number merges to any"
expression = "flag ? a : b"
input = '{"flag": true, "a": "hello", "b": 123}'
[test.loose]
return_type = '"Any"'
[test.strict]
return_type = '"Any"'
[[test]]
name = "member on conditional any result is permissive in strict"
expression = "(flag ? obj1 : obj2).name"
input = '{"flag": true, "obj1": {"name": "John"}, "obj2": {"age": 30}}'
[test.loose]
return_type = '"String"'
[test.strict]
return_type = '"String"'
[[test]]
name = "slice with non-number from index"
expression = "arr[bad:2]"
input = '{"arr": [1, 2, 3], "bad": "x"}'
[test.loose]
diagnostics = [
{ source = "type_check", severity = "error" },
]
[test.strict]
diagnostics = [
{ source = "type_check", severity = "error" },
]
[[test]]
name = "slice with non-number to index"
expression = "arr[0:bad]"
input = '{"arr": [1, 2, 3], "bad": "x"}'
[test.loose]
diagnostics = [
{ source = "type_check", severity = "error" },
]
[test.strict]
diagnostics = [
{ source = "type_check", severity = "error" },
]
[[test]]
name = "valid slice has no diagnostics"
expression = "arr[1:2]"
input = '{"arr": [1, 2, 3]}'
[test.loose]
return_type = '{"Array":"Number"}'
[test.strict]
return_type = '{"Array":"Number"}'
# Diagnostic codes
[[test]]
name = "code: unknown member"
expression = "customer.nme"
input = '{"customer": {"name": "x"}}'
[test.strict]
diagnostics = [
{ source = "type_check", severity = "error", code = "type.unknown-member", args = { name = "nme" } },
]
[[test]]
name = "code: invalid enum member"
expression = "status == 'pending'"
input = '{"Object":{"status":{"Enum":["status",["open","closed"]]}}}'
[test.loose]
diagnostics = [
{ source = "type_check", severity = "error", code = "type.invalid-enum-member", args = { value = "pending", enum = "status" } },
]
[[test]]
name = "code: function argument mismatch"
expression = "upper(5)"
[test.loose]
diagnostics = [
{ source = "type_check", severity = "error", code = "type.mismatch", args = { got = "number" } },
]
[[test]]
name = "code: expected property"
expression = "customer."
input = '{"customer": {"name": "x"}}'
[test.loose]
diagnostics = [
{ source = "parser", severity = "error", code = "expr.expected-property" },
]
[[test]]
name = "code: missing value after operator"
expression = "a *"
input = '{"a": 1}'
[test.loose]
diagnostics = [
{ source = "parser", severity = "error", code = "expr.missing-value", args = { operator = "*" } },
]