~ chicken-core (chicken-5) 1cb05bb5d90a81580da7b3d33d55d1b52c60f65b
commit 1cb05bb5d90a81580da7b3d33d55d1b52c60f65b Author: megane <meganeka@gmail.com> AuthorDate: Fri Nov 16 18:07:23 2018 +0200 Commit: Evan Hanson <evhan@foldling.org> CommitDate: Sat Mar 9 20:28:05 2019 +1300 Extract most scrutinizer messages into separate functions There are two main benefits from doing this: * Message formatting logic doesn't muddy the actual scrutinizer logic. * Giving names has a documentation benefit. If you refer to that name in tests you can grep for an examples of what might cause that error message. Changes: * scrutinizer.scm (scrutinize): Copy report, report-notice to toplevel as report2, report-notice2 * scrutinizer.scm: Add global *complain?*, needed by report2, report-notice2 * scrutinizer.scm: Move multiples, node-source-prefix, location-name, fragment, pp-fragment under comment "Report helpers" * scrutinizer.scm (scrutinize): Remove report-error so 'errors' variable doesn't need to be made global As a side effect (the symbol 1) now always gives a warning, which is for the best; If you annotate a * return value to have some type with 'the, there will be no warning without -verbose if the return value changes to some explicit, and possibly incompatible, type. A trivial example: You want to write this: (+ 1 (foo)) You also know foo returns a fixnum, but foo does not have a type annotation. So you might annotate the return value yourself: (+ 1 (the fixnum (foo))) Now the + is likely specialized to fx+, which is fine. Now the API for foo changes: foo returns a string and is given a type annotation (-> string). Now your code that annotates the return type is wrong. You are calling fx+ with a string value. That will lead to undefined behaviour. Before this commit you'll only see a warning when you use the -verbose flag. Signed-off-by: Evan Hanson <evhan@foldling.org> diff --git a/scrutinizer.scm b/scrutinizer.scm index a8c8b3de..988f56cc 100644 --- a/scrutinizer.scm +++ b/scrutinizer.scm @@ -53,6 +53,7 @@ (define d-depth 0) (define scrutiny-debug #t) +(define *complain?* #f) (define (d fstr . args) (when (and scrutiny-debug (##sys#debug-mode?)) @@ -162,9 +163,6 @@ (define specialization-statistics '()) (define trail '()) -(define (multiples n) - (if (= n 1) "" "s")) - (define (walked-result n) (first (node-parameters n))) ; assumes ##core#the/result node @@ -177,25 +175,21 @@ ((memq t '(eof null fixnum char boolean undefined)) #t) (else #f))) -(define (node-source-prefix n) - (let ((line (node-line-number n))) - (if (not line) "" (sprintf "(~a) " line)))) +(define (scrutinize node db complain specialize strict block-compilation) + (define (report-notice loc msg . args) + (when *complain?* + (##sys#notice + (conc (location-name loc) + (sprintf "~?" msg (map type-name args)))))) -(define (location-name loc) - (define (lname loc1) - (if loc1 - (sprintf "procedure `~a'" (real-name loc1)) - "unknown procedure")) - (cond ((null? loc) "at toplevel:\n ") - ((null? (cdr loc)) - (sprintf "in toplevel ~a:\n " (lname (car loc)))) - (else - (let rec ((loc loc)) - (if (null? (cdr loc)) - (location-name loc) - (sprintf "in local ~a,\n ~a" (lname (car loc)) (rec (cdr loc)))))))) + (define (report loc msg . args) + (when *complain?* + (warning + (conc (location-name loc) + (sprintf "~?" msg (map type-name args)))))) + + (set! *complain?* complain) -(define (scrutinize node db complain specialize strict block-compilation) (let ((blist '()) ; (((VAR . FLOW) TYPE) ...) (aliased '()) (noreturn #f) @@ -284,17 +278,12 @@ (define (always-true if-node test-node t loc) (and-let* ((_ (always-true1 t))) - (report-notice - loc "~aexpected a value of type boolean in conditional, but \ - was given a value of type `~a' which is always true:~%~%~a" - (node-source-prefix test-node) t (pp-fragment if-node)) + (r-cond-test-always-true loc test-node t if-node) #t)) (define (always-false if-node test-node t loc) (and-let* ((_ (eq? t 'false))) - (report-notice - loc "~ain conditional, test expression will always return false:~%~%~a" - (node-source-prefix test-node) (pp-fragment if-node)) + (r-cond-test-always-false loc test-node if-node) #t)) (define (always-immediate var t loc) @@ -320,43 +309,8 @@ (node-source-prefix node) what n (multiples n)) (first tv)))))) - (define (report-notice loc msg . args) - (when complain - (##sys#notice - (conc (location-name loc) - (sprintf "~?" msg (map type-name args)))))) - - (define (report loc msg . args) - (when complain - (warning - (conc (location-name loc) - (sprintf "~?" msg (map type-name args)))))) - - (define (report-error loc msg . args) - (set! errors #t) - (apply report loc msg args)) - (define add-loc cons) - (define (fragment x) - (let ((x (build-expression-tree (source-node-tree x)))) - (let walk ((x x) (d 0)) - (cond ((atom? x) (strip-syntax x)) - ((>= d +fragment-max-depth+) '...) - ((list? x) - (let* ((len (length x)) - (xs (if (< +fragment-max-length+ len) - (append (take x +fragment-max-length+) '(...)) - x))) - (map (cute walk <> (add1 d)) xs))) - (else (strip-syntax x)))))) - - (define (pp-fragment x) - (string-chomp - (with-output-to-string - (lambda () - (pp (fragment x)))))) - (define (get-specializations name) (let* ((a (variable-mark name '##compiler#local-specializations)) (b (variable-mark name '##compiler#specializations)) @@ -377,23 +331,14 @@ (op #f)) (d " call: ~a, te: ~a" actualtypes typeenv) (cond ((and (not pptype?) (not (match-types xptype ptype typeenv))) - (report - loc - "~aexpected a value of type `~a' but was given a value of type `~a'" - (pname) - (resolve xptype typeenv) - (resolve ptype typeenv)) + (r-invalid-called-procedure-type + loc (pname) (resolve xptype typeenv) (resolve ptype typeenv)) (values '* #f)) (else (let-values (((atypes values-rest ok alen) (procedure-argument-types ptype nargs typeenv))) (unless ok - (report - loc - "~aexpected ~a argument~a but was given ~a argument~a" - (pname) - alen (multiples alen) - nargs (multiples nargs))) + (r-proc-call-argument-count-mismatch loc (pname) alen nargs)) (do ((actualtypes (cdr actualtypes) (cdr actualtypes)) (atypes atypes (cdr atypes)) (i 1 (add1 i))) @@ -402,11 +347,8 @@ (car atypes) (car actualtypes) typeenv) - (report - loc - "~aexpected argument #~a of type `~a' but was given an argument of type `~a'" - (pname) - i + (r-proc-call-argument-type-mismatch + loc (pname) i (resolve (car atypes) typeenv) (resolve (car actualtypes) typeenv)))) (when (noreturn-procedure-type? ptype) @@ -419,11 +361,7 @@ (variable-mark pn '##compiler#predicate)) => (lambda (pt) (cond ((match-argument-types (list pt) (cdr actualtypes) typeenv) - (report-notice - loc - "~athe predicate is called with an argument of type `~a' \ - and will always return true" - (pname) (cadr actualtypes)) + (r-pred-call-always-true loc (pname) (cadr actualtypes)) (when specialize (specialize-node! node (cdr args) @@ -433,11 +371,7 @@ ((begin (trail-restore trail0 typeenv) (match-argument-types (list `(not ,pt)) (cdr actualtypes) typeenv)) - (report-notice - loc - "~athe predicate is called with an argument of type `~a' \ - and will always return false" - (pname) (cadr actualtypes)) + (r-pred-call-always-false loc (pname) (cadr actualtypes)) (when specialize (specialize-node! node (cdr args) @@ -568,10 +502,7 @@ ;;(dd " branches: ~s:~s / ~s:~s" nor1 r1 nor2 r2) (cond ((and (not nor1) (not nor2) (not (= (length r1) (length r2)))) - (report - loc - "branches in conditional expression differ in the number of results:~%~%~a" - (pp-fragment n)) + (r-cond-branch-value-count-mismatch loc n) '*) (nor1 r2) (nor2 r1) @@ -670,11 +601,8 @@ (and (pair? type) (eq? (car type) 'deprecated)))) (not (match-types type rt typeenv))) - ((if strict report-error report) - loc - "assignment of value of type `~a' to toplevel variable `~a' \ - does not match declared type `~a'" - rt var type)) + (when strict (set! errors #t)) + (r-toplevel-var-assignment-type-mismatch loc rt var type)) (when (and (not type) ;XXX global declaration could allow this (not b) (not (eq? '* rt)) @@ -837,24 +765,14 @@ (let ((t (first params)) (rt (walk (first subs) e loc dest tail flow ctags))) (cond ((eq? rt '*)) - ((null? rt) - (report - loc - "expression returns zero values but is declared to have \ - a single result of type `~a'" t)) + ((null? rt) (r-zero-values-for-the loc t)) (else (when (> (length rt) 1) - (report - loc - "expression returns ~a values but is declared to have \ - a single result" (length rt))) + (r-too-many-values-for-the loc rt)) (when (and (second params) (not (compatible-types? t (first rt)))) - ((if strict report-error report-notice) - loc - "expression returns a result of type `~a' but is \ - declared to return `~a', which is not compatible" - (first rt) t)))) + (when strict (set! errors #t)) + (r-type-mismatch-in-the loc (first rt) t)))) (list t))) ((##core#typecase) (let* ((ts (walk (first subs) e loc #f #f flow ctags)) @@ -863,14 +781,7 @@ ;; first exp is always a variable so ts must be of length 1 (let loop ((types (cdr params)) (subs (cdr subs))) (if (null? types) - (quit-compiling - "~a~ano clause applies in `compiler-typecase' for expression of type `~a':~a" - (location-name loc) - (node-source-prefix n) - (type-name (car ts)) - (string-intersperse - (map (lambda (t) (sprintf "\n ~a" (type-name t))) - (cdr params)) "")) + (fail-compiler-typecase loc n (car ts) (cdr params)) (let ((typeenv (append (type-typeenv (car types)) typeenv0))) (if (match-types (car types) (car ts) typeenv #t) (begin ; drops exp @@ -2484,4 +2395,148 @@ (else (restore) (loop (cdr ts) ok)))))) + +;;; Report helpers + +(define (multiples n) + (if (= n 1) "" "s")) + +(define (fragment x) + (let ((x (build-expression-tree (source-node-tree x)))) + (let walk ((x x) (d 0)) + (cond ((atom? x) (strip-syntax x)) + ((>= d +fragment-max-depth+) '...) + ((list? x) + (let* ((len (length x)) + (xs (if (< +fragment-max-length+ len) + (append (take x +fragment-max-length+) '(...)) + x))) + (map (cute walk <> (add1 d)) xs))) + (else (strip-syntax x)))))) + +(define (pp-fragment x) + (string-chomp + (with-output-to-string + (lambda () + (pp (fragment x)))))) + +(define (node-source-prefix n) + (let ((line (node-line-number n))) + (if (not line) "" (sprintf "(~a) " line)))) + +(define (location-name loc) + (define (lname loc1) + (if loc1 + (sprintf "procedure `~a'" (real-name loc1)) + "unknown procedure")) + (cond ((null? loc) "at toplevel:\n ") + ((null? (cdr loc)) + (sprintf "in toplevel ~a:\n " (lname (car loc)))) + (else + (let rec ((loc loc)) + (if (null? (cdr loc)) + (location-name loc) + (sprintf "in local ~a,\n ~a" (lname (car loc)) (rec (cdr loc)))))))) + +(define (report2 loc msg . args) + (when *complain?* + (warning + (conc (location-name loc) + (sprintf "~?" msg (map type-name args)))))) + +(define (report-notice2 loc msg . args) + (when *complain?* + (##sys#notice + (conc (location-name loc) + (sprintf "~?" msg (map type-name args)))))) + +;;; Reports + +(define (r-invalid-called-procedure-type loc pname xptype ptype) + (report2 + loc + "~aexpected a value of type `~a' but was given a value of type `~a'" + pname xptype ptype)) + +(define (r-proc-call-argument-count-mismatch loc pname exp-count argc) + (report2 + loc + "~aexpected ~a argument~a but was given ~a argument~a" + pname + exp-count (multiples exp-count) + argc (multiples argc))) + +(define (r-proc-call-argument-type-mismatch loc pname i xptype atype) + (report2 + loc + "~aexpected argument #~a of type `~a' but was given an argument of type `~a'" + pname i xptype atype)) + +(define (r-pred-call-always-true loc pname atype) + (report-notice2 + loc + "~athe predicate is called with an argument of type `~a' \ + and will always return true" + pname atype)) + +(define (r-pred-call-always-false loc pname atype) + (report-notice2 + loc + "~athe predicate is called with an argument of type `~a' \ + and will always return false" + pname atype)) + +(define (r-cond-test-always-true loc test-node t if-node) + (report-notice2 + loc "~aexpected a value of type boolean in conditional, but \ + was given a value of type `~a' which is always true:~%~%~a" + (node-source-prefix test-node) t (pp-fragment if-node))) + +(define (r-cond-test-always-false loc test-node if-node) + (report-notice2 + loc "~ain conditional, test expression will always return false:~%~%~a" + (node-source-prefix test-node) (pp-fragment if-node))) + +(define (r-zero-values-for-the loc the-type) + ;; (the t r) expects r returns exactly 1 value + (report2 + loc + "expression returns zero values but is declared to have \ + a single result of type `~a'" + the-type)) + +(define (r-too-many-values-for-the loc rtypes) + (report2 + loc + "expression returns ~a values but is declared to have \ + a single result" (length rtypes))) + +(define (r-type-mismatch-in-the loc first-rtype the-type) + (report2 + loc + "expression returns a result of type `~a' but is \ + declared to return `~a', which is not compatible" + first-rtype the-type)) + +(define (fail-compiler-typecase loc node atype ct-types) + (quit-compiling + "~a~ano clause applies in `compiler-typecase' for expression of type `~a':~a" + (location-name loc) + (node-source-prefix node) + (type-name atype) + (string-intersperse (map (lambda (t) (sprintf "\n ~a" (type-name t))) ct-types) + ""))) + +(define (r-cond-branch-value-count-mismatch loc node) + (report2 + loc + "branches in conditional expression differ in the number of results:~%~%~a" + (pp-fragment node))) + +(define (r-toplevel-var-assignment-type-mismatch loc atype var xptype) + (report2 + loc + "assignment of value of type `~a' to toplevel variable `~a' \ + does not match declared type `~a'" + atype var xptype)) ) diff --git a/tests/scrutinizer-message-format.expected b/tests/scrutinizer-message-format.expected index 4c8fcc89..1855a5fc 100644 --- a/tests/scrutinizer-message-format.expected +++ b/tests/scrutinizer-message-format.expected @@ -44,7 +44,7 @@ Note: in toplevel procedure `r-cond-test-always-false': (if #f 1 (##core#undefined)) -Note: in toplevel procedure `r-type-mismatch-in-the': +Warning: in toplevel procedure `r-type-mismatch-in-the': expression returns a result of type `fixnum' but is declared to return `symbol', which is not compatible Warning: in toplevel procedure `r-zero-values-for-the': @@ -53,7 +53,7 @@ Warning: in toplevel procedure `r-zero-values-for-the': Warning: in toplevel procedure `r-too-many-values-for-the': expression returns 2 values but is declared to have a single result -Note: in toplevel procedure `r-too-many-values-for-the': +Warning: in toplevel procedure `r-too-many-values-for-the': expression returns a result of type `fixnum' but is declared to return `symbol', which is not compatible Warning: in toplevel procedure `r-toplevel-var-assignment-type-mismatch': @@ -155,7 +155,7 @@ Note: in local procedure `r-cond-test-always-false', (if #f 1 (##core#undefined)) -Note: in local procedure `r-type-mismatch-in-the', +Warning: in local procedure `r-type-mismatch-in-the', in local procedure `local-bar', in toplevel procedure `m#toplevel-foo': expression returns a result of type `fixnum' but is declared to return `symbol', which is not compatible @@ -170,7 +170,7 @@ Warning: in local procedure `r-too-many-values-for-the', in toplevel procedure `m#toplevel-foo': expression returns 2 values but is declared to have a single result -Note: in local procedure `r-too-many-values-for-the', +Warning: in local procedure `r-too-many-values-for-the', in local procedure `local-bar', in toplevel procedure `m#toplevel-foo': expression returns a result of type `fixnum' but is declared to return `symbol', which is not compatible diff --git a/tests/scrutiny.expected b/tests/scrutiny.expected index 0bc6a8dd..bab33e34 100644 --- a/tests/scrutiny.expected +++ b/tests/scrutiny.expected @@ -79,7 +79,7 @@ Warning: in toplevel procedure `foo10': Warning: in toplevel procedure `foo10': (scrutiny-tests.scm:XXX) in procedure call to `scheme#+', expected argument #1 of type `number' but was given an argument of type `string' -Note: in toplevel procedure `foo10': +Warning: in toplevel procedure `foo10': expression returns a result of type `string' but is declared to return `pair', which is not compatible Warning: in toplevel procedure `foo10':Trap