;;;; tests for the code walker

;;;; This software is part of the SBCL system. See the README file for
;;;; more information.

;;;; This software is derived from software originally released by Xerox
;;;; Corporation. Copyright and release statements follow. Later modifications
;;;; to the software are in the public domain and are provided with
;;;; absolutely no warranty. See the COPYING and CREDITS files for more
;;;; information.

;;;; copyright information from original PCL sources:
;;;;
;;;; Copyright (c) 1985, 1986, 1987, 1988, 1989, 1990 Xerox Corporation.
;;;; All rights reserved.
;;;;
;;;; Use and copying of this software and preparation of derivative works based
;;;; upon this software are permitted. Any distribution of this software or
;;;; derivative works must comply with all applicable United States export
;;;; control laws.
;;;;
;;;; This software is made available AS IS, and Xerox Corporation makes no
;;;; warranty about the software, its performance or its conformity to any
;;;; specification.

(in-package :sb-walker)

;;;; utilities to support tests

;;; string equality modulo deletion of consecutive whitespace (as a crude way
;;; of washing away irrelevant differences in indentation)
(defun string-modulo-tabspace (s)
  (let ((s (string-trim '(#\Space) (substitute #\Space #\Newline
                                               (substitute #\Space #\Tab s)))))
    (loop (let ((p (search "  " s)))
            (if (not p) (return s))
            ;; Extremely inefficient but simple algorithm.
            (setq s (concatenate 'string (subseq s 0 p) (subseq s (1+ p))))))))

(defun string=-modulo-tabspace (x y)
  (string= (string-modulo-tabspace x)
           (string-modulo-tabspace y)))

;;;; tests based on stuff at the end of the original CMU CL
;;;; pcl/walk.lisp file

(defmacro take-it-out-for-a-test-walk (form)
  `(take-it-out-for-a-test-walk-1 ',form))

(defun take-it-out-for-a-test-walk-1 (form)
  (let ((copy-of-form (copy-tree form))
        (result (walk-form form nil
                  (lambda (x y env)
                    (format t "~&Form: ~S ~3T Context: ~A" x y)
                    (when (symbolp x)
                      (let ((lexical (var-lexical-p x env))
                            (special (var-special-p x env)))
                        (when lexical
                          (format t ";~3T")
                          (format t "lexically bound"))
                        (when special
                          (format t ";~3T")
                          (format t "declared special"))
                        (when (boundp x)
                          (format t ";~3T")
                          (format t "bound: ~S " (eval x)))))
                    x))))
    (cond ((not (equal result copy-of-form))
           (format t "~%Warning: Result not EQUAL to copy of start."))
          ((not (eq result form))
           (format t "~%Warning: Result not EQ to copy of start.")))
    (pprint result)
    nil))

(defmacro foo (&rest ignore)
  (declare (ignore ignore))
  ''global-foo)

(defmacro bar (&rest ignore)
  (declare (ignore ignore))
  ''global-bar)

(test-util:with-test (:name (:walk list))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (list arg1 arg2 arg3)))
           "Form: (LIST ARG1 ARG2 ARG3)   Context: EVAL
Form: ARG1   Context: EVAL
Form: ARG2   Context: EVAL
Form: ARG3   Context: EVAL
\(LIST ARG1 ARG2 ARG3)")))

(test-util:with-test (:name (:walk list cons))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (list (cons 1 2) (list 3 4 5))))
           "Form: (LIST (CONS 1 2) (LIST 3 4 5))   Context: EVAL
Form: (CONS 1 2)   Context: EVAL
Form: 1   Context: EVAL
Form: 2   Context: EVAL
Form: (LIST 3 4 5)   Context: EVAL
Form: 3   Context: EVAL
Form: 4   Context: EVAL
Form: 5   Context: EVAL
\(LIST (CONS 1 2) (LIST 3 4 5))")))

(test-util:with-test (:name (:walk progn 1))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (progn (foo) (bar 1))))
           "Form: (PROGN (FOO) (BAR 1))   Context: EVAL
Form: (FOO)   Context: EVAL
Form: 'GLOBAL-FOO   Context: EVAL
Form: (BAR 1)   Context: EVAL
Form: 'GLOBAL-BAR   Context: EVAL
\(PROGN (FOO) (BAR 1))")))

(test-util:with-test (:name (:walk block))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (block block-name a b c)))
           "Form: (BLOCK BLOCK-NAME A B C)   Context: EVAL
Form: A   Context: EVAL
Form: B   Context: EVAL
Form: C   Context: EVAL
\(BLOCK BLOCK-NAME A B C)")))

(test-util:with-test (:name (:walk block list))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (block block-name (list a) b c)))
           "Form: (BLOCK BLOCK-NAME (LIST A) B C)   Context: EVAL
Form: (LIST A)   Context: EVAL
Form: A   Context: EVAL
Form: B   Context: EVAL
Form: C   Context: EVAL
\(BLOCK BLOCK-NAME (LIST A) B C)")))

(test-util:with-test (:name (:walk catch list))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (catch catch-tag (list a) b c)))
           "Form: (CATCH CATCH-TAG (LIST A) B C)   Context: EVAL
Form: CATCH-TAG   Context: EVAL
Form: (LIST A)   Context: EVAL
Form: A   Context: EVAL
Form: B   Context: EVAL
Form: C   Context: EVAL
\(CATCH CATCH-TAG (LIST A) B C)")))

;;; This is a fairly simple MACROLET case. While walking the body of the
;;; macro, X should be lexically bound. In the body of the MACROLET form
;;; itself, X should not be bound.
(test-util:with-test (:name (:walk macrolet))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk
              (macrolet ((foo (x) (list x) ''inner))
                x
                (foo 1))))
           "Form: (MACROLET ((FOO (X)
                   (LIST X)
                   ''INNER))
        X
        (FOO 1))   Context: EVAL
Form: (LIST X)   Context: EVAL
Form: X   Context: EVAL; lexically bound
Form: ''INNER   Context: EVAL
Form: X   Context: EVAL
Form: (FOO 1)   Context: EVAL
Form: 'INNER   Context: EVAL
\(MACROLET ((FOO (X)
             (LIST X)
             ''INNER))
  X
  (FOO 1))")))

;;; The original PCL documentation for this test said
;;;   A slightly more complex MACROLET case. In the body of the macro
;;;   X should not be lexically bound. In the body of the macrolet
;;;   form itself X should be bound. Note that THIS CASE WILL CAUSE AN
;;;   ERROR when it tries to macroexpand the call to FOO.
;;;
;;; This test is commented out in SBCL because ANSI says, in the
;;; definition of the special operator MACROLET,
;;;    The macro-expansion functions defined by MACROLET are defined
;;;    in the lexical environment in which the MACROLET form appears.
;;;    Declarations and MACROLET and SYMBOL-MACROLET definitions affect
;;;    the local macro definitions in a MACROLET, but the consequences
;;;    are undefined if the local macro definitions reference any
;;;    local variable or function bindings that are visible in that
;;;    lexical environment.
;;; Since the behavior is undefined, anything we do conforms.:-|
;;; This is of course less than ideal; see bug 124.
#+nil
(multiple-value-bind (res cond)
    (ignore-errors
      (take-it-out-for-a-test-walk
       (let ((x 1))
         (macrolet ((foo () (list x) ''inner))
           x
           (foo)))))
  (assert (and (null res) cond)))

(test-util:with-test (:name (:walk flet 1))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk
              (flet ((foo (x) (list x y))
                     (bar (x) (list x y)))
                (foo 1))))
           "Form: (FLET ((FOO (X)
               (LIST X Y))
             (BAR (X)
               (LIST X Y)))
        (FOO 1))   Context: EVAL
Form: (LIST X Y)   Context: EVAL
Form: X   Context: EVAL; lexically bound
Form: Y   Context: EVAL
Form: (LIST X Y)   Context: EVAL
Form: X   Context: EVAL; lexically bound
Form: Y   Context: EVAL
Form: (FOO 1)   Context: EVAL
Form: 1   Context: EVAL
\(FLET ((FOO (X)
         (LIST X Y))
       (BAR (X)
         (LIST X Y)))
  (FOO 1))")))

(test-util:with-test (:name (:walk let flet))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk
              (let ((y 2))
                (flet ((foo (x) (list x y))
                       (bar (x) (list x y)))
                  (foo 1)))))
           "Form: (LET ((Y 2))
        (FLET ((FOO (X)
                 (LIST X Y))
               (BAR (X)
                 (LIST X Y)))
          (FOO 1)))   Context: EVAL
Form: 2   Context: EVAL
Form: (FLET ((FOO (X)
               (LIST X Y))
             (BAR (X)
               (LIST X Y)))
        (FOO 1))   Context: EVAL
Form: (LIST X Y)   Context: EVAL
Form: X   Context: EVAL; lexically bound
Form: Y   Context: EVAL; lexically bound
Form: (LIST X Y)   Context: EVAL
Form: X   Context: EVAL; lexically bound
Form: Y   Context: EVAL; lexically bound
Form: (FOO 1)   Context: EVAL
Form: 1   Context: EVAL
\(LET ((Y 2))
  (FLET ((FOO (X)
           (LIST X Y))
         (BAR (X)
           (LIST X Y)))
    (FOO 1)))")))

(test-util:with-test (:name (:walk labels))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk
              (labels ((foo (x) (bar x))
                       (bar (x) (foo x)))
                (foo 1))))
           "Form: (LABELS ((FOO (X)
                 (BAR X))
               (BAR (X)
                 (FOO X)))
        (FOO 1))   Context: EVAL
Form: (BAR X)   Context: EVAL
Form: X   Context: EVAL; lexically bound
Form: (FOO X)   Context: EVAL
Form: X   Context: EVAL; lexically bound
Form: (FOO 1)   Context: EVAL
Form: 1   Context: EVAL
\(LABELS ((FOO (X)
           (BAR X))
         (BAR (X)
           (FOO X)))
  (FOO 1))")))

(test-util:with-test (:name (:walk flet 2))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk
              (flet ((foo (x) (foo x)))
                (foo 1))))
           "Form: (FLET ((FOO (X)
               (FOO X)))
        (FOO 1))   Context: EVAL
Form: (FOO X)   Context: EVAL
Form: 'GLOBAL-FOO   Context: EVAL
Form: (FOO 1)   Context: EVAL
Form: 1   Context: EVAL
\(FLET ((FOO (X)
         (FOO X)))
  (FOO 1))")))

(test-util:with-test (:name (:walk flet 3))
  (assert (string=-modulo-tabspace
         (with-output-to-string (*standard-output*)
           (take-it-out-for-a-test-walk
            (flet ((foo (x) (foo x)))
              (flet ((bar (x) (foo x)))
                (bar 1)))))
         "Form: (FLET ((FOO (X)
               (FOO X)))
        (FLET ((BAR (X)
                 (FOO X)))
          (BAR 1)))   Context: EVAL
Form: (FOO X)   Context: EVAL
Form: 'GLOBAL-FOO   Context: EVAL
Form: (FLET ((BAR (X)
               (FOO X)))
        (BAR 1))   Context: EVAL
Form: (FOO X)   Context: EVAL
Form: X   Context: EVAL; lexically bound
Form: (BAR 1)   Context: EVAL
Form: 1   Context: EVAL
\(FLET ((FOO (X)
         (FOO X)))
  (FLET ((BAR (X)
           (FOO X)))
    (BAR 1)))")))

(test-util:with-test (:name (:walk progn special))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (prog () (declare (special a b)))))
           "Form: (PROG () (DECLARE (SPECIAL A B)))   Context: EVAL
Form: (BLOCK NIL
        (LET ()
          (DECLARE (SPECIAL A B))
          (TAGBODY)))   Context: EVAL
Form: (LET ()
        (DECLARE (SPECIAL A B))
        (TAGBODY))   Context: EVAL
Form: (TAGBODY)   Context: EVAL
\(PROG () (DECLARE (SPECIAL A B)))")))

(test-util:with-test (:name (:walk let special 1))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (let (a b c)
                                            (declare (special a b))
                                            (foo a) b c)))
           "Form: (LET (A B C)
        (DECLARE (SPECIAL A B))
        (FOO A)
        B
        C)   Context: EVAL
Form: (FOO A)   Context: EVAL
Form: 'GLOBAL-FOO   Context: EVAL
Form: B   Context: EVAL; lexically bound; declared special
Form: C   Context: EVAL; lexically bound
\(LET (A B C)
  (DECLARE (SPECIAL A B))
  (FOO A)
  B
  C)")))

(test-util:with-test (:name (:walk let special 2))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (let (a b c)
                                            (declare (special a) (special b))
                                            (foo a) b c)))
           "Form: (LET (A B C)
        (DECLARE (SPECIAL A) (SPECIAL B))
        (FOO A)
        B
        C)   Context: EVAL
Form: (FOO A)   Context: EVAL
Form: 'GLOBAL-FOO   Context: EVAL
Form: B   Context: EVAL; lexically bound; declared special
Form: C   Context: EVAL; lexically bound
\(LET (A B C)
  (DECLARE (SPECIAL A) (SPECIAL B))
  (FOO A)
  B
  C)")))

(test-util:with-test (:name (:walk let special 3))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (let (a b c)
                                            (declare (special a))
                                            (declare (special b))
                                            (foo a) b c)))
           "Form: (LET (A B C)
        (DECLARE (SPECIAL A))
        (DECLARE (SPECIAL B))
        (FOO A)
        B
        C)   Context: EVAL
Form: (FOO A)   Context: EVAL
Form: 'GLOBAL-FOO   Context: EVAL
Form: B   Context: EVAL; lexically bound; declared special
Form: C   Context: EVAL; lexically bound
\(LET (A B C)
  (DECLARE (SPECIAL A))
  (DECLARE (SPECIAL B))
  (FOO A)
  B
  C)")))

(test-util:with-test (:name (:walk let special 4))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (let (a b c)
                                            (declare (special a))
                                            (declare (special b))
                                            (let ((a 1))
                                              (foo a) b c))))
           "Form: (LET (A B C)
        (DECLARE (SPECIAL A))
        (DECLARE (SPECIAL B))
        (LET ((A 1))
          (FOO A)
          B
          C))   Context: EVAL
Form: (LET ((A 1))
        (FOO A)
        B
        C)   Context: EVAL
Form: 1   Context: EVAL
Form: (FOO A)   Context: EVAL
Form: 'GLOBAL-FOO   Context: EVAL
Form: B   Context: EVAL; lexically bound; declared special
Form: C   Context: EVAL; lexically bound
\(LET (A B C)
  (DECLARE (SPECIAL A))
  (DECLARE (SPECIAL B))
  (LET ((A 1))
    (FOO A)
    B
    C))")))

(test-util:with-test (:name (:walk eval-when 1))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (eval-when ()
                                            a
                                            (foo a))))
           "Form: (EVAL-WHEN NIL A (FOO A))   Context: EVAL
Form: A   Context: EVAL
Form: (FOO A)   Context: EVAL
Form: 'GLOBAL-FOO   Context: EVAL
\(EVAL-WHEN NIL A (FOO A))")))

(test-util:with-test (:name (:walk eval-when 2))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk
              (eval-when (:execute :compile-toplevel :load-toplevel)
                a
                (foo a))))
         "Form: (EVAL-WHEN (:EXECUTE :COMPILE-TOPLEVEL :LOAD-TOPLEVEL) A (FOO A))   Context: EVAL
Form: A   Context: EVAL
Form: (FOO A)   Context: EVAL
Form: 'GLOBAL-FOO   Context: EVAL
\(EVAL-WHEN (:EXECUTE :COMPILE-TOPLEVEL :LOAD-TOPLEVEL) A (FOO A))")))

(test-util:with-test (:name (:walk progn function))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (progn (function foo))))
           "Form: (PROGN #'FOO)   Context: EVAL
Form: #'FOO   Context: EVAL
\(PROGN #'FOO)")))

(test-util:with-test (:name (:walk progn go))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (progn a b (go a))))
         "Form: (PROGN A B (GO A))   Context: EVAL
Form: A   Context: EVAL
Form: B   Context: EVAL
Form: (GO A)   Context: EVAL
\(PROGN A B (GO A))")))

(test-util:with-test (:name (:walk if 1))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (if a b c)))
           "Form: (IF A B C)   Context: EVAL
Form: A   Context: EVAL
Form: B   Context: EVAL
Form: C   Context: EVAL
\(IF A B C)")))

(test-util:with-test (:name (:walk if 2))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (if a b)))
           "Form: (IF A B)   Context: EVAL
Form: A   Context: EVAL
Form: B   Context: EVAL
Form: NIL   Context: EVAL; bound: NIL
\(IF A B)")))

(test-util:with-test (:name (:walk lambda))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk ((lambda (a b) (list a b)) 1 2)))
           "Form: ((LAMBDA (A B) (LIST A B)) 1 2)   Context: EVAL
Form: (LAMBDA (A B) (LIST A B))   Context: EVAL
Form: (LIST A B)   Context: EVAL
Form: A   Context: EVAL; lexically bound
Form: B   Context: EVAL; lexically bound
Form: 1   Context: EVAL
Form: 2   Context: EVAL
\((LAMBDA (A B) (LIST A B)) 1 2)")))

(test-util:with-test (:name (:walk lambda special))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk ((lambda (a b)
                                             (declare (special a))
                                             (list a b))
                                           1 2)))
           "Form: ((LAMBDA (A B) (DECLARE (SPECIAL A)) (LIST A B)) 1 2)   Context: EVAL
Form: (LAMBDA (A B) (DECLARE (SPECIAL A)) (LIST A B))   Context: EVAL
Form: (LIST A B)   Context: EVAL
Form: A   Context: EVAL; lexically bound; declared special
Form: B   Context: EVAL; lexically bound
Form: 1   Context: EVAL
Form: 2   Context: EVAL
\((LAMBDA (A B) (DECLARE (SPECIAL A)) (LIST A B)) 1 2)")))

(test-util:with-test (:name (:walk let list))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (let ((a a) (b a) (c b))
                                            (list a b c))))
           "Form: (LET ((A A) (B A) (C B))
        (LIST A B C))   Context: EVAL
Form: A   Context: EVAL
Form: A   Context: EVAL
Form: B   Context: EVAL
Form: (LIST A B C)   Context: EVAL
Form: A   Context: EVAL; lexically bound
Form: B   Context: EVAL; lexically bound
Form: C   Context: EVAL; lexically bound
\(LET ((A A) (B A) (C B))
  (LIST A B C))")))

(test-util:with-test (:name (:walk let* list))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (let* ((a a) (b a) (c b)) (list a b c))))
           "Form: (LET* ((A A) (B A) (C B))
        (LIST A B C))   Context: EVAL
Form: A   Context: EVAL
Form: A   Context: EVAL; lexically bound
Form: B   Context: EVAL; lexically bound
Form: (LIST A B C)   Context: EVAL
Form: A   Context: EVAL; lexically bound
Form: B   Context: EVAL; lexically bound
Form: C   Context: EVAL; lexically bound
\(LET* ((A A) (B A) (C B))
  (LIST A B C))")))

(test-util:with-test (:name (:walk let special list))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (let ((a a) (b a) (c b))
                                            (declare (special a b))
                                            (list a b c))))
           "Form: (LET ((A A) (B A) (C B))
        (DECLARE (SPECIAL A B))
        (LIST A B C))   Context: EVAL
Form: A   Context: EVAL
Form: A   Context: EVAL
Form: B   Context: EVAL
Form: (LIST A B C)   Context: EVAL
Form: A   Context: EVAL; lexically bound; declared special
Form: B   Context: EVAL; lexically bound; declared special
Form: C   Context: EVAL; lexically bound
\(LET ((A A) (B A) (C B))
  (DECLARE (SPECIAL A B))
  (LIST A B C))")))

;;;; Bug in LET* walking!
(test-util:with-test (:name (:walk let* special list :hairy-specials))
  (assert
   (string=-modulo-tabspace
    (with-output-to-string (*standard-output*)
      (take-it-out-for-a-test-walk (let* ((a a) (b a) (c b))
                                     (declare (special a b))
                                     (list a b c))))
    "Form: (LET* ((A A) (B A) (C B))
             (DECLARE (SPECIAL A B))
             (LIST A B C))   Context: EVAL
     Form: A   Context: EVAL
     Form: A   Context: EVAL; lexically bound; declared special
     Form: B   Context: EVAL; lexically bound; declared special
     Form: (LIST A B C)   Context: EVAL
     Form: A   Context: EVAL; lexically bound; declared special
     Form: B   Context: EVAL; lexically bound; declared special
     Form: C   Context: EVAL; lexically bound
     (LET* ((A A) (B A) (C B))
       (DECLARE (SPECIAL A B))
       (LIST A B C))")))

(test-util:with-test (:name (:walk let special 5))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (let ((a 1) (b 2))
                                            (foo bar)
                                            (let ()
                                              (declare (special a))
                                              (foo a b)))))
           "Form: (LET ((A 1) (B 2))
        (FOO BAR)
        (LET ()
          (DECLARE (SPECIAL A))
          (FOO A B)))   Context: EVAL
Form: 1   Context: EVAL
Form: 2   Context: EVAL
Form: (FOO BAR)   Context: EVAL
Form: 'GLOBAL-FOO   Context: EVAL
Form: (LET ()
        (DECLARE (SPECIAL A))
        (FOO A B))   Context: EVAL
Form: (FOO A B)   Context: EVAL
Form: 'GLOBAL-FOO   Context: EVAL
\(LET ((A 1) (B 2))
  (FOO BAR)
  (LET ()
    (DECLARE (SPECIAL A))
    (FOO A B)))")))

(test-util:with-test (:name (:walk multiple-value-call))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (multiple-value-call #'foo a b c)))
           "Form: (MULTIPLE-VALUE-CALL #'FOO A B C)   Context: EVAL
Form: #'FOO   Context: EVAL
Form: A   Context: EVAL
Form: B   Context: EVAL
Form: C   Context: EVAL
\(MULTIPLE-VALUE-CALL #'FOO A B C)")))

(test-util:with-test (:name (:walk multiple-value-prog1))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (multiple-value-prog1 a b c)))
         "Form: (MULTIPLE-VALUE-PROG1 A B C)   Context: EVAL
Form: A   Context: EVAL
Form: B   Context: EVAL
Form: C   Context: EVAL
\(MULTIPLE-VALUE-PROG1 A B C)")))

(test-util:with-test (:name (:walk progn 2))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (progn a b c)))
         "Form: (PROGN A B C)   Context: EVAL
Form: A   Context: EVAL
Form: B   Context: EVAL
Form: C   Context: EVAL
\(PROGN A B C)")))

(test-util:with-test (:name (:walk progv))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (progv vars vals a b c)))
         "Form: (PROGV VARS VALS A B C)   Context: EVAL
Form: VARS   Context: EVAL
Form: VALS   Context: EVAL
Form: A   Context: EVAL
Form: B   Context: EVAL
Form: C   Context: EVAL
\(PROGV VARS VALS A B C)")))

(test-util:with-test (:name (:walk quote))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (quote a)))
         "Form: 'A   Context: EVAL
'A")))

(test-util:with-test (:name (:walk return-from))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (return-from block-name a b c)))
           "Form: (RETURN-FROM BLOCK-NAME A B C)   Context: EVAL
Form: A   Context: EVAL
Form: B   Context: EVAL
Form: C   Context: EVAL
\(RETURN-FROM BLOCK-NAME A B C)")))


(test-util:with-test (:name (:walk setq 1))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (setq a 1)))
           "Form: (SETQ A 1)   Context: EVAL
Form: A   Context: SET
Form: 1   Context: EVAL
\(SETQ A 1)")))
(makunbound 'a)

(test-util:with-test (:name (:walk setq 2))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (setq a (foo 1) b (bar 2) c 3)))
           "Form: (SETQ A (FOO 1) B (BAR 2) C 3)   Context: EVAL
Form: (SETQ A (FOO 1))   Context: EVAL
Form: A   Context: SET
Form: (FOO 1)   Context: EVAL
Form: 'GLOBAL-FOO   Context: EVAL
Form: (SETQ B (BAR 2))   Context: EVAL
Form: B   Context: SET
Form: (BAR 2)   Context: EVAL
Form: 'GLOBAL-BAR   Context: EVAL
Form: (SETQ C 3)   Context: EVAL
Form: C   Context: SET
Form: 3   Context: EVAL
\(SETQ A (FOO 1) B (BAR 2) C 3)")))
(makunbound 'a)
(makunbound 'b)
(makunbound 'c)

(test-util:with-test (:name (:walk tagbody))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (tagbody a b c (go a))))
           "Form: (TAGBODY A B C (GO A))   Context: EVAL
Form: (GO A)   Context: EVAL
\(TAGBODY A B C (GO A))")))

(test-util:with-test (:name (:walk the))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (the foo (foo-form a b c))))
           "Form: (THE FOO (FOO-FORM A B C))   Context: EVAL
Form: (FOO-FORM A B C)   Context: EVAL
Form: A   Context: EVAL
Form: B   Context: EVAL
Form: C   Context: EVAL
\(THE FOO (FOO-FORM A B C))")))

(test-util:with-test (:name (:walk throw))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (throw tag-form a)))
           "Form: (THROW TAG-FORM A)   Context: EVAL
Form: TAG-FORM   Context: EVAL
Form: A   Context: EVAL
\(THROW TAG-FORM A)")))

(test-util:with-test (:name (:walk unwind-protect))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (unwind-protect (foo a b) d e f)))
           "Form: (UNWIND-PROTECT (FOO A B) D E F)   Context: EVAL
Form: (FOO A B)   Context: EVAL
Form: 'GLOBAL-FOO   Context: EVAL
Form: D   Context: EVAL
Form: E   Context: EVAL
Form: F   Context: EVAL
\(UNWIND-PROTECT (FOO A B) D E F)")))

(defmacro flet-1 (a b)
  (declare (ignore a b))
  ''outer)

(defmacro labels-1 (a b)
  (declare (ignore a b))
  ''outer)

(test-util:with-test (:name (:walk flet defmacro))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk
              (flet ((flet-1 (a b) () (flet-1 a b) (list a b)))
                (flet-1 1 2)
                (foo 1 2))))
           "Form: (FLET ((FLET-1 (A B)
               NIL
               (FLET-1 A B)
               (LIST A B)))
        (FLET-1 1 2)
        (FOO 1 2))   Context: EVAL
Form: NIL   Context: EVAL; bound: NIL
Form: (FLET-1 A B)   Context: EVAL
Form: 'OUTER   Context: EVAL
Form: (LIST A B)   Context: EVAL
Form: A   Context: EVAL; lexically bound
Form: B   Context: EVAL; lexically bound
Form: (FLET-1 1 2)   Context: EVAL
Form: 1   Context: EVAL
Form: 2   Context: EVAL
Form: (FOO 1 2)   Context: EVAL
Form: 'GLOBAL-FOO   Context: EVAL
\(FLET ((FLET-1 (A B)
         NIL
         (FLET-1 A B)
         (LIST A B)))
  (FLET-1 1 2)
  (FOO 1 2))")))

(test-util:with-test (:name (:walk labels defmacro))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk
              (labels ((label-1 (a b) () (label-1 a b)(list a b)))
                (label-1 1 2)
                (foo 1 2))))
           "Form: (LABELS ((LABEL-1 (A B)
                 NIL
                 (LABEL-1 A B)
                 (LIST A B)))
        (LABEL-1 1 2)
        (FOO 1 2))   Context: EVAL
Form: NIL   Context: EVAL; bound: NIL
Form: (LABEL-1 A B)   Context: EVAL
Form: A   Context: EVAL; lexically bound
Form: B   Context: EVAL; lexically bound
Form: (LIST A B)   Context: EVAL
Form: A   Context: EVAL; lexically bound
Form: B   Context: EVAL; lexically bound
Form: (LABEL-1 1 2)   Context: EVAL
Form: 1   Context: EVAL
Form: 2   Context: EVAL
Form: (FOO 1 2)   Context: EVAL
Form: 'GLOBAL-FOO   Context: EVAL
\(LABELS ((LABEL-1 (A B)
           NIL
           (LABEL-1 A B)
           (LIST A B)))
  (LABEL-1 1 2)
  (FOO 1 2))")))

(test-util:with-test (:name (:walk macrolet 1))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (macrolet ((macrolet-1 (a b) (list a b)))
                                            (macrolet-1 a b)
                                            (foo 1 2))))
           "Form: (MACROLET ((MACROLET-1 (A B)
                   (LIST A B)))
        (MACROLET-1 A B)
        (FOO 1 2))   Context: EVAL
Form: (LIST A B)   Context: EVAL
Form: A   Context: EVAL; lexically bound
Form: B   Context: EVAL; lexically bound
Form: (MACROLET-1 A B)   Context: EVAL
Form: (A B)   Context: EVAL
Form: B   Context: EVAL
Form: (FOO 1 2)   Context: EVAL
Form: 'GLOBAL-FOO   Context: EVAL
\(MACROLET ((MACROLET-1 (A B)
             (LIST A B)))
  (MACROLET-1 A B)
  (FOO 1 2))")))

(test-util:with-test (:name (:walk macrolet 2))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (macrolet ((foo (a) `(inner-foo-expanded ,a)))
                                            (foo 1))))
           "Form: (MACROLET ((FOO (A)
                   `(INNER-FOO-EXPANDED ,A)))
        (FOO 1))   Context: EVAL
Form: `(INNER-FOO-EXPANDED ,A)   Context: EVAL
Form: (LIST 'INNER-FOO-EXPANDED A)   Context: EVAL
Form: 'INNER-FOO-EXPANDED   Context: EVAL
Form: A   Context: EVAL; lexically bound
Form: (FOO 1)   Context: EVAL
Form: (INNER-FOO-EXPANDED 1)   Context: EVAL
Form: 1   Context: EVAL
\(MACROLET ((FOO (A)
             `(INNER-FOO-EXPANDED ,A)))
  (FOO 1))")))

(test-util:with-test (:name (:walk macrolet progn 1))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (progn (bar 1)
                                                 (macrolet ((bar (a)
                                                              `(inner-bar-expanded ,a)))
                                                   (bar 2)))))
           "Form: (PROGN
       (BAR 1)
       (MACROLET ((BAR (A)
                    `(INNER-BAR-EXPANDED ,A)))
         (BAR 2)))   Context: EVAL
Form: (BAR 1)   Context: EVAL
Form: 'GLOBAL-BAR   Context: EVAL
Form: (MACROLET ((BAR (A)
                   `(INNER-BAR-EXPANDED ,A)))
        (BAR 2))   Context: EVAL
Form: `(INNER-BAR-EXPANDED ,A)   Context: EVAL
Form: (LIST 'INNER-BAR-EXPANDED A)   Context: EVAL
Form: 'INNER-BAR-EXPANDED   Context: EVAL
Form: A   Context: EVAL; lexically bound
Form: (BAR 2)   Context: EVAL
Form: (INNER-BAR-EXPANDED 2)   Context: EVAL
Form: 2   Context: EVAL
\(PROGN
  (BAR 1)
  (MACROLET ((BAR (A)
               `(INNER-BAR-EXPANDED ,A)))
    (BAR 2)))")))

