better type propagation for MULTIPLE-VALUE-BIND
[sbcl.git] / src / compiler / ir1opt.lisp
index d1d9e68..8a4d87e 100644 (file)
 (defun constant-lvar-p (thing)
   (declare (type (or lvar null) thing))
   (and (lvar-p thing)
-       (let ((use (principal-lvar-use thing)))
-         (and (ref-p use) (constant-p (ref-leaf use))))))
+       (or (let ((use (principal-lvar-use thing)))
+             (and (ref-p use) (constant-p (ref-leaf use))))
+           ;; check for EQL types (but not singleton numeric types)
+           (let ((type (lvar-type thing)))
+             (values (type-singleton-p type))))))
 
 ;;; Return the constant value for an LVAR whose only use is a constant
 ;;; node.
 (declaim (ftype (function (lvar) t) lvar-value))
 (defun lvar-value (lvar)
-  (let ((use (principal-lvar-use lvar)))
-    (constant-value (ref-leaf use))))
+  (let ((use  (principal-lvar-use lvar))
+        (type (lvar-type lvar))
+        leaf)
+    (if (and (ref-p use)
+             (constant-p (setf leaf (ref-leaf use))))
+        (constant-value leaf)
+        (multiple-value-bind (constantp value) (type-singleton-p type)
+          (unless constantp
+            (error "~S used on non-constant LVAR ~S" 'lvar-value lvar))
+          value))))
 \f
 ;;;; interface for obtaining results of type inference
 
 ;;; The result value is cached in the LVAR-%DERIVED-TYPE slot. If the
 ;;; slot is true, just return that value, otherwise recompute and
 ;;; stash the value there.
+(eval-when (:compile-toplevel :execute)
+  (#+sb-xc-host cl:defmacro
+   #-sb-xc-host sb!xc:defmacro
+        lvar-type-using (lvar accessor)
+     `(let ((uses (lvar-uses ,lvar)))
+        (cond ((null uses) *empty-type*)
+              ((listp uses)
+               (do ((res (,accessor (first uses))
+                         (values-type-union (,accessor (first current))
+                                            res))
+                    (current (rest uses) (rest current)))
+                   ((or (null current) (eq res *wild-type*))
+                    res)))
+              (t
+               (,accessor uses))))))
+
 #!-sb-fluid (declaim (inline lvar-derived-type))
 (defun lvar-derived-type (lvar)
   (declare (type lvar lvar))
       (setf (lvar-%derived-type lvar)
             (%lvar-derived-type lvar))))
 (defun %lvar-derived-type (lvar)
-  (declare (type lvar lvar))
-  (let ((uses (lvar-uses lvar)))
-    (cond ((null uses) *empty-type*)
-          ((listp uses)
-           (do ((res (node-derived-type (first uses))
-                     (values-type-union (node-derived-type (first current))
-                                        res))
-                (current (rest uses) (rest current)))
-               ((or (null current) (eq res *wild-type*))
-                res)))
-          (t
-           (node-derived-type uses)))))
+  (lvar-type-using lvar node-derived-type))
 
 ;;; Return the derived type for LVAR's first value. This is guaranteed
 ;;; not to be a VALUES or FUNCTION type.
 (defun lvar-type (lvar)
   (single-value-type (lvar-derived-type lvar)))
 
+;;; LVAR-CONSERVATIVE-TYPE
+;;;
+;;; Certain types refer to the contents of an object, which can
+;;; change without type derivation noticing: CONS types and ARRAY
+;;; types suffer from this:
+;;;
+;;;  (let ((x (the (cons fixnum fixnum) (cons a b))))
+;;;     (setf (car x) c)
+;;;     (+ (car x) (cdr x)))
+;;;
+;;; Python doesn't realize that the SETF CAR can change the type of X -- so we
+;;; cannot use LVAR-TYPE which gets the derived results. Worse, still, instead
+;;; of (SETF CAR) we might have a call to a user-defined function FOO which
+;;; does the same -- so there is no way to use the derived information in
+;;; general.
+;;;
+;;; So, the conservative option is to use the derived type if the leaf has
+;;; only a single ref -- in which case there cannot be a prior call that
+;;; mutates it. Otherwise we use the declared type or punt to the most general
+;;; type we know to be correct for sure.
+(defun lvar-conservative-type (lvar)
+  (let ((derived-type (lvar-type lvar))
+        (t-type *universal-type*))
+    ;; Recompute using NODE-CONSERVATIVE-TYPE instead of derived type if
+    ;; necessary -- picking off some easy cases up front.
+    (cond ((or (eq derived-type t-type)
+               ;; Can't use CSUBTYPEP!
+               (type= derived-type (specifier-type 'list))
+               (type= derived-type (specifier-type 'null)))
+           derived-type)
+          ((and (cons-type-p derived-type)
+                (eq t-type (cons-type-car-type derived-type))
+                (eq t-type (cons-type-cdr-type derived-type)))
+           derived-type)
+          ((and (array-type-p derived-type)
+                (or (not (array-type-complexp derived-type))
+                    (let ((dimensions (array-type-dimensions derived-type)))
+                      (or (eq '* dimensions)
+                          (every (lambda (dim) (eq '* dim)) dimensions)))))
+           derived-type)
+          ((type-needs-conservation-p derived-type)
+           (single-value-type (lvar-type-using lvar node-conservative-type)))
+          (t
+           derived-type))))
+
+(defun node-conservative-type (node)
+  (let* ((derived-values-type (node-derived-type node))
+         (derived-type (single-value-type derived-values-type)))
+    (if (ref-p node)
+        (let ((leaf (ref-leaf node)))
+          (if (and (basic-var-p leaf)
+                   (cdr (leaf-refs leaf)))
+              (coerce-to-values
+               (if (eq :declared (leaf-where-from leaf))
+                   (leaf-type leaf)
+                   (conservative-type derived-type)))
+              derived-values-type))
+        derived-values-type)))
+
+(defun conservative-type (type)
+  (cond ((or (eq type *universal-type*)
+             (eq type (specifier-type 'list))
+             (eq type (specifier-type 'null)))
+         type)
+        ((cons-type-p type)
+         (specifier-type 'cons))
+        ((array-type-p type)
+         (if (array-type-complexp type)
+             (make-array-type
+              ;; ADJUST-ARRAY may change dimensions, but rank stays same.
+              :dimensions
+              (let ((old (array-type-dimensions type)))
+                (if (eq '* old)
+                    old
+                    (mapcar (constantly '*) old)))
+              ;; Complexity cannot change.
+              :complexp (array-type-complexp type)
+              ;; Element type cannot change.
+              :element-type (array-type-element-type type)
+              :specialized-element-type (array-type-specialized-element-type type))
+             ;; Simple arrays cannot change at all.
+             type))
+        (t
+         ;; If the type contains some CONS types, the conservative type contains all
+         ;; of them.
+         (when (types-equal-or-intersect type (specifier-type 'cons))
+           (setf type (type-union type (specifier-type 'cons))))
+         ;; Similarly for non-simple arrays -- it should be possible to preserve
+         ;; more information here, but really...
+         (let ((non-simple-arrays (specifier-type '(and array (not simple-array)))))
+           (when (types-equal-or-intersect type non-simple-arrays)
+             (setf type (type-union type non-simple-arrays))))
+         type)))
+
+(defun type-needs-conservation-p (type)
+  (cond ((eq type *universal-type*)
+         ;; Excluding T is necessary, because we do want type derivation to
+         ;; be able to narrow it down in case someone (most like a macro-expansion...)
+         ;; actually declares something as having type T.
+         nil)
+        ((or (cons-type-p type) (and (array-type-p type) (array-type-complexp type)))
+         ;; Covered by the next case as well, but this is a quick test.
+         t)
+        ((types-equal-or-intersect type (specifier-type '(or cons (and array (not simple-array)))))
+         t)))
+
 ;;; If LVAR is an argument of a function, return a type which the
 ;;; function checks LVAR for.
 #!-sb-fluid (declaim (inline lvar-externally-checkable-type))
                                 it (coerce-to-values type)))
                               (t (coerce-to-values type)))))
                dest)))))
-  (lvar-%externally-checkable-type lvar))
+  (or (lvar-%externally-checkable-type lvar) *wild-type*))
 #!-sb-fluid(declaim (inline flush-lvar-externally-checkable-type))
 (defun flush-lvar-externally-checkable-type (lvar)
   (declare (type lvar lvar))
           (dest (lvar-dest lvar)))
       (substitute-lvar internal-lvar lvar)
       (let ((cast (insert-cast-before dest lvar type policy)))
-        (use-lvar cast internal-lvar))))
-  (values))
+        (use-lvar cast internal-lvar)
+        t))))
 
 \f
 ;;;; IR1-OPTIMIZE
          (delete-ref node)
          (unlink-node node))
         (combination
-         (let ((kind (combination-kind node))
-               (info (combination-fun-info node)))
-           (when (and (eq kind :known) (fun-info-p info))
-             (let ((attr (fun-info-attributes info)))
-               (when (and (not (ir1-attributep attr call))
-                          ;; ### For now, don't delete potentially
-                          ;; flushable calls when they have the CALL
-                          ;; attribute. Someday we should look at the
-                          ;; functional args to determine if they have
-                          ;; any side effects.
-                          (if (policy node (= safety 3))
-                              (ir1-attributep attr flushable)
-                              (ir1-attributep attr unsafely-flushable)))
-                 (flush-combination node))))))
+         (when (flushable-combination-p node)
+           (flush-combination node)))
         (mv-combination
          (when (eq (basic-combination-kind node) :local)
            (let ((fun (combination-lambda node)))
 \f
 ;;;; IF optimization
 
-;;; If the test has multiple uses, replicate the node when possible.
-;;; Also check whether the predicate is known to be true or false,
+;;; Utility: return T if both argument cblocks are equivalent.  For now,
+;;; detect only blocks that read the same leaf into the same lvar, and
+;;; continue to the same block.
+(defun cblocks-equivalent-p (x y)
+  (declare (type cblock x y))
+  (and (ref-p (block-start-node x))
+       (eq (block-last x) (block-start-node x))
+
+       (ref-p (block-start-node y))
+       (eq (block-last y) (block-start-node y))
+
+       (equal (block-succ x) (block-succ y))
+       (eql (ref-lvar (block-start-node x)) (ref-lvar (block-start-node y)))
+       (eql (ref-leaf (block-start-node x)) (ref-leaf (block-start-node y)))))
+
+;;; Check whether the predicate is known to be true or false,
 ;;; deleting the IF node in favor of the appropriate branch when this
 ;;; is the case.
+;;; Similarly, when both branches are equivalent, branch directly to either
+;;; of them.
+;;; Also, if the test has multiple uses, replicate the node when possible.
 (defun ir1-optimize-if (node)
   (declare (type cif node))
   (let ((test (if-test node))
         (block (node-block node)))
-
-    (when (and (eq (block-start-node block) node)
-               (listp (lvar-uses test)))
-      (do-uses (use test)
-        (when (immediately-used-p test use)
-          (convert-if-if use node)
-          (when (not (listp (lvar-uses test))) (return)))))
-
     (let* ((type (lvar-type test))
+           (consequent  (if-consequent  node))
+           (alternative (if-alternative node))
            (victim
             (cond ((constant-lvar-p test)
-                   (if (lvar-value test)
-                       (if-alternative node)
-                       (if-consequent node)))
+                   (if (lvar-value test) alternative consequent))
                   ((not (types-equal-or-intersect type (specifier-type 'null)))
-                   (if-alternative node))
+                   alternative)
                   ((type= type (specifier-type 'null))
-                   (if-consequent node)))))
+                   consequent)
+                  ((cblocks-equivalent-p alternative consequent)
+                   alternative))))
       (when victim
         (flush-dest test)
         (when (rest (block-succ block))
           (unlink-blocks block victim))
         (setf (component-reanalyze (node-component node)) t)
-        (unlink-node node))))
+        (unlink-node node)
+        (return-from ir1-optimize-if (values))))
+
+    (when (and (eq (block-start-node block) node)
+               (listp (lvar-uses test)))
+      (do-uses (use test)
+        (when (immediately-used-p test use)
+          (convert-if-if use node)
+          (when (not (listp (lvar-uses test))) (return))))))
   (values))
 
 ;;; Create a new copy of an IF node that tests the value of the node
        (dolist (arg args)
          (when arg
            (setf (lvar-reoptimize arg) nil)))
-       (when info
-         (check-important-result node info)
-         (let ((fun (fun-info-destroyed-constant-args info)))
-           (when fun
-             (let ((destroyed-constant-args (funcall fun args)))
-               (when destroyed-constant-args
-                 (let ((*compiler-error-context* node))
-                   (warn 'constant-modified
-                         :fun-name (lvar-fun-name
-                                    (basic-combination-fun node)))
-                   (setf (basic-combination-kind node) :error)
-                   (return-from ir1-optimize-combination))))))
-         (let ((fun (fun-info-derive-type info)))
-           (when fun
-             (let ((res (funcall fun node)))
-               (when res
-                 (derive-node-type node (coerce-to-values res))
-                 (maybe-terminate-block node nil)))))))
+       (cond (info
+              (check-important-result node info)
+              (let ((fun (fun-info-destroyed-constant-args info)))
+                (when fun
+                  (let ((destroyed-constant-args (funcall fun args)))
+                    (when destroyed-constant-args
+                      (let ((*compiler-error-context* node))
+                        (warn 'constant-modified
+                              :fun-name (lvar-fun-name
+                                         (basic-combination-fun node)))
+                        (setf (basic-combination-kind node) :error)
+                        (return-from ir1-optimize-combination))))))
+              (let ((fun (fun-info-derive-type info)))
+                (when fun
+                  (let ((res (funcall fun node)))
+                    (when res
+                      (derive-node-type node (coerce-to-values res))
+                      (maybe-terminate-block node nil))))))
+             (t
+              ;; Check against the DEFINED-TYPE unless TYPE is already good.
+              (let* ((fun (basic-combination-fun node))
+                     (uses (lvar-uses fun))
+                     (leaf (when (ref-p uses) (ref-leaf uses))))
+                (multiple-value-bind (type defined-type)
+                    (if (global-var-p leaf)
+                        (values (leaf-type leaf) (leaf-defined-type leaf))
+                        (values nil nil))
+                  (when (and (not (fun-type-p type)) (fun-type-p defined-type))
+                    (validate-call-type node type leaf)))))))
       (:known
        (aver info)
        (dolist (arg args)
 ;;; syntax check, arg/result type processing, but still call
 ;;; RECOGNIZE-KNOWN-CALL, since the call might be to a known lambda,
 ;;; and that checking is done by local call analysis.
-(defun validate-call-type (call type defined-type ir1-converting-not-optimizing-p)
+(defun validate-call-type (call type fun &optional ir1-converting-not-optimizing-p)
   (declare (type combination call) (type ctype type))
-  (cond ((not (fun-type-p type))
-         (aver (multiple-value-bind (val win)
-                   (csubtypep type (specifier-type 'function))
-                 (or val (not win))))
-         ;; In the commonish case where the function has been defined
-         ;; in another file, we only get FUNCTION for the type; but we
-         ;; can check whether the current call is valid for the
-         ;; existing definition, even if only to STYLE-WARN about it.
-         (when defined-type
-           (valid-fun-use call defined-type
+  (let* ((where (when fun (leaf-where-from fun)))
+         (same-file-p (eq :defined-here where)))
+    (cond ((not (fun-type-p type))
+           (aver (multiple-value-bind (val win)
+                     (csubtypep type (specifier-type 'function))
+                   (or val (not win))))
+           ;; Using the defined-type too early is a bit of a waste: during
+           ;; conversion we cannot use the untrusted ASSERT-CALL-TYPE, etc.
+           (when (and fun (not ir1-converting-not-optimizing-p))
+             (let ((defined-type (leaf-defined-type fun)))
+               (when (and (fun-type-p defined-type)
+                          (neq fun (combination-type-validated-for-leaf call)))
+                 ;; Don't validate multiple times against the same leaf --
+                 ;; it doesn't add any information, but may generate the same warning
+                 ;; multiple times.
+                 (setf (combination-type-validated-for-leaf call) fun)
+                 (when (and (valid-fun-use call defined-type
+                                           :argument-test #'always-subtypep
+                                           :result-test nil
+                                           :lossage-fun (if same-file-p
+                                                            #'compiler-warn
+                                                            #'compiler-style-warn)
+                                           :unwinnage-fun #'compiler-notify)
+                            same-file-p)
+                   (assert-call-type call defined-type nil)
+                   (maybe-terminate-block call ir1-converting-not-optimizing-p)))))
+           (recognize-known-call call ir1-converting-not-optimizing-p))
+          ((valid-fun-use call type
                           :argument-test #'always-subtypep
                           :result-test nil
-                          :lossage-fun #'compiler-style-warn
-                          :unwinnage-fun #'compiler-notify))
-         (recognize-known-call call ir1-converting-not-optimizing-p))
-        ((valid-fun-use call type
-                        :argument-test #'always-subtypep
-                        :result-test nil
-                        ;; KLUDGE: Common Lisp is such a dynamic
-                        ;; language that all we can do here in
-                        ;; general is issue a STYLE-WARNING. It
-                        ;; would be nice to issue a full WARNING
-                        ;; in the special case of of type
-                        ;; mismatches within a compilation unit
-                        ;; (as in section 3.2.2.3 of the spec)
-                        ;; but at least as of sbcl-0.6.11, we
-                        ;; don't keep track of whether the
-                        ;; mismatched data came from the same
-                        ;; compilation unit, so we can't do that.
-                        ;; -- WHN 2001-02-11
-                        ;;
-                        ;; FIXME: Actually, I think we could
-                        ;; issue a full WARNING if the call
-                        ;; violates a DECLAIM FTYPE.
-                        :lossage-fun #'compiler-style-warn
-                        :unwinnage-fun #'compiler-notify)
-         (assert-call-type call type)
-         (maybe-terminate-block call ir1-converting-not-optimizing-p)
-         (recognize-known-call call ir1-converting-not-optimizing-p))
-        (t
-         (setf (combination-kind call) :error)
-         (values nil nil))))
+                          :lossage-fun #'compiler-warn
+                          :unwinnage-fun #'compiler-notify)
+           (assert-call-type call type)
+           (maybe-terminate-block call ir1-converting-not-optimizing-p)
+           (recognize-known-call call ir1-converting-not-optimizing-p))
+          (t
+           (setf (combination-kind call) :error)
+           (values nil nil)))))
 
 ;;; This is called by IR1-OPTIMIZE when the function for a call has
 ;;; changed. If the call is local, we try to LET-convert it, and
            (derive-node-type call (tail-set-type (lambda-tail-set fun))))))
       (:full
        (multiple-value-bind (leaf info)
-           (validate-call-type call (lvar-type fun-lvar) nil nil)
+           (let* ((uses (lvar-uses fun-lvar))
+                  (leaf (when (ref-p uses) (ref-leaf uses))))
+             (validate-call-type call (lvar-type fun-lvar) leaf))
          (cond ((functional-p leaf)
                 (convert-call-if-possible
                  (lvar-uses (basic-combination-fun call))
 \f
 ;;;; local call optimization
 
-;;; Propagate TYPE to LEAF and its REFS, marking things changed. If
-;;; the leaf type is a function type, then just leave it alone, since
-;;; TYPE is never going to be more specific than that (and
-;;; TYPE-INTERSECTION would choke.)
+;;; Propagate TYPE to LEAF and its REFS, marking things changed.
+;;;
+;;; If the leaf type is a function type, then just leave it alone, since TYPE
+;;; is never going to be more specific than that (and TYPE-INTERSECTION would
+;;; choke.)
+;;;
+;;; Also, if the type is one requiring special care don't touch it if the leaf
+;;; has multiple references -- otherwise LVAR-CONSERVATIVE-TYPE is screwed.
 (defun propagate-to-refs (leaf type)
   (declare (type leaf leaf) (type ctype type))
-  (let ((var-type (leaf-type leaf)))
-    (unless (fun-type-p var-type)
+  (let ((var-type (leaf-type leaf))
+        (refs (leaf-refs leaf)))
+    (unless (or (fun-type-p var-type)
+                (and (cdr refs)
+                     (eq :declared (leaf-where-from leaf))
+                     (type-needs-conservation-p var-type)))
       (let ((int (type-approx-intersection2 var-type type)))
         (when (type/= int var-type)
           (setf (leaf-type leaf) int)
           (let ((s-int (make-single-value-type int)))
-            (dolist (ref (leaf-refs leaf))
+            (dolist (ref refs)
               (derive-node-type ref s-int)
               ;; KLUDGE: LET var substitution
               (let* ((lvar (node-lvar ref)))
   (declare (type lvar arg) (type lambda-var var))
   (binding* ((ref (first (leaf-refs var)))
              (lvar (node-lvar ref) :exit-if-null)
-             (dest (lvar-dest lvar)))
+             (dest (lvar-dest lvar))
+             (dest-lvar (when (valued-node-p dest) (node-lvar dest))))
     (when (and
            ;; Think about (LET ((A ...)) (IF ... A ...)): two
            ;; LVAR-USEs should not be met on one path. Another problem
            ;; is with dynamic-extent.
            (eq (lvar-uses lvar) ref)
            (not (block-delete-p (node-block ref)))
+           ;; If the destinatation is dynamic extent, don't substitute unless
+           ;; the source is as well.
+           (or (not dest-lvar)
+               (not (lvar-dynamic-extent dest-lvar))
+               (lvar-dynamic-extent lvar))
            (typecase dest
              ;; we should not change lifetime of unknown values lvars
              (cast
 ;;; variable, we compute the union of the types across all calls and
 ;;; propagate this type information to the var's refs.
 ;;;
-;;; If the function has an XEP, then we don't do anything, since we
-;;; won't discover anything.
+;;; If the function has an entry-fun, then we don't do anything: since
+;;; it has a XEP we would not discover anything.
+;;;
+;;; If the function is an optional-entry-point, we will just make sure
+;;; &REST lists are known to be lists. Doing the regular rigamarole
+;;; can erronously propagate too strict types into refs: see
+;;; BUG-655203-REGRESSION in tests/compiler.pure.lisp.
 ;;;
 ;;; We can clear the LVAR-REOPTIMIZE flags for arguments in all calls
 ;;; corresponding to changed arguments in CALL, since the only use in
 ;;; right here.
 (defun propagate-local-call-args (call fun)
   (declare (type combination call) (type clambda fun))
-  (unless (or (functional-entry-fun fun)
-              (lambda-optional-dispatch fun))
-    (let* ((vars (lambda-vars fun))
-           (union (mapcar (lambda (arg var)
-                            (when (and arg
-                                       (lvar-reoptimize arg)
-                                       (null (basic-var-sets var)))
-                              (lvar-type arg)))
-                          (basic-combination-args call)
-                          vars))
-           (this-ref (lvar-use (basic-combination-fun call))))
-
-      (dolist (arg (basic-combination-args call))
-        (when arg
-          (setf (lvar-reoptimize arg) nil)))
-
-      (dolist (ref (leaf-refs fun))
-        (let ((dest (node-dest ref)))
-          (unless (or (eq ref this-ref) (not dest))
-            (setq union
-                  (mapcar (lambda (this-arg old)
-                            (when old
-                              (setf (lvar-reoptimize this-arg) nil)
-                              (type-union (lvar-type this-arg) old)))
-                          (basic-combination-args dest)
-                          union)))))
-
-      (loop for var in vars
-            and type in union
-            when type do (propagate-to-refs var type))))
+  (unless (functional-entry-fun fun)
+    (if (lambda-optional-dispatch fun)
+        ;; We can still make sure &REST is known to be a list.
+        (loop for var in (lambda-vars fun)
+              do (let ((info (lambda-var-arg-info var)))
+                   (when (and info (eq :rest (arg-info-kind info)))
+                     (propagate-from-sets var (specifier-type 'list)))))
+        ;; The normal case.
+        (let* ((vars (lambda-vars fun))
+               (union (mapcar (lambda (arg var)
+                                (when (and arg
+                                           (lvar-reoptimize arg)
+                                           (null (basic-var-sets var)))
+                                  (lvar-type arg)))
+                              (basic-combination-args call)
+                              vars))
+               (this-ref (lvar-use (basic-combination-fun call))))
+
+          (dolist (arg (basic-combination-args call))
+            (when arg
+              (setf (lvar-reoptimize arg) nil)))
+
+          (dolist (ref (leaf-refs fun))
+            (let ((dest (node-dest ref)))
+              (unless (or (eq ref this-ref) (not dest))
+                (setq union
+                      (mapcar (lambda (this-arg old)
+                                (when old
+                                  (setf (lvar-reoptimize this-arg) nil)
+                                  (type-union (lvar-type this-arg) old)))
+                              (basic-combination-args dest)
+                              union)))))
+
+          (loop for var in vars
+                and type in union
+                when type do (propagate-to-refs var type)))))
 
   (values))
 \f
         (unlink-node call)
         (when vals
           (reoptimize-lvar (first vals)))
+        ;; Propagate derived types from the VALUES call to its args:
+        ;; transforms can leave the VALUES call with a better type
+        ;; than its args have, so make sure not to throw that away.
+        (let ((types (values-type-types (node-derived-type use))))
+          (dolist (val vals)
+            (when types
+              (let ((type (pop types)))
+                (assert-lvar-type val type '((type-check . 0)))))))
+        ;; Propagate declared types of MV-BIND variables.
         (propagate-to-args use fun)
         (reoptimize-call use))
       t)))
         (unless (eq value-type *empty-type*)
 
           ;; FIXME: Do it in one step.
-          (filter-lvar
-           value
-           (if (cast-single-value-p cast)
-               `(list 'dummy)
-               `(multiple-value-call #'list 'dummy)))
-          (filter-lvar
-           (cast-value cast)
-           ;; FIXME: Derived type.
-           `(%compile-time-type-error 'dummy
-                                      ',(type-specifier atype)
-                                      ',(type-specifier value-type)))
+          (let ((context (cons (node-source-form cast)
+                               (lvar-source (cast-value cast)))))
+            (filter-lvar
+             value
+             (if (cast-single-value-p cast)
+                 `(list 'dummy)
+                 `(multiple-value-call #'list 'dummy)))
+            (filter-lvar
+             (cast-value cast)
+             ;; FIXME: Derived type.
+             `(%compile-time-type-error 'dummy
+                                        ',(type-specifier atype)
+                                        ',(type-specifier value-type)
+                                        ',context)))
           ;; KLUDGE: FILTER-LVAR does not work for non-returning
           ;; functions, so we declare the return type of
           ;; %COMPILE-TIME-TYPE-ERROR to be * and derive the real type