~ chicken-core (chicken-5) 83014573891718a2a05df13a246ef2e76ba9b7c6


commit 83014573891718a2a05df13a246ef2e76ba9b7c6
Author:     megane <meganeka@gmail.com>
AuthorDate: Wed Nov 28 10:48:43 2018 +0200
Commit:     Evan Hanson <evhan@foldling.org>
CommitDate: Sat Mar 9 20:35:49 2019 +1300

    Add more information to scrutinizer messages
    
    Notably for "incorrect argument to function" print the argument
    expression. This is convenient when it's the 5th argument to 9 argument
    function, or something similiar requiring complicated mental arithmetic.
    
    Also pretty print value count mismatches for `let', `set!' and `if'.
    
    * scrutinizer.scm (describe-expression): Use source-node-tree to get
      the non-mutated AST. Skip "the/result" node so we can see the call
      node, if there is one.
    
    * scrutinizer.scm (scrutinize): Pass argument node to
      r-invalid-called-procedure-type, r-proc-call-argument-type-mismatch
    
    * scrutinizer.scm (r-invalid-called-procedure-type): Describe the
      invalid procedure expression
    
    * scrutinizer.scm (r-proc-call-argument-type-mismatch): Describe the
      invalid argument expression
    
    * scrutinizer.scm (report2): Flush output so the last printed warning
      gets printed fully right away, and not once the compilation finishes,
      which can take a while on bigger files.
    
    Signed-off-by: Evan Hanson <evhan@foldling.org>

diff --git a/scrutinizer.scm b/scrutinizer.scm
index 9e691cc3..f59ba388 100644
--- a/scrutinizer.scm
+++ b/scrutinizer.scm
@@ -276,25 +276,7 @@
 	(d "assignment to var ~a in ~a is always immediate" var loc)
 	#t))
 
