datalog: reject body lits with reserved names
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 39s

Nested `not(not(P))` silently misparsed: outer `not(...)` is
recognised as negation, but the inner `not(banned(X))` was parsed
as a positive call to a relation called `not`. With no `not`
relation present, the inner match was empty, the outer negation
succeeded vacuously, and `vip(X) :- u(X), not(not(banned(X))).`
collapsed to `vip(X) :- u(X).` — a silent double-negation = identity
fallacy.

Fix in `dl-rule-check-safety`: the positive-literal branch and
`dl-process-neg!` both reject any body literal whose relation
name is in `dl-reserved-rel-names`. Error message names the
relation and points the user at stratified negation through an
intermediate relation.

1 regression test; conformance 260/260.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-11 07:41:49 +00:00
parent dcae125955
commit 285cd530eb
5 changed files with 70 additions and 19 deletions

View File

@@ -203,6 +203,24 @@
(dl-et-throws? (fn () (dl-program "is(N, +(1, 2)) :- p(N).")))
true)
;; Body literal with a reserved-name positive head is rejected.
;; The parser only treats outer-level `not(P)` as negation; nested
;; `not(not(P))` would otherwise silently parse as a positive call
;; to a relation named `not` and succeed vacuously. The safety
;; checker now flags this so the user gets a clear error.
;; Body literal with a reserved-name positive head is rejected.
;; The parser only treats outer-level `not(P)` as negation; nested
;; `not(not(P))` would otherwise silently parse as a positive call
;; to a relation named `not` and succeed vacuously — so the safety
;; checker now flags this to give the user a clear error.
(dl-et-test!
"nested not(not(...)) rejected"
(dl-et-throws?
(fn ()
(dl-program
"banned(a). u(a). vip(X) :- u(X), not(not(banned(X))).")))
true)
(dl-et-test!
"unsafe head var"
(dl-et-throws? (fn () (dl-program "p(X, Y) :- q(X).")))