1 ;;;; tests related to setf
3 ;;;; This file is impure because we want to be able to use DEFUN.
5 ;;;; This software is part of the SBCL system. See the README file for
8 ;;;; While most of SBCL is derived from the CMU CL system, the test
9 ;;;; files (like this one) were written from scratch after the fork
12 ;;;; This software is in the public domain and is provided with
13 ;;;; absolutely no warranty. See the COPYING and CREDITS files for
14 ;;;; more information.
18 (load "compiler-test-util.lisp")
21 (defun (setf foo
) (bar)
24 ;;; Regression test for get-setf-expansion without explicit
25 ;;; environment object.
26 (assert (multiple-value-list (get-setf-expansion '(foo))))
28 ;;; Regression test for SHIFTF of values.
31 (shiftf (values (car x
) (car y
)) (values (car y
) (car x
)))
32 (assert (equal (list x y
) '((2) (1)))))
34 ;;; SETF of values with multiple-value place forms
35 (let ((a t
) (b t
) (c t
) (d t
))
36 (let ((list (multiple-value-list
37 (setf (values (values a b
) (values c d
)) (values 1 2 3 4)))))
38 (assert (equal list
'(1 2)))
44 ;;; SETF of THE with VALUES.
46 (setf (the (values fixnum fixnum
) (values x y
))
51 ;;; SETF of MACRO-FUNCTION must accept a NIL environment
52 (let ((fun (constantly 'ok
)))
53 (setf (macro-function 'nothing-at-all nil
) fun
)
54 (assert (eq fun
(macro-function 'nothing-at-all nil
))))
57 ;;; DEFSETF accepts &ENVIRONMENT but not &AUX
58 (defsetf test-defsetf-env-1
(&environment env
) (new)
59 ;; Note: we're not trying to ignore NEW, we're trying to ignore
60 ;; the variable that SETF binds whose name is in NEW.
61 (if (macro-function 'defsetf-env-trick env
)
63 `(progn ,new
:global
)))
65 (defsetf test-defsetf-env-2
(local global
&environment env
) (new)
66 ;; As above, NEW values are generally not supposed to be ignored.
67 (if (macro-function 'defsetf-env-trick env
)
69 `(progn ,new
,global
)))
71 ;; Returning an atom is not illegal, though is strange.
72 (defsetf test-defsetf-trick-3
() (new) new
'bork
)
73 (with-test (:name
:setf-expander-returns-atom
)
74 ;; Simply don't crash in SETF and we're good.
75 (macroexpand-1 '(setf (test-defsetf-trick-3) 'a
)))
77 (assert (eq :local
(macrolet ((defsetf-env-trick ()))
78 (setf (test-defsetf-env-1) 13))))
80 (assert (eq :global
(setf (test-defsetf-env-1) 13)))
82 (assert (eq :local
(macrolet ((defsetf-env-trick ()))
83 (setf (test-defsetf-env-2 :local
:oops
) 13))))
85 (assert (eq :global
(setf (test-defsetf-env-2 :oops
:global
) 13)))
89 (eval '(defsetf test-defsetf-aux
(&aux aux
) (new) nil
))
93 (handler-bind ((style-warning #'error
))
94 (compile nil
'(lambda ()
95 (defsetf test-defsetf-no-env
(foo) (new)
96 `(set-foo ,foo
,new
))))
97 (compile nil
'(lambda ()
98 (defsetf test-defsetf-ignore-env
(foo &environment env
) (new)
99 (declare (ignore env
))
100 `(set-foo ,foo
,new
)))))
102 ;;; Not required by the spec, but allowes compiler-macros for SETF-functiosn
103 ;;; to see their constant argument forms.
104 (with-test (:name
:constantp-aware-get-setf-expansion
)
105 (multiple-value-bind (temps values stores set get
)
106 (get-setf-expansion '(foo 1 2 3))
108 (assert (not values
))
109 (assert (equal `(funcall #'(setf foo
) ,@stores
1 2 3) set
))
110 (assert (equal '(foo 1 2 3) get
))))
112 (with-test (:name
:update-fn-should-be-a-symbol-in-defsetf
)
115 (eval '(defsetf access-fn
5))
119 (with-test (:name
:getf-unused-default-variable
)
120 (handler-bind ((style-warning #'error
))
121 (compile nil
`(lambda (x y
)
122 (setf (gethash :x x
0) 4)
123 (setf (getf y
:y
0) 4)
124 (setf (get 'z
:z
0) 4)))))
126 (with-test (:name
:setf-fun-and-macro-full-warn
)
127 ;; make the compiler assume existence of #'(setf shoe-color)
128 (handler-bind ((warning #'muffle-warning
))
129 (compile nil
'(lambda (x) (setf (shoe-color x
) 'cordovan
))))
130 ;; now we get a full warning because the macro was seen too late.
131 (assert (typep (handler-case (eval '(defsetf shoe-color set-shoe-color
))
133 '(and warning
(not style-warning
)))))
135 (with-test (:name
:setf-fun-and-macro-style-1
)
136 (eval '(defun (setf shoe-size
) (new x
) x new
))
137 ;; unlike above, this is merely a style-warning
138 (assert (typep (handler-case (eval '(defsetf shoe-size set-shoe-size
))
142 ;; This is a test of the compiler, but it belongs with the above.
143 ;; FIXME: does this need to go through COMPILE-FILE, or will COMPILE work?
144 (defvar *tmpfile
* "setf-tmp.lisp")
145 (with-test (:name
:setf-fun-and-macro-style-2
)
148 ;; verify the test's precondition, for sanity
149 (assert (not (fboundp '(setf shoe-count
))))
150 (with-open-file (f *tmpfile
* :direction
:output
151 :if-exists
:supersede
)
152 (prin1 '(defun (setf shoe-count
) (new x
) (print x
) new
) f
)
153 (prin1 '(defsetf shoe-count set-shoe-count
) f
))
154 ;; Expect a warning because the compiler knows about
155 ;; (SETF SHOE-COUNT), which isn't yet FBOUNDP,
156 ;; and then we also compile a SETF inverse.
157 (multiple-value-bind (output warnings-p failure-p
)
158 (compile-file *tmpfile
*)
159 (ignore-errors (delete-file output
))
160 (assert (and output warnings-p
(not failure-p
)))))
161 (ignore-errors (delete-file *tmpfile
*))))
163 ;; The expander for (CADAR x) should behave as (CAR (CDAR x)) etc.
164 ;; This mainly affects read/modify/write semantics.
165 (with-test (:name
:car
+cdr-compositions-lp1450968
)
166 (flet ((maketree (n &aux
(count -
1))
167 (labels ((recurse (n)
170 (cons (recurse (1- n
)) (recurse (1- n
))))))
173 for n-ops from
2 to
4
174 do
(dotimes (bitmask (ash 1 n-ops
))
175 (let* ((ops (coerce (loop for i below n-ops
176 collect
(if (logbitp i bitmask
) #\D
#\A
))
178 (accessor (sb-int:symbolicate
"C" ops
"R"))
179 (tree (maketree n-ops
))
182 (assert (eql (funcall accessor tree
) bitmask
))
183 (let ((f (compile nil
185 (incf (,accessor obj
)
186 (progn (rplaca obj nil
)
190 (let ((tree* (cons left right
)))
191 (assert (eql (funcall accessor tree
*)
192 (+ bitmask
1000))))))))))
194 (define-symbol-macro %foofy1%
(values a b c
))
195 (define-symbol-macro %foofy2%
(values x y z
))
196 ;; PSETF and PSETQ eliminate vacuous LET* forms.
197 (with-test (:name
:psetf-expansion-maximally-concise
)
198 (dolist (op '(psetq psetf
))
199 (let* ((form `(,op %foofy1%
(f) %foofy2%
(g)))
200 (expansion (let ((*gensym-counter
* 1)) (macroexpand-1 form
)))
201 (expect '(multiple-value-bind (new1 new2 new3
) (f)
202 (multiple-value-bind (new4 new5 new6
) (g)
203 (setq a new1
) (setq b new2
) (setq c new3
)
204 (setq x new4
) (setq y new5
) (setq z new6
)
206 (assert (equal-mod-gensyms expansion expect
)))))
208 (with-test (:name
:defsetf-syntax-errors
)
209 (dolist (test '((defsetf foo set-foo junk other-junk
) ; would accept
210 (defsetf foo set-foo . junk
))) ; would crash
211 (assert (search "Ill-formed DEFSETF"
212 (simple-condition-format-control
213 (nth-value 1 (ignore-errors (macroexpand-1 test
)))))))
214 ;; no (SETF (SETF f)) methods
215 (assert-error (macroexpand-1 '(defsetf (setf foo
) set-setf-foo
))))
217 (defmacro mymacro
() '*x
*)
218 (define-symbol-macro foox
(car *x
*))
219 (with-test (:name
:setf-of-symbol-macro
)
220 (assert (equal (macroexpand-1 '(setf foox
3)) '(sb-kernel:%rplaca
*x
* 3))))
221 (with-test (:name
:setf-of-macro
)
222 (assert (equal (macroexpand-1 '(setf (mymacro) 3)) '(setq *x
* 3))))
224 (defvar *x
* (list 1))
226 (declare (type (integer 1 20) foox
))
228 (with-test (:name
:setf-of-symbol-macro-typecheck
)
229 ;; this was not broken, but since I've deleted the comment
230 ;; "FIXME: [Free] type declaration. -- APD, 2002-01-26"
231 ;; from ir1-translators, it's probably worth a test
232 ;; since at some point it must not have worked as intended.
233 (assert-error (set-foox 99)))
235 (declaim (special *foo-array
*))
236 ;; When dealing with symbol-macros, compiled SETQ would locate the leaf
237 ;; for the symbol and then covertly stuff in the expansion to a SETF form.
238 ;; *MACROEXPAND-HOOK* would see the SETF but not the expansion
239 ;; of the symbol, except those expansions occurring with GET-SETF-EXPANSION.
240 ;; Now it can see the first-round expansion too.
241 ;; The macroexpand hook for this test needs to be compiled, but you can't
242 ;; pass a quoted lambda (as a sexpr) to COMPILE because it needs to
243 ;; capture EXPANSIONS, but you can't pass an function-quoted lambda
244 ;; because WITH-TEST creates a too-complex environment for conversion
245 ;; from an interpreted lambda.
246 (with-test (:name
:compiled-setq-macroexpand-hook
:skipped-on
:interpreter
)
247 (sb-int:collect
((expansions))
248 (let ((*macroexpand-hook
*
249 (lambda (expander form env
)
250 (let ((new (funcall expander form env
)))
251 (when (or (symbolp form
) (eq (car form
) 'setf
))
252 (expansions (list form new
)))
254 (compile nil
'(lambda (x)
255 (symbol-macrolet ((ref-it (aref a
0))
259 (assert (equal-mod-gensyms
265 (let* ((a0 a
) (new1 x
))
266 (funcall #'(setf aref
) new1 a0
0))))))))
268 (with-test (:name
:remf-basic-correctness
)
269 (flet ((try (indicator input expect
)
270 (handler-case (sb-impl::%remf indicator
(copy-list input
))
271 (error () (assert (eq expect
:error
)))
272 (:no-error
(newval flag
)
273 (assert (and (equal newval expect
)
274 (eq flag
(not (equal newval input
)))))
275 (let* ((foo (vector (copy-list input
)))
276 (removedp (remf (aref foo
0) indicator
)))
277 (assert (equal (aref foo
0) expect
))
278 (assert (eq removedp
(not (equal input expect
)))))))))
282 (try 'x
'(a . b
) :error
)
283 (try 'x
'(a b . c
) :error
)
284 ;; indicator not found
285 (try 'weazel
'(a b
:foo
:goo
) '(a b
:foo
:goo
))
286 (try 'weazel
'(a b
:foo
:goo .
3) :error
) ; improper
287 (try 'weazel
'(a b
:foo
:goo baz
) :error
) ; odd length
288 ;; pair deleted from head
289 (try 'a
'(a b
:foo
:goo a
3) '(:foo
:goo a
3))
290 (try 'a
'(a b
:foo
:goo
) '(:foo
:goo
))
291 (try 'a
'(a b
:foo
) '(:foo
)) ; odd length unnoticed
292 (try 'a
'(a b .
:foo
) :error
) ; but won't return a non-list
293 ;; pair deleted from interior
294 (try :foo
'(a b
:foo
:goo
) '(a b
))
295 (try :foo
'(a b
:foo
:goo
:boo
) '(a b
:boo
)) ; odd length unnoticed
296 (try :foo
'(a b
:foo
:goo
:foo
) '(a b
:foo
)) ; other :FOO is irrelevant
297 (try :foo
'(a b
:foo
:goo . bad
) :error
)
300 (with-test (:name
:incf-argument-eval-order
)
302 (flet ((double-it () (setq testvar
(* 2 testvar
))))
303 (incf testvar
(double-it)))
304 ;; testvar should be 4, not 3, because the read for INCF
305 ;; occurs after doubling.
306 (assert (eql testvar
4))))
308 ;; Simple DEFSETF test
309 (with-test (:name
:defsetf-subseq-constant-indices
)
310 (assert (equal-mod-gensyms
311 (macroexpand-1 '(setf (subseq (foo) 4 6) "Hi"))
312 '(let* ((subform (foo)) (newval "Hi"))
313 (replace subform newval
:start1
4 :end1
6)
316 (with-test (:name
:defsetf-gethash
)
317 (assert (equal-mod-gensyms
318 (macroexpand-1 '(push 1 (gethash :k tbl
'(none))))
319 ;; the only temp var should be for TBL
320 '(let* ((#1=#:hashtable tbl
))
321 (sb-kernel:%puthash
:k
#1# (cons 1 (gethash :k
#1# '(none))))))))
323 ;; Setup for CLHS hairy example (not working)
324 (defvar *xy
* (make-array '(10 10)))
325 (defun xy (&key
((x x
) 0) ((y y
) 0)) (aref *xy
* x y
))
326 (defun set-xy (new-value &key
((x x
) 0) ((y y
) 0))
327 (setf (aref *xy
* x y
) new-value
))
328 (defsetf xy
(&key
((x x
) 0) ((y y
) 0)) (store)
329 `(set-xy ,store
'x
,x
'y
,y
))
332 (with-test (:name
:setf-of-apply-aref
)
334 (array (make-array '(3 2 7)))
335 (indices (list 0 0 0)))
336 (flet ((bump-index ()
337 (let ((i (1- (length indices
))))
338 (loop (cond ((< (nth i indices
) (1- (array-dimension array i
)))
339 (return (incf (nth i indices
))))
340 ((= i
0) (return nil
))
341 (t (setf (nth i indices
) 0) (decf i
)))))))
342 (loop (setf (apply #'aref array indices
) (incf n
))
343 (unless (bump-index) (return)))
345 #3A
(((1 2 3 4 5 6 7) (8 9 10 11 12 13 14))
346 ((15 16 17 18 19 20 21) (22 23 24 25 26 27 28))
347 ((29 30 31 32 33 34 35) (36 37 38 39 40 41 42)))
350 (define-modify-macro append2
+ (a b
&optional c
&rest more
) append
351 "append at least two more lists onto the place")
352 (define-modify-macro other-incf
(&optional
(delta 1)) +)
354 (with-test (:name
:define-modify-macro-arg-eval-order
)
355 ;; Uses a bunch of temps
356 (assert (equal-mod-gensyms
357 (macroexpand-1 '(append2+ (car x
) (f) (g) (h) (i) (j)))
358 '(let* ((x1 x
) (a (f)) (b (g)) (c (h)) (g3 (i)) (g4 (j)))
359 (sb-kernel:%rplaca x1
(append (car x1
) a b c g3 g4
)))))
361 ;; Calling OTHER-INCF with the default delta of 1 uses no temps.
362 (assert (equal (macroexpand '(other-incf *foo-base
*))
363 '(setq *foo-base
* (+ *foo-base
* 1))))
364 ;; Otherwise, it uses a temp because it "doesn't know" that + commutes.
365 (assert (equal-mod-gensyms (macroexpand '(other-incf b
(ff)))
366 '(let* ((delta (ff))) (setq b
(+ b delta
)))))
367 ;; And the following result should be identical to that of ordinary INCF.
369 (flet ((double-it () (setq testvar
(* 2 testvar
))))
370 (other-incf testvar
(double-it)))
371 (assert (eql testvar
4))))
373 (with-test (:name
:incf-avoid-temp-vars
)
374 (assert (equal (macrolet ((x () 'y
)
375 (try (&environment env
)
376 (list 'quote
(macroexpand-1 '(incf (x)) env
))))
380 (with-test (:name
:push-getf-avoid-temp-vars
)
381 ;; Not only should subforms of PLACE avoid binding temp vars for constants,
382 ;; so should the arguments to GETF and %PUTF.
383 ;; This reads (AREF A) twice but I think that's unavoidable
384 (assert (equal-mod-gensyms
385 (macroexpand-1 '(push 'foo
(getf (aref a
(x) 1) :my-indicator
'(t))))
389 (cons 'foo
(getf (aref a642 g643
1) :my-indicator
'(t)))))
391 (sb-impl::%putf
(aref a642 g643
1) :my-indicator new645
)))
392 (funcall #'(setf aref
) new644 a642 g643
1)
395 (defparameter *foobar-list
* (list 1 2 3))
396 (defun my-foobar-list () *foobar-list
*)
397 (defun (setf my-foobar-list
) (newval)
398 (incf (car *foobar-list
*))
399 (setq *foobar-list
* newval
))
400 (with-test (:name
:pop-eval-order-bug-1454021
)
401 ;; Assert that POP reads CAR of the list before invoking the setter
402 (assert (eq (pop (my-foobar-list)) 1)))
405 (with-test (:name
:pushnew-evals-keyword-args
)
406 ;; Though not directly supported by an example in CLHS,
407 ;; convention seems to dictate that :KEY, :TEST, :TEST-NOT are not
408 ;; parsed by DS-BIND, but instead are blindly forwarded to ADJOIN.
409 (let ((k :test
) (v #'equal
) (list nil
))
410 (pushnew '(hi) list k v
)
411 (assert (equal list
'((hi))))))
413 (with-test (:name
:setf-ldb-syntax
)
414 ;; this gets both a warning and an error.
415 (assert-error (let ((x 0)) (setf (ldb (byte 4 2 3) x
) 1))))
417 (with-test (:name
:setf-ldb-recognize-local-macros
)
418 ;; This lambda should call neither %LDB nor %DPB
419 (assert (not (ctu:find-named-callees
422 (declare (type (cons) x
))
423 (symbol-macrolet ((b (byte 4 3)))
424 (incf (ldb b
(truly-the fixnum
(car x
)))))))))))
426 ;; There's aren't a lot of reasonable uses of the setf "getter" for LOGBITP.
427 ;; It might come in handy for SHIFTF or ROTATEF, or this:
428 (define-modify-macro negatef
() not
)
429 (with-test (:name
:modify-macro-logbitp
)
431 (let ((foo (list 0)))
432 ;; To be extra tricky, flip the Ith bit in a 9-bit subfield
433 ;; starting at 2. This should have no effect for I >= 9.
434 (negatef (logbitp i
(ldb (byte 9 2) (car foo
))))
436 (assert (= (car foo
) (ash 1 (+ i
2))))
437 (assert (= (car foo
) 0))))))
439 ;; a DEFSETF lambda list is not a macro lambda-list
440 (with-test (:name
:defsetf-lambda-list-strictness
)
442 ;; All implementations agree that this is malformed.
443 (macroexpand-1 '(defsetf baz
(a . other-stuff
) (v) ''who-cares
))))
446 (with-test (:name
:shiftf-let
*)
447 (define-setf-expander shiftf-let
* ()
448 (let ((a (gensym "A"))
450 (store (gensym "STORE")))
455 (list 'list a b store
)
457 (assert (eql (funcall (compile nil
`(lambda () (shiftf (shiftf-let*) 21)))) 11)))