-    (define (single node what tv loc)
-      (if (eq? '* tv)
-	  '*
-	  (let ((n (length tv)))
-	    (cond ((= 1 n) (car tv))
-		  ((zero? n)
-		   (report
-		    loc
-		    "~aexpected a single result ~a, but received zero results"
-		    (node-source-prefix node) what)
-		   'undefined)
-		  (else
-		   (report
-		    loc
-		    "~aexpected a single result ~a, but received ~a result~a"
-		    (node-source-prefix node) what n (multiples n))
-		   (first tv))))))
-
-    (define (single2 tv r-value-count-mismatch)
+    (define (single tv r-value-count-mismatch)
       (if (eq? '* tv)
 	  '*
 	  (let ((n (length tv)))
@@ -464,7 +446,8 @@
 			(tst (first subs))
 			(nor-1 noreturn))
 		    (set! noreturn #f)
-		    (let* ((rt (single n "in conditional" (walk tst e loc #f #f flow tags) loc))
+		    (let* ((rt (single (walk tst e loc #f #f flow tags)
+				       (cut r-conditional-value-count-invalid loc n tst <>)))
 			   (c (second subs))
 			   (a (third subs))
 			   (nor0 noreturn))
@@ -517,11 +500,8 @@
 			(walk (car body) (append e2 e) loc dest tail flow ctags)
 			(let* ((var (car vars))
 			       (val (car body))
-			       (t (single
-				   n
-				   (sprintf "in `let' binding of `~a'" (real-name var))
-				   (walk val e loc var #f flow #f) 
-				   loc)))
+			       (t (single (walk val e loc var #f flow #f)
+					  (cut r-let-value-count-invalid loc var n val <>))))
 			  (when (and (eq? (node-class val) '##core#variable)
 				     (not (db-get db var 'assigned)))
 			    (let ((var2 (first (node-parameters val))))
@@ -585,11 +565,9 @@
 		 ((set! ##core#set!)
 		  (let* ((var (first params))
 			 (type (variable-mark var '##compiler#type))
-			 (rt (single
-			      n
-			      (sprintf "in assignment to `~a'" var)
-			      (walk (first subs) e loc var #f flow #f)
-			      loc))
+			 (rt (single (walk (first subs) e loc var #f flow #f)
+				     (cut r-assignment-value-count-invalid
+					  loc var n (first subs) <>)))
 			 (typeenv (append 
 				   (if type (type-typeenv type) '())
 				   (type-typeenv rt)))
@@ -662,7 +640,7 @@
 				      (make-node
 				       '##core#the/result
 				       (list
-					(single2
+					(single
 					 (walk n2 e loc #f #f flow #f)
 					 (cut r-proc-call-argument-value-count loc n i n2 <>)))
 				       (list n2)))
@@ -2248,8 +2226,9 @@
 		(unless (or (null? (cdr arg-types))
 			    (potentially-proper-list? arg1))
 		  (r-proc-call-argument-type-mismatch
-		   loc node index 'list
-		   (car arg-types) arg1 (variable-mark 'scheme#append '##compiler#type)))
+		   loc node index 'list arg1
+		   (car arg-types)
+		   (variable-mark 'scheme#append '##compiler#type)))
 		#f))))))
     (cond ((derive-result-type) => list)
 	  (else rtypes)))
@@ -2659,6 +2638,71 @@
        (sprintf "a negative index ~a." idx)
        (sprintf "index `~a' for a ~a of length `~a'." idx obj-name obj-length))))
 
+(define (r-conditional-value-count-invalid loc if-node test-node atype)
+  (define (p short long)
+    (report2 short warning (list test-node if-node)
+	     loc
+	     (string-append
+	      "In conditional:"
+	      "~%~%"
+	      "~a"
+	      "~%~%"
+	      "The test expression ~a"
+	      "~%~%"
+	      "~a")
+	     (pp-fragment if-node)
+	     long
+	     (describe-expression test-node)))
+  (if (zero? (length atype))
+      (p "Zero values for conditional"
+	 "returns 0 values.")
+      (p "Too many values for conditional"
+	 (sprintf "returns ~a values." (length atype)))))
+
+(define (r-let-value-count-invalid loc var let-node val-node atype)
+  (define (p short long)
+    (report2 short warning (list val-node let-node)
+	     loc
+	     (string-append
+	      "In let expression:"
+	      "~%~%"
+	      "~a"
+	      "~%~%"
+	      "Variable `~a' is bound to an expression that ~a"
+	      "~%~%"
+	      "~a")
+	     (pp-fragment let-node)
+	     (real-name var)
+	     long
+	     (describe-expression val-node)))
+  (if (zero? (length atype))
+      (p (sprintf "Let binding to `~a' has zero values" (real-name var))
+	 "returns 0 values.")
+      (p (sprintf "Let binding to `~a' has ~a values" (real-name var) (length atype))
+	 (sprintf "returns ~a values." (length atype)))))
+
+(define (r-assignment-value-count-invalid loc var set-node val-node atype)
+  (define (p short long)
+    (report2 short warning (list val-node set-node)
+	     loc
+	     (string-append
+	      "In assignment:"
+	      "~%~%"
+	      "~a"
+	      "~%~%"
+	      "Variable `~a' is assigned from expression that ~a"
+	      "~%~%"
+	      "~a")
+	     (pp-fragment set-node)
+	     (strip-namespace var)
+	     long
+	     (describe-expression val-node)))
+  (if (zero? (length atype))
+      (p (sprintf "Assignment to `~a' has zero values" (strip-namespace var))
+	 "returns 0 values.")
+      (p (sprintf "Assignment to `~a' has ~a values" (strip-namespace var) (length atype))
+	 (sprintf "returns ~a values." (length atype)))))
+
 (define (r-pred-call-always-true loc node pred-type atype)
   (define pname (call-node-procedure-name node))
   (report-notice
diff --git a/tests/scrutinizer-message-format.expected b/tests/scrutinizer-message-format.expected
index c3b34600..eeaf7d2a 100644
--- a/tests/scrutinizer-message-format.expected
+++ b/tests/scrutinizer-message-format.expected
@@ -71,8 +71,21 @@ Warning: Not enough argument values (test-scrutinizer-message-format.scm:XXX)
 
     (scheme#values)
 
-Warning: In `r-proc-call-argument-value-count', a toplevel procedure
-  expected a single result in `let' binding of `gXXX', but received zero results
+Warning: Let binding to `gXXX' has zero values (test-scrutinizer-message-format.scm:XXX) 
+  In `r-proc-call-argument-value-count', a toplevel procedure
+  In let expression:
+
+    (let ((gXXX (scheme#values))) (gXXX))
+
+  Variable `gXXX' is bound to an expression that returns 0 values.
+
+  It is a call to `values' from module `scheme' which has this type:
+
+    (procedure (#!rest values) . *)
+
+  This is the expression:
+
+    (scheme#values)
 
 Warning: Branch values mismatch (test-scrutinizer-message-format.scm:XXX) 
   In `r-cond-branch-value-count-mismatch', a toplevel procedure
@@ -288,17 +301,69 @@ Warning: Negative vector index (test-scrutinizer-message-format.scm:XXX)
 
   Procedure `vector-ref' from module `scheme' is called with a negative index -1.
 
-Warning: In `zero-values-for-let', a toplevel procedure
-  expected a single result in `let' binding of `a', but received zero results
+Warning: Let binding to `a' has zero values (test-scrutinizer-message-format.scm:XXX) 
+  In `zero-values-for-let', a toplevel procedure
+  In let expression:
+
+    (let ((a (scheme#values))) a)
+
+  Variable `a' is bound to an expression that returns 0 values.
+
+  It is a call to `values' from module `scheme' which has this type:
+
+    (procedure (#!rest values) . *)
+
+  This is the expression:
+
+    (scheme#values)
+
+Warning: Let binding to `a' has 2 values (test-scrutinizer-message-format.scm:XXX) 
+  In `multiple-values-for-let', a toplevel procedure
+  In let expression:
 
-Warning: In `multiple-values-for-let', a toplevel procedure
-  expected a single result in `let' binding of `a', but received 2 results
+    (let ((a (scheme#values 1 2))) a)
 
-Warning: In `zero-values-for-conditional', a toplevel procedure
-  expected a single result in conditional, but received zero results
+  Variable `a' is bound to an expression that returns 2 values.
+
+  It is a call to `values' from module `scheme' which has this type:
+
+    (procedure (#!rest values) . *)
 
-Warning: In `multiple-values-for-conditional', a toplevel procedure
-  expected a single result in conditional, but received 2 results
+  This is the expression:
+
+    (scheme#values 1 2)
+
+Warning: Zero values for conditional (test-scrutinizer-message-format.scm:XXX) 
+  In `zero-values-for-conditional', a toplevel procedure
+  In conditional:
+
+    (if (scheme#values) 1 (##core#undefined))
+
+  The test expression returns 0 values.
+
+  It is a call to `values' from module `scheme' which has this type:
+
+    (procedure (#!rest values) . *)
+
+  This is the expression:
+
+    (scheme#values)
+
+Warning: Too many values for conditional (test-scrutinizer-message-format.scm:XXX) 
+  In `multiple-values-for-conditional', a toplevel procedure
+  In conditional:
+
+    (if (scheme#values 1 2) 1 (##core#undefined))
+
+  The test expression returns 2 values.
+
+  It is a call to `values' from module `scheme' which has this type:
+
+    (procedure (#!rest values) . *)
+
+  This is the expression:
+
+    (scheme#values 1 2)
 
 Note: Test is always true (test-scrutinizer-message-format.scm:XXX) 
   In `multiple-values-for-conditional', a toplevel procedure
@@ -310,8 +375,21 @@ Note: Test is always true (test-scrutinizer-message-format.scm:XXX)
 
     fixnum
 
-Warning: In `multiple-values-for-conditional', a toplevel procedure
-  expected a single result in `let' binding of `gXXX', but received 2 results
+Warning: Let binding to `gXXX' has 2 values (test-scrutinizer-message-format.scm:XXX) 
+  In `multiple-values-for-conditional', a toplevel procedure
+  In let expression:
+
+    (if (scheme#values 1 2) 1 (##core#undefined))
+
+  Variable `gXXX' is bound to an expression that returns 2 values.
+
+  It is a call to `values' from module `scheme' which has this type:
+
+    (procedure (#!rest values) . *)
+
+  This is the expression:
+
+    (scheme#values 1 2)
 
 Warning: Wrong number of arguments (test-scrutinizer-message-format.scm:XXX) 
   In `m#toplevel-foo', a toplevel procedure
@@ -387,10 +465,23 @@ Warning: Not enough argument values (test-scrutinizer-message-format.scm:XXX)
 
     (scheme#values)
 
-Warning: In `m#toplevel-foo', a toplevel procedure
+Warning: Let binding to `gXXX' has zero values (test-scrutinizer-message-format.scm:XXX) 
+  In `m#toplevel-foo', a toplevel procedure
   In `local-bar', a local procedure
   In `r-proc-call-argument-value-count', a local procedure
-  expected a single result in `let' binding of `gXXX', but received zero results
+  In let expression:
+
+    (let ((gXXX (scheme#values))) (gXXX))
+
+  Variable `gXXX' is bound to an expression that returns 0 values.
+
+  It is a call to `values' from module `scheme' which has this type:
+
+    (procedure (#!rest values) . *)
+
+  This is the expression:
+
+    (scheme#values)
 
 Warning: Branch values mismatch (test-scrutinizer-message-format.scm:XXX) 
   In `m#toplevel-foo', a toplevel procedure
@@ -622,25 +713,77 @@ Warning: Negative vector index (test-scrutinizer-message-format.scm:XXX)
 
   Procedure `vector-ref' from module `scheme' is called with a negative index -1.
 
-Warning: In `m#toplevel-foo', a toplevel procedure
+Warning: Let binding to `a' has zero values (test-scrutinizer-message-format.scm:XXX) 
+  In `m#toplevel-foo', a toplevel procedure
   In `local-bar', a local procedure
   In `zero-values-for-let', a local procedure
-  expected a single result in `let' binding of `a', but received zero results
+  In let expression:
+
+    (let ((a (scheme#values))) a)
+
+  Variable `a' is bound to an expression that returns 0 values.
+
+  It is a call to `values' from module `scheme' which has this type:
+
+    (procedure (#!rest values) . *)
 
-Warning: In `m#toplevel-foo', a toplevel procedure
+  This is the expression:
+
+    (scheme#values)
+
+Warning: Let binding to `a' has 2 values (test-scrutinizer-message-format.scm:XXX) 
+  In `m#toplevel-foo', a toplevel procedure
   In `local-bar', a local procedure
   In `multiple-values-for-let', a local procedure
-  expected a single result in `let' binding of `a', but received 2 results
+  In let expression:
+
+    (let ((a (scheme#values 1 2))) a)
+
+  Variable `a' is bound to an expression that returns 2 values.
+
+  It is a call to `values' from module `scheme' which has this type:
 
-Warning: In `m#toplevel-foo', a toplevel procedure
+    (procedure (#!rest values) . *)
+
+  This is the expression:
+
+    (scheme#values 1 2)
+
+Warning: Zero values for conditional (test-scrutinizer-message-format.scm:XXX) 
+  In `m#toplevel-foo', a toplevel procedure
   In `local-bar', a local procedure
   In `zero-values-for-conditional', a local procedure
-  expected a single result in conditional, but received zero results
+  In conditional:
+
+    (if (scheme#values) 1 (##core#undefined))
 
-Warning: In `m#toplevel-foo', a toplevel procedure
+  The test expression returns 0 values.
+
+  It is a call to `values' from module `scheme' which has this type:
+
+    (procedure (#!rest values) . *)
+
+  This is the expression:
+
+    (scheme#values)
+
+Warning: Too many values for conditional (test-scrutinizer-message-format.scm:XXX) 
+  In `m#toplevel-foo', a toplevel procedure
   In `local-bar', a local procedure
   In `multiple-values-for-conditional', a local procedure
-  expected a single result in conditional, but received 2 results
+  In conditional:
+
+    (if (scheme#values 1 2) 1 (##core#undefined))
+
+  The test expression returns 2 values.
+
+  It is a call to `values' from module `scheme' which has this type:
+
+    (procedure (#!rest values) . *)
+
+  This is the expression:
+
+    (scheme#values 1 2)
 
 Note: Test is always true (test-scrutinizer-message-format.scm:XXX) 
   In `m#toplevel-foo', a toplevel procedure
@@ -654,10 +797,23 @@ Note: Test is always true (test-scrutinizer-message-format.scm:XXX)
 
     fixnum
 
-Warning: In `m#toplevel-foo', a toplevel procedure
+Warning: Let binding to `gXXX' has 2 values (test-scrutinizer-message-format.scm:XXX) 
+  In `m#toplevel-foo', a toplevel procedure
   In `local-bar', a local procedure
   In `multiple-values-for-conditional', a local procedure
-  expected a single result in `let' binding of `gXXX', but received 2 results
+  In let expression:
+
+    (if (scheme#values 1 2) 1 (##core#undefined))
+
+  Variable `gXXX' is bound to an expression that returns 2 values.
+
+  It is a call to `values' from module `scheme' which has this type:
+
+    (procedure (#!rest values) . *)
+
+  This is the expression:
+
+    (scheme#values 1 2)
 
 Error: No typecase match (test-scrutinizer-message-format.scm:XXX) 
   In `m#toplevel-foo', a toplevel procedure
diff --git a/tests/scrutiny.expected b/tests/scrutiny.expected
index bf66e77d..bb42c9f6 100644
--- a/tests/scrutiny.expected
+++ b/tests/scrutiny.expected
@@ -179,8 +179,21 @@ Warning: Invalid assignment
 
     ((pair 'a335 *) -> 'a335)
 
-Warning: At toplevel
-  expected a single result in `let' binding of `gXXX', but received 2 results
+Warning: Let binding to `gXXX' has 2 values (scrutiny-tests.scm:XXX) 
+  At toplevel
+  In let expression:
+
+    (let ((gXXX (scheme#values 1 2))) (gXXX))
+
+  Variable `gXXX' is bound to an expression that returns 2 values.
+
+  It is a call to `values' from module `scheme' which has this type:
+
+    (procedure (#!rest values) . *)
+
+  This is the expression:
+
+    (scheme#values 1 2)
 
 Warning: Invalid procedure
   At toplevel
Trap