Files
zen/core/expression/tests/data/analysis/nullable.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

1065 lines
26 KiB
TOML

# Nullable type system: merge behavior, member access, operators, strict mode
#
# Tests use [test.loose] and [test.strict] sections to verify both modes in one entry.
# =============================================================================
# Merge via ternary: T | null => Nullable(T)
# =============================================================================
[[test]]
name = "merge: string | null"
expression = "flag ? name : null"
input = '{"Object": {"flag": "Bool", "name": "String"}}'
[test.loose]
return_type = '{"Nullable": "String"}'
[test.strict]
return_type = '{"Nullable": "String"}'
[[test]]
name = "merge: null | string"
expression = "flag ? null : name"
input = '{"Object": {"flag": "Bool", "name": "String"}}'
[test.loose]
return_type = '{"Nullable": "String"}'
[test.strict]
return_type = '{"Nullable": "String"}'
[[test]]
name = "merge: number | null"
expression = "flag ? age : null"
input = '{"Object": {"flag": "Bool", "age": "Number"}}'
[test.loose]
return_type = '{"Nullable": "Number"}'
[test.strict]
return_type = '{"Nullable": "Number"}'
[[test]]
name = "merge: null | number"
expression = "flag ? null : age"
input = '{"Object": {"flag": "Bool", "age": "Number"}}'
[test.loose]
return_type = '{"Nullable": "Number"}'
[test.strict]
return_type = '{"Nullable": "Number"}'
[[test]]
name = "merge: bool | null"
expression = "flag ? active : null"
input = '{"Object": {"flag": "Bool", "active": "Bool"}}'
[test.loose]
return_type = '{"Nullable": "Bool"}'
[test.strict]
return_type = '{"Nullable": "Bool"}'
[[test]]
name = "merge: date | null"
expression = "flag ? d : null"
input = '{"Object": {"flag": "Bool", "d": "Date"}}'
[test.loose]
return_type = '{"Nullable": "Date"}'
[test.strict]
return_type = '{"Nullable": "Date"}'
[[test]]
name = "merge: null | null => Null"
expression = "flag ? null : null"
input = '{"Object": {"flag": "Bool"}}'
[test.loose]
return_type = '"Null"'
[test.strict]
return_type = '"Null"'
[[test]]
name = "merge: same type no nullable"
expression = "flag ? a : b"
input = '{"Object": {"flag": "Bool", "a": "String", "b": "String"}}'
[test.loose]
return_type = '"String"'
[test.strict]
return_type = '"String"'
[[test]]
name = "merge: same number no nullable"
expression = "flag ? a : b"
input = '{"Object": {"flag": "Bool", "a": "Number", "b": "Number"}}'
[test.loose]
return_type = '"Number"'
[test.strict]
return_type = '"Number"'
[[test]]
name = "merge: same bool no nullable"
expression = "flag ? a : b"
input = '{"Object": {"flag": "Bool", "a": "Bool", "b": "Bool"}}'
[test.loose]
return_type = '"Bool"'
[test.strict]
return_type = '"Bool"'
# =============================================================================
# Merge via ternary: Const / Enum interactions
# =============================================================================
[[test]]
name = "merge: const | null => Nullable(Const)"
expression = "flag ? 'hello' : null"
input = '{"Object": {"flag": "Bool"}}'
[test.loose]
return_type = '{"Nullable": {"Const": "hello"}}'
[test.strict]
return_type = '{"Nullable": {"Const": "hello"}}'
[[test]]
name = "merge: null | const => Nullable(Const)"
expression = "flag ? null : 'world'"
input = '{"Object": {"flag": "Bool"}}'
[test.loose]
return_type = '{"Nullable": {"Const": "world"}}'
[test.strict]
return_type = '{"Nullable": {"Const": "world"}}'
[[test]]
name = "merge: const | const => Enum"
expression = "flag ? 'hello' : 'world'"
input = '{"Object": {"flag": "Bool"}}'
[test.loose]
return_type = '{"Enum": [null, ["hello", "world"]]}'
[test.strict]
return_type = '{"Enum": [null, ["hello", "world"]]}'
[[test]]
name = "merge: same const => Const"
expression = "flag ? 'same' : 'same'"
input = '{"Object": {"flag": "Bool"}}'
[test.loose]
return_type = '{"Const": "same"}'
[test.strict]
return_type = '{"Const": "same"}'
[[test]]
name = "merge: const | string => String"
expression = "flag ? 'literal' : name"
input = '{"Object": {"flag": "Bool", "name": "String"}}'
[test.loose]
return_type = '"String"'
[test.strict]
return_type = '"String"'
[[test]]
name = "merge: string | const => String"
expression = "flag ? name : 'literal'"
input = '{"Object": {"flag": "Bool", "name": "String"}}'
[test.loose]
return_type = '"String"'
[test.strict]
return_type = '"String"'
# =============================================================================
# Merge via ternary: incompatible types
# =============================================================================
[[test]]
name = "merge: string | number => Any"
expression = "flag ? name : age"
input = '{"Object": {"flag": "Bool", "name": "String", "age": "Number"}}'
[test.loose]
return_type = '"Any"'
[test.strict]
return_type = '"Any"'
[[test]]
name = "merge: bool | string => Any"
expression = "flag ? active : name"
input = '{"Object": {"flag": "Bool", "active": "Bool", "name": "String"}}'
[test.loose]
return_type = '"Any"'
[test.strict]
return_type = '"Any"'
[[test]]
name = "merge: number | bool => Any"
expression = "flag ? age : active"
input = '{"Object": {"flag": "Bool", "age": "Number", "active": "Bool"}}'
[test.loose]
return_type = '"Any"'
[test.strict]
return_type = '"Any"'
[[test]]
name = "merge: any | T => Any"
expression = "flag ? x : name"
input = '{"Object": {"flag": "Bool", "x": "Any", "name": "String"}}'
[test.loose]
return_type = '"Any"'
[test.strict]
return_type = '"Any"'
[[test]]
name = "merge: T | any => Any"
expression = "flag ? name : x"
input = '{"Object": {"flag": "Bool", "name": "String", "x": "Any"}}'
[test.loose]
return_type = '"Any"'
[test.strict]
return_type = '"Any"'
# =============================================================================
# Merge via ternary: Nullable inputs
# =============================================================================
[[test]]
name = "merge: Nullable(String) | Nullable(String)"
expression = "flag ? a : b"
input = '{"Object": {"flag": "Bool", "a": {"Nullable": "String"}, "b": {"Nullable": "String"}}}'
[test.loose]
return_type = '{"Nullable": "String"}'
[test.strict]
return_type = '{"Nullable": "String"}'
[[test]]
name = "merge: Nullable(Number) | Number"
expression = "flag ? a : b"
input = '{"Object": {"flag": "Bool", "a": {"Nullable": "Number"}, "b": "Number"}}'
[test.loose]
return_type = '{"Nullable": "Number"}'
[test.strict]
return_type = '{"Nullable": "Number"}'
[[test]]
name = "merge: Number | Nullable(Number)"
expression = "flag ? a : b"
input = '{"Object": {"flag": "Bool", "a": "Number", "b": {"Nullable": "Number"}}}'
[test.loose]
return_type = '{"Nullable": "Number"}'
[test.strict]
return_type = '{"Nullable": "Number"}'
[[test]]
name = "merge: Nullable(String) | null"
expression = "flag ? a : null"
input = '{"Object": {"flag": "Bool", "a": {"Nullable": "String"}}}'
[test.loose]
return_type = '{"Nullable": "String"}'
[test.strict]
return_type = '{"Nullable": "String"}'
[[test]]
name = "merge: null | Nullable(Bool)"
expression = "flag ? null : a"
input = '{"Object": {"flag": "Bool", "a": {"Nullable": "Bool"}}}'
[test.loose]
return_type = '{"Nullable": "Bool"}'
[test.strict]
return_type = '{"Nullable": "Bool"}'
[[test]]
name = "merge: Nullable(String) | Number => Any (incompatible inner)"
expression = "flag ? a : b"
input = '{"Object": {"flag": "Bool", "a": {"Nullable": "String"}, "b": "Number"}}'
[test.loose]
return_type = '"Any"'
[test.strict]
return_type = '"Any"'
[[test]]
name = "merge: Any | Nullable(String) => Any"
expression = "flag ? a : b"
input = '{"Object": {"flag": "Bool", "a": "Any", "b": {"Nullable": "String"}}}'
[test.loose]
return_type = '"Any"'
[test.strict]
return_type = '"Any"'
# =============================================================================
# Merge via ternary: arrays
# =============================================================================
[[test]]
name = "merge: array | null => Nullable(Array)"
expression = "flag ? items : null"
input = '{"Object": {"flag": "Bool", "items": {"Array": "Number"}}}'
[test.loose]
return_type = '{"Nullable": {"Array": "Number"}}'
[test.strict]
return_type = '{"Nullable": {"Array": "Number"}}'
[[test]]
name = "merge: array | array => Array"
expression = "flag ? a : b"
input = '{"Object": {"flag": "Bool", "a": {"Array": "String"}, "b": {"Array": "String"}}}'
[test.loose]
return_type = '{"Array": "String"}'
[test.strict]
return_type = '{"Array": "String"}'
# =============================================================================
# Nullable member access
# =============================================================================
[[test]]
name = "member: nullable object property"
expression = "user.name"
input = '{"Object": {"user": {"Nullable": {"Object": {"name": "String"}}}}}'
[test.loose]
return_type = '"String"'
[test.strict]
return_type = '{"Nullable": "String"}'
[[test]]
name = "member: nullable object nested"
expression = "user.address"
input = '{"Object": {"user": {"Nullable": {"Object": {"address": {"Object": {"city": "String"}}}}}}}'
[test.loose]
return_type = '{"Object": {"city": "String"}}'
[test.strict]
return_type = '{"Nullable": {"Object": {"city": "String"}}}'
[[test]]
name = "member: nullable object nested chain"
expression = "user.address.city"
input = '{"Object": {"user": {"Nullable": {"Object": {"address": {"Object": {"city": "String"}}}}}}}'
[test.loose]
return_type = '"String"'
[test.strict]
return_type = '{"Nullable": "String"}'
[[test]]
name = "member: nullable array index"
expression = "items[0]"
input = '{"Object": {"items": {"Nullable": {"Array": "Number"}}}}'
[test.loose]
return_type = '"Number"'
[test.strict]
return_type = '{"Nullable": "Number"}'
[[test]]
name = "member: array index introduces nullable in strict"
expression = "items[0]"
input = '{"Object": {"items": {"Array": "Number"}}}'
[test.loose]
return_type = '"Number"'
[test.strict]
return_type = '{"Nullable": "Number"}'
[[test]]
name = "member: indexed property is nullable in strict"
expression = "companies[0].revenue"
input = '{"Object": {"companies": {"Array": {"Object": {"revenue": "Number"}}}}}'
[test.loose]
return_type = '"Number"'
[test.strict]
return_type = '{"Nullable": "Number"}'
[[test]]
name = "member: coalesce recovers from nullable index"
expression = "companies[0].revenue ?? 0"
input = '{"Object": {"companies": {"Array": {"Object": {"revenue": "Number"}}}}}'
[test.loose]
return_type = '"Number"'
[test.strict]
return_type = '"Number"'
[[test]]
name = "member: nullable object unknown key"
expression = "user.unknown"
input = '{"Object": {"user": {"Nullable": {"Object": {"name": "String"}}}}}'
[test.loose]
return_type = '"Any"'
[test.strict]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[[test]]
name = "member: null type returns Null"
expression = "x.foo"
input = '{"Object": {"x": "Null"}}'
[test.loose]
return_type = '"Null"'
[test.strict]
return_type = '"Null"'
# =============================================================================
# Nullish coalescing (??)
# =============================================================================
[[test]]
name = "coalesce: Nullable(String) ?? const"
expression = "name ?? 'default'"
input = '{"Object": {"name": {"Nullable": "String"}}}'
[test.loose]
return_type = '"String"'
[test.strict]
return_type = '"String"'
[[test]]
name = "coalesce: Nullable(Number) ?? number"
expression = "val ?? 0"
input = '{"Object": {"val": {"Nullable": "Number"}}}'
[test.loose]
return_type = '"Number"'
[test.strict]
return_type = '"Number"'
[[test]]
name = "coalesce: Null ?? number"
expression = "x ?? 42"
input = '{"Object": {"x": "Null"}}'
[test.loose]
return_type = '"Number"'
[test.strict]
return_type = '"Number"'
[[test]]
name = "coalesce: Nullable(Const) ?? const merges into an enum"
expression = "kind ?? 'world'"
input = '{"Object": {"kind": {"Nullable": {"Const": "hello"}}}}'
[test.loose]
return_type = '{"Enum":[null,["hello","world"]]}'
[test.strict]
return_type = '{"Enum":[null,["hello","world"]]}'
[[test]]
name = "coalesce: non-nullable passes through"
expression = "name ?? 'fallback'"
input = '{"Object": {"name": "String"}}'
[test.loose]
return_type = '"String"'
[test.strict]
return_type = '"String"'
[[test]]
name = "coalesce: Nullable(Const) ?? const => Enum"
expression = "x ?? 'other'"
input = '{"Object": {"x": {"Nullable": {"Const": "hello"}}}}'
[test.loose]
return_type = '{"Enum": [null, ["hello", "other"]]}'
[test.strict]
return_type = '{"Enum": [null, ["hello", "other"]]}'
[[test]]
name = "coalesce: Nullable(Bool) ?? bool"
expression = "active ?? false"
input = '{"Object": {"active": {"Nullable": "Bool"}}}'
[test.loose]
return_type = '"Bool"'
[test.strict]
return_type = '"Bool"'
[[test]]
name = "coalesce: Any ?? string => Any"
expression = "x ?? 'hi'"
input = '{"Object": {"x": "Any"}}'
[test.loose]
return_type = '"Any"'
[test.strict]
return_type = '"Any"'
# =============================================================================
# Nullable in comparisons
# =============================================================================
[[test]]
name = "compare: nullable == literal"
expression = "val == 5"
input = '{"Object": {"val": {"Nullable": "Number"}}}'
[test.loose]
return_type = '"Bool"'
[test.strict]
return_type = '"Bool"'
[[test]]
name = "compare: nullable != literal"
expression = "val != 5"
input = '{"Object": {"val": {"Nullable": "Number"}}}'
[test.loose]
return_type = '"Bool"'
[test.strict]
return_type = '"Bool"'
[[test]]
name = "compare: nullable == null"
expression = "val == null"
input = '{"Object": {"val": {"Nullable": "Number"}}}'
[test.loose]
return_type = '"Bool"'
[test.strict]
return_type = '"Bool"'
[[test]]
name = "compare: nullable != null"
expression = "val != null"
input = '{"Object": {"val": {"Nullable": "Number"}}}'
[test.loose]
return_type = '"Bool"'
[test.strict]
return_type = '"Bool"'
[[test]]
name = "compare: nullable string == const"
expression = "name == 'hello'"
input = '{"Object": {"name": {"Nullable": "String"}}}'
[test.loose]
return_type = '"Bool"'
[test.strict]
return_type = '"Bool"'
[[test]]
name = "compare: nullable < number => error"
expression = "val < 10"
input = '{"Object": {"val": {"Nullable": "Number"}}}'
[test.loose]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[test.strict]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[[test]]
name = "compare: nullable > number => error"
expression = "val > 0"
input = '{"Object": {"val": {"Nullable": "Number"}}}'
[test.loose]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[test.strict]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
# =============================================================================
# Nullable in arithmetic (must null-check first)
# =============================================================================
[[test]]
name = "arith: nullable + number => error"
expression = "val + 1"
input = '{"Object": {"val": {"Nullable": "Number"}}}'
[test.loose]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[test.strict]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[[test]]
name = "arith: nullable - number => error"
expression = "val - 1"
input = '{"Object": {"val": {"Nullable": "Number"}}}'
[test.loose]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[test.strict]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[[test]]
name = "arith: nullable * number => error"
expression = "val * 2"
input = '{"Object": {"val": {"Nullable": "Number"}}}'
[test.loose]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[test.strict]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[[test]]
name = "arith: nullable string + string => error"
expression = "name + ' world'"
input = '{"Object": {"name": {"Nullable": "String"}}}'
[test.loose]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[test.strict]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
# =============================================================================
# Nullable in logical operators
# =============================================================================
[[test]]
name = "logical: nullable and bool => error"
expression = "a and b"
input = '{"Object": {"a": {"Nullable": "Bool"}, "b": "Bool"}}'
[test.loose]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[test.strict]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[[test]]
name = "logical: nullable or bool => error"
expression = "a or b"
input = '{"Object": {"a": {"Nullable": "Bool"}, "b": "Bool"}}'
[test.loose]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[test.strict]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[[test]]
name = "logical: not nullable => error"
expression = "not a"
input = '{"Object": {"a": {"Nullable": "Bool"}}}'
[test.loose]
return_type = '"Bool"'
diagnostics = [{ source = "type_check", severity = "error" }]
[test.strict]
return_type = '"Bool"'
diagnostics = [{ source = "type_check", severity = "error" }]
# =============================================================================
# Edge cases
# =============================================================================
[[test]]
name = "edge: deeply nested ternary with nulls"
expression = "a ? (b ? x : null) : null"
input = '{"Object": {"a": "Bool", "b": "Bool", "x": "Number"}}'
[test.loose]
return_type = '{"Nullable": "Number"}'
[test.strict]
return_type = '{"Nullable": "Number"}'
[[test]]
name = "edge: chained coalesce"
expression = "a ?? b ?? 'default'"
input = '{"Object": {"a": {"Nullable": "String"}, "b": {"Nullable": "String"}}}'
[test.loose]
return_type = '"String"'
[test.strict]
return_type = '"String"'
[[test]]
name = "edge: coalesce then member"
expression = "(user ?? fallback).name"
input = '{"Object": {"user": {"Nullable": {"Object": {"name": "String"}}}, "fallback": {"Object": {"name": "String"}}}}'
[test.loose]
return_type = '"String"'
[test.strict]
return_type = '"String"'
[[test]]
name = "edge: ternary producing Nullable then coalesce"
expression = "(flag ? name : null) ?? 'none'"
input = '{"Object": {"flag": "Bool", "name": "String"}}'
[test.loose]
return_type = '"String"'
[test.strict]
return_type = '"String"'
[[test]]
name = "edge: ternary with Any branch"
expression = "flag ? x : null"
input = '{"Object": {"flag": "Bool", "x": "Any"}}'
[test.loose]
return_type = '"Any"'
[test.strict]
return_type = '"Any"'
[[test]]
name = "edge: double nullable merge stays single"
expression = "a ? (b ? x : null) : null"
input = '{"Object": {"a": "Bool", "b": "Bool", "x": "String"}}'
[test.loose]
return_type = '{"Nullable": "String"}'
[test.strict]
return_type = '{"Nullable": "String"}'
[[test]]
name = "edge: Nullable(Nullable) does not nest from merge"
expression = "flag ? a : null"
input = '{"Object": {"flag": "Bool", "a": {"Nullable": "String"}}}'
[test.loose]
return_type = '{"Nullable": "String"}'
[test.strict]
return_type = '{"Nullable": "String"}'
[[test]]
name = "edge: in operator with nullable array => error"
expression = "3 in items"
input = '{"Object": {"items": {"Nullable": {"Array": "Number"}}}}'
[test.loose]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[test.strict]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[[test]]
name = "edge: not in with nullable array => error"
expression = "3 not in items"
input = '{"Object": {"items": {"Nullable": {"Array": "Number"}}}}'
[test.loose]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[test.strict]
return_type = '"Any"'
diagnostics = [{ source = "type_check", severity = "error" }]
[[test]]
name = "edge: in with non-nullable array works"
expression = "3 in items"
input = '{"Object": {"items": {"Array": "Number"}}}'
[test.loose]
return_type = '"Bool"'
[test.strict]
return_type = '"Bool"'
# =============================================================================
# Nullable in closures
# =============================================================================
[[test]]
name = "closure: map over nullable array"
expression = "map(items as item, item.price)"
input = '{"Object": {"items": {"Nullable": {"Array": {"Object": {"price": "Number"}}}}}}'
[test.loose]
return_type = '{"Array": "Number"}'
[test.strict]
return_type = '{"Array": "Number"}'
diagnostics = [{ source = "type_check", severity = "error" }]
[[test]]
name = "closure: map over nullable array with fallback"
expression = "map(items ?? [], #.price)"
input = '{"Object": {"items": {"Nullable": {"Array": {"Object": {"price": "Number"}}}}}}'
[test.loose]
return_type = '{"Array": "Number"}'
[test.strict]
return_type = '{"Array": "Number"}'
[[test]]
name = "closure: map unknown property on nullable array (strict error)"
expression = "map(items as item, item.unknown)"
input = '{"Object": {"items": {"Nullable": {"Array": {"Object": {"price": "Number"}}}}}}'
[test.loose]
return_type = '{"Array": "Any"}'
[test.strict]
return_type = '{"Array": "Any"}'
diagnostics = [{ source = "type_check", severity = "error" }]
[[test]]
name = "closure: filter over nullable array"
expression = "filter(items as item, item.active)"
input = '{"Object": {"items": {"Nullable": {"Array": {"Object": {"active": "Bool"}}}}}}'
[test.loose]
return_type = '{"Nullable": {"Array": {"Object": {"active": "Bool"}}}}'
[test.strict]
return_type = '{"Nullable": {"Array": {"Object": {"active": "Bool"}}}}'
diagnostics = [{ source = "type_check", severity = "error" }]
[[test]]
name = "closure: some over nullable array"
expression = "some(items as item, item.active)"
input = '{"Object": {"items": {"Nullable": {"Array": {"Object": {"active": "Bool"}}}}}}'
[test.loose]
return_type = '"Bool"'
[test.strict]
return_type = '"Bool"'
diagnostics = [{ source = "type_check", severity = "error" }]
[[test]]
name = "closure: map over non-nullable array valid member"
expression = "map(companies as c, c.name)"
input = '{"Object": {"companies": {"Array": {"Object": {"name": "String", "revenue": "Number"}}}}}'
[test.loose]
return_type = '{"Array": "String"}'
[test.strict]
return_type = '{"Array": "String"}'
[[test]]
name = "closure: map over non-nullable array unknown member (strict error)"
expression = "map(companies as c, c.unknownProperty)"
input = '{"Object": {"companies": {"Array": {"Object": {"name": "String", "revenue": "Number"}}}}}'
[test.loose]
return_type = '{"Array": "Any"}'
[test.strict]
return_type = '{"Array": "Any"}'
diagnostics = [{ source = "type_check", severity = "error" }]
[[test]]
name = "closure: callback ref on nullable array"
expression = "map(items, #.price)"
input = '{"Object": {"items": {"Nullable": {"Array": {"Object": {"price": "Number"}}}}}}'
[test.loose]
return_type = '{"Array": "Number"}'
[test.strict]
return_type = '{"Array": "Number"}'
diagnostics = [{ source = "type_check", severity = "error" }]
[[test]]
name = "closure: callback ref unknown property (strict error)"
expression = "map(items, #.unknown)"
input = '{"Object": {"items": {"Nullable": {"Array": {"Object": {"price": "Number"}}}}}}'
[test.loose]
return_type = '{"Array": "Any"}'
[test.strict]
return_type = '{"Array": "Any"}'
diagnostics = [{ source = "type_check", severity = "error" }]
[[test]]
name = "closure: nested nullable array closure"
expression = "map(groups as g, map(g.items as item, item.value))"
input = '{"Object": {"groups": {"Nullable": {"Array": {"Object": {"items": {"Array": {"Object": {"value": "Number"}}}}}}}}}'
[test.loose]
return_type = '{"Array": {"Array": "Number"}}'
[test.strict]
return_type = '{"Array": {"Array": "Number"}}'
diagnostics = [{ source = "type_check", severity = "error" }]
[[test]]
name = "closure: nested nullable array closure with fallback"
expression = "map(groups ?? [], map(#.items, #.value))"
input = '{"Object": {"groups": {"Nullable": {"Array": {"Object": {"items": {"Array": {"Object": {"value": "Number"}}}}}}}}}'
[test.loose]
return_type = '{"Array": {"Array": "Number"}}'
[test.strict]
return_type = '{"Array": {"Array": "Number"}}'
[[test]]
name = "string number index"
expression = "name[0]"
input = '{"name": "hello"}'
[test.loose]
return_type = '"String"'
[test.strict]
return_type = '{"Nullable": "String"}'
[[test]]
name = "string number index concat"
expression = "name[0] + 'x'"
input = '{"name": "hello"}'
[test.loose]
return_type = '"String"'
[test.strict]
diagnostics = [
{ source = "type_check", severity = "error" },
]
[[test]]
name = "string number index coalesce concat"
expression = "(name[0] ?? '') + 'x'"
input = '{"name": "hello"}'
[test.loose]
return_type = '"String"'
[test.strict]
return_type = '"String"'
[[test]]
name = "flatten nullable array strict error"
expression = "flatten(matrix)"
input = '{"Object": {"matrix": {"Nullable": {"Array": {"Array": "Number"}}}}}'
[test.loose]
return_type = '{"Array": "Number"}'
[test.strict]
return_type = '{"Array": "Number"}'
diagnostics = [{ source = "type_check", severity = "error" }]
[[test]]
name = "flatten nullable array with fallback"
expression = "flatten(matrix ?? [])"
input = '{"Object": {"matrix": {"Nullable": {"Array": {"Array": "Number"}}}}}'
[test.loose]
return_type = '{"Array": "Number"}'
[test.strict]
return_type = '{"Array": "Number"}'
[[test]]
name = "const string number index"
expression = "code[0]"
input = '{"Object": {"code": {"Const": "abc"}}}'
[test.loose]
return_type = '"String"'
[test.strict]
return_type = '{"Nullable": "String"}'
[[test]]
name = "enum string number index"
expression = "status[0]"
input = '{"Object": {"status": {"Enum": [null, ["active", "inactive"]]}}}'
[test.loose]
return_type = '"String"'
[test.strict]
return_type = '{"Nullable": "String"}'