~ chicken-core (chicken-5) b1e934e85b1258d590a80f8ccfd5938b9e745a8c


commit b1e934e85b1258d590a80f8ccfd5938b9e745a8c
Author:     felix <felix@call-with-current-continuation.org>
AuthorDate: Mon Mar 28 05:45:58 2011 -0400
Commit:     felix <felix@call-with-current-continuation.org>
CommitDate: Mon Mar 28 05:45:58 2011 -0400

    flow-sensitive variable types

diff --git a/scrutinizer.scm b/scrutinizer.scm
index 5763099b..78b371c1 100755
--- a/scrutinizer.scm
+++ b/scrutinizer.scm
@@ -64,55 +64,57 @@
 (define-constant +fragment-max-depth+ 3)
 
 (define (scrutinize node db)
-  (define (constant-result lit)
-    (cond ((string? lit) 'string)
-	  ((symbol? lit) 'symbol)
-	  ((fixnum? lit) 'fixnum)
-	  ((flonum? lit) 'float)
-	  ((number? lit) 'number)	; in case...
-	  ((boolean? lit) 'boolean)
-	  ((list? lit) 'list)
-	  ((pair? lit) 'pair)
-	  ((eof-object? lit) 'eof)
-	  ((vector? lit) 'vector)
-	  ((and (not (##sys#immediate? lit)) ##sys#generic-structure? lit)
-	   `(struct ,(##sys#slot lit 0)))
-	  ((null? lit) 'null)
-	  ((char? lit) 'char)
-	  (else '*)))
-  (define (global-result id loc)
-    (cond ((##sys#get id '##core#type) =>
-	   (lambda (a) 
-	     (cond #;((and (get db id 'assigned)      ; remove assigned global from type db
-			 (not (##sys#get id '##core#declared-type)))
-		    (##sys#put! id '##core#type #f)
-		    '*)
-		   ((eq? a 'deprecated)
-		    (report
-		     loc
-		     (sprintf "use of deprecated library procedure `~a'" id) )
-		    '*)
-		   ((and (pair? a) (eq? (car a) 'deprecated))
-		    (report 
-		     loc
-		     (sprintf 
-			 "use of deprecated library procedure `~a' - consider using `~a' instead"
-		       id (cadr a)))
-		     '*)
-		   (else (list a)))))
-	  (else '*)))
-  (define (variable-result id e loc)
-    (cond ((and (get db id 'assigned) 
+  (let ((blist '()))
+    (define (constant-result lit)
+      (cond ((string? lit) 'string)
+	    ((symbol? lit) 'symbol)
+	    ((fixnum? lit) 'fixnum)
+	    ((flonum? lit) 'float)
+	    ((number? lit) 'number)	; in case...
+	    ((boolean? lit) 'boolean)
+	    ((list? lit) 'list)
+	    ((pair? lit) 'pair)
+	    ((eof-object? lit) 'eof)
+	    ((vector? lit) 'vector)
+	    ((and (not (##sys#immediate? lit)) ##sys#generic-structure? lit)
+	     `(struct ,(##sys#slot lit 0)))
+	    ((null? lit) 'null)
+	    ((char? lit) 'char)
+	    (else '*)))
+    (define (global-result id loc)
+      (cond ((##sys#get id '##core#type) =>
+	     (lambda (a) 
+	       (cond #;((and (get db id 'assigned)      ; remove assigned global from type db
+		(not (##sys#get id '##core#declared-type)))
+	       (##sys#put! id '##core#type #f)
+	       '*)
+	       ((eq? a 'deprecated)
+		(report
+		 loc
+		 (sprintf "use of deprecated library procedure `~a'" id) )
+		'(*))
+	       ((and (pair? a) (eq? (car a) 'deprecated))
+		(report 
+		 loc
+		 (sprintf 
+		     "use of deprecated library procedure `~a' - consider using `~a' instead"
+		   id (cadr a)))
+		'(*))
+	       (else (list a)))))
+      (else '(*))))
+  (define (variable-result id e loc flow)
+    (cond ((find (lambda (b) (memq (cdr b) flow)) blist) => cdr)
+	  ((and (get db id 'assigned) 
 		(not (##sys#get id '##core#declared-type)) )
-	   '*)
+	   '(*))
 	  ((assq id e) =>
 	   (lambda (a)
 	     (cond ((eq? 'undefined (cdr a))
 		    (report 
 		     loc
 		     (sprintf "access to variable `~a' which has an undefined value"
-			      (real-name id db)))
-		    '*)
+		       (real-name id db)))
+		    '(*))
 		   (else (list (cdr a))))))
 	  (else (global-result id loc))))
   (define (always-true1 t)
@@ -126,9 +128,9 @@
 	(report 
 	 loc
 	 (sprintf
-	  "expected value of type boolean in conditional but were given a value of\ntype `~a' which is always true:~%~%~a"
-	  t
-	  (pp-fragment x))))
+	     "expected value of type boolean in conditional but were given a value of\ntype `~a' which is always true:~%~%~a"
+	   t
+	   (pp-fragment x))))
       f))
   (define (typename t)
     (case t
@@ -142,8 +144,8 @@
 		 (if (or (string? (cadr t)) (symbol? (cadr t)))
 		     (->string (cadr t))
 		     (sprintf "a procedure with ~a returning ~a"
-			      (argument-string (cadr t))
-			      (result-string (cddr t)))))
+		       (argument-string (cadr t))
+		       (result-string (cddr t)))))
 		((or)
 		 (string-intersperse
 		  (map typename (cdr t))
@@ -154,24 +156,24 @@
 	     (else (bomb "invalid type: ~a" t))))))
   (define (argument-string args)
     (let* ((len (length args))
-	  (m (multiples len)))
+	   (m (multiples len)))
       (if (zero? len)
 	  "zero arguments"
 	  (sprintf 
-	   "~a argument~a of type~a ~a"
-	   len m m
-	   (map typename args)))))
+	      "~a argument~a of type~a ~a"
+	    len m m
+	    (map typename args)))))
   (define (result-string results)
     (if (eq? '* results) 
 	"an unknown number of values"
 	(let* ((len (length results))
-	      (m (multiples len)))
+	       (m (multiples len)))
 	  (if (zero? len)
 	      "zero values"
 	      (sprintf 
-	       "~a value~a of type~a ~a"
-	       len m m
-	       (map typename results))))))
+		  "~a value~a of type~a ~a"
+		len m m
+		(map typename results))))))
   (define (simplify t)
     (let ((t2 (simplify1 t)))
       (d "simplify: ~a -> ~a" t t2)
@@ -207,7 +209,7 @@
 				   (let ((t (simplify t)))
 				     (cond ((and (pair? t) (eq? 'or (car t)))
 					    (cdr t))
-					   ;((eq? t 'noreturn) '())
+					;((eq? t 'noreturn) '())
 					   ((eq? t 'undefined) (return 'undefined))
 					   (else (list t)))))
 				 (cdr t)))
@@ -254,7 +256,7 @@
 		    ,(simplify 
 		      `(or ,(rest-type (cdr ts1))
 			   ,(rest-type (cdr ts2))))))
-		 (else '(#!rest))))		;XXX giving up
+		 (else '(#!rest))))	;XXX giving up
 	  ((eq? '#!optional (car ts1))
 	   (cond ((and (pair? ts2) (eq? '#!optional (car ts2)))
 		  `(#!optional 
@@ -263,7 +265,7 @@
 		 (else '(#!rest))))	;XXX
 	  (else (cons (simplify `(or ,(car ts1) ,(car ts2)))
 		      (merge-argument-types (cdr ts1) (cdr ts2))))))
-  (define (merge-result-types ts1 ts2)	;XXX possibly overly conservative
+  (define (merge-result-types ts1 ts2) ;XXX possibly overly conservative
     (cond ((null? ts1) ts2)
 	  ((null? ts2) ts1)
 	  ((or (atom? ts1) (atom? ts2)) '*)
@@ -391,7 +393,7 @@
 		 (report 
 		  loc
 		  (sprintf "expected ~a a single result, but were given ~a result~a"
-			   what n (multiples n)))
+		    what n (multiples n)))
 		 (first tv))))))
   (define (report loc desc)
     (warning
@@ -426,12 +428,12 @@
   (define (call-result args e loc x params)
     (define (pname)
       (sprintf "~ain procedure call to `~s', " 
-	  (if (and (pair? params) (pair? (cdr params)))
-	      (let ((n (source-info->line (cadr params))))
-		(if n
-		    (sprintf "~a: " n)
-		    ""))
-	      "")
+	(if (and (pair? params) (pair? (cdr params)))
+	    (let ((n (source-info->line (cadr params))))
+	      (if n
+		  (sprintf "~a: " n)
+		  ""))
+	    "")
 	(fragment x)))
     (d "call-result: ~a (~a)" args loc)
     (let* ((ptype (car args))
@@ -442,10 +444,10 @@
 	(report
 	 loc
 	 (sprintf
-	  "~aexpected a value of type `~a', but were given a value of type `~a'"
-	  (pname) 
-	  xptype
-	  ptype)))
+	     "~aexpected a value of type `~a', but were given a value of type `~a'"
+	   (pname) 
+	   xptype
+	   ptype)))
       (let-values (((atypes values-rest) (procedure-argument-types ptype (length (cdr args)))))
 	(d "  argument-types: ~a (~a)" atypes values-rest)
 	(unless (= (length atypes) nargs)
@@ -453,9 +455,9 @@
 	    (report 
 	     loc
 	     (sprintf
-	      "~aexpected ~a argument~a, but where given ~a argument~a"
-	      (pname) alen (multiples alen)
-	      nargs (multiples nargs)))))
+		 "~aexpected ~a argument~a, but where given ~a argument~a"
+	       (pname) alen (multiples alen)
+	       nargs (multiples nargs)))))
 	(do ((args (cdr args) (cdr args))
 	     (atypes atypes (cdr atypes))
 	     (i 1 (add1 i)))
@@ -464,8 +466,8 @@
 	    (report
 	     loc
 	     (sprintf
-	      "~aexpected argument #~a of type `~a', but where given an argument of type `~a'"
-	      (pname) i (car atypes) (car args)))))
+		 "~aexpected argument #~a of type `~a', but where given an argument of type `~a'"
+	       (pname) i (car atypes) (car args)))))
 	(let ((r (procedure-result-types ptype values-rest (cdr args))))
 	  (d  "  result-types: ~a" r)
 	  r))))
@@ -528,25 +530,38 @@
       ((let)
        (self-call? (last (node-subexpressions node)) loc))
       (else #f)))
-  (define (walk n e loc dest tail)		; returns result specifier
+  (define tag
+    (let ((n 0))
+      (lambda () 
+	(set! n (add1 n))
+	n)))
+  (define (invalidate-blist)
+    (for-each
+     (lambda (b)
+       (when (get db (car b) 'assigned)
+	 (set-cdr! b '*)))
+     blist))
+  (define (walk n e loc dest tail flow ctags) ; returns result specifier
     (let ((subs (node-subexpressions n))
 	  (params (node-parameters n)) 
 	  (class (node-class n)) )
-      (d "walk: ~a ~a (loc: ~a, dest: ~a, tail: ~a, e: ~a)" class params loc dest tail e)
+      (d "walk: ~a ~a (loc: ~a, dest: ~a, tail: ~a, flow: ~a, e: ~a)"
+	 class params loc dest tail flow e)
       (let ((results
 	     (case class
 	       ((quote) (list (constant-result (first params))))
 	       ((##core#undefined) '(*))
 	       ((##core#proc) '(procedure))
 	       ((##core#global-ref) (global-result (first params) loc))
-	       ((##core#variable) (variable-result (first params) e loc))
+	       ((##core#variable) (variable-result (first params) e loc flow))
 	       ((if)
-		(let ((rt (single "in conditional" (walk (first subs) e loc #f #f) loc))
-		      (c (second subs))
-		      (a (third subs)))
+		(let* ((tags (cons (tag) (tag)))
+		       (rt (single "in conditional" (walk (first subs) e loc #f #f flow tags) loc))
+		       (c (second subs))
+		       (a (third subs)))
 		  (always-true rt loc n)
-		  (let ((r1 (walk c e loc dest tail))
-			(r2 (walk a e loc dest tail)))
+		  (let ((r1 (walk c e loc dest tail (cons (car tags) flow) #f))
+			(r2 (walk a e loc dest tail (cons (cdr tags) flow) #f)))
 		    (cond ((and (not (eq? '* r1)) (not (eq? '* r2)))
 			   (when (and (not (any noreturn-type? r1))
 				      (not (any noreturn-type? r2))
@@ -563,10 +578,10 @@
 		;; before CPS-conversion, `let'-nodes may have multiple bindings
 		(let loop ((vars params) (body subs) (e2 '()))
 		  (if (null? vars)
-		      (walk (car body) (append e2 e) loc dest tail)
+		      (walk (car body) (append e2 e) loc dest tail flow #f)
 		      (let ((t (single 
 				(sprintf "in `let' binding of `~a'" (real-name (car vars)))
-				(walk (car body) e loc (car vars) #f) 
+				(walk (car body) e loc (car vars) #f flow #f) 
 				loc)))
 			(loop (cdr vars) (cdr body) (alist-cons (car vars) t e2))))))
 	       ((##core#lambda lambda)
@@ -577,23 +592,24 @@
 			  (args (append (make-list argc '*) (if rest '(#!rest) '()))) 
 			  (e2 (append (map (lambda (v) (cons v '*)) 
 					   (if rest (butlast vars) vars))
-				      e))
-			  (r (walk (first subs)
-				   (if rest (alist-cons rest 'list e2) e2)
-				   (add-loc dest loc)
-				   #f #t)))
-		     (list 
-		      (append
-		       '(procedure) 
-		       name
-		       (list args)
-		       r))))))
+				      e)))
+		     (fluid-let ((blist '()))
+		       (let ((r (walk (first subs)
+				      (if rest (alist-cons rest 'list e2) e2)
+				      (add-loc dest loc)
+				      #f #t (list (tag)) #f)))
+			 (list 
+			  (append
+			   '(procedure) 
+			   name
+			   (list args)
+			   r))))))))
 	       ((set! ##core#set!)
 		(let* ((var (first params))
 		       (type (##sys#get var '##core#type))
 		       (rt (single 
 			    (sprintf "in assignment to `~a'" var)
-			    (walk (first subs) e loc var #f)
+			    (walk (first subs) e loc var #f flow #f)
 			    loc))
 		       (b (assq var e)) )
 		  (when (and type (not b)
@@ -602,10 +618,12 @@
 		    (report
 		     loc
 		     (sprintf 
-		      "assignment of value of type `~a' to toplevel variable `~a' does not match declared type `~a'"
-		      rt var type)))
+			 "assignment of value of type `~a' to toplevel variable `~a' does not match declared type `~a'"
+		       rt var type)))
 		  (when (and b (eq? 'undefined (cdr b)))
 		    (set-cdr! b rt))
+		  (when b
+		    (set! blist (alist-cons (cons var (car flow)) rt blist)))
 		  '(undefined)))
 	       ((##core#primitive ##core#inline_ref) '*)
 	       ((##core#call)
@@ -613,22 +631,40 @@
 		       (args (map (lambda (n i)
 				    (single 
 				     (sprintf 
-				      "in ~a of procedure call `~s'"
-				      (if (zero? i)
-					  "operator position"
-					  (sprintf "argument #~a" i))
-				      f)
-				     (walk n e loc #f #f) loc))
+					 "in ~a of procedure call `~s'"
+				       (if (zero? i)
+					   "operator position"
+					   (sprintf "argument #~a" i))
+				       f)
+				     (walk n e loc #f #f flow #f) loc))
 				  subs (iota (length subs)))))
-		  (call-result args e loc (first subs) params)))
+		  (let ((r (call-result args e loc (first subs) params))
+			(f #f))
+		    (invalidate-blist)
+		    (for-each
+		     (lambda (arg argr)
+		       (when (eq? '##core#variable (node-class arg))
+			 (let* ((var (first (node-parameters arg)))
+				(a (assq var e)))
+			   (when a
+			     (set! blist 
+			       (alist-cons
+				(cons var (car flow))
+				(merge-result-types 
+				 (list (if f argr (if (eq? '* argr) 'procedure argr)))
+				 (list argr))
+				blist)))))
+		       (set! f #t))
+		     subs args)
+		    r)))
 	       ((##core#switch ##core#cond)
 		(bomb "unexpected node class: ~a" class))
 	       (else
-		(for-each (lambda (n) (walk n e loc #f #f)) subs)
+		(for-each (lambda (n) (walk n e loc #f #f flow #f)) subs)
 		'*))))
 	(d "  -> ~a" results)
 	results)))
-  (walk (first (node-subexpressions node)) '() '() #f #f))
+  (walk (first (node-subexpressions node)) '() '() #f #f (list (tag)) #f)))
 
 (define (load-type-database name #!optional (path (repository-path)))
   (and-let* ((dbfile (file-exists? (make-pathname path name))))
Trap