From a3d877d5e269eadece6f7b35b2b6f78fcd9c13d7 Mon Sep 17 00:00:00 2001 From: amazingjoe Date: Sat, 14 Nov 2026 15:56:54 -0800 Subject: [PATCH] feat: support extraction of NoneLiteral in AST helpers --- packages/compiler/src/ast-helpers.ts | 4 ++ packages/compiler/test/ast-helpers.test.ts | 12 ++++- .../compiler/test/state-variables.test.ts | 45 +++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/ast-helpers.ts b/packages/compiler/src/ast-helpers.ts index 0b784608..939b785c 100644 --- a/packages/compiler/src/ast-helpers.ts +++ b/packages/compiler/src/ast-helpers.ts @@ -353,6 +353,10 @@ export function extractExpressionValue(value: unknown): unknown { if (typeof value === 'object') { const obj = value as { __kind?: string; elements?: unknown[] }; + if (obj.__kind === 'NoneLiteral') { + return null; + } + if (obj.__kind === 'DictLiteral') { return extractDictExpression(value as Expression); } diff --git a/packages/compiler/test/ast-helpers.test.ts b/packages/compiler/test/ast-helpers.test.ts index d2c81d3d..a0872689 100644 --- a/packages/compiler/test/ast-helpers.test.ts +++ b/packages/compiler/test/ast-helpers.test.ts @@ -12,8 +12,12 @@ import { AtIdentifier, Identifier, MemberExpression, + NoneLiteral, } from '@agentscript/language'; -import { resolveAtReference } from '../src/ast-helpers.js'; +import { + extractExpressionValue, + resolveAtReference, +} from '../src/ast-helpers.js'; import { CompilerContext } from '../src/compiler-context.js'; let ctx: CompilerContext; @@ -22,6 +26,12 @@ beforeEach(() => { ctx = new CompilerContext(); }); +describe('extractExpressionValue', () => { + it('should extract NoneLiteral as JSON null', () => { + expect(extractExpressionValue(new NoneLiteral())).toBeNull(); + }); +}); + describe('resolveAtReference', () => { it('should resolve @namespace.property to the property name', () => { const expr = new MemberExpression(new AtIdentifier('actions'), 'do_thing'); diff --git a/packages/compiler/test/state-variables.test.ts b/packages/compiler/test/state-variables.test.ts index 9aeb2153..f028a0fa 100644 --- a/packages/compiler/test/state-variables.test.ts +++ b/packages/compiler/test/state-variables.test.ts @@ -1140,6 +1140,51 @@ variables: // --------------------------------------------------------------------------- describe('state variables: dict and object defaults', () => { + it.each([ + { + name: 'object properties containing None', + type: 'object', + literal: '{"missing": None, "kept": 1}', + expected: { missing: null, kept: 1 }, + }, + { + name: 'list positions containing None', + type: 'list[object]', + literal: '[None, {"n": 1}, None]', + expected: [null, { n: 1 }, null], + }, + { + name: 'None inside an object containing a list and another object', + type: 'object', + literal: + '{"outer": {"items": [None, {"missing": None, "kept": 1}, None]}}', + expected: { outer: { items: [null, { missing: null, kept: 1 }, null] } }, + }, + { + name: 'None inside a list containing another list and an object', + type: 'list[object]', + literal: '[None, [None, {"missing": None, "kept": 1}], None]', + expected: [null, [null, { missing: null, kept: 1 }], null], + }, + { + name: 'falsy values inside objects and lists', + type: 'object', + literal: '{"no": False, "zero": 0, "empty": "", "items": [False, 0, ""]}', + expected: { no: false, zero: 0, empty: '', items: [false, 0, ''] }, + }, + ])('should preserve $name in defaults', ({ type, literal, expected }) => { + const result = compileSource( + agentSource(` +variables: + value: mutable ${type} = ${literal}`) + ); + + expect(result.diagnostics).toEqual([]); + const sv = findStateVar(result, 'value'); + expect(sv).toBeDefined(); + expect(sv!.default).toStrictEqual(expected); + }); + it('should compile variable with empty dict default', () => { const result = compileSource( agentSource(`