From b82477dac3a532ae865aebf0307a5e7b31d76731 Mon Sep 17 00:00:00 2001 From: Yogthos Date: Fri, 5 Jun 2026 13:37:27 -0400 Subject: [PATCH] feat(strictness): peek/pop/vec/key/val reject malformed args - peek/pop are stack-only (vectors/lists): throw on sets/maps/strings/scalars, and pop throws on an empty vector/list - vec throws on non-seqable args (numbers/keywords/transients) - key/val require a map entry (2-element vector); throw on nil/numbers/maps/sets pop clean; peek 9/2; vec 17/2/1; key/val 12/2/1 (remaining are the 2-vector-vs-MapEntry / tuple-from-seq divergences). pass 3824->3840. spec: seq/accessor-strictness (16). jpm test green. --- src/jolt/core.janet | 29 +++++++++++-------- .../integration/clojure-test-suite-test.janet | 2 +- test/spec/sequences-spec.janet | 20 +++++++++++++ 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/jolt/core.janet b/src/jolt/core.janet index 0c1947a..eda07ba 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -557,6 +557,8 @@ coll)) (defn core-vec [coll] + (when (not (or (nil? coll) (core-coll? coll) (string? coll))) + (error (string "Don't know how to create a vector from " (type coll)))) (let [coll (realize-for-iteration coll)] (cond (array? coll) (make-vec coll) @@ -1241,25 +1243,25 @@ (indexed? m) (do (var i 0) (each x m (set acc (f acc i x)) (++ i)))) acc) +# peek/pop are defined only on stacks (vectors -> last end, lists -> front); +# Clojure throws on sets/maps/seqs/strings/scalars. (defn core-peek [coll] (cond (nil? coll) nil - (lazy-seq? coll) (ls-first coll) (plist? coll) (if (pl-empty? coll) nil (pl-first coll)) # list: first (pvec? coll) (if (= 0 (pv-count coll)) nil (pv-nth coll (- (pv-count coll) 1))) # vector: last - (= 0 (length coll)) nil - (tuple? coll) (in coll (- (length coll) 1)) # vector: last - (array? coll) (in coll 0) # list: first - (in coll 0))) + (tuple? coll) (if (= 0 (length coll)) nil (in coll (- (length coll) 1))) # vector: last + (array? coll) (if (= 0 (length coll)) nil (in coll 0)) # list: first + (error (string "peek not supported on " (type coll))))) (defn core-pop [coll] (cond (nil? coll) nil - (plist? coll) (pl-rest coll) # list: drop first - (pvec? coll) (pv-pop coll) # vector: drop last - (tuple? coll) (tuple/slice coll 0 (- (length coll) 1)) # vector: drop last - (array? coll) (array/slice coll 1) # list: rest - coll)) + (plist? coll) (if (pl-empty? coll) (error "Can't pop empty list") (pl-rest coll)) + (pvec? coll) (if (= 0 (pv-count coll)) (error "Can't pop empty vector") (pv-pop coll)) + (tuple? coll) (if (= 0 (length coll)) (error "Can't pop empty vector") (tuple/slice coll 0 (- (length coll) 1))) + (array? coll) (if (= 0 (length coll)) (error "Can't pop empty list") (array/slice coll 1)) + (error (string "pop not supported on " (type coll))))) (defn core-subvec [v start &opt end] (let [a (vview v)] @@ -3155,8 +3157,11 @@ (tuple ;out)) # Map entries (represented as 2-element vectors) -(defn core-key [e] (core-nth e 0)) -(defn core-val [e] (core-nth e 1)) +# key/val require a map entry (a 2-element vector/tuple in Jolt); Clojure throws +# otherwise. (Jolt can't distinguish a 2-vector from a real MapEntry.) +(defn- entry-like? [x] (and (or (pvec? x) (tuple? x) (array? x)) (= 2 (core-count x)))) +(defn core-key [e] (if (entry-like? e) (core-nth e 0) (error "key requires a map entry"))) +(defn core-val [e] (if (entry-like? e) (core-nth e 1) (error "val requires a map entry"))) (defn core-map-entry? [x] (and (or (pvec? x) (tuple? x)) (= 2 (core-count x)))) (defn core-rand-nth [coll] diff --git a/test/integration/clojure-test-suite-test.janet b/test/integration/clojure-test-suite-test.janet index 11363c5..b6fde55 100644 --- a/test/integration/clojure-test-suite-test.janet +++ b/test/integration/clojure-test-suite-test.janet @@ -18,7 +18,7 @@ # Baseline: assertions Jolt currently passes across the suite. Raise as Jolt # improves so a regression (previously-passing assertion breaking) is caught. -(def baseline-pass 3820) +(def baseline-pass 3835) # A file is "clean" when it ran with zero failures AND zero errors. (def baseline-clean-files 45) # Per-file wall-clock budget (seconds). Normal files finish in well under 1s; diff --git a/test/spec/sequences-spec.janet b/test/spec/sequences-spec.janet index b82f2ba..4a3bc10 100644 --- a/test/spec/sequences-spec.janet +++ b/test/spec/sequences-spec.janet @@ -145,3 +145,23 @@ ["symbol from keyword" "\"x\"" "(name (symbol :x))"] ["keyword bad 2-arg" :throws "(keyword \"abc\" nil)"] ["keyword from symbol" "\"x\"" "(name (keyword 'x))"]) + +# Stack/accessor strictness: peek/pop are stack-only; vec needs a seqable; +# key/val need a map entry. +(defspec "seq / accessor strictness" + ["peek vector" "3" "(peek [1 2 3])"] + ["peek list" "1" "(peek '(1 2 3))"] + ["peek empty vec" "nil" "(peek [])"] + ["peek on set" :throws "(peek #{1 2})"] + ["peek on number" :throws "(peek 42)"] + ["pop empty vec" :throws "(pop [])"] + ["pop on number" :throws "(pop 0)"] + ["pop vector" "[1 2]" "(pop [1 2 3])"] + ["vec on number" :throws "(vec 42)"] + ["vec on keyword" :throws "(vec :a)"] + ["vec ok" "[1 2]" "(vec '(1 2))"] + ["key on nil" :throws "(key nil)"] + ["key on map" :throws "(key {})"] + ["val on number" :throws "(val 0)"] + ["key of entry" ":a" "(key (first {:a 1}))"] + ["val of entry" "1" "(val (first {:a 1}))"])