;;;; This software is part of the SBCL system. See the README file for
;;;; more information.
;;;;
;;;; The software is in the public domain and is provided with
;;;; absolutely no warranty. See the COPYING and CREDITS files for
;;;; more information.

(defpackage :sb-cltl2-tests
  (:use :sb-cltl2 :cl :sb-rt :sb-ext :sb-kernel :sb-int))

(in-package :sb-cltl2-tests)

(rem-all-tests)

(defmacro *x*-value ()
  (declare (special *x*))
  *x*)

(deftest compiler-let.1
    (let ((*x* :outer))
      (compiler-let ((*x* :inner))
        (list *x* (*x*-value))))
  (:outer :inner))

(defvar *expansions* nil)
(defmacro macroexpand-macro (arg)
  (push arg *expansions*)
  arg)

(deftest macroexpand-all.1
    (progn
      (macroexpand-all '(defmethod foo ((x fixnum)) (1+ x)))
      t)
  t)

(deftest macroexpand-all.2
    (let ((*expansions* nil))
      (macroexpand-all '(list (macroexpand-macro 1)
                         (let (macroexpand-macro :no)
                           (macroexpand-macro 2))))
      (remove-duplicates (sort *expansions* #'<)))
  (1 2))

(deftest macroexpand-all.3
    (let ((*expansions* nil))
      (compile nil '(lambda ()
                     (macrolet ((foo (key &environment env)
                                  (macroexpand-all `(bar ,key) env)))
                       (foo
                        (macrolet ((bar (key)
                                     (push key *expansions*)
                                     key))
                          (foo 1))))))
      (remove-duplicates *expansions*))
  (1))

(defun smv (env)
  (multiple-value-bind (expansion macro-p)
      (macroexpand 'srlt env)
    (when macro-p (eval expansion))))
(defmacro testr (&environment env)
  `',(getf (smv env) nil))

(deftest macroexpand-all.4
    (macroexpand-all '(symbol-macrolet ((srlt '(nil zool))) (testr)))
  (symbol-macrolet ((srlt '(nil zool))) 'zool))

;;;; DECLARATION-INFORMATION

(defmacro dinfo (thing &environment env)
  `',(declaration-information thing env))

(macrolet ((def (x)
               `(macrolet ((frob (suffix answer &optional declaration)
                            `(deftest ,(intern (concatenate 'string
                                                            "DECLARATION-INFORMATION."
                                                            (symbol-name ',x)
                                                            suffix))
                               (locally (declare ,@(when declaration
                                                         (list declaration)))
                                 (cadr (assoc ',',x (dinfo optimize))))
                              ,answer)))
                 (frob ".DEFAULT" 1)
                 (frob ".0" 0 (optimize (,x 0)))
                 (frob ".1" 1 (optimize (,x 1)))
                 (frob ".2" 2 (optimize (,x 2)))
                 (frob ".3" 3 (optimize (,x 3)))
                 (frob ".IMPLICIT" 3 (optimize ,x)))))
  (def speed)
  (def safety)
  (def debug)
  (def compilation-speed)
  (def space))


(deftest declaration-information.restrict-compiler-policy.1
    (with-compilation-unit (:policy '(optimize) :override t)
      (restrict-compiler-policy 'speed 3)
      (eval '(cadr (assoc 'speed (dinfo optimize)))))
  3)

(deftest declaration-information.restrict-compiler-policy.2
    (with-compilation-unit (:policy '(optimize) :override t)
      (restrict-compiler-policy 'speed 3)
      (locally (declare (optimize (speed 2)))
        (cadr (assoc 'speed (dinfo optimize)))))
  2)

(deftest declaration-information.restrict-compiler-policy.3
    (locally (declare (optimize (speed 2)))
      (with-compilation-unit (:policy '(optimize) :override t)
        (restrict-compiler-policy 'speed 3)
        (cadr (assoc 'speed (dinfo optimize)))))
  2)

(deftest declaration-information.muffle-conditions.default
  (dinfo sb-ext:muffle-conditions)
  nil)
(deftest declaration-information.muffle-conditions.1
  (locally (declare (sb-ext:muffle-conditions warning))
    (dinfo sb-ext:muffle-conditions))
  warning)
(deftest declaration-information.muffle-conditions.2
  (let ((junk (dinfo sb-ext:muffle-conditions)))
    (declare (sb-ext:muffle-conditions warning))
    (locally (declare (sb-ext:unmuffle-conditions style-warning))
      (let ((dinfo (dinfo sb-ext:muffle-conditions)))
        (not
         (not
          (and (subtypep dinfo `(or (and warning (not style-warning))
                                    (and ,junk (not style-warning))))
               (subtypep '(and warning (not style-warning)) dinfo)))))))
  t)


(declaim (declaration fubar))

(deftest declaration-information.declaration
    (if (member 'fubar (declaration-information 'declaration)) 'yay)
  yay)

;;;; VARIABLE-INFORMATION

(defvar *foo*)

(defmacro var-info (var &environment env)
  (list 'quote (multiple-value-list (variable-information var env))))

(deftest variable-info.global-special/unbound
    (var-info *foo*)
  (:special nil nil))

(deftest variable-info.global-special/unbound/extra-decl
    (locally (declare (special *foo*))
      (var-info *foo*))
  (:special nil nil))

(deftest variable-info.global-special/bound
    (let ((*foo* t))
      (var-info *foo*))
  (:special nil nil))

(deftest variable-info.global-special/bound/extra-decl
    (let ((*foo* t))
      (declare (special *foo*))
      (var-info *foo*))
  (:special nil nil))

(deftest variable-info.local-special/unbound
    (locally (declare (special x))
      (var-info x))
  (:special nil nil))

(deftest variable-info.local-special/bound
    (let ((x 13))
      (declare (special x))
      (var-info x))
  (:special nil nil))

(deftest variable-info.local-special/shadowed
    (let ((x 3))
      (declare (special x))
      x
      (let ((x 3))
        x
        (var-info x)))
  (:lexical t nil))

(deftest variable-info.local-special/shadows-lexical
    (let ((x 3))
      (let ((x 3))
        (declare (special x))
        (var-info x)))
  (:special nil nil))

(deftest variable-info.lexical
    (let ((x 8))
      (var-info x))
  (:lexical t nil))

(deftest variable-info.lexical.type
    (let ((x 42))
      (declare (fixnum x))
      (var-info x))
  (:lexical t ((type . fixnum))))

(deftest variable-info.lexical.type.2
    (let ((x 42))
      (prog1
          (var-info x)
        (locally (declare (fixnum x))
          (assert (plusp x)))))
  (:lexical t nil))

(deftest variable-info.lexical.type.3
    (let ((x 42))
      (locally (declare (fixnum x))
        (var-info x)))
  (:lexical t ((type . fixnum))))

(deftest variable-info.ignore
    (let ((x 8))
      (declare (ignore x))
      (var-info x))
  (:lexical t ((ignore . t))))

(deftest variable-info.symbol-macro/local
    (symbol-macrolet ((x 8))
      (var-info x))
  (:symbol-macro t nil))

(define-symbol-macro my-symbol-macro t)

(deftest variable-info.symbol-macro/global
    (var-info my-symbol-macro)
  (:symbol-macro nil nil))

(deftest variable-info.undefined
    (var-info #:undefined)
  (nil nil nil))

(declaim (global this-is-global))
(deftest global-variable
    (var-info this-is-global)
  (:global nil nil))

(defglobal this-is-global-too 42)
(deftest global-variable.2
    (var-info this-is-global-too)
  (:global nil ((always-bound . t))))

(sb-alien:define-alien-variable "errno" sb-alien:int)
(deftest alien-variable
    (var-info errno)
  (:alien nil nil))

;;;; FUNCTION-INFORMATION

(defmacro fun-info (var &environment env)
  (list 'quote (multiple-value-list (function-information var env))))

(defun my-global-fun (x) x)

(deftest function-info.global/no-ftype
    (fun-info my-global-fun)
  (:function nil nil))

(declaim (ftype (function (cons) (values t &optional)) my-global-fun-2))

(defun my-global-fun-2 (x) x)

(deftest function-info.global/ftype
    (fun-info my-global-fun-2)
  (:function nil ((ftype function (cons) (values t &optional)))))

(defmacro my-macro (x) x)

(deftest function-info.macro
    (fun-info my-macro)
  (:macro nil nil))

(deftest function-info.macrolet
    (macrolet ((thingy () nil))
      (fun-info thingy))
  (:macro t nil))

(deftest function-info.special-form
    (fun-info progn)
  (:special-form  nil nil))

(deftest function-info.notinline/local
    (flet ((x (y) y))
      (declare (notinline x))
      (x 1)
      (fun-info x))
  (:function t ((inline . notinline))))

(declaim (notinline my-notinline))
(defun my-notinline (x) x)

(deftest function-info.notinline/global
    (fun-info my-notinline)
  (:function nil ((inline . notinline))))

(declaim (inline my-inline))
(defun my-inline (x) x)

(deftest function-info.inline/global
    (fun-info my-inline)
  (:function nil ((inline . inline))))

(deftest function-information.known-inline
    (locally (declare (inline identity))
      (fun-info identity))
  (:function nil ((inline . inline)
                  (ftype function (t) (values t &optional)))))

(deftest function-information.ftype
    (flet ((foo (x) x))
      (declare (ftype (sfunction (integer) integer) foo))
      (fun-info foo))
  (:function
   t
   ((ftype function (integer) (values integer &optional)))))

;;;;; AUGMENT-ENVIRONMENT

(defmacro ct (form &environment env)
  (let ((toeval `(let ((lexenv (quote ,env)))
                   ,form)))
    `(quote ,(eval toeval))))


(deftest augment-environment.variable1
    (multiple-value-bind (kind local alist)
        (variable-information
         'x
         (augment-environment nil :variable (list 'x) :declare '((type integer x))))
      (list kind local (cdr (assoc 'type alist))))
  (:lexical t integer))

(defvar *foo*)

(deftest augment-environment.variable2
    (identity (variable-information '*foo* (augment-environment nil :variable '(*foo*))))
  :lexical)

(deftest augment-environment.variable3
    (identity (variable-information 'foo (augment-environment nil :variable '(foo))))
  :lexical)

(deftest augment-environment.variable.special1
    (identity (variable-information 'x (augment-environment nil :variable '(x) :declare '((special x)))))
  :special)

(deftest augment-environment.variable.special12
    (locally (declare (special x))
      (ct
       (variable-information
        'x
        (identity (augment-environment lexenv :variable '(x))))))
  :lexical)

(deftest augment-environment.variable.special13
    (let* ((e1 (augment-environment nil :variable '(x) :declare '((special x))))
           (e2 (augment-environment e1  :variable '(x))))
      (identity (variable-information 'x e2)))
  :lexical)

(deftest augment-environment.variable.special.mask
    (let* ((e1 (augment-environment nil :variable '(x) :declare '((ignore x))))
           (e2 (augment-environment e1  :variable '(x))))
      (assoc 'ignore
             (nth 2 (multiple-value-list
                     (variable-information 'x e2)))))
  nil)

(deftest augment-environment.variable.ignore
    (variable-information
     'x
     (augment-environment nil
                          :variable '(x)
                          :declare  '((ignore x))))
  :lexical
  t
  ((ignore . t)))

(deftest augment-environment.function
    (function-information
     'foo
     (augment-environment nil
                          :function '(foo)
                          :declare  '((ftype (sfunction (integer) integer) foo))))
  :function
  t
  ((ftype function (integer) (values integer &optional))))


(deftest augment-environment.macro
    (macroexpand '(mac feh)
                 (augment-environment
                  nil
                  :macro (list (list 'mac #'(lambda (form benv)
                                              (declare (ignore env))
                                              `(quote ,form ,form ,form))))))
  (quote (mac feh) (mac feh) (mac feh))
  t)

(deftest augment-environment.symbol-macro
    (macroexpand 'sym
                 (augment-environment
                  nil
                  :symbol-macro (list (list 'sym '(foo bar baz)))))
  (foo bar baz)
  t)

(deftest augment-environment.macro2
    (eval (macroexpand '(newcond
                         ((= 1 2) 'foo)
                         ((= 1 1) 'bar))
                       (augment-environment nil :macro (list (list 'newcond (macro-function 'cond))))))
  bar)


(deftest augment-environment.nest
    (let ((x 1))
      (ct
       (let* ((e (augment-environment lexenv :variable '(y))))
         (list
          (variable-information 'x e)
          (variable-information 'y e)))))
  (:lexical :lexical))

(deftest augment-environment.nest2
    (symbol-macrolet ((x "x"))
      (ct
       (let* ((e (augment-environment lexenv :variable '(y))))
         (list
          (macroexpand 'x e)
          (variable-information 'y e)))))
  ("x" :lexical))

(deftest augment-environment.symbol-macro-var
    (let ((e (augment-environment
              nil
              :symbol-macro (list (list 'sym '(foo bar baz)))
              :variable '(x))))
      (list (macroexpand 'sym e)
            (variable-information 'x e)))
  ((foo bar baz)
   :lexical))



;;;;; DEFINE-DECLARATION

(defmacro third-value (form)
  (sb-int::with-unique-names (a b c)
    `(multiple-value-bind (,a ,b ,c) ,form
       (declare (ignore ,a ,b))
       ,c)))

(deftest define-declaration.declare
    (progn
      (define-declaration zaphod (spec env)
        (declare (ignore env))
        (values :declare (cons 'zaphod spec)))
      (locally (declare (zaphod beblebrox))
         (locally (declare (zaphod and ford))
           (ct (declaration-information 'zaphod lexenv)))))
  (zaphod and ford))


(deftest define-declaration.declare2
    (progn
      (define-declaration zaphod (spec env)
        (declare (ignore env))
        (values :declare (cons 'zaphod spec)))
      (locally
           (declare (zaphod beblebrox)
                    (special x))
         (ct (declaration-information 'zaphod lexenv))))
  (zaphod beblebrox))

(deftest define-declaration.variable
    (progn
      (define-declaration vogon (spec env)
        (declare (ignore env))
        (values :variable `((,(cadr spec) vogon-key vogon-value))))
      (locally (declare (vogon poetry))
        (ct
         (assoc 'vogon-key
                (third-value
                 (variable-information
                  'poetry
                  lexenv))))))
  (vogon-key . vogon-value))


(deftest define-declaration.variable.special
    (progn
      (define-declaration vogon (spec env)
        (declare (ignore env))
        (values :variable `((,(cadr spec) vogon-key vogon-value))))
      (let (x)
        (declare (vogon x))
        (declare (special x))
        (ct
         (assoc 'vogon-key
                (third-value
                 (variable-information 'x lexenv))))))
  (vogon-key . vogon-value))

(deftest define-declaration.variable.special2
    (progn
      (define-declaration vogon (spec env)
        (declare (ignore env))
        (values :variable `((,(cadr spec) vogon-key vogon-value))))
      (let (x)
        (declare (special x))
        (declare (vogon x))
        (ct
         (assoc 'vogon-key
                (third-value
                 (variable-information 'x lexenv))))))
  (vogon-key . vogon-value))

(deftest define-declaration.variable.mask
    (progn
      (define-declaration vogon (spec env)
        (declare (ignore env))
        (values :variable `((,(cadr spec) vogon-key vogon-value))))
      (let (x)
        (declare (vogon x))
        (let (x)
          (ct
           (assoc
            'vogon-key
            (third (multiple-value-list (variable-information 'x lexenv))))))))
  nil)

(deftest define-declaration.variable.macromask
    (progn
      (define-declaration vogon (spec env)
        (declare (ignore env))
        (values :variable `((,(cadr spec) vogon-key vogon-value))))
      (let (x)
        (declare (vogon x))
        (symbol-macrolet ((x 42))
          (ct
           (assoc
            'vogon-key
            (third (multiple-value-list (variable-information 'x lexenv))))))))
  nil)

(deftest define-declaration.variable.macromask2
    (progn
      (define-declaration vogon (spec env)
        (declare (ignore env))
        (values :variable `((,(cadr spec) vogon-key vogon-value))))
      (symbol-macrolet ((x 42))
        (declare (vogon x))
        (list
         (let (x)
           (ct
            (assoc
             'vogon-key
             (third (multiple-value-list (variable-information 'x lexenv))))))
         (ct
          (assoc
           'vogon-key
           (third (multiple-value-list (variable-information 'x lexenv))))))))
  (nil (vogon-key . vogon-value)))

(deftest define-declaration.variable.mask2
    (progn
      (define-declaration vogon-a (spec env)
        (declare (ignore env))
        (values :variable `((,(cadr spec) vogon-key a))))
      (define-declaration vogon-b (spec env)
        (declare (ignore env))
        (values :variable `((,(cadr spec) vogon-key b))))
      (let (x)
        (declare (vogon-a x))
        (let (x)
          (declare (vogon-b x)))
        (ct
         (assoc
          'vogon-key
          (third (multiple-value-list (variable-information 'x lexenv)))))))
  (vogon-key . a))

(deftest define-declaration.variable.specialmask
    (progn
      (define-declaration vogon (spec env)
        (declare (ignore env))
        (values :variable `((,(cadr spec) vogon-key vogon-value))))
      (locally
          (declare (vogon *foo*))
        (let (*foo*)
          (ct
           (assoc
            'vogon-key
            (third (multiple-value-list (variable-information '*foo* lexenv))))))))
  (vogon-key . vogon-value))



(deftest define-declaration.function
    (progn
      (define-declaration sad (spec env)
        (declare (ignore env))
        (values :function `((,(cadr spec) emotional-state sad))))
      (locally (declare (zaphod beblebrox))
        (locally (declare (sad robot))
          (ct
           (assoc 'emotional-state
                  (third-value (function-information
                                'robot
                                lexenv)))))))
  (emotional-state . sad))

(deftest define-declaration.function.lexical
    (progn
      (define-declaration sad (spec env)
        (declare (ignore env))
        (values :function `((,(cadr spec) emotional-state sad))))
      (flet ((robot nil))
        (locally (declare (sad robot))
          (ct
           (assoc 'emotional-state
                  (third-value (function-information
                                'robot
                                lexenv)))))))
  (emotional-state . sad))


(deftest define-declaration.function.lexical2
    (progn
      (define-declaration sad (spec env)
        (declare (ignore env))
        (values :function `((,(cadr spec) emotional-state sad))))
      (labels ((robot nil))
        (declare (sad robot))
        (ct
         (assoc 'emotional-state
                (third-value (function-information
                              'robot
                              lexenv))))))
  (emotional-state . sad))

(deftest define-declaration.function.mask
    (progn
      (define-declaration sad (spec env)
        (declare (ignore env))
        (values :function `((,(cadr spec) emotional-state sad))))
      (labels ((robot nil))
        (declare (sad robot))
        (labels ((robot nil))
          (ct
           (assoc 'emotional-state
                  (third-value (function-information
                                'robot
                                lexenv)))))))
  nil)


(deftest define-declaration.function.mask2
    (progn
      (define-declaration sad (spec env)
        (declare (ignore env))
        (values :function `((,(cadr spec) emotional-state sad))))
      (locally
          (declare (sad robot))
        (labels ((robot nil))
          (ct
           (assoc 'emotional-state
                  (third-value (function-information
                                'robot
                                lexenv)))))))
  nil)

(deftest define-declaration.function2
    (progn
      (define-declaration happy (spec env)
        (declare (ignore env))
        (values :function `((,(cadr spec) emotional-state happy))))
      (locally (declare (zaphod beblebrox))
        (locally (declare (sad robot))
          (locally (declare (happy robot))
            (ct
             (assoc 'emotional-state
                    (third-value (function-information
                                  'robot
                                  lexenv))))))))
  (emotional-state . happy))

(deftest macroexpand-all.special-binding
    (let ((form '(macrolet ((v (x &environment env)
                             (sb-cltl2:variable-information x env)))
                  (let* ((x :foo)
                         (y (v x)))
                    (declare (special x))
                    (list y (v x))))))
      (list (eval form)
            (eval (sb-cltl2:macroexpand-all form))))
  ((:special :special) (:special :special)))

(deftest macroexpand-all.symbol-macro-shadowed
    (let ((form '(macrolet ((v (x &environment env)
                             (macroexpand x env)))
                  (symbol-macrolet ((x :bad))
                    (let* ((x :good)
                           (y (v x)))
                      y)))))
      (list (eval form)
            (eval (sb-cltl2:macroexpand-all form))))
  (:good :good))