(test-util:with-test (:name (:walk macrolet progn 2))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (progn (bar 1)
                                                 (macrolet ((bar (s)
                                                              (bar s)
                                                              `(inner-bar-expanded ,s)))
                                                   (bar 2)))))
           "Form: (PROGN
       (BAR 1)
       (MACROLET ((BAR (S)
                    (BAR S)
                    `(INNER-BAR-EXPANDED ,S)))
         (BAR 2)))   Context: EVAL
Form: (BAR 1)   Context: EVAL
Form: 'GLOBAL-BAR   Context: EVAL
Form: (MACROLET ((BAR (S)
                   (BAR S)
                   `(INNER-BAR-EXPANDED ,S)))
        (BAR 2))   Context: EVAL
Form: (BAR S)   Context: EVAL
Form: 'GLOBAL-BAR   Context: EVAL
Form: `(INNER-BAR-EXPANDED ,S)   Context: EVAL
Form: (LIST 'INNER-BAR-EXPANDED S)   Context: EVAL
Form: 'INNER-BAR-EXPANDED   Context: EVAL
Form: S   Context: EVAL; lexically bound
Form: (BAR 2)   Context: EVAL
Form: (INNER-BAR-EXPANDED 2)   Context: EVAL
Form: 2   Context: EVAL
\(PROGN
  (BAR 1)
  (MACROLET ((BAR (S)
               (BAR S)
               `(INNER-BAR-EXPANDED ,S)))
    (BAR 2)))")))

(test-util:with-test (:name (:walk cond))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk (cond (a b)
                                                ((foo bar) a (foo a)))))
           "Form: (COND (A B) ((FOO BAR) A (FOO A)))   Context: EVAL
Form: (IF A B (IF (FOO BAR) (PROGN A (FOO A)) NIL))   Context: EVAL
Form: A   Context: EVAL
Form: B   Context: EVAL
Form: (IF (FOO BAR) (PROGN A (FOO A)) NIL)   Context: EVAL
Form: (FOO BAR)   Context: EVAL
Form: 'GLOBAL-FOO   Context: EVAL
Form: (PROGN A (FOO A))   Context: EVAL
Form: A   Context: EVAL
Form: (FOO A)   Context: EVAL
Form: 'GLOBAL-FOO   Context: EVAL
Form: NIL   Context: EVAL; bound: NIL
\(COND (A B) ((FOO BAR) A (FOO A)))")))

(test-util:with-test (:name (:walk let lambda))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (let ((the-lexical-variables ()))
               (walk-form '(let ((a 1) (b 2))
                            (lambda (x) (list a b x y)))
                          ()
                          (lambda (form context env)
                            (declare (ignore context))
                            (when (and (symbolp form)
                                       (var-lexical-p form env))
                              (push form the-lexical-variables))
                            form))
               (or (and (= (length the-lexical-variables) 3)
                        (member 'a the-lexical-variables)
                        (member 'b the-lexical-variables)
                        (member 'x the-lexical-variables))
                   (error "Walker didn't do lexical variables of a closure properly."))))
           "")))

(test-util:with-test (:name (:walk setq :macro))
  (assert (string=-modulo-tabspace
           (with-output-to-string (*standard-output*)
             (take-it-out-for-a-test-walk
              (macrolet ((x () 'y))
                (setq (x) 3))))
           "Form: (MACROLET ((X ()
                   'Y))
        (SETQ (X) 3))   Context: EVAL
Form: 'Y   Context: EVAL
Form: (SETQ (X) 3)   Context: EVAL
Form: (X)   Context: SET
Form: 3   Context: EVAL
\(MACROLET ((X ()
             'Y))
  (SETQ (X) 3))"
)))

(test-util:with-test (:name (:walk let* special list :hairier-specials))
  (assert
   (string=-modulo-tabspace
    (with-output-to-string (*standard-output*)
      (take-it-out-for-a-test-walk (let* ((a a) (b a) (c b) (b c))
                                     (declare (special a b))
                                     (list a b c))))
    "Form: (LET* ((A A) (B A) (C B) (B C))
        (DECLARE (SPECIAL A B))
        (LIST A B C))   Context: EVAL
Form: A   Context: EVAL
Form: A   Context: EVAL; lexically bound; declared special
Form: B   Context: EVAL; lexically bound
Form: C   Context: EVAL; lexically bound
Form: (LIST A B C)   Context: EVAL
Form: A   Context: EVAL; lexically bound; declared special
Form: B   Context: EVAL; lexically bound; declared special
Form: C   Context: EVAL; lexically bound
\(LET* ((A A) (B A) (C B) (B C))
  (DECLARE (SPECIAL A B))
  (LIST A B C))")))

(test-util:with-test (:name (:walk defclass :type :initform))
  ;; A slot with :TYPE and :INITFORM causes SB-C::WITH-SOURCE-FORM to
  ;; appear in the expansion which didn't have a walker template at
  ;; some point. We just make sure walking the form doesn't signal an
  ;; error.
  (with-output-to-string (*standard-output*)
    (take-it-out-for-a-test-walk
     (defclass foo () ((%bar :type integer :initform 'string))))))

;;;; more tests

;;; Old PCL hung up on this.
(defmethod #:foo ()
  (defun #:bar ()))

;; lp#1912362
(defmethod zook (x) (let ((typep x 'vector)) typep))
(test-util:with-test (:name :let-syntax-error)
  (assertoid:assert-error (zook 1)))

(declaim (inline inlined-fun))

(test-util:with-test (:name :inlined-defun)
  (eval '(defmethod inlined-defun () (defun inlined-fun ()))))

(test-util:with-test (:name :symbol-macrolet-declarations)
  (test-util:checked-compile
   `(lambda ()
      (defmethod ,(gensym) (obj)
        (declare (optimize speed))
        (symbol-macrolet ((x (slot-value obj 'x)))
          (declare (fixnum x))
          (incf x 1))))
   :allow-notes nil))
