From 69a53ece43a2306d5f91d1b36b50736a0dd6fc70 Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 10 May 2026 21:13:30 +0000 Subject: [PATCH] datalog: dl-magic-query shape validator (255/255) Bug: dl-magic-query crashed with cryptic "rest: 1 list arg" when the goal argument was a string, number, or arbitrary dict. The first thing the function does is dl-rel-name + dl-adorn-goal, both of which assume a positive-literal list shape. Fix: explicit shape check up front. A goal must be a non-empty list whose first element is a symbol. Otherwise raise with a clear diagnostic. Built-in / aggregate / negation dispatch (the fall-back to dl-query) is unchanged. 2 new magic tests cover string and bare-dict goal rejection. --- lib/datalog/magic.sx | 5 +++++ lib/datalog/scoreboard.json | 8 ++++---- lib/datalog/scoreboard.md | 4 ++-- lib/datalog/tests/magic.sx | 18 ++++++++++++++++++ 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/lib/datalog/magic.sx b/lib/datalog/magic.sx index ffdf4cf4..ce8b20a8 100644 --- a/lib/datalog/magic.sx +++ b/lib/datalog/magic.sx @@ -382,6 +382,11 @@ ;; (built-ins, aggregates, EDB-only relations) the seed is either ;; non-ground or unused; fall back to dl-query. (cond + ((not (and (list? query-goal) + (> (len query-goal) 0) + (symbol? (first query-goal)))) + (error (str "dl-magic-query: goal must be a positive literal " + "(non-empty list with a symbol head), got " query-goal))) ((or (dl-builtin? query-goal) (dl-aggregate? query-goal) (and (dict? query-goal) (has-key? query-goal :neg))) diff --git a/lib/datalog/scoreboard.json b/lib/datalog/scoreboard.json index 72c71703..ba1017d8 100644 --- a/lib/datalog/scoreboard.json +++ b/lib/datalog/scoreboard.json @@ -1,8 +1,8 @@ { "lang": "datalog", - "total_passed": 253, + "total_passed": 255, "total_failed": 0, - "total": 253, + "total": 255, "suites": [ {"name":"tokenize","passed":29,"failed":0,"total":29}, {"name":"parse","passed":22,"failed":0,"total":22}, @@ -13,8 +13,8 @@ {"name":"negation","passed":10,"failed":0,"total":10}, {"name":"aggregates","passed":20,"failed":0,"total":20}, {"name":"api","passed":20,"failed":0,"total":20}, - {"name":"magic","passed":34,"failed":0,"total":34}, + {"name":"magic","passed":36,"failed":0,"total":36}, {"name":"demo","passed":21,"failed":0,"total":21} ], - "generated": "2026-05-10T21:09:18+00:00" + "generated": "2026-05-10T21:13:14+00:00" } diff --git a/lib/datalog/scoreboard.md b/lib/datalog/scoreboard.md index 391f5d79..a55a1dbb 100644 --- a/lib/datalog/scoreboard.md +++ b/lib/datalog/scoreboard.md @@ -1,6 +1,6 @@ # datalog scoreboard -**253 / 253 passing** (0 failure(s)). +**255 / 255 passing** (0 failure(s)). | Suite | Passed | Total | Status | |-------|--------|-------|--------| @@ -13,5 +13,5 @@ | negation | 10 | 10 | ok | | aggregates | 20 | 20 | ok | | api | 20 | 20 | ok | -| magic | 34 | 34 | ok | +| magic | 36 | 36 | ok | | demo | 21 | 21 | ok | diff --git a/lib/datalog/tests/magic.sx b/lib/datalog/tests/magic.sx index 6778dc19..29508c87 100644 --- a/lib/datalog/tests/magic.sx +++ b/lib/datalog/tests/magic.sx @@ -242,6 +242,24 @@ (= (len semi) (len magic)))) true) + ;; Shape validation: dl-magic-query rejects non-list / non- + ;; dict goal shapes cleanly rather than crashing in `rest`. + (dl-mt-test! "magic rejects string goal" + (let ((threw false)) + (do + (guard (e (#t (set! threw true))) + (dl-magic-query (dl-make-db) "foo")) + threw)) + true) + + (dl-mt-test! "magic rejects bare dict goal" + (let ((threw false)) + (do + (guard (e (#t (set! threw true))) + (dl-magic-query (dl-make-db) {:foo "bar"})) + threw)) + true) + ;; 3-stratum program under magic — distinct rule heads at ;; strata 0/1/2 must all rewrite via the worklist. (dl-mt-test! "magic 3-stratum program"