datalog: anonymous _ in negation is existential, not unbound
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
The canonical Datalog idiom for "no X has any Y": orphan(X) :- person(X), not(parent(X, _)). was rejected by the safety check with "negation refers to unbound variable(s) (\"_anon1\")". The parser renames each anonymous `_` to a fresh `_anon*` symbol so multiple `_` occurrences don't unify with each other, and the negation safety walk then demanded all free vars in the negated lit be bound by an earlier positive body lit — including the renamed anonymous vars. Anonymous vars in a negation are existentially quantified within the negation, not requirements from outside. Added dl-non-anon-vars to strip `_anon*` names from the `needed` set before the binding check in dl-process-neg!. Real vars (like `X` in the orphan idiom) still must be bound by an earlier positive body lit, just as before. 2 new regression tests (orphan idiom + multi-anon "solo" pattern); conformance 273/273. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -179,6 +179,27 @@
|
||||
vs)
|
||||
out))))
|
||||
|
||||
;; Filter a list of variable-name strings to exclude anonymous-renamed
|
||||
;; vars (`_` in source → `_anon*` by dl-rename-anon-term). Used by
|
||||
;; the negation safety check, where anonymous vars are existential
|
||||
;; within the negated literal.
|
||||
(define
|
||||
dl-non-anon-vars
|
||||
(fn
|
||||
(vs)
|
||||
(let
|
||||
((out (list)))
|
||||
(do
|
||||
(for-each
|
||||
(fn
|
||||
(v)
|
||||
(when
|
||||
(not (and (>= (len v) 5)
|
||||
(= (slice v 0 5) "_anon")))
|
||||
(append! out v)))
|
||||
vs)
|
||||
out))))
|
||||
|
||||
(define
|
||||
dl-rule-check-safety
|
||||
(fn
|
||||
@@ -282,8 +303,14 @@
|
||||
((and (list? inner) (> (len inner) 0))
|
||||
(dl-rel-name inner))
|
||||
(else nil)))
|
||||
(needed (dl-vars-of inner))
|
||||
(missing (dl-vars-not-in (dl-vars-of inner) bound)))
|
||||
;; Anonymous variables (`_` in source → `_anon*` after
|
||||
;; renaming) are existentially quantified within the
|
||||
;; negated literal — they don't need to be bound by
|
||||
;; an earlier body lit, since `not p(X, _)` is a
|
||||
;; valid idiom for "no Y exists s.t. p(X, Y)". Filter
|
||||
;; them out of the safety check.
|
||||
(needed (dl-non-anon-vars (dl-vars-of inner)))
|
||||
(missing (dl-vars-not-in needed bound)))
|
||||
(cond
|
||||
((and (not (nil? inner-rn)) (dl-reserved-rel? inner-rn))
|
||||
(set! err
|
||||
|
||||
Reference in New Issue
Block a user