;;; lint for s7 scheme ;;; ;;; (lint "file.scm") checks file.scm for infelicities ;;; to control the kinds of checks, set the variables below. ;;; for tests and examples, see lint-test in s7test.scm (provide 'lint.scm) (define *report-unused-parameters* #f) ; many of these are reported anyway if they are passed some non-#f value (define *report-unused-top-level-functions* #f) ; very common in Scheme, but #t makes the ghastly leakage of names obvious (define *report-shadowed-variables* #f) ; shadowed parameters, etc (define *report-undefined-identifiers* #f) ; names we can't account for (define *report-multiply-defined-top-level-functions* #f) ; top-level funcs defined in more than one file (define *report-nested-if* 4) ; 3 is lowest, this sets the nesting level that triggers an if->cond suggestion (define *report-short-branch* 12) ; controls when a lop-sided if triggers a reordering suggestion (define *report-one-armed-if* 90) ; if -> when/unless, can be #f/#t; if an integer, sets tree length which triggers revision (80 is too small) (define *report-loaded-files* #f) ; if load is encountered, include that file in the lint process (define *report-any-!-as-setter* #t) ; unknown funcs/macros ending in ! are treated as setters (define *report-doc-strings* #f) ; old-style (CL) doc strings (define *report-func-as-arg-arity-mismatch* #f) ; as it says... (slow, and this error almost never happens) (define *report-constant-expressions-in-do* #f) ; kinda dumb (define *report-bad-variable-names* '(l ll O ~)) ; bad names -- a list to check such as: ;;; '(l ll .. ~ data datum new item info temp tmp temporary val vals value foo bar baz aux dummy O var res retval result count str) (define *report-built-in-functions-used-as-variables* #f) ; string and length are the most common cases (define *report-forward-functions* #f) ; functions used before being defined (define *report-sloppy-assoc* #t) ; i.e. (cdr (assoc x y)) and the like (define *report-bloated-arg* 24) ; min arg expr tree size that can trigger a rewrite-as-let suggestion (32 is too high I think) (define *report-clobbered-function-return-value* #f) ; function returns constant sequence, which is then stomped on -- very rare! (define *report-boolean-functions-misbehaving* #t) ; function name ends in #\? but function returns a non-boolean value -- dubious. (define *report-repeated-code-fragments* #t) ;;; work-in-progress (define *fragments-size* 128) ; biggest seen if 512: 180 -- appears to be in a test suite (define *report-blocks* #f) ; report huge blocks that could be moved into the closure (define *lint* #f) ; the lint let ;; this gives other programs a way to extend or edit lint's tables: for example, the ;; table of functions that are simple (no side effects) is (*lint* 'no-side-effect-functions) ;; see snd-lint.scm. ;;; -------------------------------------------------------------------------------- (when (provided? 'pure-s7) (define (make-polar mag ang) (complex (* mag (cos ang)) (* mag (sin ang)))) (define (char-ci=? . chars) (apply char=? (map char-upcase chars))) (define (char-ci<=? . chars) (apply char<=? (map char-upcase chars))) (define (char-ci>=? . chars) (apply char>=? (map char-upcase chars))) (define (char-ci? . chars) (apply char>? (map char-upcase chars))) (define (string-ci=? . strs) (apply string=? (map string-upcase strs))) (define (string-ci<=? . strs) (apply string<=? (map string-upcase strs))) (define (string-ci>=? . strs) (apply string>=? (map string-upcase strs))) (define (string-ci? . strs) (apply string>? (map string-upcase strs))) (define (let->list e) (if (let? e) (reverse! (map values e)) (error 'wrong-type-arg "let->list argument should be an environment: ~A" str)))) (format *stderr* "loading lint.scm~%") (set! reader-cond #f) (define-macro (reader-cond . clauses) `(values)) ; clobber reader-cond to avoid (incorrect) unbound-variable errors #| ;; debugging version (define-expansion (lint-format str caller . args) `(begin (format outport "lint.scm line ~A~%" ,(port-line-number)) (lint-format-1 ,str ,caller ,@args))) (define-expansion (lint-format* caller . args) `(begin (format outport "lint.scm line ~A~%" ,(port-line-number)) (lint-format*-1 ,caller ,@args))) |# ;;; -------------------------------------------------------------------------------- (define lint (let ((no-side-effect-functions (let ((ht (make-hash-table))) (for-each (lambda (op) (hash-table-set! ht op #t)) '(* + - / < <= = > >= abs acos acosh and angle append aritable? arity ash asin asinh assoc assq assv atan atanh begin boolean? byte-vector byte-vector? caaaar caaadr caaar caadar caaddr caadr caar cadaar cadadr cadar caddar cadddr caddr cadr call-with-input-string call-with-input-file c-pointer c-pointer? c-object? call-with-exit car case catch cdaaar cdaadr cdaar cdadar cdaddr cdadr cdar cddaar cddadr cddar cdddar cddddr cdddr cddr cdr ceiling char->integer char-alphabetic? char-ci<=? char-ci=? char-ci>? char-downcase char-lower-case? char-numeric? char-position char-ready? char-upcase char-upper-case? char-whitespace? char<=? char=? char>? char? complex complex? cond cons constant? continuation? cos cosh curlet current-error-port current-input-port current-output-port cyclic-sequences defined? denominator dilambda? do dynamic-wind eof-object? eq? equal? eqv? even? exact->inexact exact? exp expt float? float-vector float-vector-ref float-vector? floor for-each funclet gcd gensym gensym? ; why was gensym omitted earlier? hash-table hash-table* hash-table-entries hash-table-ref hash-table? help hook-functions if imag-part inexact->exact inexact? infinite? inlet input-port? int-vector int-vector-ref int-vector? iterator-at-end? iterator-sequence integer->char integer-decode-float integer-length integer? iterator? keyword->symbol keyword? lambda lambda* lcm let->list length let let* let-ref let? letrec letrec* list list->string list->vector list-ref list-tail list? log logand logbit? logior lognot logxor macro? magnitude make-byte-vector make-float-vector make-int-vector make-hash-table make-hook make-iterator make-keyword make-list make-polar make-rectangular make-shared-vector make-string make-vector map max member memq memv min modulo morally-equal? nan? negative? not null? number->string number? numerator object->string odd? openlet? or outlet output-port? owlet pair-line-number pair-filename pair? port-closed? port-filename port-line-number positive? procedure-documentation procedure-setter procedure-signature procedure-source procedure? proper-list? provided? quasiquote quote quotient random-state random-state->list random-state? rational? rationalize real-part real? remainder reverse rootlet round s7-version sequence? sin sinh square sqrt stacktrace string string->list string->number string->symbol string-append string-ci<=? string-ci=? string-ci>? string-downcase string-length string-position string-ref string-upcase string<=? string=? string>? string? sublet substring symbol symbol->dynamic-value symbol->keyword symbol->string symbol->value symbol? tan tanh tree-leaves truncate unless values vector vector-append vector->list vector-dimensions vector-length vector-ref vector? when with-baffle with-let with-input-from-file with-input-from-string with-output-to-string zero? #_{list} #_{apply_values} #_{append} unquote)) ;; do not include file-exists? or directory? ;; should this include peek-char or unlet ? ht)) (built-in-functions (let ((ht (make-hash-table))) (for-each (lambda (op) (hash-table-set! ht op #t)) '(symbol? gensym? keyword? let? openlet? iterator? constant? macro? c-pointer? c-object? input-port? output-port? eof-object? integer? number? real? complex? rational? random-state? char? string? list? pair? vector? float-vector? int-vector? byte-vector? hash-table? continuation? procedure? dilambda? boolean? float? proper-list? sequence? null? gensym symbol->string string->symbol symbol symbol->value symbol->dynamic-value symbol-access make-keyword symbol->keyword keyword->symbol outlet rootlet curlet unlet sublet varlet cutlet inlet owlet coverlet openlet let-ref let-set! make-iterator iterate iterator-sequence iterator-at-end? provided? provide defined? c-pointer port-line-number port-filename pair-line-number pair-filename port-closed? current-input-port current-output-port current-error-port let->list char-ready? close-input-port close-output-port flush-output-port open-input-file open-output-file open-input-string open-output-string get-output-string newline write display read-char peek-char write-char write-string read-byte write-byte read-line read-string read call-with-input-string call-with-input-file with-input-from-string with-input-from-file call-with-output-string call-with-output-file with-output-to-string with-output-to-file real-part imag-part numerator denominator even? odd? zero? positive? negative? infinite? nan? complex magnitude angle rationalize abs exp log sin cos tan asin acos atan sinh cosh tanh asinh acosh atanh sqrt expt floor ceiling truncate round lcm gcd + - * / max min quotient remainder modulo = < > <= >= logior logxor logand lognot ash random-state random inexact->exact exact->inexact integer-length make-polar make-rectangular logbit? integer-decode-float exact? inexact? random-state->list number->string string->number char-upcase char-downcase char->integer integer->char char-upper-case? char-lower-case? char-alphabetic? char-numeric? char-whitespace? char=? char? char<=? char>=? char-position string-position make-string string-ref string-set! string=? string? string<=? string>=? char-ci=? char-ci? char-ci<=? char-ci>=? string-ci=? string-ci? string-ci<=? string-ci>=? string-copy string-fill! list->string string-length string->list string-downcase string-upcase string-append substring string object->string format cons car cdr set-car! set-cdr! caar cadr cdar cddr caaar caadr cadar cdaar caddr cdddr cdadr cddar caaaar caaadr caadar cadaar caaddr cadddr cadadr caddar cdaaar cdaadr cdadar cddaar cdaddr cddddr cddadr cdddar assoc member list list-ref list-set! list-tail make-list length copy fill! reverse reverse! sort! append assq assv memq memv vector-append list->vector vector-fill! vector-length vector->list vector-ref vector-set! vector-dimensions make-vector make-shared-vector vector float-vector make-float-vector float-vector-set! float-vector-ref int-vector make-int-vector int-vector-set! int-vector-ref string->byte-vector byte-vector make-byte-vector hash-table hash-table* make-hash-table hash-table-ref hash-table-set! hash-table-entries cyclic-sequences call/cc call-with-current-continuation call-with-exit load autoload eval eval-string apply for-each map dynamic-wind values catch throw error procedure-documentation procedure-signature help procedure-source funclet procedure-setter arity aritable? not eq? eqv? equal? morally-equal? gc s7-version emergency-exit exit dilambda make-hook hook-functions stacktrace tree-leaves object->let #_{list} #_{apply_values} #_{append} unquote)) ht)) (makers (let ((h (make-hash-table))) (for-each (lambda (op) (set! (h op) #t)) '(gensym sublet inlet make-iterator let->list random-state random-state->list number->string object->let make-string string string-copy copy list->string string->list string-append substring object->string format cons list make-list reverse append vector-append list->vector vector->list make-vector make-shared-vector vector make-float-vector float-vector make-int-vector int-vector byte-vector hash-table hash-table* make-hash-table make-hook #_{list} #_{append} gentemp)) ; gentemp for other schemes h)) (non-negative-ops (let ((h (make-hash-table))) (for-each (lambda (op) (set! (h op) #t)) '(string-length vector-length abs magnitude denominator gcd lcm tree-leaves char->integer byte-vector-ref byte-vector-set! hash-table-entries write-byte char-position string-position pair-line-number port-line-number)) h)) (numeric-ops (let ((h (make-hash-table))) (for-each (lambda (op) (set! (h op) #t)) '(+ * - / sin cos tan asin acos atan sinh cosh tanh asinh acosh atanh log exp expt sqrt make-polar complex imag-part real-part abs magnitude angle max min exact->inexact modulo remainder quotient lcm gcd rationalize inexact->exact random logior lognot logxor logand numerator denominator floor round truncate ceiling ash)) h)) (bools (let ((h (make-hash-table))) (for-each (lambda (op) (set! (h op) #t)) '(symbol? integer? rational? real? number? complex? float? keyword? gensym? byte-vector? string? list? sequence? char? boolean? float-vector? int-vector? vector? let? hash-table? input-port? null? pair? proper-list? output-port? iterator? continuation? dilambda? procedure? macro? random-state? eof-object? c-pointer? unspecified? c-object? constant?)) h)) (booleans (let ((h (make-hash-table))) (for-each (lambda (op) (set! (h op) #t)) '(symbol? integer? rational? real? number? complex? float? keyword? gensym? byte-vector? string? list? sequence? char? boolean? float-vector? int-vector? vector? let? hash-table? input-port? null? pair? proper-list? output-port? iterator? continuation? dilambda? procedure? macro? random-state? eof-object? c-pointer? c-object? unspecified? exact? inexact? defined? provided? even? odd? char-whitespace? char-numeric? char-alphabetic? negative? positive? zero? constant? infinite? nan? char-upper-case? char-lower-case? directory? file-exists?)) h)) (reversibles (let ((h (make-hash-table))) (for-each (lambda (op) (set! (h (car op)) (cadr op))) '((< >) (> <) (<= >=) (>= <=) (* *) (+ +) (= =) (char=? char=?) (string=? string=?) (eq? eq?) (eqv? eqv?) (equal? equal?) (morally-equal? morally-equal?) (logand logand) (logxor logxor) (logior logior) (max max) (min min) (lcm lcm) (gcd gcd) (char?) (char>? char=?) (char>=? char<=?) (string?) (string>? string=?) (string>=? string<=?) (char-ci?) (char-ci>? char-ci=?) (char-ci>=? char-ci<=?) (string-ci?) (string-ci>? string-ci=?) (string-ci>=? string-ci<=?))) h)) (syntaces (let ((h (make-hash-table))) (for-each (lambda (op) (set! (h op) #t)) '(quote if begin let let* letrec letrec* cond case or and do set! unless when with-let with-baffle lambda lambda* define define* define-macro define-macro* define-bacro define-bacro* define-constant define-expansion)) h)) (outport #t) (linted-files ()) (big-constants (make-hash-table)) (other-names-counts (make-hash-table)) (*e* #f) (other-identifiers (make-hash-table)) (quote-warnings 0) (last-simplify-boolean-line-number -1) (last-simplify-numeric-line-number -1) (last-simplify-cxr-line-number -1) (last-if-line-number -1) (last-checker-line-number -1) (last-cons-line-number -1) (last-rewritten-internal-define #f) (line-number -1) (pp-left-margin 4) (lint-left-margin 1) (*current-file* "") (*top-level-objects* (make-hash-table)) (*output-port* *stderr*) (fragments (let ((v (make-vector *fragments-size* #f))) (do ((i 0 (+ i 1))) ((= i *fragments-size*)) (set! (v i) (make-hash-table))) v)) (*max-cdr-len* 16)) ; 40 is too high, 24 questionable, if #f the let+do rewrite is turned off (set! *e* (curlet)) (set! *lint* *e*) ; external access to (for example) the built-in-functions hash-table via (*lint* 'built-in-functions) ;; -------- lint-format -------- (define target-line-length 80) (define (truncated-list->string form) ;; return form -> string with limits on its length (let* ((str (object->string form)) (len (length str))) (if (< len target-line-length) str (do ((i (- target-line-length 6) (- i 1))) ((or (= i 40) (char-whitespace? (str i))) (string-append (substring str 0 (if (<= i 40) (- target-line-length 6) i)) "...")))))) (define lint-pp #f) ; avoid crosstalk with other schemes' definitions of pp and pretty-print (make-var also collides) (define lint-pretty-print #f) (let () (require write.scm) (set! lint-pp pp); (set! lint-pretty-print pretty-print)) (define (lists->string f1 f2) ;; same but 2 strings that may need to be lined up vertically (let ((str1 (object->string f1)) (str2 (object->string f2))) (let ((len1 (length str1)) (len2 (length str2))) (when (> len1 target-line-length) (set! str1 (truncated-list->string f1)) (set! len1 (length str1))) (when (> len2 target-line-length) (set! ((funclet lint-pretty-print) '*pretty-print-left-margin*) pp-left-margin) (set! ((funclet lint-pretty-print) '*pretty-print-length*) (- 114 pp-left-margin)) (set! str2 (lint-pp f2)) (set! len2 (length str2))) (format #f (if (< (+ len1 len2) target-line-length) (values "~A -> ~A" str1 str2) (values "~%~NC~A ->~%~NC~A" pp-left-margin #\space str1 pp-left-margin #\space str2)))))) (define (truncated-lists->string f1 f2) ;; same but 2 strings that may need to be lined up vertically and both are truncated (let ((str1 (object->string f1)) (str2 (object->string f2))) (let ((len1 (length str1)) (len2 (length str2))) (when (> len1 target-line-length) (set! str1 (truncated-list->string f1)) (set! len1 (length str1))) (when (> len2 target-line-length) (set! str2 (truncated-list->string f2)) (set! len2 (length str2))) (format #f (if (< (+ len1 len2) target-line-length) (values "~A -> ~A" str1 str2) (values "~%~NC~A ->~%~NC~A" pp-left-margin #\space str1 pp-left-margin #\space str2)))))) (define made-suggestion 0) (define (lint-format str caller . args) (let ((outstr (apply format #f (string-append (if (< 0 line-number 100000) "~NC~A (line ~D): " "~NC~A: ") str "~%") lint-left-margin #\space (truncated-list->string caller) (if (< 0 line-number 100000) (values line-number args) args)))) (set! made-suggestion (+ made-suggestion 1)) (display outstr outport) (if (> (length outstr) (+ target-line-length 40)) (newline outport)))) (define (lint-format* caller . strs) (let* ((outstr (format #f (if (< 0 line-number 100000) "~NC~A (line ~D): " "~NC~A:~A") lint-left-margin #\space (truncated-list->string caller) (if (< 0 line-number 100000) line-number " "))) (current-end (length outstr))) ;; (set! made-suggestion (+ made-suggestion 1)) (display outstr outport) (for-each (lambda (s) (let ((len (length s))) (if (> (+ len current-end) target-line-length) (begin (format outport "~%~NC~A" (+ lint-left-margin 4) #\space s) (set! current-end len)) (begin (display s outport) (set! current-end (+ current-end len)))))) strs) (newline outport))) (define (local-line-number tree) (let ((tree-line (if (pair? tree) (pair-line-number tree) 0))) (if (and (< 0 tree-line 100000) (not (= tree-line line-number))) (format #f " (line ~D)" tree-line) ""))) ;; -------- vars -------- (define var-name car) (define (var? v) (and (pair? v) (let? (cdr v)))) (define var-member assq) (define var-ref (dilambda (lambda (v) (let-ref (cdr v) 'ref)) (lambda (v x) (let-set! (cdr v) 'ref x)))) (define var-set (dilambda (lambda (v) (let-ref (cdr v) 'set)) (lambda (v x) (let-set! (cdr v) 'set x)))) (define var-history (dilambda (lambda (v) (let-ref (cdr v) 'history)) (lambda (v x) (let-set! (cdr v) 'history x)))) (define var-ftype (dilambda (lambda (v) (let-ref (cdr v) 'ftype)) (lambda (v x) (let-set! (cdr v) 'ftype x)))) (define var-retcons (dilambda (lambda (v) (let-ref (cdr v) 'retcons)) (lambda (v x) (let-set! (cdr v) 'retcons x)))) (define var-arglist (dilambda (lambda (v) (let-ref (cdr v) 'arglist)) (lambda (v x) (let-set! (cdr v) 'arglist x)))) (define var-definer (dilambda (lambda (v) (let-ref (cdr v) 'definer)) (lambda (v x) (let-set! (cdr v) 'definer x)))) (define var-leaves (dilambda (lambda (v) (let-ref (cdr v) 'leaves)) (lambda (v x) (let-set! (cdr v) 'leaves x)))) (define var-scope (dilambda (lambda (v) (let-ref (cdr v) 'scope)) (lambda (v x) (let-set! (cdr v) 'scope x)))) (define var-setters (dilambda (lambda (v) (let-ref (cdr v) 'setters)) (lambda (v x) (let-set! (cdr v) 'setters x)))) (define var-env (dilambda (lambda (v) (let-ref (cdr v) 'env)) (lambda (v x) (let-set! (cdr v) 'env x)))) (define var-decl (dilambda (lambda (v) (let-ref (cdr v) 'decl)) (lambda (v x) (let-set! (cdr v) 'decl x)))) (define var-match-list (dilambda (lambda (v) (let-ref (cdr v) 'match-list)) (lambda (v x) (let-set! (cdr v) 'match-list x)))) (define var-initial-value (lambda (v) (let-ref (cdr v) 'initial-value))) ; not (easily) settable (define var-side-effect (dilambda (lambda (v) (if (null? (let-ref (cdr v) 'side-effect)) (let-set! (cdr v) 'side-effect (get-side-effect v)) (let-ref (cdr v) 'side-effect))) (lambda (v x) (let-set! (cdr v) 'side-effect x)))) (define var-signature (dilambda (lambda (v) (if (null? (let-ref (cdr v) 'signature)) (let-set! (cdr v) 'signature (get-signature v)) (let-ref (cdr v) 'signature))) (lambda (v x) (let-set! (cdr v) 'signature x)))) (define* (make-var name initial-value definer) (let ((old (hash-table-ref other-identifiers name))) (cons name (inlet 'initial-value initial-value 'definer definer 'history (if old (begin (hash-table-set! other-identifiers name #f) (if initial-value (cons initial-value old) old)) (if initial-value (list initial-value) ())) 'scope () 'setters () 'set 0 'ref (if old (length old) 0))))) ;; -------- the usual list functions -------- (define (remove item sequence) (cond ((null? sequence) ()) ((equal? item (car sequence)) (cdr sequence)) (else (cons (car sequence) (remove item (cdr sequence)))))) (define (remove-all item sequence) (map (lambda (x) (if (equal? x item) (values) x)) sequence)) (define (remove-if p lst) (cond ((null? lst) ()) ((p (car lst)) (remove-if p (cdr lst))) (else (cons (car lst) (remove-if p (cdr lst)))))) (define (lint-remove-duplicates lst env) (reverse (let rem-dup ((lst lst) (nlst ())) (cond ((null? lst) nlst) ((and (member (car lst) nlst) (not (and (pair? (car lst)) (side-effect? (car lst) env)))) (rem-dup (cdr lst) nlst)) (else (rem-dup (cdr lst) (cons (car lst) nlst))))))) (define applicable? arity) (define every? (let ((documentation "(every? func sequence) returns #t if func approves of every member of sequence") (signature '(boolean? procedure? sequence?))) (lambda (f sequence) (call-with-exit (lambda (return) (for-each (lambda (arg) (if (not (f arg)) (return #f))) sequence) #t))))) (define any? (let ((documentation "(any? func sequence) returns #t if func approves of any member of sequence") (signature '(boolean? procedure? sequence?))) (lambda (f sequence) (call-with-exit (lambda (return) (for-each (lambda (arg) (if (f arg) (return #t))) sequence) #f))))) (define collect-if (let ((documentation "(collect-if type func sequence) gathers the elements of sequence that satisfy func, and returns them via type:\n\ (collect-if list integer? #(1.4 2/3 1 1+i 2)) -> '(1 2)")) (lambda (type f sequence) (apply type (map (lambda (arg) (if (f arg) arg (values))) sequence))))) (define find-if (let ((documentation "(find-if func sequence) applies func to each member of sequence.\n\ If func approves of one, find-if returns that member of the sequence")) (lambda (f sequence) (call-with-exit (lambda (return) (for-each (lambda (arg) (if (f arg) (return arg))) sequence) #f))))) ;; -------- trees -------- (define copy-tree (let ((documentation "(copy-tree lst) returns a full copy of lst")) (lambda (lis) (if (pair? lis) (cons (copy-tree (car lis)) (copy-tree (cdr lis))) lis)))) (define (tree-count1 x tree count) (if (eq? x tree) (+ count 1) (if (or (>= count 2) (not (pair? tree)) (eq? (car tree) 'quote)) count (tree-count1 x (car tree) (tree-count1 x (cdr tree) count))))) (define (tree-count2 x tree count) (if (eq? x tree) (+ count 1) (if (or (>= count 3) (not (pair? tree)) (eq? (car tree) 'quote)) count (tree-count2 x (car tree) (tree-count2 x (cdr tree) count))))) (define (proper-tree? tree) (or (not (pair? tree)) (and (proper-list? tree) (every? proper-tree? (cdr tree))))) (define (gather-symbols tree) (let ((syms ())) (let walk ((p tree)) (if (pair? p) (if (symbol? (car p)) (if (not (eq? (car p) 'quote)) (for-each (lambda (a) (if (symbol? a) (if (not (memq a syms)) (set! syms (cons a syms))) (if (pair? a) (walk a)))) (cdr p))) (if (pair? (car p)) (begin (walk (car p)) (walk (cdr p))))) (if (and (symbol? tree) (not (memq tree syms))) (set! syms (cons tree syms))))) syms)) (define (tree-arg-member sym tree) (and (proper-list? tree) (or (and (memq sym (cdr tree)) tree) (and (pair? (car tree)) (tree-arg-member sym (car tree))) (and (pair? (cdr tree)) (call-with-exit (lambda (return) (for-each (lambda (b) (cond ((and (pair? b) (tree-arg-member sym b)) => return))) (cdr tree)) #f)))))) (define (tree-memq sym tree) ; ignore quoted lists, accept symbol outside a pair (or (eq? sym tree) (and (pair? tree) (not (eq? (car tree) 'quote)) (or (eq? (car tree) sym) (tree-memq sym (car tree)) (tree-memq sym (cdr tree)))))) (define (tree-member sym tree) (and (pair? tree) (or (eq? (car tree) sym) (tree-member sym (car tree)) (tree-member sym (cdr tree))))) (define (tree-equal-member sym tree) (and (pair? tree) (or (equal? (car tree) sym) (tree-member sym (car tree)) (tree-member sym (cdr tree))))) (define (tree-unquoted-member sym tree) (and (pair? tree) (not (eq? (car tree) 'quote)) (or (eq? (car tree) sym) (tree-unquoted-member sym (car tree)) (tree-unquoted-member sym (cdr tree))))) (define (tree-car-member sym tree) (and (pair? tree) (or (eq? (car tree) sym) (and (pair? (car tree)) (tree-car-member sym (car tree))) (and (pair? (cdr tree)) (member sym (cdr tree) tree-car-member))))) (define (tree-sym-set-member sym set tree) ; sym as arg, set as car (and (pair? tree) (or (memq (car tree) set) (and (pair? (car tree)) (tree-sym-set-member sym set (car tree))) (and (pair? (cdr tree)) (or (member sym (cdr tree)) (member #f (cdr tree) (lambda (a b) (tree-sym-set-member sym set b)))))))) (define (tree-set-member set tree) (and (pair? tree) (not (eq? (car tree) 'quote)) (or (memq (car tree) set) (tree-set-member set (car tree)) (tree-set-member set (cdr tree))))) (define (tree-table-member table tree) (and (pair? tree) (or (hash-table-ref table (car tree)) (tree-table-member table (car tree)) (tree-table-member table (cdr tree))))) (define (tree-set-car-member set tree) ; set as car (and (pair? tree) (or (and (memq (car tree) set) tree) (and (pair? (car tree)) (tree-set-car-member set (car tree))) (and (pair? (cdr tree)) (member #f (cdr tree) (lambda (a b) (tree-set-car-member set b))))))) (define (tree-table-car-member set tree) ; hash-table as car (and (pair? tree) (or (and (hash-table-ref set (car tree)) tree) (and (pair? (car tree)) (tree-table-car-member set (car tree))) (and (pair? (cdr tree)) (member #f (cdr tree) (lambda (a b) (tree-table-car-member set b))))))) (define (maker? tree) (tree-table-car-member makers tree)) (define (tree-symbol-walk tree syms) (if (pair? tree) (if (eq? (car tree) 'quote) (if (and (pair? (cdr tree)) (symbol? (cadr tree)) (not (memq (cadr tree) (car syms)))) (tree-symbol-walk (cddr tree) (begin (set-car! syms (cons (cadr tree) (car syms))) syms))) (if (eq? (car tree) {list}) (if (and (pair? (cdr tree)) (pair? (cadr tree)) (eq? (caadr tree) 'quote) (symbol? (cadadr tree)) (not (memq (cadadr tree) (cadr syms)))) (tree-symbol-walk (cddr tree) (begin (list-set! syms 1 (cons (cadadr tree) (cadr syms))) syms))) (begin (tree-symbol-walk (car tree) syms) (tree-symbol-walk (cdr tree) syms)))))) ;; -------- types -------- (define (quoted-undotted-pair? x) (and (pair? x) (eq? (car x) 'quote) (pair? (cdr x)) (pair? (cadr x)) (positive? (length (cadr x))))) (define (quoted-null? x) (and (pair? x) (eq? (car x) 'quote) (pair? (cdr x)) (null? (cadr x)))) (define (any-null? x) (or (null? x) (and (pair? x) (case (car x) ((quote) (and (pair? (cdr x)) (null? (cadr x)))) ((list) (null? (cdr x))) (else #f))))) (define (quoted-not? x) (and (pair? x) (eq? (car x) 'quote) (pair? (cdr x)) (not (cadr x)))) (define (quoted-symbol? x) (and (pair? x) (eq? (car x) 'quote) (pair? (cdr x)) (symbol? (cadr x)))) (define (code-constant? x) (and (or (not (symbol? x)) (keyword? x)) (or (not (pair? x)) (eq? (car x) 'quote)))) (define (just-symbols? form) (or (null? form) (symbol? form) (and (pair? form) (symbol? (car form)) (just-symbols? (cdr form))))) (define (list-any? f lst) (if (pair? lst) (or (f (car lst)) (list-any? f (cdr lst))) (f lst))) (define syntax? (let ((syns (let ((h (make-hash-table))) (for-each (lambda (x) (hash-table-set! h x #t)) (list quote if when unless begin set! let let* letrec letrec* cond and or case do lambda lambda* define define* define-macro define-macro* define-bacro define-bacro* define-constant with-baffle macroexpand with-let)) h))) (lambda (obj) ; a value, not a symbol (hash-table-ref syns obj)))) ;; -------- func info -------- (define (arg-signature fnc env) (and (symbol? fnc) (let ((fd (var-member fnc env))) (if (var? fd) (and (symbol? (var-ftype fd)) (var-signature fd)) (or (and (eq? *e* *lint*) (procedure-signature fnc)) (let ((f (symbol->value fnc *e*))) (and (procedure? f) (procedure-signature f)))))))) (define (arg-arity fnc env) (and (symbol? fnc) (let ((fd (var-member fnc env))) (if (var? fd) (and (not (eq? (var-decl fd) 'error)) (arity (var-decl fd))) (let ((f (symbol->value fnc *e*))) (and (procedure? f) (arity f))))))) (define (dummy-func caller form f) (catch #t (lambda () (eval f)) (lambda args (lint-format* caller (string-append "in " (truncated-list->string form) ", ") (apply format #f (cadr args)))))) (define (count-values body) (let ((mn #f) (mx #f)) (if (pair? body) (let counter ((ignored #f) ; 'ignored is for member's benefit (tree (list-ref body (- (length body) 1)))) (if (pair? tree) (if (eq? (car tree) 'values) (let ((args (- (length tree) 1))) (for-each (lambda (p) (if (and (pair? p) (eq? (car p) 'values)) (set! args (- (+ (args (length p)) 2))))) (cdr tree)) (set! mn (min (or mn args) args)) (set! mx (max (or mx args) args))) (begin (if (pair? (car tree)) (counter 'values (car tree))) (if (pair? (cdr tree)) (member #f (cdr tree) counter))))) #f)) ; return #f so member doesn't quit early (and mn (list mn mx)))) (define (get-signature v) (define (signer endb env) (and (not (side-effect? endb env)) (cond ((not (pair? endb)) (and (not (symbol? endb)) (list (->lint-type endb)))) ((arg-signature (car endb) env) => (lambda (a) (and (pair? a) (list (car a))))) ((and (eq? (car endb) 'if) (pair? (cddr endb))) (let ((a1 (signer (caddr endb) env)) (a2 (and (pair? (cdddr endb)) (signer (cadddr endb) env)))) (if (not a2) a1 (and (equal? a1 a2) a1)))) (else #f)))) (let ((ftype (var-ftype v)) (initial-value (var-initial-value v)) (arglist (var-arglist v)) (env (var-env v))) (let ((body (and (memq ftype '(define define* lambda lambda* let)) (cddr initial-value)))) (and (pair? body) (let ((sig (signer (list-ref body (- (length body) 1)) env))) (if (not (pair? sig)) (set! sig (list #t))) (when (and (proper-list? arglist) (not (any? keyword? arglist))) (for-each (lambda (arg) ; new function's parameter (set! sig (cons #t sig)) ;; (if (pair? arg) (set! arg (car arg))) ;; causes trouble when tree-count1 sees keyword args in s7test.scm (if (= (tree-count1 arg body 0) 1) (let ((p (tree-arg-member arg body))) (when (pair? p) (let ((f (car p)) (m (memq arg (cdr p)))) (if (pair? m) (let ((fsig (arg-signature f env))) (if (pair? fsig) (let ((chk (catch #t (lambda () (fsig (- (length p) (length m)))) (lambda args #f)))) (if (and (symbol? chk) ; it defaults to #t (not (memq chk '(integer:any? integer:real?)))) (set-car! sig chk))))))))))) arglist)) (and (any? (lambda (a) (not (eq? a #t))) sig) (reverse sig))))))) (define (args->proper-list args) (cond ((symbol? args) (list args)) ((not (pair? args)) args) ((pair? (car args)) (cons (caar args) (args->proper-list (cdr args)))) (else (cons (car args) (args->proper-list (cdr args)))))) (define (out-vars func-name arglist body) (let ((ref ()) (set ())) (let var-walk ((tree body) (e (cons func-name arglist))) (define (var-walk-body tree e) (when (pair? tree) (for-each (lambda (p) (set! e (var-walk p e))) tree))) (define (shadowed v) (if (and (or (memq v e) (memq v ref)) (not (memq v set))) (set! set (cons v set))) v) (if (symbol? tree) (if (not (or (memq tree e) (memq tree ref) (defined? tree (rootlet)))) (set! ref (cons tree ref))) (when (pair? tree) (if (not (pair? (cdr tree))) (var-walk (car tree) e) (case (car tree) ((set! vector-set! list-set! hash-table-set! float-vector-set! int-vector-set! string-set! let-set! fill! string-fill! list-fill! vector-fill! reverse! sort! set-car! set-cdr!) (let ((sym (if (symbol? (cadr tree)) (cadr tree) (if (pair? (cadr tree)) (caadr tree))))) (if (not (or (memq sym e) (memq sym set))) (set! set (cons sym set))) (var-walk (cddr tree) e))) ((let letrec) (if (and (pair? (cdr tree)) (pair? (cddr tree))) (let* ((named (symbol? (cadr tree))) (vars (if named (list (shadowed (cadr tree))) ())) (varlist ((if named caddr cadr) tree))) (when (pair? varlist) (for-each (lambda (v) (when (and (pair? v) (pair? (cdr v))) (var-walk (cadr v) e) (set! vars (cons (shadowed (car v)) vars)))) ((if named caddr cadr) tree))) (var-walk-body ((if named cdddr cddr) tree) (append vars e))))) ((let* letrec*) (let* ((named (symbol? (cadr tree))) (vars (if named (list (cadr tree)) ())) (varlist ((if named caddr cadr) tree))) (when (pair? varlist) (for-each (lambda (v) (when (and (pair? v) (pair? (cdr v))) (var-walk (cadr v) (append vars e)) (set! vars (cons (shadowed (car v)) vars)))) varlist)) (var-walk-body ((if named cdddr cddr) tree) (append vars e)))) ((case) (when (and (pair? (cdr tree)) (pair? (cddr tree))) (for-each (lambda (c) (when (pair? c) (var-walk (cdr c) e))) (cddr tree)))) ((quote) #f) ((do) (let ((vars ())) (when (pair? (cadr tree)) (for-each (lambda (v) (when (and (pair? v) (pair? (cdr v))) (var-walk (cadr v) e) (set! vars (cons (shadowed (car v)) vars)))) (cadr tree)) (for-each (lambda (v) (if (and (pair? v) (pair? (cdr v)) (pair? (cddr v))) (var-walk (caddr v) (append vars e)))) (cadr tree))) (when (pair? (cddr tree)) (var-walk (caddr tree) (append vars e)) (var-walk-body (cdddr tree) (append vars e))))) ((lambda lambda*) (var-walk-body (cddr tree) (append (args->proper-list (cadr tree)) e))) ((define* define-macro define-macro* define-bacro define-bacro*) (if (and (pair? (cdr tree)) (pair? (cddr tree))) (begin (set! e (cons (caadr tree) e)) (var-walk-body (cddr tree) (append (args->proper-list (cdadr tree)) e))))) ((define define-constant) (if (and (pair? (cdr tree)) (pair? (cddr tree))) (if (symbol? (cadr tree)) (begin (var-walk (caddr tree) e) (set! e (cons (cadr tree) e))) (begin (set! e (cons (caadr tree) e)) (var-walk-body (cddr tree) (append (args->proper-list (cdadr tree)) e)))))) (else (var-walk (car tree) e) (var-walk (cdr tree) e)))))) e) (list ref set))) (define (get-side-effect v) (let ((ftype (var-ftype v))) (or (not (memq ftype '(define define* lambda lambda*))) (let ((body (cddr (var-initial-value v))) (env (var-env v)) (args (cons (var-name v) (args->proper-list (var-arglist v))))) (let ((outvars (append (cadr (out-vars (var-name v) args body)) args))) (any? (lambda (f) (side-effect-with-vars? f env outvars)) body)))))) (define (last-par x) (let ((len (length x))) (and (positive? len) (x (- len 1))))) (define* (make-fvar name ftype arglist decl initial-value env) ;(format *stderr* "fvar: ~A~%" name) (let ((new (let ((old (hash-table-ref other-identifiers name))) (cons name (inlet 'signature () 'side-effect () 'allow-other-keys (and (pair? arglist) (memq ftype '(define* define-macro* define-bacro* defmacro*)) (eq? (last-par arglist) :allow-other-keys)) 'scope () 'setters () 'env env 'initial-value initial-value 'values (and (pair? initial-value) (count-values (cddr initial-value))) 'leaves #f 'match-list #f 'decl decl 'arglist arglist 'ftype ftype 'retcons #f 'history (if old (begin (hash-table-set! other-identifiers name #f) (if initial-value (cons initial-value old) old)) (if initial-value (list initial-value) ())) 'set 0 'ref (if old (length old) 0)))))) (reduce-function-tree new env) new)) (define (return-type sym e) (let ((sig (arg-signature sym e))) (and (pair? sig) (or (eq? (car sig) 'values) ; turn it into #t for now (car sig))))) ; this might be undefined in the current context (eg oscil? outside clm) (define any-macro? (let ((macros (let ((h (make-hash-table))) (for-each (lambda (m) (set! (h m) #t)) '(call-with-values let-values define-values let*-values cond-expand require quasiquote multiple-value-bind reader-cond match while)) h))) (lambda (f env) (or (hash-table-ref macros f) (let ((fd (var-member f env))) (and (var? fd) (memq (var-ftype fd) '(define-macro define-macro* define-expansion define-bacro define-bacro* defmacro defmacro* define-syntax)))))))) (define (any-procedure? f env) (or (hash-table-ref built-in-functions f) (let ((v (var-member f env))) (and (var? v) (memq (var-ftype v) '(define define* lambda lambda*)))))) (define ->simple-type (let ((markers (list (cons :call/exit 'continuation?) (cons :call/cc 'continuation?) (cons :dilambda 'dilambda?) (cons :lambda 'procedure?)))) (lambda (c) (cond ((pair? c) 'pair?) ((integer? c) 'integer?) ((rational? c) 'rational?) ((real? c) 'real?) ((number? c) 'number?) ((string? c) 'string?) ((null? c) 'null?) ((char? c) 'char?) ((boolean? c) 'boolean?) ((keyword? c) (cond ((assq c markers) => cdr) (else 'keyword?))) ((vector? c) 'vector?) ((float-vector? c) 'float-vector?) ((int-vector? c) 'int-vector?) ((byte-vector? c) 'byte-vector?) ((let? c) 'let?) ((hash-table? c) 'hash-table?) ((input-port? c) 'input-port?) ((output-port? c) 'output-port?) ((iterator? c) 'iterator?) ((continuation? c) 'continuation?) ((dilambda? c) 'dilambda?) ((procedure? c) 'procedure?) ((macro? c) 'macro?) ((random-state? c) 'random-state?) ((c-pointer? c) 'c-pointer?) ((c-object? c) 'c-object?) ((eof-object? c) 'eof-object?) ((syntax? c) 'syntax?) ((assq c '((# . unspecified?) (# . undefined?))) => cdr) (#t #t))))) (define (define->type c) (and (pair? c) (case (car c) ((define) (if (and (pair? (cdr c)) (pair? (cadr c))) 'procedure? (and (pair? (cddr c)) (->lint-type (caddr c))))) ((define* lambda lambda* case-lambda) 'procedure?) ((dilambda) 'dilambda?) ((define-macro define-macro* define-bacro define-bacro* defmacro defmacro* define-expansion) 'macro?) ((:call/cc :call/exit) 'continuation?) (else #t)))) (define (->lint-type c) (cond ((not (pair? c)) (->simple-type c)) ((not (symbol? (car c))) (or (pair? (car c)) 'pair?)) ((not (eq? (car c) 'quote)) (or (return-type (car c) ()) (define->type c))) ((symbol? (cadr c)) 'symbol?) (else (->simple-type (cadr c))))) ; don't look for return type! (define (compatible? type1 type2) ; we want type1, we have type2 -- is type2 ok? (or (eq? type1 type2) (not (symbol? type1)) (not (symbol? type2)) (not (hash-table-ref booleans type1)) (not (hash-table-ref booleans type2)) (eq? type2 'constant?) (case type1 ((number? complex?) (memq type2 '(float? real? rational? integer? number? complex? exact? inexact? zero? negative? positive? even? odd? infinite? nan?))) ((real?) (memq type2 '(float? rational? integer? complex? number? exact? inexact? zero? negative? positive? even? odd? infinite? nan?))) ((zero?) (memq type2 '(float? real? rational? integer? number? complex? exact? inexact? even?))) ((negative? positive?) (memq type2 '(float? real? rational? integer? complex? number? exact? inexact? even? odd? infinite? nan?))) ((float?) (memq type2 '(real? complex? number? inexact? zero? negative? positive? infinite? nan?))) ((rational?) (memq type2 '(integer? real? complex? number? exact? zero? negative? positive? even? odd?))) ((integer?) (memq type2 '(real? rational? complex? number? exact? even? odd? zero? negative? positive?))) ((odd? even?) (memq type2 '(real? rational? complex? number? exact? integer? zero? negative? positive?))) ((exact?) (memq type2 '(real? rational? complex? number? integer? zero? negative? positive?))) ((inexact?) (memq type2 '(real? number? complex? float? zero? negative? positive? infinite? nan?))) ((infinite? nan?) (memq type2 '(real? number? complex? positive? negative? inexact? float?))) ((vector?) (memq type2 '(float-vector? int-vector? sequence?))) ((float-vector? int-vector?) (memq type2 '(vector? sequence?))) ((sequence?) (memq type2 '(list? pair? null? proper-list? vector? float-vector? int-vector? byte-vector? string? let? hash-table? c-object? iterator? procedure?))) ; procedure? for extended iterator ((symbol?) (memq type2 '(gensym? keyword? defined? provided?))) ((constant?) #t) ((keyword? gensym? defined? provided?) (eq? type2 'symbol?)) ((list?) (memq type2 '(null? pair? proper-list? sequence?))) ((proper-list?) (memq type2 '(null? pair? list? sequence?))) ((pair? null?) (memq type2 '(list? proper-list? sequence?))) ((dilambda?) (memq type2 '(procedure? macro? iterator?))) ((procedure?) (memq type2 '(dilambda? iterator? macro? sequence?))) ((macro?) (memq type2 '(dilambda? iterator? procedure?))) ((iterator?) (memq type2 '(dilambda? procedure? sequence?))) ((string?) (memq type2 '(byte-vector? sequence? directory? file-exists?))) ((hash-table? let? c-object?) (eq? type2 'sequence?)) ((byte-vector? directory? file-exists?) (memq type2 '(string? sequence?))) ((input-port? output-port?) (eq? type2 'boolean?)) ((char? char-whitespace? char-numeric? char-alphabetic? char-upper-case? char-lower-case?) (memq type2 '(char? char-whitespace? char-numeric? char-alphabetic? char-upper-case? char-lower-case?))) (else #f)))) (define (any-compatible? type1 type2) ;; type1 and type2 can be either a list of types or a type (if (symbol? type1) (if (symbol? type2) (compatible? type1 type2) (and (pair? type2) (or (compatible? type1 (car type2)) (any-compatible? type1 (cdr type2))))) (and (pair? type1) (or (compatible? (car type1) type2) (any-compatible? (cdr type1) type2))))) (define (subsumes? type1 type2) (or (eq? type1 type2) (case type1 ((integer?) (memq type2 '(even? odd?))) ((rational?) (memq type2 '(integer? exact? odd? even?))) ((exact?) (memq type2 '(integer? rational?))) ((real?) (memq type2 '(integer? rational? float? negative? positive? zero? odd? even?))) ((complex? number?) (memq type2 '(integer? rational? float? real? complex? number? negative? positive? zero? even? odd? exact? inexact? nan? infinite?))) ((list?) (memq type2 '(pair? null? proper-list?))) ((proper-list?) (eq? type2 'null?)) ((vector?) (memq type2 '(float-vector? int-vector?))) ((symbol?) (memq type2 '(keyword? gensym? defined? provided?))) ((sequence?) (memq type2 '(list? pair? null? proper-list? vector? float-vector? int-vector? byte-vector? string? let? hash-table? c-object? directory? file-exists?))) ((char?) (memq type2 '(char-whitespace? char-numeric? char-alphabetic? char-upper-case? char-lower-case?))) (else #f)))) (define (never-false expr) (or (eq? expr #t) (let ((type (if (pair? expr) (return-type (car expr) ()) (->lint-type expr)))) (and (symbol? type) (not (symbol? expr)) (not (memq type '(boolean? values))))))) (define (never-true expr) (or (not expr) (and (pair? expr) (eq? (car expr) 'not) (pair? (cdr expr)) (never-false (cadr expr))))) (define (prettify-checker-unq op) (if (pair? op) (string-append (prettify-checker-unq (car op)) " or " (prettify-checker-unq (cadr op))) (case op ((rational?) "rational") ((real?) "real") ((complex?) "complex") ((null?) "null") ((length) "a sequence") ((unspecified?) "untyped") ((undefined?) "not defined") (else (let ((op-name (symbol->string op))) (string-append (if (memv (op-name 0) '(#\a #\e #\i #\o #\u)) "an " "a ") (substring op-name 0 (- (length op-name) 1)))))))) (define (prettify-checker op) (if (pair? op) (string-append (prettify-checker-unq (car op)) " or " (prettify-checker (cadr op))) (let ((op-name (symbol->string op))) (case op ((rational? real? complex? null?) op-name) ((unspecified?) "untyped") ((undefined?) "not defined") (else (string-append (if (memv (op-name 0) '(#\a #\e #\i #\o #\u)) "an " "a ") op-name)))))) (define (side-effect-with-vars? form env vars) ;; (format *stderr* "~A~%" form) ;; could evaluation of form have any side effects (like IO etc) (if (or (not (proper-list? form)) ; we don't want dotted lists or () here (null? form)) (and (symbol? form) (or (eq? form '=>) ; (cond ((x => y))...) -- someday check y... (let ((e (var-member form env))) (if (var? e) (and (symbol? (var-ftype e)) (var-side-effect e)) (and (not (hash-table-ref no-side-effect-functions form)) (procedure? (symbol->value form *e*))))))) ; i.e. function passed as argument ;; can't optimize ((...)...) because the car might eval to a function (or (and (not (hash-table-ref no-side-effect-functions (car form))) ;; if it's not in the no-side-effect table and ... (let ((e (var-member (car form) env))) (or (not (var? e)) (not (symbol? (var-ftype e))) (var-side-effect e))) (or (not (eq? (car form) 'format)) ; (format #f ...) (not (pair? (cdr form))) ; (format)! (cadr form)) (or (null? vars) (not (memq (car form) '(set! ;vector-set! list-set! hash-table-set! float-vector-set! int-vector-set! string-set! let-set! ;fill! string-fill! list-fill! vector-fill! ;reverse! sort! define define* define-macro define-macro* define-bacro define-bacro*))))) ;; it's not the common (format #f ...) special case, then...(goto case below) ;; else return #t: side-effects are possible -- this is too hard to read (case (car form) ((define-constant define-expansion) #t) ((define define* define-macro define-macro* define-bacro define-bacro*) (null? vars)) ((set! ;vector-set! list-set! hash-table-set! float-vector-set! int-vector-set! string-set! let-set! ;fill! string-fill! list-fill! vector-fill! ;reverse! sort! ) (or (not (pair? (cdr form))) (not (symbol? (cadr form))) (memq (cadr form) vars))) ((quote) #f) ((case) (or (not (pair? (cdr form))) (side-effect-with-vars? (cadr form) env vars) ; the selector (let case-effect? ((f (cddr form))) (and (pair? f) (or (not (pair? (car f))) (any? (lambda (ff) (side-effect-with-vars? ff env vars)) (cdar f)) (case-effect? (cdr f))))))) ((cond) (or (not (pair? (cadr form))) (let cond-effect? ((f (cdr form)) (e env)) (and (pair? f) (or (and (pair? (car f)) (any? (lambda (ff) (side-effect-with-vars? ff e vars)) (car f))) (cond-effect? (cdr f) e)))))) ((let let* letrec letrec*) ;; here if the var value involves a member of vars, we have to add it to vars (or (< (length form) 3) (let ((syms (cadr form)) (body (cddr form))) (when (symbol? (cadr form)) (set! syms (caddr form)) (set! body (cdddr form))) (if (and (pair? vars) (pair? syms)) (for-each (lambda (sym) (when (and (pair? sym) (pair? (cdr sym)) (tree-set-member vars (cdr sym))) (set! vars (cons (car sym) vars)))) syms)) (or (let let-effect? ((f syms) (e env) (v vars)) (and (pair? f) (or (not (pair? (car f))) (not (pair? (cdar f))) ; an error, reported elsewhere: (let ((x)) x) (side-effect-with-vars? (cadar f) e v) (let-effect? (cdr f) e v)))) (any? (lambda (ff) (side-effect-with-vars? ff env vars)) body))))) ((do) (or (< (length form) 3) (not (list? (cadr form))) (not (list? (caddr form))) (let do-effect? ((f (cadr form)) (e env)) (and (pair? f) (or (not (pair? (car f))) (not (pair? (cdar f))) (side-effect-with-vars? (cadar f) e vars) (and (pair? (cddar f)) (side-effect-with-vars? (caddar f) e vars)) (do-effect? (cdr f) e)))) (any? (lambda (ff) (side-effect-with-vars? ff env vars)) (caddr form)) (any? (lambda (ff) (side-effect-with-vars? ff env vars)) (cdddr form)))) ;; ((lambda lambda*) (any? (lambda (ff) (side-effect-with-vars? ff env vars)) (cddr form))) ; this is trickier than it looks (else ;(format *stderr* "check args: ~A~%" form) (or (any? (lambda (f) ; any subform has a side-effect (and (not (null? f)) (side-effect-with-vars? f env vars))) (cdr form)) (let ((sig (procedure-signature (car form)))) ; sig has func arg and it is not known safe (and (pair? sig) (memq 'procedure? (cdr sig)) (call-with-exit (lambda (return) (for-each (lambda (sg arg) (when (and (eq? sg 'procedure?) (not (and (symbol? arg) (hash-table-ref no-side-effect-functions arg)))) (return #t))) (cdr sig) (cdr form)) #f)))))))))) (define (side-effect? form env) (side-effect-with-vars? form env ())) (define (just-constants? form env) ;; can we probably evaluate form given just built-in stuff? ;; watch out here -- this is used later by 'if, so (defined 'hiho) should not be evalled to #f! (if (not (pair? form)) (constant? form) (and (symbol? (car form)) (hash-table-ref no-side-effect-functions (car form)) (hash-table-ref built-in-functions (car form)) ; and not hook-functions (not (var-member (car form) env)) ; e.g. exp declared locally as a list (every? (lambda (p) (just-constants? p env)) (cdr form))))) (define (equal-ignoring-constants? a b) (or (morally-equal? a b) (and (symbol? a) (constant? a) (morally-equal? (symbol->value a) b)) (and (symbol? b) (constant? b) (morally-equal? (symbol->value b) a)) (and (pair? a) (pair? b) (equal-ignoring-constants? (car a) (car b)) (equal-ignoring-constants? (cdr a) (cdr b))))) (define (repeated-member? lst env) (and (pair? lst) (or (and (not (and (pair? (car lst)) (side-effect? (car lst) env))) (pair? (cdr lst)) (member (car lst) (cdr lst))) (repeated-member? (cdr lst) env)))) (define (update-scope v caller env) (unless (or (memq caller (var-scope v)) (assq caller (var-scope v))) (let ((cv (var-member caller env))) (set! (var-scope v) (cons (if (and (var? cv) (memq (var-ftype cv) '(define lambda define* lambda*))) ; named-let does not define ftype caller (cons caller env)) (var-scope v)))))) (define (check-for-bad-variable-name caller vname) (define (bad-variable-name-numbered vname bad-names) (let ((str (symbol->string vname))) (let loop ((bads bad-names)) (and (pair? bads) (let* ((badstr (symbol->string (car bads))) (pos (string-position badstr str))) (or (and (eqv? pos 0) (string->number (substring str (length badstr)))) (loop (cdr bads)))))))) (if (and (symbol? vname) (pair? *report-bad-variable-names*) (or (memq vname *report-bad-variable-names*) (let ((sname (symbol->string vname))) (and (> (length sname) 8) (or (string=? "compute" (substring sname 0 7)) ; compute-* is as bad as get-* (string=? "calculate" (substring sname 0 9))))) ; perhaps one exception: computed-goto* (bad-variable-name-numbered vname *report-bad-variable-names*))) (lint-format "surely there's a better name for this variable than ~A" caller vname))) (define (set-ref name caller form env) ;; if name is in env, set its "I've been referenced" flag (let ((data (var-member name env))) (if (var? data) (begin (set! (var-ref data) (+ (var-ref data) 1)) (update-scope data caller env) (if (and form (not (memq form (var-history data)))) (set! (var-history data) (cons form (var-history data))))) (if (not (defined? name (rootlet))) (let ((old (hash-table-ref other-identifiers name))) (check-for-bad-variable-name caller name) (hash-table-set! other-identifiers name (if old (cons form old) (list form))))))) env) (define (set-set name caller form env) (let ((data (var-member name env))) (when (var? data) (set! (var-set data) (+ (var-set data) 1)) (update-scope data caller env) (if (not (memq caller (var-setters data))) (set! (var-setters data) (cons caller (var-setters data)))) (if (not (memq form (var-history data))) (set! (var-history data) (cons form (var-history data)))) (set! (var-signature data) #f) (set! (var-ftype data) #f)))) (define (proper-list lst) ;; return lst as a proper list (if (not (pair? lst)) lst (cons (car lst) (if (pair? (cdr lst)) (proper-list (cdr lst)) (if (null? (cdr lst)) () (list (cdr lst))))))) (define (keywords lst) (do ((count 0) (p lst (cdr p))) ((null? p) count) (if (keyword? (car p)) (set! count (+ count 1))))) (define (eqv-selector clause) (if (not (pair? clause)) (memq clause '(else #t)) (case (car clause) ((memq memv member) (and (= (length clause) 3) (cadr clause))) ((eq? eqv? = equal? char=? char-ci=? string=? string-ci=?) (and (= (length clause) 3) ((if (code-constant? (cadr clause)) caddr cadr) clause))) ((or) (and (pair? (cdr clause)) (eqv-selector (cadr clause)))) ((not null? eof-object? zero? boolean?) (and (pair? (cdr clause)) (cadr clause))) (else #f)))) (define (->eqf x) (case x ((char?) '(eqv? char=?)) ((integer? rational? real? number? complex?) '(eqv? =)) ((symbol? keyword? boolean? null? procedure? syntax? macro? undefined? unspecified?) '(eq? eq?)) ((string? byte-vector?) '(equal? string=?)) ((pair? vector? float-vector? int-vector? hash-table?) '(equal? equal?)) ((eof-object?) '(eq? eof-object?)) (else (if (and (pair? x) (pair? (cdr x)) (null? (cddr x)) (or (and (memq 'boolean? x) (or (memq 'real? x) (memq 'number? x) (memq 'integer? x))) (and (memq 'eof-object? x) (or (memq 'char? x) (memq 'integer? x))))) '(eqv? eqv?) '(#t #t))))) (define (eqf selector env) (cond ((symbol? selector) (if (and (not (var-member selector env)) (or (hash-table-ref built-in-functions selector) (hash-table-ref syntaces selector))) '(eq? eq?) '(#t #t))) ((not (pair? selector)) (->eqf (->lint-type selector))) ((eq? (car selector) 'quote) (cond ((or (symbol? (cadr selector)) (memq (cadr selector) '(#f #t # # # ()))) '(eq? eq?)) ((char? (cadr selector)) '(eqv? char=?)) ((string? (cadr selector)) '(equal? string=?)) ((number? (cadr selector)) '(eqv? =)) (else '(equal? equal?)))) ((and (eq? (car selector) 'list) (null? (cdr selector))) '(eq? eq?)) ((symbol? (car selector)) (let ((sig (arg-signature (car selector) env))) (if (pair? sig) (->eqf (car sig)) '(#t #t)))) (else '(#t #t)))) (define (unquoted x) (if (and (pair? x) (eq? (car x) 'quote)) (cadr x) x)) (define (distribute-quote x) (map (lambda (item) (if (or (symbol? item) (pair? item)) `(quote ,item) item)) x)) (define (focus-str str focus) (let ((len (length str))) (if (< len 40) str (let ((pos (string-position focus str)) (focus-len (length focus))) (if (not pos) str (if (<= pos 20) (string-append (substring str 0 (min 60 (- len 1) (+ focus-len pos 20))) " ...") (string-append "... " (substring str (- pos 20) (min (- len 1) (+ focus-len pos 20))) " ..."))))))) (define (check-star-parameters f args env) (if (list-any? (lambda (k) (memq k '(:key :optional))) args) (let ((kw (if (memq :key args) :key :optional))) (format outport "~NC~A: ~A is no longer accepted: ~A~%" lint-left-margin #\space f kw (focus-str (object->string args) (symbol->string kw))))) (if (member 'pi args (lambda (a b) (or (eq? b 'pi) (and (pair? b) (eq? (car b) 'pi))))) (format outport "~NC~A: parameter can't be a constant: ~A~%" lint-left-margin #\space f (focus-str (object->string args) "pi"))) (let ((r (memq :rest args))) (when (pair? r) (if (not (pair? (cdr r))) (format outport "~NC~A: :rest parameter needs a name: ~A~%" lint-left-margin #\space f args) (if (pair? (cadr r)) (format outport "~NC~A: :rest parameter can't specify a default value: ~A~%" lint-left-margin #\space f args))))) (let ((a (memq :allow-other-keys args))) (if (and (pair? a) (pair? (cdr a))) (format outport "~NC~A: :allow-other-keys should be at the end of the parameter list: ~A~%" lint-left-margin #\space f (focus-str (object->string args) ":allow-other-keys")))) (for-each (lambda (p) (if (and (pair? p) (pair? (cdr p))) (lint-walk f (cadr p) env))) args)) (define (checked-eval form) (and (proper-list? form) ;(not (infinite? (length form))) but when would a dotted list work? (catch #t (lambda () (eval (copy form :readable))) (lambda args :checked-eval-error)))) (define (return-type-ok? type ret) (or (eq? type ret) (and (pair? ret) (memq type ret)))) (define last-and-incomplete-arg2 #f) (define (and-incomplete form head arg1 arg2 env) ; head: 'and | 'or (not ...) | 'if | 'if2 -- symbol arg1 in any case (unless (memq (car arg2) '(and or not list cons vector)) ; these don't tell us anything about arg1's type (let ((v (var-member arg1 env))) ; try to avoid the member->cdr trope (unless (or (eq? arg2 last-and-incomplete-arg2) (and (var? v) (pair? (var-history v)) (member #f (var-history v) (lambda (a b) (and (pair? b) (memq (car b) '(char-position string-position format string->number assoc assq assv memq memv member))))))) (let* ((pos (do ((i 0 (+ i 1)) ; get arg number of arg1 in arg2 (p arg2 (cdr p))) ; 0th=car -> (and x (x)) ((or (null? p) (eq? (car p) arg1)) i))) (arg-type (let ((sig (and (positive? pos) ; procedure-signature for arg2 (arg-signature (car arg2) env)))) (if (zero? pos) ; it's type indication for arg1's position 'procedure? ; or sequence? -- how to distinguish? use 'applicable? (and (pair? sig) (< pos (length sig)) (list-ref sig pos)))))) (let ((ln (and (< 0 line-number 100000) line-number)) (comment (if (and (eq? arg-type 'procedure?) (= pos 0) (pair? (cdr arg2))) " ; or maybe sequence? " ""))) (set! last-and-incomplete-arg2 arg2) ; ignore unwanted repetitions due to recursive simplifications (if (symbol? arg-type) (let ((old-arg (case head ((and if cond when) arg1) ((or if2) `(not ,arg1)))) (new-arg (case head ((and if cond when) `(,arg-type ,arg1)) ((or if2) `(not (,arg-type ,arg1)))))) (format outport "~NCin ~A~A,~%~NCperhaps change ~S to ~S~A~%" lint-left-margin #\space (truncated-list->string form) (if ln (format #f " (line ~D)" ln) "") (+ lint-left-margin 4) #\space old-arg new-arg comment))))))))) (define (and-redundant? arg1 arg2) (let ((type1 (car arg1)) (type2 (car arg2))) (and (symbol? type1) (symbol? type2) (hash-table-ref booleans type1) (or (hash-table-ref booleans type2) ; return #f if not (obviously) redundant, else return which of the two to keep (memq type2 '(= char=? string=? not eq?))) (if (eq? type1 type2) type1 (case type1 ((number? complex?) (case type2 ((float? real? rational? integer?) type2) ((number? complex?) type1) ((=) (let ((x ((if (number? (caddr arg2)) caddr cadr) arg2))) (and (number? x) (if (= x (floor x)) 'memv 'eqv?)))) (else #f))) ((real?) (case type2 ((float? rational? integer?) type2) ((number? complex?) type1) ((=) (let ((x ((if (real? (caddr arg2)) caddr cadr) arg2))) (and (real? x) (if (= x (floor x)) 'memv 'eqv?)))) (else #f))) ((float?) (and (memq type2 '(real? complex? number? inexact?)) type1)) ((rational?) (case type2 ((integer?) type2) ((real? complex? number? exact?) type1) ((=) (and (or (rational? (caddr arg2)) (rational? (cadr arg2))) 'eqv?)) (else #f))) ((integer?) (case type2 ((real? rational? complex? number? exact?) type1) ((=) (and (or (integer? (caddr arg2)) (integer? (cadr arg2))) 'eqv?)) (else #f))) ((exact?) (and (memq type2 '(rational? integer?)) type2)) ((even? odd?) (and (memq type2 '(integer? rational? real? complex? number?)) type1)) ; not zero? -> 0.0 ((zero?) (and (memq type2 '(complex? number? real?)) type1)) ((negative? positive?) (and (eq? type2 'real?) type1)) ((inexact?) (and (eq? type2 'float?) type2)) ((infinite? nan?) (and (memq type2 '(number? complex? inexact?)) type1)) ((vector?) (and (memq type2 '(float-vector? int-vector?)) type2)) ((float-vector? int-vector?) (and (eq? type2 'vector?) type1)) ((symbol?) (case type2 ((keyword? gensym?) type2) ((eq?) (and (or (quoted-symbol? (cadr arg2)) (quoted-symbol? (caddr arg2))) 'eq?)) (else #f))) ((keyword?) (case type2 ((symbol? constant?) type1) ((eq?) (and (or (keyword? (cadr arg2)) (keyword? (caddr arg2))) 'eq?)) (else #f))) ((gensym? defined? provided?) (and (eq? type2 'symbol?) type1)) ((boolean?) (and (or (eq? type2 'not) (and (eq? type2 'eq?) (or (boolean? (cadr arg2)) (boolean? (caddr arg2))))) type2)) ((list?) (and (memq type2 '(null? pair? proper-list?)) type2)) ((null?) (and (memq type2 '(list? proper-list?)) type1)) ((pair?) (and (eq? type2 'list?) type1)) ((proper-list?) (and (eq? type2 'null?) type2)) ((string?) (case type2 ((byte-vector?) type2) ((string=?) (and (or (eq? (->lint-type (cadr arg2)) 'string?) (eq? (->lint-type (caddr arg2)) 'string?)) 'equal?)) (else #f))) ((char?) (and (eq? type2 'char=?) (or (eq? (->lint-type (cadr arg2)) 'char?) (eq? (->lint-type (caddr arg2)) 'char?)) 'eqv?)) ((char-numeric? char-whitespace? char-alphabetic? char-upper-case? char-lower-case?) (and (eq? type2 'char?) type1)) ((byte-vector? directory? file-exists?) (and (eq? type2 'string?) type1)) (else #f)))))) (define (and-forgetful form head arg1 arg2 env) (unless (or (memq (car arg2) '(and or not list cons vector)) ; these don't tell us anything about arg1's type (eq? arg2 last-and-incomplete-arg2)) (let* ((pos (do ((i 0 (+ i 1)) ; get arg number of arg1 in arg2 (p arg2 (cdr p))) ; 0th=car -> (and x (x)) ((or (null? p) (equal? (car p) (cadr arg1))) (if (null? p) -1 i)))) (arg-type (let ((sig (and (positive? pos) ; procedure-signature for arg2 (arg-signature (car arg2) env)))) (if (zero? pos) ; its type indication for arg1's position 'procedure? ; or sequence? -- how to distinguish? use 'applicable? (and (pair? sig) (< pos (length sig)) (list-ref sig pos)))))) (when (symbol? arg-type) (let ((new-type (and-redundant? arg1 (cons arg-type (cdr arg1))))) (when (and new-type (not (eq? new-type (car arg1)))) (let ((old-arg (case head ((and if cond when) arg1) ((or if2) `(not ,arg1)))) (new-arg (case head ((and if cond when) `(,new-type ,(cadr arg1))) ((or if2) `(not (,new-type ,(cadr arg1)))))) (ln (and (< 0 line-number 100000) line-number)) (comment (if (and (eq? arg-type 'procedure?) (= pos 0) (pair? (cdr arg2))) " ; or maybe sequence? " ""))) (set! last-and-incomplete-arg2 arg2) (format outport "~NCin ~A~A,~%~NCperhaps change ~S to ~S~A~%" lint-left-margin #\space (truncated-list->string form) (if ln (format #f " (line ~D)" ln) "") (+ lint-left-margin 4) #\space old-arg new-arg comment))))))) ;; perhaps change pair? -> eq? or ignore it? (when (and (pair? (cdr arg2)) (not (eq? (car arg1) 'pair?))) (let ((a2 (if (eq? (car arg2) 'not) (cadr arg2) arg2))) (when (and (pair? a2) (memq (car a2) '(memq memv member assq assv assoc eq? eqv? equal?)) (equal? (cadr arg1) (cadr a2))) (let ((new-e (case (car (->eqf (car arg1))) ((eq?) (case (car a2) ((memq assq eq?) (car a2)) ((memv member) 'memq) ((assv assoc) 'assq) ((eqv? equal?) 'eq?))) ((eqv?) (case (car a2) ((memv assv eqv?) (car a2)) ((memq member) 'memv) ((assq assoc) 'assv) ((eq? equal?) 'eqv?))) ((equal?) (case (car a2) ((member assoc equal?) (car a2)) ((memq memv) 'member) ((assq assv) 'assoc) ((eq? eqv?) 'equal?))) (else (car a2))))) (when (and (not (eq? (car a2) new-e)) (symbol? new-e)) (let ((ln (and (< 0 line-number 100000) line-number))) (format outport "~NCin ~A~A,~%~NCperhaps change ~A to ~A~%" lint-left-margin #\space (truncated-list->string form) (if ln (format #f " (line ~D)" ln) "") (+ lint-left-margin 4) #\space (truncated-list->string a2) `(,new-e ...))))))))) ;; -------------------------------- (define simplify-boolean (let ((notables (let ((h (make-hash-table))) (for-each (lambda (op) (set! (h (car op)) (cadr op))) '((< >=) (> <=) (<= >) (>= <) (char=?) (char>? char<=?) (char<=? char>?) (char>=? char=?) (string>? string<=?) (string<=? string>?) (string>=? string=?) (char-ci>? char-ci<=?) (char-ci<=? char-ci>?) (char-ci>=? char-ci=?) (string-ci>? string-ci<=?) (string-ci<=? string-ci>?) (string-ci>=? string-ci number?) (<= < >= number?) (> >= < number?) (>= > <= number?) (char? char?) (char<=? char=? char?) ; these never happen (char>? char>=? char=? char>? char<=? char?) (string? string?) (string<=? string=? string?) (string>? string>=? string=? string>? string<=? string?)))) (lambda (A B rel-op env) (call-with-exit (lambda (return) (when (and (pair? A) (pair? B) (= (length A) (length B) 3)) (let ((Adata (assq (car A) relops)) (Bdata (assq (car B) relops))) (when (and Adata Bdata) (let ((op1 (car A)) (op2 (car B)) (A1 (cadr A)) (A2 (caddr A)) (B1 (cadr B)) (B2 (caddr B))) (let ((x (if (and (not (number? A1)) (member A1 B)) A1 (and (not (number? A2)) (member A2 B) A2)))) (when x (let ((c1 (if (equal? x A1) A2 A1)) (c2 (if (equal? x B1) B2 B1)) (type (cadddr Adata))) (if (or (side-effect? c1 env) (side-effect? c2 env) (side-effect? x env)) (return 'ok)) (if (equal? x A2) (set! op1 (caddr Adata))) (if (equal? x B2) (set! op2 (caddr Bdata))) (let ((typer #f) (gtes #f) (gts #f) (eqop #f)) (case type ((number?) (set! typer number?) (set! gtes '(>= <=)) (set! gts '(< >)) (set! eqop '=)) ((char?) (set! typer char?) (set! gtes '(char>=? char<=?)) (set! gts '(char?)) (set! eqop 'char=?)) ((string?) (set! typer string?) (set! gtes '(string>=? string<=?)) (set! gts '(string?)) (set! eqop 'string=?))) (case rel-op ((and) (cond ((equal? c1 c2) (cond ((eq? op1 op2) (return `(,op1 ,x ,c1))) ((eq? op2 (cadr (assq op1 relops))) (return `(,(if (memq op2 gtes) op1 op2) ,x ,c1))) ((and (memq op1 gtes) (memq op2 gtes)) (return `(,eqop ,x ,c1))) (else (return #f)))) ((and (typer c1) (typer c2)) (cond ((or (eq? op1 op2) (eq? op2 (cadr (assq op1 relops)))) (return (if ((symbol->value op1) c1 c2) `(,op1 ,x ,c1) `(,op2 ,x ,c2)))) ((eq? op1 (caddr (assq op2 relops))) (if ((symbol->value op1) c2 c1) (return `(,op1 ,c2 ,x ,c1)) (if (memq op1 gts) (return #f)))) ((and (eq? op2 (hash-table-ref reversibles (cadr (assq op1 relops)))) ((symbol->value op1) c1 c2)) (return #f)))) ((eq? op2 (caddr (assq op1 relops))) (return `(,op1 ,c2 ,x ,c1))))) ((or) (cond ((equal? c1 c2) (cond ((eq? op1 op2) (return `(,op1 ,x ,c1))) ((eq? op2 (cadr (assq op1 relops))) (return `(,(if (memq op2 gtes) op2 op1) ,x ,c1))) ((and (memq op1 gts) (memq op2 gts)) (return `(not (,eqop ,x ,c1)))) (else (return #t)))) ((and (typer c1) (typer c2)) (cond ((or (eq? op1 op2) (eq? op2 (cadr (assq op1 relops)))) (return (if ((symbol->value op1) c1 c2) `(,op2 ,x ,c2) `(,op1 ,x ,c1)))) ((eq? op1 (caddr (assq op2 relops))) (if ((symbol->value op1) c2 c1) (return #t)) (return `(not (,(cadr (assq op1 relops)) ,c1 ,x ,c2)))) ((and (eq? op2 (hash-table-ref reversibles (cadr (assq op1 relops)))) ((symbol->value op1) c2 c1)) (return #t)))) ((eq? op2 (caddr (assq op1 relops))) (return `(not (,(cadr (assq op1 relops)) ,c1 ,x ,c2))))))))))))))) 'ok)))))) (lambda (in-form true false env) (define (classify e) (if (not (just-constants? e env)) e (catch #t (lambda () (let ((val (eval e))) (if (boolean? val) val e))) (lambda ignore e)))) (define (contradictory? ands) (let ((vars ())) (call-with-exit (lambda (return) (do ((b ands (cdr b))) ((null? b) #f) (if (and (pair? b) (pair? (car b)) (pair? (cdar b))) (let ((func (caar b)) (args (cdar b))) (if (memq func '(eq? eqv? equal?)) (if (and (symbol? (car args)) (code-constant? (cadr args))) (set! func (->lint-type (cadr args))) (if (and (symbol? (cadr args)) (code-constant? (car args))) (set! func (->lint-type (car args)))))) (if (symbol? func) (for-each (lambda (arg) (if (symbol? arg) (let ((type (assq arg vars))) (if (not type) (set! vars (cons (cons arg func) vars)) (if (not (compatible? (cdr type) func)) (return #t)))))) args))))))))) (define (and-redundants env . args) (do ((locals ()) (diffs #f) (p args (cdr p))) ((or (null? p) (not (and (pair? (car p)) (pair? (cdar p)) (hash-table-ref booleans (caar p))))) (and (null? p) (pair? locals) (or diffs (any? (lambda (a) (pair? (cddr a))) locals)) (let ((keepers ())) (for-each (lambda (a) (let ((next-a (cdr a))) (cond ((null? (cdr next-a)) (set! keepers (cons (car next-a) keepers))) ((null? (cddr next-a)) (let ((res (apply and-redundant? (reverse next-a)))) (if res (begin (set! keepers (cons ((if (eq? res (caar next-a)) car cadr) next-a) keepers)) (set! diffs #t)) (set! keepers (cons (car next-a) (cons (cadr next-a) keepers)))))) (else (let ((ar (reverse next-a))) (let ((ar1 (car ar)) (ar2 (cadr ar)) (ar3 (caddr ar))) (let ((res1 (and-redundant? ar1 ar2)) ; if res1 either 1 or 2 is out (res2 (and-redundant? ar2 ar3)) ; if res2 either 2 or 3 is out (res3 (and-redundant? ar1 ar3))) ; if res3 either 1 or 3 is out ;; only in numbers can 3 actually be reducible (if (not (or res1 res2 res3)) (set! keepers (append (cdr a) keepers)) (begin (set! diffs #t) (if (and (or (not res1) (eq? res1 (car ar1))) (or (not res3) (eq? res3 (car ar1)))) (set! keepers (cons ar1 keepers))) (if (and (or (not res1) (eq? res1 (car ar2))) (or (not res2) (eq? res2 (car ar2)))) (set! keepers (cons ar2 keepers))) (if (and (or (not res2) (eq? res2 (car ar3))) (or (not res3) (eq? res3 (car ar3)))) (set! keepers (cons ar3 keepers))) (if (pair? (cdddr ar)) (set! keepers (append (reverse (cdddr ar)) keepers)))))))))))) (reverse locals)) (and diffs (reverse keepers))))) (let* ((bool (car p)) (local (assoc (cadr bool) locals))) (if (pair? local) (if (member bool (cdr local)) (set! diffs #t) (set-cdr! local (cons bool (cdr local)))) (set! locals (cons (list (cadr bool) bool) locals)))))) (define (and-not-redundant arg1 arg2) (let ((type1 (car arg1)) ; (? ...) (type2 (caadr arg2))) ; (not (? ...)) (and (symbol? type1) (symbol? type2) (or (hash-table-ref booleans type1) (memq type1 '(= char=? string=?))) (hash-table-ref booleans type2) (if (eq? type1 type2) ; (and (?) (not (?))) -> #f 'contradictory (case type1 ((pair?) (case type2 ((list?) 'contradictory) ((proper-list?) #f) (else arg1))) ((null?) (if (eq? type2 'list?) 'contradictory arg1)) ((list?) (case type2 ((pair?) 'null?) ((null?) 'pair?) ((proper-list?) #f) (else arg1))) ((proper-list?) (case type2 ((list? pair?) 'contradictory) ((null?) #f) (else arg1))) ((symbol?) (and (not (memq type2 '(keyword? gensym?))) arg1)) ((char=?) (if (eq? type2 'char?) 'contradictory (and (or (char? (cadr arg1)) (char? (caddr arg1))) `(eqv? ,@(cdr arg1))))) ; arg2 might be (not (eof-object?...)) ((real?) (case type2 ((rational? exact?) `(float? ,@(cdr arg1))) ((inexact?) `(rational? ,@(cdr arg1))) ((complex? number?) 'contradictory) ((negative? positive? even? odd? zero? integer?) #f) (else arg1))) ((integer?) (case type2 ((real? complex? number? rational? exact?) 'contradictory) ((float? inexact? infinite? nan?) arg1) (else #f))) ((rational?) (case type2 ((real? complex? number? exact?) 'contradictory) ((float? inexact? infinite? nan?) arg1) (else #f))) ((complex? number?) (and (memq type2 '(complex? number?)) 'contradictory)) ((float?) (case type2 ((real? complex? number? inexact?) 'contradictory) ((rational? integer? exact?) arg1) (else #f))) ((exact?) (case type2 ((rational?) 'contradictory) ((inexact? infinite? nan?) arg1) (else #f))) ((even? odd?) (case type2 ((integer? exact? rational? real? number? complex?) 'contradictory) ((infinite? nan?) arg1) (else #f))) ((zero? negative? positive?) (and (memq type2 '(complex? number? real?)) 'contradictory)) ((infinite? nan?) (case type2 ((number? complex? inexact?) 'contradictory) ((integer? rational? exact? even? odd?) arg1) (else #f))) ((char-whitespace? char-numeric? char-alphabetic? char-upper-case? char-lower-case?) (and (eq? type2 'char?) 'contradictory)) ((directory? file-exists?) (and (memq type2 '(string? sequence?)) 'contradictory)) (else ;; none of the rest happen #f)))))) (define (or-not-redundant arg1 arg2) (let ((type1 (car arg1)) ; (? ...) (type2 (caadr arg2))) ; (not (? ...)) (and (symbol? type1) (symbol? type2) (or (hash-table-ref bools type1) (memq type1 '(= char=? string=?))) (hash-table-ref bools type2) (if (eq? type1 type2) ; (or (?) (not (?))) -> #t 'fatuous (case type1 ((null?) (case type2 ((list?) ; not proper-list? here `(not (pair? ,(cadr arg1)))) ((proper-list?) #f) (else arg2))) ((eof-object?) arg2) ; not keyword? here because (or (not (symbol? x)) (keyword? x)) is not reducible to (not (symbol? x)) ((string?) (and (not (eq? type2 'byte-vector?)) arg2)) (else #f)))))) (define (bsimp x) ; quick check for common easy cases (set! last-simplify-boolean-line-number line-number) (if (not (and (pair? x) (pair? (cdr x)))) x (case (car x) ((and) (and (cadr x) ; (and #f ...) -> #f x)) ((or) (if (and (cadr x) ; (or #t ...) -> #t (code-constant? (cadr x))) (cadr x) x)) (else (if (not (and (= (length x) 2) (pair? (cadr x)) (symbol? (caadr x)))) x (let ((rt (if (eq? (caadr x) 'quote) (->simple-type (cadadr x)) (return-type (caadr x) env))) (head (car x))) (or (and (subsumes? head rt) #t) ; don't return the memq list! (and (or (memq rt '(#t #f values)) (any-compatible? head rt)) (case head ((null?) (if (eq? (caadr x) 'list) (null? (cdadr x)) x)) ((pair?) (if (eq? (caadr x) 'list) (pair? (cdadr x)) x)) ((negative?) (and (not (hash-table-ref non-negative-ops (caadr x))) x)) (else x)))))))))) (define (bcomp x) ; not so quick... (cond ((not (pair? x)) x) ((eq? (car x) 'and) (call-with-exit (lambda (return) (let ((newx (list 'and))) (do ((p (cdr x) (cdr p)) (sidex newx) (endx newx)) ((null? p) newx) (let ((next (car p))) (if (or (not next) ; #f in and -> end of expr (member next false)) (if (eq? sidex newx) ; no side-effects (return #f) (begin (set-cdr! endx (list #f)) (return newx))) (if (or (code-constant? next) ; (and ... true-expr ...) (member next sidex) ; if a member, and no side-effects since, it must be true (member next true)) (if (and (null? (cdr p)) (not (equal? next (car endx)))) (set-cdr! endx (list next))) (begin (set-cdr! endx (list next)) (set! endx (cdr endx)) (if (side-effect? next env) (set! sidex endx))))))))))) ((not (eq? (car x) 'or)) x) (else (call-with-exit (lambda (return) (let ((newx (list 'or))) (do ((p (cdr x) (cdr p)) (sidex newx) (endx newx)) ((null? p) newx) (let ((next (car p))) (if (or (and next ; (or ... #t ...) (code-constant? next)) (member next true)) (begin (set-cdr! endx (list next)) (return newx)) ; we're done since this is true (if (or (not next) (member next sidex) ; so its false in some way (member next false)) (if (and (null? (cdr p)) (not (equal? next (car endx)))) (set-cdr! endx (list next))) (begin (set-cdr! endx (list next)) (set! endx (cdr endx)) (if (side-effect? next env) (set! sidex endx))))))))))))) (define (gather-or-eqf-elements eqfnc sym vals) (let* ((func (case eqfnc ((eq?) 'memq) ((eqv? char=?) 'memv) (else 'member))) (equals (if (and (eq? func 'member) (not (eq? eqfnc 'equal?))) (list eqfnc) ())) (elements (lint-remove-duplicates (map unquoted vals) env))) (cond ((null? (cdr elements)) `(,eqfnc ,sym ,@elements)) ((and (eq? eqfnc 'char=?) (= (length elements) 2) (char-ci=? (car elements) (cadr elements))) `(char-ci=? ,sym ,(car elements))) ((and (eq? eqfnc 'string=?) (= (length elements) 2) (string-ci=? (car elements) (cadr elements))) `(string-ci=? ,sym ,(car elements))) ((member elements '((#t #f) (#f #t))) `(boolean? ,sym)) ; zero? doesn't happen (else `(,func ,sym ',(reverse elements) ,@equals))))) (define (reversible-member expr lst) (and (pair? lst) (or (member expr lst) (and (eqv? (length expr) 3) (let ((rev-op (hash-table-ref reversibles (car expr)))) (and rev-op (member (list rev-op (caddr expr) (cadr expr)) lst))))))) (define and-rel-ops (let ((h (make-hash-table))) (for-each (lambda (op) (hash-table-set! h op #t)) '(< = > <= >= char-ci>=? char-ci? char<=? char-ci>? char-ci<=? char>=? char=? string-ci<=? string=? string-ci>=? string? string>=? string<=? string>? eqv? equal? eq? morally-equal?)) h)) ;; -------------------------------- ;; start of simplify-boolean code ;; this is not really simplify boolean as in boolean algebra because in scheme there are many unequal truths, but only one falsehood ;; 'and and 'or are not boolean operators in a sense ;; (format *stderr* "simplify: ~A~%" in-form) (and (not (or (reversible-member in-form false) (and (pair? in-form) (eq? (car in-form) 'not) (pair? (cdr in-form)) ; (not)! (reversible-member (cadr in-form) true)))) (or (and (reversible-member in-form true) #t) (and (pair? in-form) (eq? (car in-form) 'not) (pair? (cdr in-form)) (reversible-member (cadr in-form) false) #t) (if (not (pair? in-form)) in-form (let ((form (bcomp (bsimp in-form)))) (if (not (and (pair? form) (memq (car form) '(or and not)))) (classify form) (let ((len (length form))) (let ((op (case (car form) ((or) 'and) ((and) 'or) (else #f)))) (if (and op (>= len 3) (every? (lambda (p) (and (pair? p) (pair? (cdr p)) (pair? (cddr p)) (eq? (car p) op))) (cdr form))) (let ((first (cadadr form))) (if (every? (lambda (p) (equal? (cadr p) first)) (cddr form)) (set! form `(,op ,first (,(car form) ,@(map (lambda (p) (if (null? (cdddr p)) (caddr p) `(,op ,@(cddr p)))) (cdr form))))) (if (null? (cdddr (cadr form))) (let ((last (caddr (cadr form)))) (if (every? (lambda (p) (and (null? (cdddr p)) (equal? (caddr p) last))) (cddr form)) (set! form `(,op (,(car form) ,@(map cadr (cdr form))) ,last))))))))) ;; (or (and A B) (and A C)) -> (and A (or B C)) ;; (or (and A B) (and C B)) -> (and (or A C) B) ;; (and (or A B) (or A C)) -> (or A (and B C)) ;; (and (or A B) (or C B)) -> (or (and A C) B) (case (car form) ;; -------------------------------- ((not) (if (not (= len 2)) form (let* ((arg (cadr form)) (val (classify (if (and (pair? arg) (memq (car arg) '(and or not))) (simplify-boolean arg true false env) arg))) (arg-op (and (pair? arg) (car arg)))) (cond ((boolean? val) (not val)) ((or (code-constant? arg) (and (pair? arg) (symbol? arg-op) (hash-table-ref no-side-effect-functions arg-op) (let ((ret (return-type arg-op env))) (and (or (symbol? ret) (pair? ret)) (not (return-type-ok? 'boolean? ret)))) (not (var-member arg-op env)))) #f) ((and (pair? val) ; (not (not ...)) -> ... (pair? (cdr val)) ; this is usually internally generated, (memq (car val) '(not if cond case begin))) ; so the message about (and x #t) is in special-case-functions below (case (car val) ((not) (cadr val)) ((if) (let ((if-true (simplify-boolean `(not ,(caddr val)) () () env)) (if-false (or (not (pair? (cdddr val))) ; (not #) -> #t (simplify-boolean `(not ,(cadddr val)) () () env)))) ;; ideally we'd call if-walker on this to simplify further `(if ,(cadr val) ,if-true ,if-false))) ((cond case) `(,(car val) ,@(if (eq? (car val) 'cond) () (list (cadr val))) ,@(map (lambda (c) (if (not (and (pair? c) (pair? (cdr c)))) c (let* ((len (length (cdr c))) (new-last (let ((last (list-ref c len))) (if (and (pair? last) (eq? (car last) 'error)) last (simplify-boolean `(not ,last) () () env))))) `(,(car c) ,@(copy (cdr c) (make-list (- len 1))) ,new-last)))) ((if (eq? (car val) 'cond) cdr cddr) val)))) ((begin) (let* ((len1 (- (length val) 1)) (new-last (simplify-boolean `(not ,(list-ref val len1)) () () env))) `(,@(copy val (make-list len1)) ,new-last))))) ((not (equal? val arg)) `(not ,val)) ((not (pair? arg)) form) ((and (memq arg-op '(and or)) ; (not (or|and x (not y))) -> (and|or (not x) y) (= (length arg) 3) (or (and (pair? (cadr arg)) (eq? (caadr arg) 'not)) (and (pair? (caddr arg)) (eq? (caaddr arg) 'not)))) (let ((rel (if (eq? arg-op 'or) 'and 'or))) `(,rel ,@(map (lambda (p) (if (and (pair? p) (eq? (car p) 'not)) (cadr p) (simplify-boolean `(not ,p) () () env))) (cdr arg))))) ((<= (length arg) 3) ; avoid (<= 0 i 12) and such (case arg-op ((< > <= >= odd? even? exact? inexact?char? char<=? char>=? string? string<=? string>=? char-ci? char-ci<=? char-ci>=? string-ci? string-ci<=? string-ci>=?) `(,(hash-table-ref notables arg-op) ,@(cdr arg))) ;; null? is not quite right because (not (null? 3)) -> #t ;; char-upper-case? and lower are not switchable here ((zero?) ; (not (zero? (logand p 2^n | (ash 1 i)))) -> (logbit? p i) (let ((zarg (cadr arg))) ; (logand...) (if (not (and (pair? zarg) (eq? (car zarg) 'logand) (pair? (cdr zarg)) (pair? (cddr zarg)) (null? (cdddr zarg)))) form (let ((arg1 (cadr zarg)) (arg2 (caddr zarg))) ; these are never reversed (or (and (pair? arg2) (pair? (cdr arg2)) (eq? (car arg2) 'ash) (eqv? (cadr arg2) 1) `(logbit? ,arg1 ,(caddr arg2))) (and (integer? arg2) (positive? arg2) (zero? (logand arg2 (- arg2 1))) ; it's a power of 2 `(logbit? ,arg1 ,(floor (log arg2 2)))) ; floor for freeBSD? form))))) (else form))) (else form))))) ;; -------------------------------- ((or) (case len ((1) #f) ((2) (if (code-constant? (cadr form)) (cadr form) (classify (cadr form)))) (else (call-with-exit (lambda (return) (when (= len 3) (let ((arg1 (cadr form)) (arg2 (caddr form))) (if (and (pair? arg2) ; (or A (and ... A ...)) -> A (eq? (car arg2) 'and) (member arg1 (cdr arg2)) (not (side-effect? arg2 env))) (return arg1)) (if (and (pair? arg1) ; (or (and ... A) A) -> A (eq? (car arg1) 'and) (equal? arg2 (list-ref arg1 (- (length arg1) 1))) (not (side-effect? arg1 env))) (return arg2)) (when (pair? arg2) (if (and (eq? (car arg2) 'and) ; (or A (and (not A) B)) -> (or A B) (pair? (cadr arg2)) (eq? (caadr arg2) 'not) (equal? arg1 (cadadr arg2))) (return `(or ,arg1 ,@(cddr arg2)))) (when (pair? arg1) (when (eq? (car arg1) 'not) (if (symbol? (cadr arg1)) (if (memq (cadr arg1) arg2) (begin (if (eq? (car arg2) 'boolean?) (return arg2)) (and-incomplete form 'or (cadr arg1) arg2 env)) (do ((p arg2 (cdr p))) ((or (not (pair? p)) (and (pair? (car p)) (memq (cadr arg1) (car p)))) (if (pair? p) (and-incomplete form 'or (cadr arg1) (car p) env))))) (if (and (pair? (cadr arg1)) ; (or (not (number? x)) (> x 2)) -> (or (not (real? x)) (> x 2)) (hash-table-ref bools (caadr arg1))) (if (member (cadadr arg1) arg2) (and-forgetful form 'or (cadr arg1) arg2 env) (do ((p arg2 (cdr p))) ((or (not (pair? p)) (and (pair? (car p)) (member (cadadr arg1) (car p)))) (if (pair? p) (and-forgetful form 'or (cadr arg1) (car p) env))))))) (if (and (eq? (car arg2) 'and) ; (or (not A) (and A B)) -> (or (not A) B) -- this stuff actually happens! (equal? (cadr arg1) (cadr arg2))) (return `(or ,arg1 ,@(cddr arg2))))) (when (and (eq? (car arg1) 'and) (eq? (car arg2) 'and) (= 3 (length arg1) (length arg2)) ;; (not (side-effect? arg1 env)) ; maybe?? (or (equal? (cadr arg1) `(not ,(cadr arg2))) (equal? `(not ,(cadr arg1)) (cadr arg2))) (not (equal? (caddr arg1) `(not ,(caddr arg2)))) (not (equal? `(not ,(caddr arg1)) (caddr arg2)))) ;; kinda dumb, but common: (or (and A B) (and (not A) C)) -> (if A B C) ;; the other side: (and (or A B) (or (not A) C)) -> (if A C (and B #t)), but it never happens (lint-format "perhaps ~A" 'or (lists->string form (if (and (pair? (cadr arg1)) (eq? (caadr arg1) 'not)) `(if ,(cadr arg2) ,(caddr arg2) ,(caddr arg1)) `(if ,(cadr arg1) ,(caddr arg1) ,(caddr arg2)))))) (let ((t1 (and (pair? (cdr arg1)) (pair? (cdr arg2)) (or (equal? (cadr arg1) (cadr arg2)) (and (pair? (cddr arg2)) (null? (cdddr arg2)) (equal? (cadr arg1) (caddr arg2)))) (not (side-effect? arg1 env)) (and-redundant? arg1 arg2)))) (if t1 (return (if (eq? t1 (car arg1)) arg2 arg1)))) ;; if all clauses are (eq-func x y) where one of x/y is a symbol|simple-expr repeated throughout ;; and the y is a code-constant, or -> memq and friends. ;; This could also handle cadr|caddr reversed, but it apparently never happens. (if (and (or (and (eq? (car arg2) '=) (memq (car arg1) '(< > <= >=))) (and (eq? (car arg1) '=) (memq (car arg2) '(< > <= >=)))) (= (length arg1) 3) (equal? (cdr arg1) (cdr arg2))) (return `(,(if (or (memq (car arg1) '(< <=)) (memq (car arg2) '(< <=))) '<= '>=) ,@(cdr arg1)))) ;; this makes some of the code above redundant (let ((rel (relsub arg1 arg2 'or env))) (if (or (boolean? rel) (pair? rel)) (return rel))) ;; (or (pair? x) (null? x)) -> (list? x) (when (and (pair? (cdr arg1)) (pair? (cdr arg2)) (equal? (cadr arg1) (cadr arg2))) (if (and (memq (car arg1) '(null? pair?)) (memq (car arg2) '(null? pair?)) (not (eq? (car arg1) (car arg2)))) (return `(list? ,(cadr arg1)))) (if (and (eq? (car arg1) 'zero?) ; (or (zero? x) (positive? x)) -> (not (negative? x)) -- other cases don't happen (memq (car arg2) '(positive? negative?))) (return `(not (,(if (eq? (car arg2) 'positive?) 'negative? 'positive?) ,(cadr arg1)))))) ;; (or (and A B) (and (not A) (not B))) -> (eq? (not A) (not B)) ;; more accurately (if A B (not B)), but every case I've seen is just boolean ;; perhaps also (or (not (or A B)) (not (or (not A) (not B)))), but it never happens (let ((a1 (cadr form)) (a2 (caddr form))) (when (and (pair? a1) (pair? a2) (eq? (car a1) 'and) (eq? (car a2) 'and) (= (length a1) 3) (= (length a2) 3)) (let ((A ((if (and (pair? (cadr a1)) (eq? (caadr a1) 'not)) cadadr cadr) a1)) (B (if (and (pair? (caddr a1)) (eq? (caaddr a1) 'not)) (cadr (caddr a1)) (caddr a1)))) (if (or (equal? form `(or (and ,A ,B) (and (not ,A) (not ,B)))) (equal? form `(or (and (not ,A) (not ,B)) (and ,A ,B)))) (return `(eq? (not ,A) (not ,B)))) (if (or (equal? form `(or (and ,A (not ,B)) (and (not ,A) ,B))) (equal? form `(or (and (not ,A) ,B) (and ,A (not ,B))))) (return `(not (eq? (not ,A) (not ,B)))))))) (when (and (pair? (cdr arg1)) (pair? (cdr arg2)) (not (eq? (car arg1) (car arg2)))) (when (subsumes? (car arg1) (car arg2)) (return arg1)) (if (eq? (car arg1) 'not) (let ((temp arg1)) (set! arg1 arg2) (set! arg2 temp))) (when (and (eq? (car arg2) 'not) (pair? (cadr arg2)) (pair? (cdadr arg2)) (not (eq? (caadr arg2) 'let?)) (or (equal? (cadr arg1) (cadadr arg2)) (and (pair? (cddr arg1)) (equal? (caddr arg1) (cadadr arg2)))) (eq? (return-type (car arg1) env) 'boolean?) (eq? (return-type (caadr arg2) env) 'boolean?)) (let ((t2 (or-not-redundant arg1 arg2))) (when t2 (if (eq? t2 'fatuous) (return #t) (if (pair? t2) (return t2))))))) ;; (or (if a c d) (if b c d)) -> (if (or a b) c d) never happens, sad to say ;; or + if + if does happen but not in this easily optimized form )))) ; len = 3 ;; len > 3 or nothing was caught above (let ((nots 0) (revers 0) (arglen (- len 1))) (for-each (lambda (a) (if (pair? a) (if (eq? (car a) 'not) (set! nots (+ nots 1)) (if (hash-table-ref notables (car a)) (set! revers (+ revers 1)))))) (cdr form)) (if (= nots arglen) ; every arg is `(not ...) (let ((nf (simplify-boolean `(and ,@(map cadr (cdr form))) () () env))) (return (simplify-boolean `(not ,nf) () () env))) (if (and (> arglen 2) (or (> nots (/ (* 2 arglen) 3)) (and (> arglen 2) (> nots (/ arglen 2)) (> revers 0)))) (let ((nf (simplify-boolean `(and ,@(map (lambda (p) (cond ((not (pair? p)) `(not ,p)) ((eq? (car p) 'not) (cadr p)) ((hash-table-ref notables (car p)) => (lambda (op) `(,op ,@(cdr p)))) (else `(not ,p)))) (cdr form))) () () env))) (return (simplify-boolean `(not ,nf) () () env)))))) (let ((sym #f) (eqfnc #f) (vals ()) (start #f)) (define (constant-arg p) (if (code-constant? (cadr p)) (set! vals (cons (cadr p) vals)) (and (code-constant? (caddr p)) (set! vals (cons (caddr p) vals))))) (define (upgrade-eqf) (set! eqfnc (if (memq eqfnc '(string=? string-ci=? = equal?)) 'equal? (if (memq eqfnc '(#f eq?)) 'eq? 'eqv?)))) (do ((fp (cdr form) (cdr fp))) ((null? fp)) (let ((p (and (pair? fp) (car fp)))) (if (and (pair? p) (if (not sym) (set! sym (eqv-selector p)) (equal? sym (eqv-selector p))) (or (not (memq eqfnc '(char-ci=? string-ci=? =))) (memq (car p) '(char-ci=? string-ci=? =))) ;; = can't share: (equal? 1 1.0) -> #f, so (or (not x) (= x 1)) can't be simplified ;; except via member+morally-equal? but that brings in float-epsilon and NaN differences. ;; We could add both: 1 1.0 as in cond? ;; ;; another problem: using memx below means the returned value of the expression ;; may not match the original (#t -> '(...)), so perhaps we should add a one-time ;; warning about this, and wrap it in (pair? (mem...)) as an example. ;; ;; and another thing... the original might be broken: (eq? x #(1)) where equal? ;; is more sensible, but that also changes the behavior of the expression: ;; (memq x '(#(1))) may be #f (or #t!) when (member x '(#(1))) is '(#(1)). ;; ;; I think I'll try to turn out a more-or-less working expression, but warn about it. (case (car p) ((string=? equal?) (set! eqfnc (if (or (not eqfnc) (eq? eqfnc (car p))) (car p) 'equal?)) (and (= (length p) 3) (constant-arg p))) ((char=?) (if (memq eqfnc '(#f char=?)) (set! eqfnc 'char=?) (if (not (eq? eqfnc 'equal?)) (set! eqfnc 'eqv?))) (and (= (length p) 3) (constant-arg p))) ((eq? eqv?) (let ((leqf (car (->eqf (->lint-type ((if (code-constant? (cadr p)) cadr caddr) p)))))) (cond ((not eqfnc) (set! eqfnc leqf)) ((or (memq leqf '(#t equal?)) (not (eq? eqfnc leqf))) (set! eqfnc 'equal?)) ((memq eqfnc '(#f eq?)) (set! eqfnc leqf)))) (and (= (length p) 3) (constant-arg p))) ((char-ci=? string-ci=? =) (and (or (not eqfnc) (eq? eqfnc (car p))) (set! eqfnc (car p)) (= (length p) 3) (constant-arg p))) ((eof-object?) (upgrade-eqf) (set! vals (cons # vals))) ((not) (upgrade-eqf) (set! vals (cons #f vals))) ((boolean?) (upgrade-eqf) (set! vals (cons #f (cons #t vals)))) ((zero?) (if (memq eqfnc '(#f eq?)) (set! eqfnc 'eqv?)) (set! vals (cons 0 (cons 0.0 vals)))) ((null?) (upgrade-eqf) (set! vals (cons () vals))) ((memq memv member) (cond ((eq? (car p) 'member) (set! eqfnc 'equal?)) ((eq? (car p) 'memv) (set! eqfnc (if (eq? eqfnc 'string=?) 'equal? 'eqv?))) ((not eqfnc) (set! eqfnc 'eq?))) (and (= (length p) 3) (pair? (caddr p)) (eq? 'quote (caaddr p)) (pair? (cadr (caddr p))) (set! vals (append (cadr (caddr p)) vals)))) (else #f))) (if (not start) (set! start fp) (if (null? (cdr fp)) (return (if (eq? start (cdr form)) (gather-or-eqf-elements eqfnc sym vals) `(or ,@(copy (cdr form) (make-list (let loop ((g (cdr form)) (len 0)) (if (eq? g start) len (loop (cdr g) (+ len 1)))))) ,(gather-or-eqf-elements eqfnc sym vals)))))) (when start (if (eq? fp (cdr start)) (begin (set! sym #f) (set! eqfnc #f) (set! vals ()) (set! start #f)) ;; here we have possible header stuff + more than one match + trailing stuff (let ((trailer (if (not (and (pair? fp) (pair? (cdr fp)))) fp (let ((nfp (simplify-boolean `(or ,@fp) () () env))) ((if (and (pair? nfp) (eq? (car nfp) 'or)) cdr list) nfp))))) (return (if (eq? start (cdr form)) `(or ,(gather-or-eqf-elements eqfnc sym vals) ,@trailer) `(or ,@(copy (cdr form) (make-list (let loop ((g (cdr form)) (len 0)) (if (eq? g start) len (loop (cdr g) (+ len 1)))))) ,(gather-or-eqf-elements eqfnc sym vals) ,@trailer))))))))) (do ((selector #f) ; (or (and (eq?...)...)....) -> (case ....) (keys ()) (fp (cdr form) (cdr fp))) ((or (null? fp) (let ((p (and (pair? fp) (car fp)))) (not (and (pair? p) (eq? (car p) 'and) (pair? (cdr p)) (pair? (cadr p)) (pair? (cdadr p)) (or selector (set! selector (cadadr p))) (let ((expr (cadr p)) (arg1 (cadadr p))) (case (car expr) ((null?) (and (equal? selector arg1) (not (memq () keys)) (set! keys (cons () keys)))) ;; we have to make sure no keys are repeated: ;; (or (and (eq? x 'a) (< y 1)) (and (eq? x 'a) (< y 2))) ;; this rewrite has become much trickier than expected... ((boolean?) (and (equal? selector arg1) (not (memq #f keys)) (not (memq #t keys)) (set! keys (cons #f (cons #t keys))))) ((eof-object?) (and (equal? selector arg1) (not (memq # keys)) (set! keys (cons # keys)))) ((zero?) (and (equal? selector arg1) (not (memv 0 keys)) (not (memv 0.0 keys)) (set! keys (cons 0.0 (cons 0 keys))))) ((memq memv) (and (equal? selector arg1) (pair? (cddr expr)) (pair? (caddr expr)) (eq? (caaddr expr) 'quote) (pair? (cadr (caddr expr))) (not (any? (lambda (g) (memv g keys)) (cadr (caddr expr)))) (set! keys (append (cadr (caddr expr)) keys)))) ((eq? eqv? char=?) (and (pair? (cddr expr)) (null? (cdddr expr)) (or (and (equal? selector arg1) (code-constant? (caddr expr)) (not (memv (unquoted (caddr expr)) keys)) (set! keys (cons (unquoted (caddr expr)) keys))) (and (equal? selector (caddr expr)) (code-constant? arg1) (not (memv (unquoted arg1) keys)) (set! keys (cons (unquoted arg1) keys)))))) ((not) ;; no hits here for last+not eq(etc)+no collision in keys (and (equal? selector arg1) (not (memq #f keys)) (set! keys (cons #f keys)))) (else #f))))))) (if (null? fp) (return `(case ,selector ,@(map (lambda (p) (let ((result (if (null? (cdddr p)) (caddr p) `(and ,@(cddr p)))) (key (let ((expr (cadr p))) (case (car expr) ((eq? eqv? char=?) (let ((repeats (equal? selector (cadr expr)))) (list (unquoted ((if repeats caddr cadr) expr))))) ((memq memv) (unquoted (caddr expr))) ((null?) (list ())) ((eof-object?) (list #)) ((zero?) (list 0 0.0)) ((not) (list #f)) ((boolean?) (list #t #f)))))) (list key result))) (cdr form)) (else #f)))))) (do ((new-form ()) (retry #f) (exprs (cdr form) (cdr exprs))) ((null? exprs) (return (and (pair? new-form) (if (null? (cdr new-form)) (car new-form) (if retry (simplify-boolean `(or ,@(reverse new-form)) () () env) `(or ,@(reverse new-form))))))) (let ((val (classify (car exprs))) (old-form new-form)) (when (and (pair? val) (memq (car val) '(and or not))) (set! val (classify (simplify-boolean val true false env))) (when (and (> len 3) (pair? val) (eq? (car val) 'not) (pair? (cdr exprs))) (if (symbol? (cadr val)) (if (and (pair? (cadr exprs)) (memq (cadr val) (cadr exprs))) (and-incomplete form 'or (cadr val) (cadr exprs) env) (do ((ip (cdr exprs) (cdr ip)) (found-it #f)) ((or found-it (not (pair? ip)))) (do ((p (car ip) (cdr p))) ((or (not (pair? p)) (and (memq (cadr val) p) (set! found-it p))) (if (pair? found-it) (and-incomplete form 'or (cadr val) found-it env)))))) (when (and (pair? (cadr val)) (pair? (cadr exprs)) (hash-table-ref bools (caadr val))) (if (member (cadadr val) (cadr exprs)) (and-forgetful form 'or (cadr val) (cadr exprs) env) (do ((p (cadr exprs) (cdr p))) ((or (not (pair? p)) (and (pair? (car p)) (member (cadadr val) (car p)))) (if (pair? p) (and-forgetful form 'or (cadr val) (car p) env))))))))) (if (not (or retry (equal? val (car exprs)))) (set! retry #t)) (cond ((not val)) ; #f in or is ignored ((or (eq? val #t) ; #t or any non-#f constant in or ends the expression (code-constant? val)) (set! new-form (if (null? new-form) ; (or x1 123) -> value of x1 first (list val) (cons val new-form))) ;; reversed when returned (set! exprs '(#t))) ((and (pair? val) ; (or ...) -> splice into current (eq? (car val) 'or)) (set! exprs (append val (cdr exprs)))) ; we'll skip the 'or in do step ((not (or (memq val new-form) (and (pair? val) ; and redundant tests (hash-table-ref booleans (car val)) (any? (lambda (p) (and (pair? p) (subsumes? (car p) (car val)) (equal? (cadr val) (cadr p)))) new-form)))) (set! new-form (cons val new-form)))) (if (and (not (eq? new-form old-form)) (pair? (cdr new-form))) (let ((rel (relsub (cadr new-form) (car new-form) 'or env))) ; new-form is reversed (if (or (boolean? rel) (pair? rel)) (set! new-form (cons rel (cddr new-form)))))))))))))) ;; -------------------------------- ((and) (case len ((1) #t) ((2) (classify (cadr form))) (else (and (not (contradictory? (cdr form))) (call-with-exit (lambda (return) (when (= len 3) (let ((arg1 (cadr form)) (arg2 (caddr form))) (if (and (pair? arg2) ; (and A (or A ...)) -> A (eq? (car arg2) 'or) (equal? arg1 (cadr arg2)) (not (side-effect? arg2 env))) (return arg1)) (if (and (pair? arg1) ; (and (or ... A ...) A) -> A (eq? (car arg1) 'or) (member arg2 (cdr arg1)) (not (side-effect? arg1 env))) (return arg2)) ;; the and equivalent of (or (not A) (and A B)) never happens (when (pair? arg2) (if (symbol? arg1) ; (and x (pair? x)) -> (pair? x) (if (memq arg1 arg2) (begin (case (car arg2) ((not) (return #f)) ((boolean?) (return `(eq? ,arg1 #t)))) (and-incomplete form 'and arg1 arg2 env) (if (hash-table-ref booleans (car arg2)) (return arg2))) (do ((p arg2 (cdr p))) ; (and x (+ (log x) 1)) -> (and (number? x)...) ((or (not (pair? p)) (and (pair? (car p)) (memq arg1 (car p)))) (if (pair? p) (and-incomplete form 'and arg1 (car p) env))))) (if (and (pair? arg1) ; (and (number? x) (> x 2)) -> (and (real? x) (> x 2)) (hash-table-ref bools (car arg1))) (if (member (cadr arg1) arg2) (and-forgetful form 'and arg1 arg2 env) (do ((p arg2 (cdr p))) ((or (not (pair? p)) (and (pair? (car p)) (member (cadr arg1) (car p)))) (if (pair? p) (and-forgetful form 'and arg1 (car p) env)))))))) (if (and (not (side-effect? arg1 env)) (equal? arg1 arg2)) ; (and x x) -> x (return arg1)) (when (and (pair? arg1) (pair? arg2) (pair? (cdr arg1)) (pair? (cdr arg2))) (let ((t1 (and (or (equal? (cadr arg1) (cadr arg2)) (and (pair? (cddr arg2)) (null? (cdddr arg2)) (equal? (cadr arg1) (caddr arg2)))) (not (side-effect? arg1 env)) (and-redundant? arg1 arg2)))) ; (and (integer? x) (number? x)) -> (integer? x) (if t1 (return (cond ((memq t1 '(eq? eqv? equal?)) `(,t1 ,@(cdr arg2))) ((eq? t1 'memv) (let ((x ((if (equal? (cadr arg1) (cadr arg2)) caddr cadr) arg2))) (if (rational? x) `(memv ,(cadr arg1) '(,x ,(* 1.0 x))) `(memv ,(cadr arg1) '(,(floor x) ,x))))) ((eq? t1 (car arg1)) arg1) (else arg2))))) (when (and (hash-table-ref reversibles (car arg1)) (pair? (cddr arg1)) (null? (cdddr arg1)) (pair? (cddr arg2)) (null? (cdddr arg2)) (not (side-effect? arg2 env)) ; arg1 is hit in any case (or (eq? (car arg1) (car arg2)) ; either ops are equal or (let ((rf (hash-table-ref reversibles (car arg2)))) ; try reversed op for arg2 (and (eq? (car arg1) rf) (set! arg2 (cons rf (reverse (cdr arg2)))))))) (when (and (memq (car arg1) '(< <= >= >)) ; (and (op x y) (op x z)) -> (op x (min|max y z)) (equal? (cadr arg1) (cadr arg2))) (if (and (rational? (caddr arg1)) (rational? (caddr arg2))) (return `(,(car arg1) ,(cadr arg1) ,((if (memq (car arg1) '(< <=)) min max) (caddr arg1) (caddr arg2))))) (return `(,(car arg1) ,(cadr arg1) (,(if (memq (car arg1) '(< <=)) 'min 'max) ,(caddr arg1) ,(caddr arg2))))) (when (or (equal? (caddr arg1) (cadr arg2)) ; (and (op x y) (op y z)) (equal? (cadr arg1) (caddr arg2)) ; (and (op x y) (op z x)) (and (memq (car arg1) '(= char=? string=? char-ci=? string-ci=?)) (or (equal? (cadr arg1) (cadr arg2)) (equal? (caddr arg1) (caddr arg2))))) (let ((op1 (car arg1)) (arg1-1 (cadr arg1)) (arg1-2 (caddr arg1)) (arg2-1 (cadr arg2)) (arg2-2 (caddr arg2))) (return (cond ((equal? arg1-2 arg2-1) ; (and (op x y) (op y z)) -> (op x y z) (if (equal? arg1-1 arg2-2) (if (memq op1 '(= char=? string=? char-ci=? string-ci=?)) arg1 (and (memq op1 '(<= >= char<=? char>=? string<=? string>=? char-ci<=? char-ci>=? string-ci<=? string-ci>=?)) `(,(case op1 ((>= <=) '=) ((char<= char>=) 'char=?) ((char-ci<= char-ci>=) 'char-ci=?) ((string<= string>=) 'string=?) ((string-ci<= string-ci>=) 'string-ci=?)) ,@(cdr arg1)))) (and (or (not (code-constant? arg1-1)) (not (code-constant? arg2-2)) ((symbol->value op1) arg1-1 arg2-2)) `(,op1 ,arg1-1 ,arg2-1 ,arg2-2)))) ((equal? arg1-1 arg2-2) ; (and (op x y) (op z x)) -> (op z x y) (if (equal? arg1-2 arg2-1) (and (memq op1 '(= char=? string=? char-ci=? string-ci=?)) arg1) (and (or (not (code-constant? arg2-1)) (not (code-constant? arg1-2)) ((symbol->value op1) arg2-1 arg1-2)) `(,op1 ,arg2-1 ,arg1-1 ,arg1-2)))) ;; here we're restricted to equalities and we know arg1 != arg2 ((equal? arg1-1 arg2-1) ; (and (op x y) (op x z)) -> (op x y z) (if (and (code-constant? arg1-2) (code-constant? arg2-2)) (and ((symbol->value op1) arg1-2 arg2-2) arg1) `(,op1 ,arg1-1 ,arg1-2 ,arg2-2))) ;; equalities again ((and (code-constant? arg1-1) (code-constant? arg2-1)) (and ((symbol->value op1) arg1-1 arg2-1) arg1)) (else `(,op1 ,arg1-1 ,arg1-2 ,arg2-1))))))) ;; check some special cases (when (and (or (equal? (cadr arg1) (cadr arg2)) (and (pair? (cddr arg2)) (null? (cdddr arg2)) (equal? (cadr arg1) (caddr arg2)))) (hash-table-ref booleans (car arg1))) (when (or (eq? (car arg1) 'zero?) ; perhaps rational? and integer? here -- not many hits (eq? (car arg2) 'zero?)) (if (or (memq (car arg1) '(integer? rational? exact?)) (memq (car arg2) '(integer? rational? exact?))) (return `(eqv? ,(cadr arg1) 0))) (if (or (eq? (car arg1) 'inexact?) (eq? (car arg2) 'inexact?)) (return `(eqv? ,(cadr arg1) 0.0)))) (when (hash-table-ref and-rel-ops (car arg2)) (when (and (eq? (car arg1) 'symbol?) (memq (car arg2) '(eq? eqv? equal?)) (or (quoted-symbol? (cadr arg2)) (quoted-symbol? (caddr arg2)))) (return `(eq? ,@(cdr arg2)))) (when (and (eq? (car arg1) 'positive?) (eq? (car arg2) '<) (eq? (cadr arg1) (cadr arg2))) (return `(< 0 ,(cadr arg1) ,(caddr arg2)))))) (when (and (member (cadr arg1) arg2) (memq (car arg2) '(string=? char=? eq? eqv? equal?)) (null? (cdddr arg2)) (hash-table-ref bools (car arg1)) (or (and (code-constant? (cadr arg2)) (compatible? (car arg1) (->lint-type (cadr arg2)))) (and (code-constant? (caddr arg2)) (compatible? (car arg1) (->lint-type (caddr arg2)))))) (return `(,(if (eq? (car arg1) 'char?) ,eqv? 'equal?) ,@(cdr arg2)))) (when (and (equal? (cadr arg1) (cadr arg2)) (eq? (car arg1) 'inexact?) (eq? (car arg2) 'real?)) (return `(and ,arg2 ,arg1))) ;; this makes some of the code above redundant (let ((rel (relsub arg1 arg2 'and env))) (if (or (boolean? rel) (pair? rel)) (return rel))) ;; (and ... (not...)) (unless (eq? (car arg1) (car arg2)) (if (eq? (car arg1) 'not) (let ((temp arg1)) (set! arg1 arg2) (set! arg2 temp))) (when (and (eq? (car arg2) 'not) (pair? (cadr arg2)) (pair? (cdadr arg2)) (not (eq? (caadr arg2) 'let?)) (or (equal? (cadr arg1) (cadadr arg2)) (and (pair? (cddr arg1)) (equal? (caddr arg1) (cadadr arg2)))) (eq? (return-type (car arg1) env) 'boolean?) (eq? (return-type (caadr arg2) env) 'boolean?)) (let ((t2 (and-not-redundant arg1 arg2))) (cond ;((not t2) #f) ((eq? t2 'contradictory) (return #f)) ((symbol? t2) (return `(,t2 ,@(cdr arg1)))) ((pair? t2) (return t2)))))) (if (hash-table-ref bools (car arg1)) (let ((p (member (cadr arg1) (cdr arg2)))) (when p (let ((sig (arg-signature (car arg2) env)) (pos (- (length arg2) (length p)))) (when (pair? sig) (let ((arg-type (and (> (length sig) pos) (list-ref sig pos)))) (unless (compatible? (car arg1) arg-type) (let ((ln (and (< 0 line-number 100000) line-number))) (format outport "~NCin ~A~A, ~A is ~A, but ~A wants ~A" lint-left-margin #\space (truncated-list->string form) (if ln (format #f " (line ~D)" ln) "") (cadr arg1) (prettify-checker-unq (car arg1)) (car arg2) (prettify-checker arg-type)))))))))) (cond ((not (and (eq? (car arg1) 'equal?) ; (and (equal? (car a1) (car a2)) (equal? (cdr a1) (cdr a2))) -> (equal? a1 a2) (eq? (car arg2) 'equal?) (pair? (cadr arg1)) (pair? (caddr arg1)) (pair? (cadr arg2)) (pair? (caddr arg2)) (eq? (caadr arg1) (caaddr arg1))))) ((assq (caadr arg1) '((car cdr #t) (caar cdar car) (cadr cddr cdr) (caaar cdaar caar) (caadr cdadr cadr) (caddr cdddr cddr) (cadar cddar cdar) (cadddr cddddr cdddr) (caaaar cdaaar caaar) (caaadr cdaadr caadr) (caadar cdadar cadar) (caaddr cdaddr caddr) (cadaar cddaar cdaar) (cadadr cddadr cdadr) (caddar cdddar cddar))) => (lambda (x) (if (and (eq? (caadr arg2) (cadr x)) (eq? (caaddr arg2) (cadr x)) (equal? (cadadr arg1) (cadadr arg2)) (equal? (cadr (caddr arg1)) (cadr (caddr arg2)))) (return (if (symbol? (caddr x)) `(equal? (,(caddr x) ,(cadadr arg1)) (,(caddr x) ,(cadr (caddr arg1)))) `(equal? ,(cadadr arg1) ,(cadr (caddr arg1))))))))) ))) ;; len > 3 or nothing was caught above (let ((nots 0) (revers 0) (arglen (- len 1))) (for-each (lambda (a) (if (pair? a) (if (eq? (car a) 'not) (set! nots (+ nots 1)) (if (hash-table-ref notables (car a)) (set! revers (+ revers 1)))))) (cdr form)) (if (= nots arglen) ; every arg is `(not ...) (let ((nf (simplify-boolean `(or ,@(map cadr (cdr form))) () () env))) (return (simplify-boolean `(not ,nf) () () env))) (if (and (> arglen 2) (or (>= nots (/ (* 3 arglen) 4)) ; > 2/3 seems to get some ugly rewrites (and (>= nots (/ (* 2 arglen) 3)) ; was > 1/2 here (> revers 0)))) (let ((nf (simplify-boolean `(or ,@(map (lambda (p) (cond ((not (pair? p)) `(not ,p)) ((eq? (car p) 'not) (cadr p)) ((hash-table-ref notables (car p)) => (lambda (op) `(,op ,@(cdr p)))) (else `(not ,p)))) (cdr form))) () () env))) (return (simplify-boolean `(not ,nf) () () env)))))) (if (every? (lambda (a) (and (pair? a) (eq? (car a) 'zero?))) (cdr form)) (return `(= 0 ,@(map cadr (cdr form))))) (let ((diff (apply and-redundants env (cdr form)))) (when diff (if (null? (cdr diff)) (return (car diff))) (return (simplify-boolean `(and ,@diff) () () env)))) ;; now there are redundancies below (see subsumes?) but they assumed the tests were side-by-side (do ((new-form ()) (retry #f) (exprs (cdr form) (cdr exprs))) ((null? exprs) (or (null? new-form) ; (and) -> #t (let ((newer-form (let ((nform (reverse new-form))) (map (lambda (x cdr-x) (if (and x (code-constant? x)) (values) x)) nform (cdr nform))))) (return (cond ((null? newer-form) (car new-form)) ((and (eq? (car new-form) #t) ; trailing #t is dumb if next-to-last is boolean func (pair? (cdr new-form)) (pair? (cadr new-form)) (symbol? (caadr new-form)) (eq? (return-type (caadr new-form) env) 'boolean?)) (if (null? (cdr newer-form)) (car newer-form) `(and ,@newer-form))) (retry (simplify-boolean `(and ,@newer-form ,(car new-form)) () () env)) (else `(and ,@newer-form ,(car new-form)))))))) (let* ((e (car exprs)) (val (classify e)) (old-form new-form)) (if (and (pair? val) (memq (car val) '(and or not))) (set! val (classify (set! e (simplify-boolean val () false env)))) (when (and (> len 3) (pair? (cdr exprs))) (if (symbol? val) (if (and (pair? (cadr exprs)) (memq val (cadr exprs))) (let ((nval (simplify-boolean `(and ,val ,(cadr exprs)) () false env))) (if (and (pair? nval) (eq? (car nval) 'and)) (and-incomplete form 'and val (cadr exprs) env) (begin (set! val nval) (set! exprs (cdr exprs))))) (do ((ip (cdr exprs) (cdr ip)) (found-it #f)) ((or found-it (not (pair? ip)))) (do ((p (car ip) (cdr p))) ((or (not (pair? p)) (and (memq val p) (let ((nval (simplify-boolean `(and ,val ,p) () false env))) (if (and (pair? nval) (eq? (car nval) 'and)) (set! found-it p) (let ((ln (and (< 0 line-number 100000) line-number))) (format outport "~NCin ~A~A,~%~NCperhaps change ~S to ~S~%" lint-left-margin #\space (truncated-list->string form) (if ln (format #f " (line ~D)" ln) "") (+ lint-left-margin 4) #\space `(and ... ,val ... ,p) nval) (set! found-it #t))))) (and (pair? (car p)) (memq val (car p)) (set! found-it (car p)))) (if (pair? found-it) (and-incomplete form 'and val found-it env)))))) (when (and (pair? val) (pair? (cadr exprs)) (hash-table-ref bools (car val))) (if (member (cadr val) (cadr exprs)) (and-forgetful form 'and val (cadr exprs) env) (do ((p (cadr exprs) (cdr p))) ((or (not (pair? p)) (and (pair? (car p)) (member (cadr val) (car p)))) (if (pair? p) (and-forgetful form 'and val (car p) env))))))))) (if (not (or retry (equal? e (car exprs)))) (set! retry #t)) ;(format *stderr* "val: ~A, e: ~A~%" val e) ;; (and x1 x2 x1) is not reducible ;; the final thing has to remain at the end, but can be deleted earlier if it can't short-circuit the evaluation, ;; but if there are expressions following the first x1, we can't be sure that it is not ;; protecting them: ;; (and false-or-0 (display (list-ref lst false-or-0)) false-or-0) ;; so I'll not try to optimize that case. But (and x x) is optimizable. (cond ((eq? val #t) (if (null? (cdr exprs)) ; (and x y #t) should not remove the #t (if (or (and (pair? e) (eq? (return-type (car e) env) 'boolean?)) (eq? e #t)) (set! new-form (cons val new-form)) (if (or (null? new-form) (not (equal? e (car new-form)))) (set! new-form (cons e new-form)))) (if (and (not (eq? e #t)) (or (null? new-form) (not (member e new-form)))) (set! new-form (cons e new-form))))) ((not val) ; #f in 'and' ends the expression (set! new-form (if (or (null? new-form) (just-symbols? new-form)) '(#f) (cons #f new-form))) (set! exprs '(#f))) ((and (pair? e) ; if (and ...) splice into current (eq? (car e) 'and)) (set! exprs (append e (cdr exprs)))) ((and (pair? e) ; (and (list? p) (pair? p) ...) -> (and (pair? p) ...) (pair? (cdr exprs)) (pair? (cadr exprs)) (eq? (and-redundant? e (cadr exprs)) (caadr exprs)) (equal? (cadr e) (cadadr exprs)))) ((and (pair? e) ; (and (list? p) (not (null? p)) ...) -> (and (pair? p) ...) (memq (car e) '(list? pair?)) (pair? (cdr exprs)) (let ((p (cadr exprs))) (and (pair? p) (eq? (car p) 'not) (pair? (cadr p)) (eq? (caadr p) 'null?) (equal? (cadr e) (cadadr p))))) (set! new-form (cons `(pair? ,(cadr e)) new-form)) (set! exprs (cdr exprs))) ((not (and (pair? e) ; (and ... (or ... 123) ...) -> splice out or (pair? (cdr exprs)) (eq? (car e) 'or) (pair? (cdr e)) (pair? (cddr e)) (cond ((list-ref e (- (length e) 1)) => code-constant?) ; (or ... #f) (else #f)))) (if (not (and (pair? new-form) (or (eq? val (car new-form)) ; omit repeated tests (and (pair? val) ; and redundant tests (hash-table-ref booleans (car val)) (any? (lambda (p) (and (pair? p) (subsumes? (car val) (car p)) (equal? (cadr val) (cadr p)))) new-form))))) (set! new-form (cons val new-form))))) (if (and (not (eq? new-form old-form)) (pair? (cdr new-form))) (let ((rel (relsub (car new-form) (cadr new-form) 'and env))) ;; rel #f should halt everything as above, and it looks ugly in the output, ;; but it never happens in real code (if (or (pair? rel) (boolean? rel)) (set! new-form (cons rel (cddr new-form)))))))))))))))))))))))) (define (splice-if f lst) (cond ((null? lst) ()) ((not (pair? lst)) lst) ((and (pair? (car lst)) (f (caar lst))) (append (splice-if f (cdar lst)) (splice-if f (cdr lst)))) (else (cons (car lst) (splice-if f (cdr lst)))))) (define (horners-rule form) (and (pair? form) (call-with-exit (lambda (return) (do ((p form (cdr p)) (coeffs #f) (top 0) (sym #f)) ((not (pair? p)) (do ((x (- top 1) (- x 1)) (result (coeffs top))) ((< x 0) result) (set! result (if (zero? (coeffs x)) `(* ,sym ,result) `(+ ,(coeffs x) (* ,sym ,result)))))) (let ((cx (car p))) (cond ((number? cx) (if (not coeffs) (set! coeffs (make-vector 4 0))) (set! (coeffs 0) (+ (coeffs 0) cx))) ((symbol? cx) (if (not sym) (set! sym cx) (if (not (eq? sym cx)) (return #f))) (if (not coeffs) (set! coeffs (make-vector 4 0))) (set! top (max top 1)) (set! (coeffs 1) (+ (coeffs 1) 1))) ((not (and (pair? cx) (eq? (car cx) '*))) (return #f)) (else (let ((ctr 0) (ax 1)) (for-each (lambda (qx) (if (symbol? qx) (if (not sym) (begin (set! sym qx) (set! ctr 1)) (if (not (eq? sym qx)) (return #f) (set! ctr (+ ctr 1)))) (if (number? qx) (set! ax (* ax qx)) (return #f)))) (cdr cx)) (if (not coeffs) (set! coeffs (make-vector 4 0))) (if (>= ctr (length coeffs)) (set! coeffs (copy coeffs (make-vector (* ctr 2) 0)))) (set! top (max top ctr)) (set! (coeffs ctr) (+ (coeffs ctr) ax))))))))))) (define (simplify-numerics form env) ;; this returns a form, possibly the original simplified (let ((real-result? (lambda (op) (memq op '(imag-part real-part abs magnitude angle max min exact->inexact inexact modulo remainder quotient lcm gcd)))) (rational-result? (lambda (op) (memq op '(rationalize inexact->exact exact)))) (integer-result? (lambda (op) (memq op '(logior lognot logxor logand numerator denominator floor round truncate ceiling ash))))) (define (inverse-op op) (case op ((sin) 'asin) ((cos) 'acos) ((tan) 'atan) ((asin) 'sin) ((acos) 'cos) ((atan) 'tan) ((sinh) 'asinh) ((cosh) 'acosh) ((tanh) 'atanh) ((asinh) 'sinh) ((acosh) 'cosh) ((atanh) 'tanh) ((log) 'exp) ((exp) 'log))) (define (just-rationals? form) (or (null? form) (rational? form) (and (pair? form) (rational? (car form)) (just-rationals? (cdr form))))) (define (just-reals? form) (or (null? form) (real? form) (and (pair? form) (real? (car form)) (just-reals? (cdr form))))) (define (just-integers? form) (or (null? form) (integer? form) (and (pair? form) (integer? (car form)) (just-integers? (cdr form))))) (define (simplify-arg x) (if (or (null? x) ; constants and the like look dumb if simplified (not (proper-list? x)) (not (hash-table-ref no-side-effect-functions (car x))) (var-member (car x) env)) x (let ((f (simplify-numerics x env))) (if (and (pair? f) (just-rationals? f)) (catch #t (lambda () (eval f)) (lambda ignore f)) f)))) (define (remove-inexactions val) (when (and (or (assq 'exact->inexact val) (assq 'inexact val)) (not (tree-member 'random val)) (any? number? val)) (set! val (map (lambda (x) (if (and (pair? x) (memq (car x) '(inexact exact->inexact))) (cadr x) x)) val)) (if (not (any? (lambda (x) (and (number? x) (inexact? x))) val)) (do ((p val (cdr p))) ((or (null? p) (number? (car p))) (if (pair? p) (set-car! p (* 1.0 (car p)))))))) val) ;; polar notation (@) is never used anywhere except test suites (let* ((args (map simplify-arg (cdr form))) (len (length args))) (case (car form) ((+) (case len ((0) 0) ((1) (car args)) (else (let ((val (remove-all 0 (splice-if (lambda (x) (eq? x '+)) args)))) (if (every? (lambda (x) (or (not (number? x)) (rational? x))) val) (let ((rats (collect-if list rational? val))) (if (and (pair? rats) (pair? (cdr rats))) (let ((y (apply + rats))) (set! val (if (zero? y) (collect-if list (lambda (x) (not (number? x))) val) (cons y (collect-if list (lambda (x) (not (number? x))) val)))))))) (set! val (remove-inexactions val)) (if (any? (lambda (p) ; collect all + and - vals -> (- (+ ...) ...) (and (pair? p) (eq? (car p) '-))) val) (let ((plus ()) (minus ()) (c 0)) (for-each (lambda (p) (if (not (and (pair? p) (eq? (car p) '-))) (if (rational? p) (set! c (+ c p)) (set! plus (cons p plus))) (if (null? (cddr p)) (if (rational? (cadr p)) (set! c (- c (cadr p))) (set! minus (cons (cadr p) minus))) (begin (if (rational? (cadr p)) (set! c (+ c (cadr p))) (set! plus (cons (cadr p) plus))) (for-each (lambda (p1) (if (rational? p1) (set! c (- c p1)) (set! minus (cons p1 minus)))) (cddr p)))))) val) (simplify-numerics `(- (+ ,@(reverse plus) ,@(if (positive? c) (list c) ())) ,@(reverse minus) ,@(if (negative? c) (list (abs c)) ())) env)) (case (length val) ((0) 0) ((1) (car val)) ; (+ x) -> x ((2) (let ((arg1 (car val)) (arg2 (cadr val))) (cond ((and (real? arg2) ; (+ x -1) -> (- x 1) (negative? arg2) (not (number? arg1))) `(- ,arg1 ,(abs arg2))) ((and (real? arg1) ; (+ -1 x) -> (- x 1) (negative? arg1) (not (number? arg2))) `(- ,arg2 ,(abs arg1))) ((and (pair? arg1) (eq? (car arg1) '*) ; (+ (* a b) (* a c)) -> (* a (+ b c)) (pair? arg2) (eq? (car arg2) '*) (any? (lambda (a) (member a (cdr arg2))) (cdr arg1))) (do ((times ()) (pluses ()) (rset (cdr arg2)) (p (cdr arg1) (cdr p))) ((null? p) ;; times won't be () because we checked above for a match ;; if pluses is (), arg1 is completely included in arg2 ;; if rset is (), arg2 is included in arg1 (simplify-numerics `(* ,@(reverse times) (+ (* ,@(reverse (if (pair? pluses) pluses (list (if (null? pluses) 1 pluses))))) (* ,@rset))) env)) (if (member (car p) rset) (begin (set! times (cons (car p) times)) (set! rset (remove (car p) rset))) (set! pluses (cons (car p) pluses))))) ((and (pair? arg1) (eq? (car arg1) '/) ; (+ (/ a b) (/ c b)) -> (/ (+ a c) b) (pair? arg2) (eq? (car arg2) '/) (pair? (cddr arg1)) (pair? (cddr arg2)) (equal? (cddr arg1) (cddr arg2))) `(/ (+ ,(cadr arg1) ,(cadr arg2)) ,@(cddr arg1))) (else `(+ ,@val))))) (else (or (horners-rule val) ;; not many cases here, oddly enough, Horner's rule gets most ;; (+ (/ (f x) 3) (/ (g x) 3) (/ (h x) 3) 15) [ignoring problems involving overflow] `(+ ,@val))))))))) ((*) (case len ((0) 1) ((1) (car args)) (else (let ((val (remove-all 1 (splice-if (lambda (x) (eq? x '*)) args)))) (if (every? (lambda (x) (or (not (number? x)) (rational? x))) val) (let ((rats (collect-if list rational? val))) (if (and (pair? rats) (pair? (cdr rats))) (let ((y (apply * rats))) (set! val (if (= y 1) (collect-if list (lambda (x) (not (number? x))) val) (cons y (collect-if list (lambda (x) (not (number? x))) val)))))))) (set! val (remove-inexactions val)) (case (length val) ((0) 1) ((1) (car val)) ; (* x) -> x ((2) (let ((arg1 (car val)) (arg2 (cadr val))) (cond ((just-rationals? val) (let ((new-val (apply * val))) ; huge numbers here are less readable (if (< (abs new-val) 1000000) new-val `(* ,@val)))) ((memv 0 val) ; (* x 0) -> 0 0) ((memv -1 val) `(- ,@(remove -1 val))) ; (* -1 x) -> (- x) ((not (pair? arg2)) `(* ,@val)) ((pair? arg1) (let ((op1 (car arg1)) (op2 (car arg2))) (cond ((and (eq? op1 '-) ; (* (- x) (- y)) -> (* x y) (null? (cddr arg1)) (eq? op2 '-) (null? (cddr arg2))) `(* ,(cadr arg1) ,(cadr arg2))) ((and (eq? op1 '/) ; (* (/ x) (/ y)) -> (/ (* x y)) etc (eq? op2 '/)) (let ((op1-arg1 (cadr arg1)) (op2-arg1 (cadr arg2))) (if (null? (cddr arg1)) (if (null? (cddr arg2)) `(/ (* ,op1-arg1 ,op2-arg1)) (if (equal? op1-arg1 op2-arg1) `(/ ,(caddr arg2)) (simplify-numerics `(/ ,op2-arg1 (* ,op1-arg1 ,(caddr arg2))) env))) (if (null? (cddr arg2)) (if (equal? op1-arg1 op2-arg1) `(/ ,(caddr arg1)) (simplify-numerics `(/ ,op1-arg1 (* ,(caddr arg1) ,op2-arg1)) env)) (simplify-numerics `(/ (* ,op1-arg1 ,op2-arg1) (* ,@(cddr arg1) ,@(cddr arg2))) env))))) ((and (= (length arg1) 3) (equal? (cdr arg1) (cdr arg2)) (case op1 ((gcd) (eq? op2 'lcm)) ((lcm) (eq? op2 'gcd)) (else #f))) `(abs (* ,@(cdr arg1)))) ; (* (gcd a b) (lcm a b)) -> (abs (* a b)) but only if 2 args? ((and (eq? op1 'exp) ; (* (exp a) (exp b)) -> (exp (+ a b)) (eq? op2 'exp)) `(exp (+ ,(cadr arg1) ,(cadr arg2)))) ((and (eq? op1 'sqrt) ; (* (sqrt x) (sqrt y)) -> (sqrt (* x y)) (eq? op2 'sqrt)) `(sqrt (* ,(cadr arg1) ,(cadr arg2)))) ((not (and (eq? op1 'expt) (eq? op2 'expt))) `(* ,@val)) ((equal? (cadr arg1) (cadr arg2)) ; (* (expt x y) (expt x z)) -> (expt x (+ y z)) `(expt ,(cadr arg1) (+ ,(caddr arg1) ,(caddr arg2)))) ((equal? (caddr arg1) (caddr arg2)) ; (* (expt x y) (expt z y)) -> (expt (* x z) y) `(expt (* ,(cadr arg1) ,(cadr arg2)) ,(caddr arg1))) (else `(* ,@val))))) ((and (number? arg1) ; (* 2 (random 3.0)) -> (random 6.0) (eq? (car arg2) 'random) (number? (cadr arg2)) (not (rational? (cadr arg2)))) `(random ,(* arg1 (cadr arg2)))) (else `(* ,@val))))) (else (cond ((just-rationals? val) (let ((new-val (apply * val))) ; huge numbers here are less readable (if (< (abs new-val) 1000000) new-val `(* ,@val)))) ((memv 0 val) ; (* x 0 2) -> 0 0) ((memv -1 val) `(- (* ,@(remove -1 val)))) ; (* -1 x y) -> (- (* x y)) ((any? (lambda (p) ; collect * and / vals -> (/ (* ...) ...) (and (pair? p) (eq? (car p) '/))) val) (let ((mul ()) (div ())) (for-each (lambda (p) (if (not (and (pair? p) (eq? (car p) '/))) (set! mul (cons p mul)) (if (null? (cddr p)) (set! div (cons (cadr p) div)) (begin (set! mul (cons (cadr p) mul)) (set! div (append (cddr p) div)))))) val) (for-each (lambda (n) (when (member n div) (set! div (remove n div)) (set! mul (remove n mul)))) (copy mul)) (let ((expr (if (null? mul) (if (null? div) `(*) ; for simplify-numerics' benefit `(/ 1 ,@(reverse div))) (if (null? div) `(* ,@(reverse mul)) `(/ (* ,@(reverse mul)) ,@(reverse div)))))) (simplify-numerics expr env)))) (else `(* ,@val))))))))) ((-) (set! args (remove-inexactions args)) (case len ((0) form) ((1) ; negate (if (number? (car args)) (- (car args)) (if (not (list? (car args))) `(- ,@args) (case (length (car args)) ((2) (if (eq? (caar args) '-) (cadar args) ; (- (- x)) -> x `(- ,@args))) ((3) (if (eq? (caar args) '-) `(- ,(caddar args) ,(cadar args)) ; (- (- x y)) -> (- y x) `(- ,@args))) (else `(- ,@args)))))) ((2) (let ((arg1 (car args)) (arg2 (cadr args))) (cond ((just-rationals? args) (apply - args)) ; (- 3 2) -> 1 ((eqv? arg1 0) `(- ,arg2)) ; (- 0 x) -> (- x) ((eqv? arg2 0) arg1) ; (- x 0) -> x ((equal? arg1 arg2) 0) ; (- x x) -> 0 ((and (pair? arg2) (eq? (car arg2) '-) (pair? (cdr arg2))) (if (null? (cddr arg2)) `(+ ,arg1 ,(cadr arg2)) ; (- x (- y)) -> (+ x y) (simplify-numerics `(- (+ ,arg1 ,@(cddr arg2)) ,(cadr arg2)) env))) ; (- x (- y z)) -> (- (+ x z) y) ((and (pair? arg2) ; (- x (+ y z)) -> (- x y z) (eq? (car arg2) '+)) (simplify-numerics `(- ,arg1 ,@(cdr arg2)) env)) ((and (pair? arg1) ; (- (- x y) z) -> (- x y z) (eq? (car arg1) '-)) (if (> (length arg1) 2) `(- ,@(cdr arg1) ,arg2) (simplify-numerics `(- (+ ,(cadr arg1) ,arg2)) env))) ; (- (- x) y) -> (- (+ x y)) ((and (pair? arg2) ; (- x (truncate x)) -> (remainder x 1) (eq? (car arg2) 'truncate) (equal? arg1 (cadr arg2))) `(remainder ,arg1 1)) ((and (real? arg2) ; (- x -1) -> (+ x 1) (negative? arg2) (not (number? arg1))) `(+ ,arg1 ,(abs arg2))) (else `(- ,@args))))) (else (if (just-rationals? args) (apply - args) (let ((val (remove-all 0 (splice-if (lambda (x) (eq? x '+)) (cdr args))))) (if (every? (lambda (x) (or (not (number? x)) (rational? x))) val) (let ((rats (collect-if list rational? val))) (if (and (pair? rats) (pair? (cdr rats))) (let ((y (apply + rats))) (set! val (if (zero? y) (collect-if list (lambda (x) (not (number? x))) val) (cons y (collect-if list (lambda (x) (not (number? x))) val)))))))) (set! val (cons (car args) val)) (let ((first-arg (car args)) (nargs (cdr val))) (if (member first-arg nargs) (begin (set! nargs (remove first-arg nargs)) ; remove once (set! first-arg 0))) (cond ((null? nargs) first-arg) ; (- x 0 0 0)? ((eqv? first-arg 0) (if (null? (cdr nargs)) (if (number? (car nargs)) (- (car nargs)) `(- ,(car nargs))) ; (- 0 0 0 x)? `(- (+ ,@nargs)))) ; (- 0 z y) -> (- (+ x y)) ((not (and (pair? (car args)) (eq? (caar args) '-))) `(- ,@(cons first-arg nargs))) ((> (length (car args)) 2) ; (- (- x y) z w) -> (- x y z w) (simplify-numerics `(- ,@(cdar args) ,@(cdr args)) env)) (else (simplify-numerics `(- (+ ,(cadar args) ,@(cdr args))) env))))))))) ((/) (set! args (remove-inexactions args)) (case len ((0) form) ((1) ; invert (if (number? (car args)) (if (zero? (car args)) `(/ ,(car args)) (/ (car args))) (if (not (pair? (car args))) `(/ ,@args) (case (caar args) ((/) (case (length (car args)) ((2) ; (/ (/ x)) -> x (cadar args)) ((3) ; (/ (/ z x)) -> (/ x z) `(/ ,@(reverse (cdar args)))) (else (if (eqv? (cadar args) 1) `(* ,@(cddar args)) ; (/ (/ 1 x y)) -> (* x y) `(/ (* ,@(cddar args)) ,(cadar args)))))) ; (/ (/ z x y)) -> (/ (* x y) z) ((expt) ; (/ (expt x y)) -> (expt x (- y)) `(expt ,(cadar args) (- ,(caddar args)))) ((exp) ; (/ (exp x)) -> (exp (- x)) `(exp (- ,(cadar args)))) (else `(/ ,@args)))))) ((2) (if (and (just-rationals? args) (not (zero? (cadr args)))) (apply / args) ; including (/ 0 12) -> 0 (let ((arg1 (car args)) (arg2 (cadr args))) (let ((op1 (and (pair? arg1) (car arg1))) (op2 (and (pair? arg2) (car arg2)))) (let ((op1-arg1 (and op1 (pair? (cdr arg1)) (cadr arg1))) (op2-arg1 (and op2 (pair? (cdr arg2)) (cadr arg2)))) (cond ((eqv? arg1 1) ; (/ 1 x) -> (/ x) (simplify-numerics `(/ ,arg2) env)) ((eqv? arg2 1) ; (/ x 1) -> x arg1) ((and (pair? arg1) ; (/ (/ a b) c) -> (/ a b c) (eq? op1 '/) (pair? (cddr arg1)) (not (and (pair? arg2) (eq? op2 '/)))) `(/ ,op1-arg1 ,@(cddr arg1) ,arg2)) ((and (pair? arg1) ; (/ (/ a) (/ b)) -> (/ b a)?? (eq? op1 '/) (pair? arg2) (eq? '/ op2)) (let ((a1 (if (null? (cddr arg1)) (list 1 op1-arg1) (cdr arg1))) (a2 (if (null? (cddr arg2)) (list 1 op2-arg1) (cdr arg2)))) (simplify-numerics `(/ (* ,(car a1) ,@(cdr a2)) (* ,@(cdr a1) ,(car a2))) env))) ((and (pair? arg2) (eq? op2 '*) (not (side-effect? arg1 env)) (member arg1 (cdr arg2))) (let ((n (remove arg1 (cdr arg2)))) (if (and (pair? n) (null? (cdr n))) `(/ ,@n) ; (/ x (* y x)) -> (/ y) `(/ 1 ,@n)))) ; (/ x (* y x z)) -> (/ 1 y z) ((and (pair? arg2) ; (/ c (/ a b)) -> (/ (* c b) a) (eq? op2 '/)) (cond ((null? (cddr arg2)) `(* ,arg1 ,op2-arg1)) ; ignoring divide by zero here (/ x (/ y)) -> (* x y) ((eqv? op2-arg1 1) `(* ,arg1 ,@(cddr arg2))) ; (/ x (/ 1 y z)) -> (* x y z) -- these never actually happen ((not (pair? (cddr arg2))) `(/ ,@args)) ; no idea... ((and (rational? arg1) (rational? op2-arg1) (null? (cdddr arg2))) (let ((val (/ arg1 op2-arg1))) (if (= val 1) (caddr arg2) (if (= val -1) `(- ,(caddr arg2)) `(* ,val ,(caddr arg2)))))) (else `(/ (* ,arg1 ,@(cddr arg2)) ,op2-arg1)))) #| ;; can't decide about this -- result usually looks cruddy ((and (pair? arg2) ; (/ x (* y z)) -> (/ x y z) (eq? op2 '*)) `(/ ,arg1 ,@(cdr arg2))) |# ((and (pair? arg1) ; (/ (log x) (log y)) -> (log x y) -- (log number) for (log y) never happens (pair? arg2) (= (length arg1) (length arg2) 2) (case op1 ((log) (eq? op2 'log)) ((sin) (and (eq? op2 'cos) (equal? op1-arg1 op2-arg1))) (else #f))) (if (eq? op1 'log) `(log ,op1-arg1 ,op2-arg1) `(tan ,op1-arg1))) ((and (pair? arg1) ; (/ (- x) (- y)) -> (/ x y) (pair? arg2) (eq? op1 '-) (eq? op2 '-) (= (length arg1) (length arg2) 2)) `(/ ,op1-arg1 ,op2-arg1)) ((and (pair? arg1) ; (/ (* x y) (* z y)) -> (/ x z) (pair? arg2) (eq? op1 '*) (case op2 ((*) (and (= (length arg1) (length arg2) 3) (equal? (caddr arg1) (caddr arg2)))) ((log) (cond ((assq 'log (cdr arg1)) => (lambda (p) (= (length p) 2))) (else #f))) (else #f)) ; (/ (* 12 (log x)) (log 2)) -> (* 12 (log x 2)) (if (eq? op2 '*) `(/ ,op1-arg1 ,op2-arg1) (let ((used-log op2-arg1)) `(* ,@(map (lambda (p) (if (and used-log (pair? p) (eq? (car p) 'log)) (let ((val `(log ,(cadr p) ,used-log))) (set! used-log #f) val) p)) (cdr arg1))))))) ((and (pair? arg1) ; (/ (sqrt x) x) -> (/ (sqrt x)) (eq? (car arg1) 'sqrt) (equal? (cadr arg1) arg2)) `(/ ,arg1)) ((and (pair? arg2) ; (/ x (sqrt x)) -> (sqrt x) (eq? (car arg2) 'sqrt) (equal? (cadr arg2) arg1)) arg2) (else `(/ ,@args)))))))) (else (if (and (just-rationals? args) (not (memv 0 (cdr args))) (not (memv 0.0 (cdr args)))) (apply / args) (let ((nargs ; (/ x a (* b 1 c) d) -> (/ x a b c d) (remove-all 1 (splice-if (lambda (x) (eq? x '*)) (cdr args))))) (if (null? nargs) ; (/ x 1 1) -> x (car args) (if (and (member (car args) (cdr args)) (not (side-effect? (car args) env))) (let ((n (remove (car args) (cdr args)))) (if (null? (cdr n)) `(/ ,@n) ; (/ x y x) -> (/ y) `(/ 1 ,@n))) ; (/ x y x z) -> (/ 1 y z) `(/ ,@(cons (car args) nargs))))))))) ((sin cos tan asin acos sinh cosh tanh asinh acosh atanh exp) ;; perhaps someday, for amusement: ;; (sin (acos x)) == (cos (asin x)) == (sqrt (- 1 (expt x 2))) ;; (asin (cos x)) == (acos (sin x)) == (- (* 1/2 pi) x) (cond ((not (= len 1)) `(,(car form) ,@args)) ((and (pair? (car args)) ; (sin (asin x)) -> x (= (length (car args)) 2) (eq? (caar args) (inverse-op (car form)))) (cadar args)) ((eqv? (car args) 0) ; (sin 0) -> 0 (case (car form) ((sin asin sinh asinh tan tanh atanh) 0) ((exp cos cosh) 1) (else `(,(car form) ,@args)))) ((and (eq? (car form) 'cos) ; (cos (- x)) -> (cos x) (pair? (car args)) (eq? (caar args) '-) (null? (cddar args))) `(cos ,(cadar args))) ((or (eq? (car args) 'pi) ; (sin pi) -> 0.0 (and (pair? (car args)) (eq? (caar args) '-) (eq? (cadar args) 'pi) (null? (cddar args)))) (case (car form) ((sin tan) 0.0) ((cos) -1.0) (else `(,(car form) ,@args)))) ((eqv? (car args) 0.0) ; (sin 0.0) -> 0.0 ((symbol->value (car form)) 0.0)) ((and (eq? (car form) 'acos) ; (acos -1) -> pi (eqv? (car args) -1)) 'pi) ((and (eq? (car form) 'exp) ; (exp (* a (log b))) -> (expt b a) (pair? (car args)) (eq? (caar args) '*)) (let ((targ (cdar args))) (cond ((not (= (length targ) 2)) `(,(car form) ,@args)) ((and (pair? (car targ)) (eq? (caar targ) 'log) (pair? (cdar targ)) (null? (cddar targ))) `(expt ,(cadar targ) ,(cadr targ))) ((and (pair? (cadr targ)) (eq? (caadr targ) 'log) (pair? (cdadr targ)) (null? (cddadr targ))) `(expt ,(cadadr targ) ,(car targ))) (else `(,(car form) ,@args))))) (else `(,(car form) ,@args)))) ((log) (cond ((not (pair? args)) form) ((eqv? (car args) 1) 0) ; (log 1 ...) -> 0 ((and (= len 1) ; (log (exp x)) -> x (pair? (car args)) (= (length (car args)) 2) (eq? (caar args) 'exp)) (cadar args)) ((and (pair? (car args)) ; (log (sqrt x)) -> (* 1/2 (log x)) (eq? (caar args) 'sqrt)) `(* 1/2 (log ,(cadar args) ,@(cdr args)))) ((and (pair? (car args)) ; (log (expt x y)) -> (* y (log x)) (eq? (caar args) 'expt)) `(* ,(caddar args) (log ,(cadar args) ,@(cdr args)))) ((not (and (= len 2) ; (log x x) -> 1.0 (equal? (car args) (cadr args)))) `(log ,@args)) ((integer? (car args)) 1) (else 1.0))) ((sqrt) (cond ((not (pair? args)) form) ((and (rational? (car args)) (rational? (sqrt (car args))) (= (car args) (sqrt (* (car args) (car args))))) (sqrt (car args))) ; don't collapse (sqrt (* a a)), a=-1 for example, or -1-i -> 1+i whereas 1-i -> 1-i etc ((and (pair? (car args)) (eq? (caar args) 'exp)) `(exp (/ ,(cadar args) 2))) ; (sqrt (exp x)) -> (exp (/ x 2)) (else `(sqrt ,@args)))) ((floor round ceiling truncate) (cond ((not (= len 1)) form) ((number? (car args)) (catch #t (lambda () (apply (symbol->value (car form)) args)) (lambda any `(,(car form) ,@args)))) ((not (pair? (car args))) `(,(car form) ,@args)) ((or (integer-result? (caar args)) (and (eq? (caar args) 'random) (integer? (cadar args)))) (car args)) ((memq (caar args) '(inexact->exact exact)) `(,(car form) ,(cadar args))) ((memq (caar args) '(* + / -)) ; maybe extend this list `(,(car form) (,(caar args) ,@(map (lambda (p) (if (and (pair? p) (memq (car p) '(inexact->exact exact))) (cadr p) p)) (cdar args))))) ((and (eq? (caar args) 'random) (eq? (car form) 'floor) (float? (cadar args)) (= (floor (cadar args)) (cadar args))) `(random ,(floor (cadar args)))) (else `(,(car form) ,@args)))) ((abs magnitude) (cond ((not (= len 1)) form) ((and (pair? (car args)) ; (abs (abs x)) -> (abs x) (hash-table-ref non-negative-ops (caar args))) (car args)) ((rational? (car args)) (abs (car args))) ((not (pair? (car args))) `(,(car form) ,@args)) ((and (memq (caar args) '(modulo random)) (= (length (car args)) 3) ; (abs (modulo x 2)) -> (modulo x 2) (real? (caddar args)) (positive? (caddar args))) (car args)) ((and (eq? (caar args) '-) ; (abs (- x)) -> (abs x) (pair? (cdar args)) (null? (cddar args))) `(,(car form) ,(cadar args))) (else `(,(car form) ,@args)))) ((imag-part) (if (not (= len 1)) form (if (or (real? (car args)) (and (pair? (car args)) (real-result? (caar args)))) 0.0 `(imag-part ,@args)))) ((real-part) (if (not (= len 1)) form (if (or (real? (car args)) (and (pair? (car args)) (real-result? (caar args)))) (car args) `(real-part ,@args)))) ((denominator) (if (not (= len 1)) form (if (or (integer? (car args)) (and (pair? (car args)) (integer-result? (caar args)))) 1 `(denominator ,(car args))))) ((numerator) (cond ((not (= len 1)) form) ((or (integer? (car args)) (and (pair? (car args)) (integer-result? (caar args)))) (car args)) ((rational? (car args)) (numerator (car args))) (else `(numerator ,(car args))))) ((random) (cond ((not (and (= len 1) (number? (car args)))) `(random ,@args)) ((eqv? (car args) 0) 0) ((morally-equal? (car args) 0.0) 0.0) (else `(random ,@args)))) ((complex make-rectangular) (if (and (= len 2) (morally-equal? (cadr args) 0.0)) ; morally so that 0 matches (car args) `(complex ,@args))) ((make-polar) (if (and (= len 2) (morally-equal? (cadr args) 0.0)) (car args) `(make-polar ,@args))) ((rationalize lognot ash modulo remainder quotient) (cond ((just-rationals? args) (catch #t ; catch needed here for things like (ash 2 64) (lambda () (apply (symbol->value (car form)) args)) (lambda ignore `(,(car form) ,@args)))) ; use this form to pick up possible arg changes ((and (eq? (car form) 'ash) ; (ash x 0) -> x (= len 2) ; length of args (eqv? (cadr args) 0)) (car args)) ((case (car form) ((quotient) ; (quotient (remainder x y) y) -> 0 (and (= len 2) (pair? (car args)) (eq? (caar args) 'remainder) (= (length (car args)) 3) (eqv? (caddar args) (cadr args)))) ((ash modulo) ; (modulo 0 x) -> 0 (and (= len 2) (eqv? (car args) 0))) (else #f)) 0) ((and (eq? (car form) 'modulo) ; (modulo (abs x) y) -> (modulo x y) (= len 2) (pair? (car args)) (eq? (caar args) 'abs)) `(modulo ,(cadar args) ,(cadr args))) (else `(,(car form) ,@args)))) ((expt) (cond ((not (= len 2)) form) ((and (eqv? (car args) 0) ; (expt 0 x) -> 0 (not (eqv? (cadr args) 0))) (if (and (integer? (cadr args)) (negative? (cadr args))) (lint-format "attempt to divide by 0: ~A" 'expt (truncated-list->string form))) 0) ((or (and (eqv? (cadr args) 0) ; (expt x 0) -> 1 (not (eqv? (car args) 0))) (eqv? (car args) 1)) ; (expt 1 x) -> 1 1) ((eqv? (cadr args) 1) ; (expt x 1) -> x (car args)) ((eqv? (cadr args) -1) ; (expt x -1) -> (/ x) `(/ ,(car args))) ((just-rationals? args) ; (expt 2 3) -> 8 (catch #t (lambda () (let ((val (apply expt args))) (if (and (integer? val) (< (abs val) 1000000)) val `(expt ,@args)))) (lambda args `(expt ,@args)))) ; (expt (expt x y) z) -> (expt x (* y z)) ((and (pair? (car args)) (eq? (caar args) 'expt)) `(expt ,(cadar args) (* ,(caddar args) ,(cadr args)))) (else `(expt ,@args)))) ((angle) (cond ((not (pair? args)) form) ((eqv? (car args) -1) 'pi) ((or (morally-equal? (car args) 0.0) (eq? (car args) 'pi)) 0.0) (else `(angle ,@args)))) ((atan) (cond ((and (= len 1) ; (atan (x y)) -> (atan x y) (pair? (car args)) (= (length (car args)) 3) (eq? (caar args) '/)) `(atan ,@(cdar args))) ((and (= len 2) ; (atan 0 -1) -> pi (eqv? (car args) 0) (eqv? (cadr args) -1)) 'pi) (else `(atan ,@args)))) ((inexact->exact exact) (cond ((not (= len 1)) form) ((or (rational? (car args)) (and (pair? (car args)) (or (rational-result? (caar args)) (integer-result? (caar args)) (and (eq? (caar args) 'random) (rational? (cadar args)))))) (car args)) ((number? (car args)) (catch #t (lambda () (inexact->exact (car args))) (lambda any `(,(car form) ,@args)))) (else `(,(car form) ,@args)))) ((exact->inexact inexact) (cond ((not (= len 1)) form) ((memv (car args) '(0 0.0)) 0.0) ((not (and (pair? (car args)) (not (eq? (caar args) 'random)) (hash-table-ref numeric-ops (caar args)) (any? number? (cdar args)))) `(,(car form) ,@args)) ((any? (lambda (x) (and (number? x) (inexact? x))) (cdar args)) (car args)) (else (let ((new-form (copy (car args)))) (do ((p (cdr new-form) (cdr p))) ((or (null? p) (number? (car p))) (if (pair? p) (set-car! p (* 1.0 (car p)))) new-form)))))) ;; not (inexact (random 3)) -> (random 3.0) because results are different ((logior) (set! args (lint-remove-duplicates (remove-all 0 (splice-if (lambda (x) (eq? x 'logior)) args)) env)) (if (every? (lambda (x) (or (not (number? x)) (integer? x))) args) (let ((rats (collect-if list integer? args))) (if (and (pair? rats) (pair? (cdr rats))) (let ((y (apply logior rats))) (set! args (if (zero? y) (collect-if list (lambda (x) (not (number? x))) args) (cons y (collect-if list (lambda (x) (not (number? x))) args)))))))) (cond ((null? args) 0) ; (logior) -> 0 ((null? (cdr args)) (car args)) ; (logior x) -> x ((memv -1 args) -1) ; (logior ... -1 ...) -> -1 ((just-integers? args) (apply logior args)) (else `(logior ,@args)))) ((logand) (set! args (lint-remove-duplicates (remove-all -1 (splice-if (lambda (x) (eq? x 'logand)) args)) env)) (if (every? (lambda (x) (or (not (number? x)) (integer? x))) args) (let ((rats (collect-if list integer? args))) (if (and (pair? rats) (pair? (cdr rats))) (let ((y (apply logand rats))) (set! args (if (= y -1) (collect-if list (lambda (x) (not (number? x))) args) (cons y (collect-if list (lambda (x) (not (number? x))) args)))))))) (cond ((null? args) -1) ((null? (cdr args)) (car args)) ; (logand x) -> x ((memv 0 args) 0) ((just-integers? args) (apply logand args)) (else `(logand ,@args)))) ;; (logand 1 (logior 2 x)) -> (logand 1 x)? ;; (logand 1 (logior 1 x)) -> 1 ;; (logand 3 (logior 1 x))? ;; similarly for (logior...(logand...)) ((logxor) (set! args (splice-if (lambda (x) (eq? x 'logxor)) args)) ; is this correct?? (cond ((null? args) 0) ; (logxor) -> 0 ((null? (cdr args)) (car args)) ; (logxor x) -> x?? ((just-integers? args) (apply logxor args)) ; (logxor 1 2) -> 3 ((and (= len 2) (equal? (car args) (cadr args))) 0) ; (logxor x x) -> 0 (else `(logxor ,@args)))) ; (logxor x (logxor y z)) -> (logxor x y z) ((gcd) (set! args (lint-remove-duplicates (splice-if (lambda (x) (eq? x 'gcd)) args) env)) (cond ((null? args) 0) ((memv 1 args) 1) ((just-rationals? args) (catch #t ; maybe (gcd -9223372036854775808 -9223372036854775808) (lambda () (apply gcd args)) (lambda ignore `(gcd ,@args)))) ((null? (cdr args)) `(abs ,(car args))) ((eqv? (car args) 0) `(abs ,(cadr args))) ((eqv? (cadr args) 0) `(abs ,(car args))) (else `(gcd ,@args)))) ((lcm) (set! args (lint-remove-duplicates (splice-if (lambda (x) (eq? x 'lcm)) args) env)) (cond ((null? args) 1) ; (lcm) -> 1 ((memv 0 args) 0) ; (lcm ... 0 ...) -> 0 ((just-rationals? args) ; (lcm 3 4) -> 12 (catch #t (lambda () (apply lcm args)) (lambda ignore `(lcm ,@args)))) ((null? (cdr args)) ; (lcm x) -> (abs x) `(abs ,(car args))) (else `(lcm ,@args)))) ((max min) (if (not (pair? args)) form (begin (set! args (lint-remove-duplicates (splice-if (lambda (x) (eq? x (car form))) args) env)) (if (any? (lambda (p) ; if non-negative-op, remove any non-positive numbers (and (pair? p) (hash-table-ref non-negative-ops (car p)))) args) (set! args (remove-if (lambda (x) (and (real? x) (not (positive? x)))) args))) (if (= len 1) (car args) (if (just-reals? args) (apply (symbol->value (car form)) args) (let ((nums (collect-if list number? args)) (other (if (eq? (car form) 'min) 'max 'min))) (if (and (pair? nums) (just-reals? nums)) ; non-real case checked elsewhere (later) (let ((relop (if (eq? (car form) 'min) >= <=))) (if (pair? (cdr nums)) (set! nums (list (apply (symbol->value (car form)) nums)))) (let ((new-args (append nums (collect-if list (lambda (x) (not (number? x))) args)))) (let ((c1 (car nums))) (set! new-args (collect-if list (lambda (x) (or (not (pair? x)) (<= (length x) 2) (not (eq? (car x) other)) (let ((c2 (find-if number? (cdr x)))) (or (not c2) (relop c1 c2))))) new-args))) (if (< (length new-args) (length args)) (set! args new-args))))) ;; if (max c1 (min c2 . args1) . args2) where (> c1 c2) -> (max c1 . args2), if = -> c1 ;; if (min c1 (max c2 . args1) . args2) where (< c1 c2) -> (min c1 . args2), if = -> c1 ;; and if (max 4 x (min x 4)) -- is it (max x 4)? ;; (max a b) is (- (min (- a) (- b))), but that doesn't help here -- the "-" gets in our way ;; (min (- a) (- b)) -> (- (max a b))? ;; (+ a (max|min b c)) = (max|min (+ a b) (+ a c))) (if (null? (cdr args)) ; (max (min x 3) (min x 3)) -> (max (min x 3)) -> (min x 3) (car args) (if (and (null? (cddr args)) ; (max|min x (min|max x ...) -> x (or (and (pair? (car args)) (eq? (caar args) other) (member (cadr args) (car args)) (not (side-effect? (cadr args) env))) (and (pair? (cadr args)) (eq? (caadr args) other) (member (car args) (cadr args)) (not (side-effect? (car args) env))))) ((if (pair? (car args)) cadr car) args) `(,(car form) ,@args))))))))) (else `(,(car form) ,@args)))))) (define (binding-ok? caller head binding env second-pass) ;; check let-style variable binding for various syntactic problems (cond (second-pass (and (pair? binding) (symbol? (car binding)) (not (constant? (car binding))) (pair? (cdr binding)) (or (null? (cddr binding)) (and (eq? head 'do) (pair? (cddr binding)) ; (do ((i 0 . 1))...) (null? (cdddr binding)))))) ((not (pair? binding)) (lint-format "~A binding is not a list? ~S" caller head binding) #f) ; (let (a) a) ((not (symbol? (car binding))) (lint-format "~A variable is not a symbol? ~S" caller head binding) #f) ; (let ((1 2)) #f) ((keyword? (car binding)) (lint-format "~A variable is a keyword? ~S" caller head binding) #f) ; (let ((:a 1)) :a) ((constant? (car binding)) (lint-format "can't bind a constant: ~S" caller binding) #f) ; (let ((pi 2)) #f) ((not (pair? (cdr binding))) (lint-format (if (null? (cdr binding)) "~A variable value is missing? ~S" ; (let ((a)) #f) "~A binding is an improper list? ~S") ; (let ((a . 1)) #f) caller head binding) #f) ((and (pair? (cddr binding)) ; (let loop ((pi 1.0) (+ pi 1))...) (or (not (eq? head 'do)) (pair? (cdddr binding)))) (lint-format "~A binding is messed up: ~A" caller head binding) #f) (else (if (and (eq? caller (car binding)) (let ((fv (var-member caller env))) (and (var? fv) (memq (var-ftype fv) '(define lambda let define* lambda*))))) (lint-format "~A variable ~A in ~S shadows the current function?" caller head caller binding) (if (and *report-shadowed-variables* ; (let ((x 1)) (+ (let ((x 2)) (+ x 1)) x)) (var-member (car binding) env)) (lint-format "~A variable ~A in ~S shadows an earlier declaration" caller head (car binding) binding))) #t))) (define (check-char-cmp caller op form) (if (and (any? (lambda (x) (and (pair? x) (eq? (car x) 'char->integer))) (cdr form)) (every? (lambda (x) (or (and (integer? x) (<= 0 x 255)) (and (pair? x) (eq? (car x) 'char->integer)))) (cdr form))) (lint-format "perhaps ~A" caller ; (< (char->integer x) 95) -> (charstring form `(,(case op ((=) 'char=?) ((>) 'char>?) ((<) 'char=) 'char>=?) (else 'char<=?)) ,@(map (lambda (arg) ((if (integer? arg) integer->char cadr) arg)) (cdr form))))))) (define (write-port expr) ; ()=not specified (*stdout*), #f=something is wrong (not enough args) (and (pair? expr) (if (eq? (car expr) 'newline) (if (pair? (cdr expr)) (cadr expr) ()) (and (pair? (cdr expr)) (if (pair? (cddr expr)) (caddr expr) ()))))) (define (display->format d) (case (car d) ((newline) (copy "~%")) ((display) (let* ((arg (cadr d)) (arg-arg (and (pair? arg) (pair? (cdr arg)) (cadr arg)))) (cond ((string? arg) arg) ((char? arg) (string arg)) ((and (pair? arg) (eq? (car arg) 'number->string)) (if (= (length arg) 3) (case (caddr arg) ((2) (values "~B" arg-arg)) ((8) (values "~O" arg-arg)) ((10) (values "~D" arg-arg)) ((16) (values "~X" arg-arg)) (else (values "~A" arg))) (values "~A" arg-arg))) ((not (and (pair? arg) (eq? (car arg) 'string-append))) (values "~A" arg)) ((null? (cddr arg)) (if (string? arg-arg) arg-arg (values "~A" arg-arg))) ((not (null? (cdddr arg))) (values "~A" arg)) ((string? arg-arg) (values (string-append arg-arg "~A") (caddr arg))) ((string? (caddr arg)) (values (string-append "~A" (caddr arg)) arg-arg)) (else (values "~A" arg))))) ((write) ;; very few special cases actually happen here, unlike display above (if (string? (cadr d)) (string-append "\"" (cadr d) "\"") (if (char? (cadr d)) (string (cadr d)) (values "~S" (cadr d))))) ((write-char) (if (char? (cadr d)) (string (cadr d)) (values "~C" (cadr d)))) ((write-string) ; same as display but with possible start|end indices (let ((indices (and (pair? (cddr d)) ; port (pair? (cdddr d)) (cdddr d)))) (if (string? (cadr d)) (if (not indices) (cadr d) (if (and (integer? (car indices)) (or (null? (cdr indices)) (and (pair? indices) (integer? (cadr indices))))) (apply substring (cadr d) indices) (values "~A" `(substring ,(cadr d) ,@indices)))) (values "~A" (if indices `(substring ,(cadr d) ,@indices) (cadr d)))))))) (define (identity? x) ; (lambda (x) x), or (define (x) x) -> procedure-source (and (pair? x) (eq? (car x) 'lambda) (pair? (cdr x)) (pair? (cadr x)) (null? (cdadr x)) (pair? (cddr x)) (null? (cdddr x)) (eq? (caddr x) (caadr x)))) (define (cdr-count c) (case c ((cdr) 1) ((cddr) 2) ((cdddr) 3) (else 4))) (define (simple-lambda? x) (and (pair? x) (eq? (car x) 'lambda) (pair? (cdr x)) (pair? (cadr x)) (null? (cdadr x)) (pair? (cddr x)) (null? (cdddr x)) (= (tree-count1 (caadr x) (caddr x) 0) 1))) (define (less-simple-lambda? x) (and (pair? x) (eq? (car x) 'lambda) (pair? (cdr x)) (pair? (cadr x)) (null? (cdadr x)) (pair? (cddr x)) (= (tree-count1 (caadr x) (cddr x) 0) 1))) (define (tree-subst new old tree) (cond ((equal? old tree) new) ((not (pair? tree)) tree) ((eq? (car tree) 'quote) (copy-tree tree)) (else (cons (tree-subst new old (car tree)) (tree-subst new old (cdr tree)))))) (define* (find-unique-name f1 f2 (i 1)) (let ((sym (string->symbol (format #f "_~D_" i)))) (if (not (or (eq? sym f1) (eq? sym f2) (tree-member sym f1) (tree-member sym f2))) sym (find-unique-name f1 f2 (+ i 1))))) (define (unrelop caller head form) ; assume len=3 (let ((arg1 (cadr form)) (arg2 (caddr form))) (if (and (pair? arg1) (= (length arg1) 3)) (if (eq? (car arg1) '-) (if (memv arg2 '(0 0.0)) ; (< (- x y) 0) -> (< x y), need both 0 and 0.0 because (eqv? 0 0.0) is #f (lint-format "perhaps ~A" caller (lists->string form `(,head ,(cadr arg1) ,(caddr arg1)))) (if (and (integer? arg2) ; (> (- x 50868) 256) -> (> x 51124) (integer? (caddr arg1))) (lint-format "perhaps ~A" caller (lists->string form `(,head ,(cadr arg1) ,(+ (caddr arg1) arg2)))))) ;; (> (- x) (- y)) (> (- x 1) (- y 1)) and so on -- do these ever happen? (no, not even if we allow +-*/) (if (and (eq? (car arg1) '+) ; (< (+ x 1) 3) -> (< x 2) (integer? arg2) (integer? (caddr arg1))) (lint-format "perhaps ~A" caller (lists->string form `(,head ,(cadr arg1) ,(- arg2 (caddr arg1))))))) (if (and (pair? arg2) (= (length arg2) 3)) (if (eq? (car arg2) '-) (if (memv arg1 '(0 0.0)) ; (< 0 (- x y)) -> (> x y) (lint-format "perhaps ~A" caller (lists->string form `(,(hash-table-ref reversibles head) ,(cadr arg2) ,(caddr arg2)))) (if (and (integer? arg1) (integer? (caddr arg2))) (lint-format "perhaps ~A" caller (lists->string form `(,(hash-table-ref reversibles head) ,(cadr arg2) ,(+ arg1 (caddr arg2))))))) (if (and (eq? (car arg2) '+) ; (< 256 (+ fltdur 50868)) -> (> fltdur -50612) (integer? arg1) (integer? (caddr arg2))) (lint-format "perhaps ~A" caller (lists->string form `(,(hash-table-ref reversibles head) ,(cadr arg2) ,(- arg1 (caddr arg2))))))))))) (define (check-start-and-end caller head form ff env) (if (or (and (integer? (car form)) (integer? (cadr form)) (apply >= form)) (and (equal? (car form) (cadr form)) (not (side-effect? (car form) env)))) (lint-format "these ~A indices make no sense: ~A" caller head ff))) ; (copy x y 1 0) (define (other-case c) ((if (char-upper-case? c) char-downcase char-upcase) c)) (define (check-boolean-affinity caller form env) ;; does built-in boolean func's arg make sense (when (= (length form) 2) (unless (or (and (symbol? (cadr form)) (not (keyword? (cadr form)))) (= line-number last-simplify-boolean-line-number)) (let ((expr (simplify-boolean form () () env))) (if (not (equal? expr form)) (lint-format "perhaps ~A" caller (lists->string form expr)) ; (char? '#\a) -> #t (if (code-constant? (cadr form)) (lint-format "perhaps ~A" caller (lists->string form (eval form))))))) (if (and (symbol? (cadr form)) ; (number? pi) -> #t (not (keyword? (cadr form))) (not (var-member (cadr form) env))) (let ((val (checked-eval form))) (if (not (eq? val :checked-eval-error)) (lint-format "perhaps ~A" caller (lists->string form val))))) (when (and (pair? (cadr form)) (symbol? (caadr form))) (let ((rt (if (eq? (caadr form) 'quote) (->simple-type (cadadr form)) (return-type (caadr form) env))) (head (car form))) (if (subsumes? head rt) (lint-format "~A is always #t" caller (truncated-list->string form)) ; (char? '#\a) is always #t (if (not (or (memq rt '(#t #f values)) (any-compatible? head rt))) (lint-format "~A is always #f" caller (truncated-list->string form)))))))) ; (number? (make-list 1)) is always #f (define combinable-cxrs (let ((h (make-hash-table))) (for-each (lambda (c) (hash-table-set! h c (let ((name (symbol->string c))) (substring name 1 (- (length name) 1))))) '(car cdr caar cadr cddr cdar caaar caadr caddr cdddr cdaar cddar cadar cdadr cadddr cddddr)) h)) ;; not combinable: caaaar caaadr caadar caaddr cadaar cadadr caddar cdaaar cdaadr cdadar cdaddr cddaar cddadr cdddar (define (combine-cxrs form) (let ((cxr? (lambda (s) (and (pair? (cdr s)) (pair? (cadr s)) (memq (caadr s) '(car cdr cadr cddr cdar cdddr cddddr)))))) (and (cxr? form) (let* ((arg1 (cadr form)) (arg2 (and arg1 (cxr? arg1) (cadr arg1))) (arg3 (and arg2 (cxr? arg2) (cadr arg2)))) (values (string-append (hash-table-ref combinable-cxrs (car form)) (hash-table-ref combinable-cxrs (car arg1)) (if arg2 (hash-table-ref combinable-cxrs (car arg2)) "") (if arg3 (hash-table-ref combinable-cxrs (car arg3)) "")) (cadr (or arg3 arg2 arg1))))))) #| ;; this builds the lists below: (let ((ci ()) (ic ())) (for-each (lambda (c) (let ((name (reverse (substring (symbol->string c) 1 (- (length (symbol->string c)) 1))))) (do ((sum 0) (len (length name)) (i 0 (+ i 1)) (bit 0 (+ bit 2))) ((= i len) (set! ci (cons (cons c sum) ci)) (set! ic (cons (cons sum c) ic))) (set! sum (+ sum (expt 2 (if (char=? (name i) #\a) bit (+ bit 1)))))))) '(car cdr caar cadr cddr cdar caaar caadr caddr cdddr cdaar cddar cadar cdadr cadddr cddddr caaaar caaadr caadar caaddr cadaar cadadr caddar cdaaar cdaadr cdadar cdaddr cddaar cddadr cdddar)) (list (reverse ci) (reverse ic))) |# (define cxr->int (hash-table '(car . 1) '(cdr . 2) '(caar . 5) '(cadr . 6) '(cddr . 10) '(cdar . 9) '(caaar . 21) '(caadr . 22) '(caddr . 26) '(cdddr . 42) '(cdaar . 37) '(cddar . 41) '(cadar . 25) '(cdadr . 38) '(cadddr . 106) '(cddddr . 170) '(caaaar . 85) '(caaadr . 86) '(caadar . 89) '(caaddr . 90) '(cadaar . 101) '(cadadr . 102) '(caddar . 105) '(cdaaar . 149) '(cdaadr . 150) '(cdadar . 153) '(cdaddr . 154) '(cddaar . 165) '(cddadr . 166) '(cdddar . 169))) (define int->cxr (hash-table '(1 . car) '(2 . cdr) '(5 . caar) '(6 . cadr) '(10 . cddr) '(9 . cdar) '(21 . caaar) '(22 . caadr) '(26 . caddr) '(42 . cdddr) '(37 . cdaar) '(41 . cddar) '(25 . cadar) '(38 . cdadr) '(106 . cadddr) '(170 . cddddr) '(85 . caaaar) '(86 . caaadr) '(89 . caadar) '(90 . caaddr) '(101 . cadaar) '(102 . cadadr) '(105 . caddar) '(149 . cdaaar) '(150 . cdaadr) '(153 . cdadar) '(154 . cdaddr) '(165 . cddaar) '(166 . cddadr) '(169 . cdddar))) (define (match-cxr c1 c2) (hash-table-ref int->cxr (logand (or (hash-table-ref cxr->int c1) 0) (or (hash-table-ref cxr->int c2) 0)))) (define (mv-range producer env) (if (symbol? producer) (let ((v (var-member producer env))) (and (var? v) (pair? ((cdr v) 'values)) ((cdr v) 'values))) (and (pair? producer) (if (memq (car producer) '(lambda lambda*)) (count-values (cddr producer)) (if (eq? (car producer) 'values) (let ((len (- (length producer) 1))) (for-each (lambda (p) (if (and (pair? p) (eq? (car p) 'values)) (set! len (- (+ len (length p)) 2)))) (cdr producer)) (list len len)) (mv-range (car producer) env)))))) (define (eval-constant-expression caller form) (if (every? code-constant? (cdr form)) (catch #t (lambda () (let ((val (eval (copy form :readable)))) (lint-format "perhaps ~A" caller (lists->string form val)))) ; (eq? #(0) #(0)) -> #f (lambda args #t)))) (define (unbegin x) ((if (and (pair? x) (eq? (car x) 'begin)) cdr list) x)) (define (un_{list} tree) (if (not (pair? tree)) tree (if (eq? (car tree) #_{list}) (if (assq #_{apply_values} (cdr tree)) (if (and (pair? (cadr tree)) (eq? (caadr tree) #_{apply_values})) `(append ,(cadadr tree) ,(cadr (caddr tree))) `(cons ,(cadr tree) ,(cadr (caddr tree)))) (cons 'list (un_{list} (cdr tree)))) (cons (if (eq? (car tree) #_{append}) 'append (un_{list} (car tree))) (un_{list} (cdr tree)))))) (define (qq-tree? tree) (and (pair? tree) (or (eq? (car tree) #_{apply_values}) (if (and (eq? (car tree) #_{list}) (assq #_{apply_values} (cdr tree))) (or (not (= (length tree) 3)) (not (and (pair? (caddr tree)) (eq? (caaddr tree) #_{apply_values}))) (qq-tree? (cadr (caddr tree))) (let ((applying (and (pair? (cadr tree)) (eq? (caadr tree) #_{apply_values})))) (qq-tree? ((if applying cadadr cadr) tree)))) (or (qq-tree? (car tree)) (qq-tree? (cdr tree))))))) (define special-case-functions (let ((special-case-table (make-hash-table))) (define (hash-special key value) (if (hash-table-ref special-case-table key) (format *stderr* "~A already has a value: ~A~%" key (hash-table-ref special-case-table key))) (hash-table-set! special-case-table key value)) ;; ---------------- member and assoc ---------------- (let () (define (sp-memx caller head form env) (define (list-one? p) (and (pair? p) (pair? (cdr p)) (null? (cddr p)) (case (car p) ((list) cadr) ((quote) (and (pair? (cadr p)) (null? (cdadr p)) (if (symbol? (caadr p)) (lambda (x) (list 'quote (caadr x))) caadr))) (else #f)))) (when (= (length form) 4) (let ((func (list-ref form 3))) (if (symbol? func) (if (memq func '(eq? eqv? equal?)) ; (member x y eq?) -> (memq x y) (let ((op (if (eq? head 'member) ; (member (car x) entries equal?) -> (member (car x) entries) (case func ((eq?) 'memq) ((eqv?) 'memv) (else 'member)) (case func ((eq?) 'assq) ((eqv?) 'assv) (else 'assoc))))) (lint-format "perhaps ~A" caller (lists->string form `(,op ,(cadr form) ,(caddr form))))) (let ((sig (procedure-signature (symbol->value func)))) ; arg-signature here is too cranky (if (and (pair? sig) (not (eq? 'boolean? (car sig))) (not (and (pair? (car sig)) (memq 'boolean? (car sig))))) (lint-format "~A is a questionable ~A function" caller func head)))) ; (member 1 x abs) ;; func not a symbol (if (and (pair? func) (= (length func) 3) ; (member 'a x (lambda (a b c) (eq? a b))) (eq? (car func) 'lambda) (pair? (cadr func)) (pair? (caddr func))) (if (not (memv (length (cadr func)) '(2 -1))) (lint-format "~A equality function (optional third arg) should take two arguments" caller head) (if (eq? head 'member) (let ((eq (caddr func)) (args (cadr func))) (if (and (memq (car eq) '(eq? eqv? equal?)) (eq? (car args) (cadr eq)) (pair? (caddr eq)) (eq? (car (caddr eq)) 'car) (pair? (cdr (caddr eq))) (pair? (cdr args)) (eq? (cadr args) (cadr (caddr eq)))) (lint-format "member might perhaps be ~A" ; (member 'a x (lambda (a b) (eq? a (car b)))) caller (if (or (eq? func 'eq?) (eq? (car (caddr func)) 'eq?)) 'assq (if (eq? (car (caddr func)) 'eqv?) 'assv 'assoc))))))))))) (when (= (length form) 3) (let ((selector (cadr form)) (items (caddr form))) (let ((current-eqf (case head ((memq assq) 'eq?) ((memv assv) 'eqv?) (else 'equal?))) (selector-eqf (car (eqf selector env))) (one-item (and (memq head '(memq memv member)) (list-one? items)))) ;; one-item assoc doesn't simplify cleanly (if one-item (let* ((target (one-item items)) (iter-eqf (eqf target env))) (if (or (symbol? target) (and (pair? target) (not (eq? (car target) 'quote)))) (set! target (list 'quote target))) ; ; (member x (list "asdf")) -> (string=? x "asdf") -- maybe equal? here? (lint-format "perhaps ~A" caller (lists->string form `(,(cadr iter-eqf) ,selector ,target)))) ;; not one-item (letrec ((duplicates? (lambda (lst fnc) (and (pair? lst) (or (fnc (car lst) (cdr lst)) (duplicates? (cdr lst) fnc))))) (duplicate-constants? (lambda (lst fnc) (and (pair? lst) (or (and (constant? (car lst)) (fnc (car lst) (cdr lst))) (duplicate-constants? (cdr lst) fnc)))))) (if (and (symbol? selector-eqf) ; (memq 1.0 x): perhaps memq -> memv (not (eq? selector-eqf current-eqf))) (lint-format "~A: perhaps ~A -> ~A" caller (truncated-list->string form) head (if (memq head '(memq memv member)) (case selector-eqf ((eq?) 'memq) ((eqv?) 'memv) ((equal?) 'member)) (case selector-eqf ((eq?) 'assq) ((eqv?) 'assv) ((equal?) 'assoc))))) ;; -------------------------------- ;; check for head mismatch with items (when (pair? items) (when (or (eq? (car items) 'list) (and (eq? (car items) 'quote) (pair? (cadr items)))) (let ((elements ((if (eq? (car items) 'quote) cadr cdr) items))) (let ((baddy #f)) (catch #t (lambda () (set! baddy ((if (eq? (car items) 'list) duplicate-constants? duplicates?) elements (symbol->value head)))) (lambda args #f)) (if (pair? baddy) ; (member x (list "asd" "abc" "asd")) (lint-format "duplicated entry ~S in ~A" caller (car baddy) items))) (when (proper-list? elements) (let ((maxf #f) (keys (if (eq? (car items) 'quote) (if (memq head '(memq memv member)) elements (and (every? pair? elements) (map car elements))) (if (memq head '(memq memv member)) (and (every? code-constant? elements) elements) (and (every? (lambda (e) (and (pair? e) (eq? (car e) 'quote))) elements) (map caadr elements)))))) (when (proper-list? keys) (if (eq? (car items) 'quote) (do ((p keys (cdr p))) ((or (null? p) (memq maxf '(equal? #t)))) (let ((element (car p))) (if (symbol? element) (if (not maxf) (set! maxf 'eq?)) (if (pair? element) (begin (if (and (eq? (car element) 'quote) (pair? (cdr element))) (lint-format "stray quote? ~A" caller form)) ; (memq x '(a 'b c)) (set! maxf #t)) (let ((type (if (symbol? element) 'eq? (car (->eqf (->simple-type element)))))) (if (or (memq maxf '(#f eq?)) (memq type '(#t equal?))) (set! maxf type))))))) ;; else (list ...) (do ((p keys (cdr p))) ((or (null? p) (memq maxf '(equal? #t)))) (let ((element (car p))) (if (symbol? element) (set! maxf #t) (let ((type (car (eqf element env)))) (if (or (memq maxf '(#f eq?)) (memq type '(#t equal?))) (set! maxf type))))))) (case maxf ((eq?) (if (not (memq head '(memq assq))) ; (member (car op) '(x y z)) (lint-format "~A could be ~A in ~A" caller head (if (memq head '(memv member)) 'memq 'assq) form))) ((eqv?) (if (not (memq head '(memv assv))) ; (memq (strname 0) '(#\{ #\[ #\())) (lint-format "~A ~Aould be ~A in ~A" caller head (if (memq head '(memq assq)) "sh" "c") (if (memq head '(memq member)) 'memv 'assv) form))) ((equal? #t) ; (memq (car op) '("a" #())) (if (not (memq head '(member assoc))) (lint-format "~A should be ~A in ~A" caller head (if (memq head '(memq memv)) 'member 'assoc) form))))))) ;; -------------------------------- (if (and (= (length elements) 2) ; (memq expr '(#t #f)) (memq #t elements) (memq #f elements)) (lint-format "perhaps ~A" caller (lists->string form `(boolean? ,selector)))))) ;; not (memv x '(0 0.0)) -> (zero? x) because x might not be a number (case (car items) ((map) (let ((memx (memq head '(memq memv member)))) (when (and memx (= (length items) 3)) (let ((mapf (cadr items)) (map-items (caddr items))) (cond ((eq? mapf 'car) ; (memq x (map car y)) -> (assq x y) (lint-format "perhaps use assoc: ~A" caller (lists->string form `(,(case current-eqf ((eq?) 'assq) ((eqv?) 'assv) ((equal?) 'assoc)) ,selector ,map-items)))) ((eq? selector #t) (if (eq? mapf 'null?) ; (memq #t (map null? items)) -> (memq () items) (lint-format "perhaps ~A" caller (lists->string form `(memq () ,map-items))) (let ((b (if (eq? mapf 'b) 'c 'b))) ;; (memq #t (map cadr items)) -> (member #t items (lambda (a b) (cadr b))) (lint-format "perhaps avoid 'map: ~A" caller (lists->string form `(member #t ,map-items (lambda (a ,b) (,mapf ,b)))))))) ((and (pair? selector) (eq? (car selector) 'string->symbol) ; this could be extended, but it doesn't happen (eq? mapf 'string->symbol) (not (and (pair? map-items) (eq? (car map-items) 'quote)))) (lint-format "perhaps ~A" caller ;; (memq (string->symbol x) (map string->symbol y)) -> (member x y string=?) (lists->string form `(member ,(cadr selector) ,map-items string=?)))) (else ;; (member x (map b items)) -> (member x items (lambda (a c) (equal? a (b c)))) (let ((b (if (eq? mapf 'b) 'c 'b))) ; a below can't collide because eqf won't return 'a (lint-format "perhaps avoid 'map: ~A" caller (lists->string form `(member ,selector ,map-items (lambda (a ,b) (,current-eqf a (,mapf ,b))))))))))))) ((string->list) ; (memv c (string->list s)) -> (char-position c s) (lint-format "perhaps ~A" caller (lists->string form `(char-position ,(cadr form) ,@(cdr items))))) ((cons) ; (member x (cons y z)) -> (or (equal? x y) (member x z)) (if (not (pair? selector)) (lint-format "perhaps avoid 'cons: ~A" caller (lists->string form `(or (,current-eqf ,selector ,(cadr items)) (,head ,selector ,(caddr items))))))) ((append) ; (member x (append (list x) y)) -> (or (equal? x x) (member x y)) (if (and (not (pair? selector)) (= (length items) 3) (pair? (cadr items)) (eq? (caadr items) 'list) (null? (cddadr items))) (lint-format "perhaps ~A" caller (lists->string form `(or (,current-eqf ,selector ,(cadadr items)) (,head ,selector ,(caddr items)))))))))))) (when (and (memq head '(memq memv)) (pair? items) (eq? (car items) 'quote) (pair? (cadr items))) (let ((nitems (length (cadr items)))) (if (pair? selector) ; (memv (string-ref x 0) '(+ -)) -> #f etc (let ((sig (arg-signature (car selector) env))) (if (and (pair? sig) (symbol? (car sig)) (not (eq? (car sig) 'values))) (let ((vals (map (lambda (item) (if ((symbol->value (car sig)) item) item (values))) (cadr items)))) (if (not (= (length vals) nitems)) (lint-format "perhaps ~A" caller (lists->string form (and (pair? vals) `(,head ,selector ',vals))))))))) (if (> nitems 20) (lint-format "perhaps use a hash-table here, rather than ~A" caller (truncated-list->string form))) (let ((bad (find-if (lambda (x) (not (or (symbol? x) (char? x) (number? x) (procedure? x) ; (memq abs '(1 #_abs 2)) ! (memq x '(#f #t () # # #))))) (cadr items)))) (if bad (lint-format (if (and (pair? bad) (eq? (car bad) 'unquote)) (values "stray comma? ~A" caller) ; (memq x '(a (unquote b) c)) (values "pointless list member: ~S in ~A" caller bad)) ;; quoted item here is caught above ; (memq x '(a (+ 1 2) 3)) form)))))))) (for-each (lambda (f) (hash-special f sp-memx)) '(memq assq memv assv member assoc))) ;; ---------------- car, cdr, etc ---------------- (let () (define (sp-crx caller head form env) (if (not (= line-number last-simplify-cxr-line-number)) ((lambda* (cxr arg) (when cxr (set! last-simplify-cxr-line-number line-number) (cond ((< (length cxr) 5) ; (car (cddr x)) -> (caddr x) (lint-format "perhaps ~A" caller (lists->string form `(,(symbol "c" cxr "r") ,arg)))) ;; if it's car|cdr followed by cdr's, use list-ref|tail ((not (char-position #\a cxr)) ; (cddddr (cddr x)) -> (list-tail x 6) (lint-format "perhaps ~A" caller (lists->string form `(list-tail ,arg ,(length cxr))))) ((not (char-position #\a (substring cxr 1))) ; (car (cddddr (cddr x))) -> (list-ref x 6) (lint-format "perhaps ~A" caller (lists->string form `(list-ref ,arg ,(- (length cxr) 1))))) (else (set! last-simplify-cxr-line-number -1))))) (combine-cxrs form))) (when (pair? (cadr form)) (let ((arg (cadr form))) (when (eq? head 'car) (case (car arg) ((list-tail) ; (car (list-tail x y)) -> (list-ref x y) (lint-format "perhaps ~A" caller (lists->string form `(list-ref ,(cadr arg) ,(caddr arg))))) ((memq memv member assq assv assoc) (if (pair? (cdr arg)) ; (car (memq x ...)) is either x or (car #f) -> error (lint-format "~A is ~A, or an error" caller (truncated-list->string form) (cadr arg)))))) (when (and (eq? (car arg) 'or) ; (cdr (or (assoc x y) (cons 1 2))) -> (cond ((assoc x y) => cdr) (else 2)) (not (eq? form last-rewritten-internal-define)) (= (length arg) 3)) (let ((arg1 (cadr arg)) (arg2 (caddr arg))) (if (and (pair? arg2) (or (and (memq (car arg2) '(cons list #_{list})) (eq? head 'cdr)) (memq (car arg2) '(error throw)) (and (eq? (car arg2) 'quote) (pair? (cdr arg2)) (pair? (cadr arg2))))) (lint-format "perhaps ~A" caller (lists->string form ; (cdr (or (assoc n oi) (list n y))) -> (cond ((assoc n oi) => cdr) (else (list y))) `(cond (,arg1 => ,head) (else ,(case (car arg2) ((quote) ((symbol->value head) (cadr arg2))) ((cons) (caddr arg2)) ((error throw) arg2) (else `(list ,@(cddr arg2))))))))))) (if (and (pair? arg) ; (cdr '(a)) -> () (eq? (car arg) 'quote) (pair? (cdr arg)) (pair? (cadr arg)) (not (var-member head env))) (let ((val (checked-eval form))) (if (not (eq? val :checked-eval-error)) (lint-format "perhaps ~A -> ~A~A" caller (object->string form) (if (or (pair? val) (symbol? val)) "'" "") (object->string val))))) (if (and (memq head '(car cdr)) (eq? (car arg) 'cons)) (lint-format "(~A~A) is the same as ~A" ; (car (cons 1 2)) is the same as 1 caller head (truncated-list->string arg) (truncated-list->string ((if (eq? head 'car) cadr caddr) arg)))) (when (memq head '(car cadr caddr cadddr)) (if (memq (car arg) '(string->list vector->list)) ; (car (string->list x)) -> (string-ref x 0) (lint-format "perhaps ~A" caller (lists->string form `(,(if (eq? (car arg) 'string->list) 'string-ref 'vector-ref) ,(cadr arg) ,(case head ((car) 0) ((cadr) 1) ((caddr) 2) (else 3))))) (if (memq (car arg) '(reverse reverse!)) (lint-format "perhaps ~A~A" caller ; (car (reverse x)) -> (list-ref x (- (length x) 1)) (if (eq? head 'car) "use 'last from srfi-1, or " "") (lists->string form (if (symbol? (cadr arg)) `(list-ref ,(cadr arg) (- (length ,(cadr arg)) ,(case head ((car) 1) ((cadr) 2) ((caddr) 3) (else 4)))) `(let ((_1_ ,(cadr arg))) ; let is almost certainly cheaper than reverse (list-ref _1_ (- (length _1_) ,(case head ((car) 1) ((cadr) 2) ((caddr) 3) (else 4)))))))))))))) (for-each (lambda (f) (hash-special (car f) sp-crx)) combinable-cxrs)) ;; not combinable cxrs: ;; caaaar caaadr caadar caaddr cadaar cadadr caddar ;; cdaaar cdaadr cdadar cdaddr cddaar cddadr cdddar ;; ---------------- set-car! ---------------- (let () (define (sp-set-car! caller head form env) (when (= (length form) 3) (let ((target (cadr form))) (if (pair? target) (case (car target) ((list-tail) ; (set-car! (list-tail x y) z) -> (list-set! x y z) (lint-format "perhaps ~A" caller (lists->string form `(list-set! ,(cadr target) ,(caddr target) ,(caddr form))))) ((cdr cddr cdddr cddddr) ; (set-car! (cddr (cdddr x)) y) -> (list-set! x 5 y) (set! last-simplify-cxr-line-number line-number) (lint-format "perhaps ~A" caller (lists->string form (if (and (pair? (cadr target)) (memq (caadr target) '(cdr cddr cdddr cddddr))) ;; (set-car! (cdr (cddr x)) y) -> (list-set! x 3 y) `(list-set! ,(cadadr target) ,(+ (cdr-count (car target)) (cdr-count (caadr target))) ,(caddr form)) ;; (set-car! (cdr x) y) -> (list-set! x 1 y) `(list-set! ,(cadr target) ,(cdr-count (car target)) ,(caddr form))))))))))) (hash-special 'set-car! sp-set-car!)) ;; ---------------- not ---------------- (let () (define (sp-not caller head form env) (if (and (pair? (cdr form)) (pair? (cadr form))) (if (eq? (caadr form) 'not) (let ((str (truncated-list->string (cadadr form)))) ; (not (not x)) -> (and x #t) (lint-format "if you want a boolean, (not (not ~A)) -> (and ~A #t)" 'paranoia str str)) (let ((sig (arg-signature (caadr form) env))) (if (and (pair? sig) (if (pair? (car sig)) ; (not (+ x y)) (not (memq 'boolean? (car sig))) (not (memq (car sig) '(#t values boolean?))))) (lint-format "~A can't be true (~A never returns #f)" caller (truncated-list->string form) (caadr form)))))) (if (not (= line-number last-simplify-boolean-line-number)) (let ((val (simplify-boolean form () () env))) (set! last-simplify-boolean-line-number line-number) (if (not (equal? form val)) ; (not (and (> x 2) (not z))) -> (or (<= x 2) z) (lint-format "perhaps ~A" caller (lists->string form val)))))) (hash-special 'not sp-not)) ;; ---------------- and/or ---------------- (let () (define (sp-and caller head form env) (if (not (= line-number last-simplify-boolean-line-number)) (let ((val (simplify-boolean form () () env))) (set! last-simplify-boolean-line-number line-number) (if (not (equal? form val)) ; (and (not x) (not y)) -> (not (or x y)) (lint-format "perhaps ~A" caller (lists->string form val))))) (if (pair? (cdr form)) (do ((p (cdr form) (cdr p))) ((null? (cdr p))) (if (and (pair? (car p)) (eq? (caar p) 'if) (= (length (car p)) 3)) ; (and (member n cvars) (if (pair? open) (not (member n open))) (not (eq? n open))) (lint-format "one-armed if might cause confusion here: ~A" caller form))))) (hash-special 'and sp-and) (hash-special 'or sp-and)) ;; ---------------- = ---------------- (let () (define (sp-= caller head form env) (let ((len (length form))) (if (and (> len 2) (let any-real? ((lst (cdr form))) ; ignore 0.0 and 1.0 in this since they normally work (and (pair? lst) (or (and (number? (car lst)) (not (rational? (car lst))) (not (member (car lst) '(0.0 1.0) =))) (any-real? (cdr lst)))))) ; (= x 1.5) (lint-format "= can be troublesome with floats: ~A" caller (truncated-list->string form))) (let ((cleared-form (cons = (remove-if (lambda (x) (not (number? x))) (cdr form))))) (if (and (> (length cleared-form) 2) (not (checked-eval cleared-form))) ; (= 1 y 2) (lint-format "this comparison can't be true: ~A" caller (truncated-list->string form)))) (when (= len 3) (let ((arg1 (cadr form)) (arg2 (caddr form))) ;; (= (+ x a) (+ y a)) and various equivalents happen very rarely (only in test suites it appears) (let ((var (or (and (memv arg1 '(0 1)) (pair? arg2) (eq? (car arg2) 'length) (cadr arg2)) (and (memv arg2 '(0 1)) (pair? arg1) (eq? (car arg1) 'length) (cadr arg1))))) ;; we never seem to have var-member/initial-value/history here to distinguish types ;; and a serious attempt to do so was a bust. (if var (if (or (eqv? arg1 0) ; (= (length x) 0) -> (null? x) (eqv? arg2 0)) (lint-format "perhaps (assuming ~A is a list), ~A" caller var (lists->string form `(null? ,var))) (if (symbol? var) ; (= (length x) 1) -> (and (pair? x) (null? (cdr x))) (lint-format "perhaps (assuming ~A is a list), ~A" caller var (lists->string form `(and (pair? ,var) (null? (cdr ,var)))))))))) (unrelop caller '= form)) (check-char-cmp caller '= form))) (hash-special '= sp-=)) ;; ---------------- < > <= >= ---------------- (let () (define (sp-< caller head form env) (let ((cleared-form (cons head ; keep operator (remove-if (lambda (x) (not (number? x))) (cdr form))))) (if (and (> (length cleared-form) 2) (not (checked-eval cleared-form))) ; (< x 1 2 0 y) (lint-format "this comparison can't be true: ~A" caller (truncated-list->string form)))) (if (= (length form) 3) (unrelop caller head form) (when (> (length form) 3) (if (and (memq head '(< >)) ; (< x y x) -> #f (repeated-member? (cdr form) env)) (lint-format "perhaps ~A" caller (truncated-lists->string form #f)) (if (and (memq head '(<= >=)) (repeated-member? (cdr form) env)) (do ((last-arg (cadr form)) (new-args (list (cadr form))) (lst (cddr form) (cdr lst))) ((null? lst) (if (repeated-member? new-args env) ; (<= x y x z x) -> (= x y z) (lint-format "perhaps ~A" caller (truncated-lists->string form `(= ,@(lint-remove-duplicates (reverse new-args) env)))) (if (< (length new-args) (length (cdr form))) (lint-format "perhaps ~A" caller ; (<= x x y z) -> (= x y z) (truncated-lists->string form (or (null? (cdr new-args)) `(= ,@(reverse new-args)))))))) (unless (equal? (car lst) last-arg) (set! last-arg (car lst)) (set! new-args (cons last-arg new-args)))))))) (cond ((not (= (length form) 3))) ((and (real? (cadr form)) (or (< (cadr form) 0) (and (zero? (cadr form)) (eq? head '>))) (pair? (caddr form)) ; (> 0 (string-length x)) (hash-table-ref non-negative-ops (caaddr form))) (lint-format "~A can't be negative: ~A" caller (caaddr form) (truncated-list->string form))) ((and (real? (caddr form)) (or (< (caddr form) 0) (and (zero? (caddr form)) (eq? head '<))) (pair? (cadr form)) ; (< (string-length x) 0) (hash-table-ref non-negative-ops (caadr form))) (lint-format "~A can't be negative: ~A" caller (caadr form) (truncated-list->string form))) ((and (pair? (cadr form)) (eq? (caadr form) 'length)) (let ((arg (cadadr form))) (when (symbol? arg) ; (>= (length x) 0) -> (list? x) ;; see comment above about distinguishing types! (twice I've wasted my time) (if (eqv? (caddr form) 0) (lint-format "perhaps~A ~A" caller (if (eq? head '<) "" (format #f " (assuming ~A is a proper list)," arg)) (lists->string form (case head ((<) `(and (pair? ,arg) (not (proper-list? ,arg)))) ((<=) `(null? ,arg)) ((>) `(pair? ,arg)) ((>=) `(list? ,arg))))) (if (and (eqv? (caddr form) 1) (not (eq? head '>))) ; (<= (length x) 1) -> (or (null? x) (null? (cdr x))) (lint-format "perhaps (assuming ~A is a proper list), ~A" caller arg (lists->string form (case head ((<) `(null? ,arg)) ((<=) `(or (null? ,arg) (null? (cdr ,arg)))) ((>) `(and (pair? ,arg) (pair? (cdr ,arg)))) ((>=) `(pair? ,arg)))))))))) ((and (pair? (caddr form)) (eq? (caaddr form) 'length)) (let ((arg (cadr (caddr form)))) (when (symbol? arg) ; (>= 0 (length x)) -> (null? x) (if (eqv? (cadr form) 0) (lint-format "perhaps~A ~A" caller (if (eq? head '>) "" (format #f " (assuming ~A is a proper list)," arg)) (lists->string form (case head ((<) `(pair? ,arg)) ((<=) `(list? ,arg)) ((>) `(and (pair? ,arg) (not (proper-list? ,arg)))) ((>=) `(null? ,arg))))) (if (and (eqv? (cadr form) 1) (not (eq? head '<))) ; (> 1 (length x)) -> (null? x) (lint-format "perhaps (assuming ~A is a proper list), ~A" caller arg (lists->string form (case head ((<) `(and (pair? ,arg) (pair? (cdr ,arg)))) ((<=) `(pair? ,arg)) ((>) `(null? ,arg)) ((>=) `(or (null? ,arg) (null? (cdr ,arg)))))))))))) ((and (eq? head '<) (eqv? (caddr form) 1) (pair? (cadr form)) ; (< (vector-length x) 1) -> (equal? x #()) (memq (caadr form) '(string-length vector-length))) (lint-format "perhaps ~A" caller (lists->string form `(,(if (eq? (caadr form) 'string-length) 'string=? 'equal?) ,(cadadr form) ,(if (eq? (caadr form) 'string-length) "" #()))))) ((and (eq? head '>) (eqv? (cadr form) 1) (pair? (caddr form)) ; (> 1 (string-length x)) -> (string=? x "") (memq (caaddr form) '(string-length vector-length))) (lint-format "perhaps ~A" caller (lists->string form `(,(if (eq? (caaddr form) 'string-length) 'string=? 'equal?) ,(cadr (caddr form)) ,(if (eq? (caaddr form) 'string-length) "" #()))))) ((and (memq head '(<= >=)) (or (and (eqv? (caddr form) 0) (pair? (cadr form)) ; (<= (string-length m) 0) -> (= (string-length m) 0) (hash-table-ref non-negative-ops (caadr form))) (and (eqv? (cadr form) 0) (pair? (caddr form)) (hash-table-ref non-negative-ops (caaddr form))))) (lint-format "~A is never negative, so ~A" caller ((if (eqv? (caddr form) 0) caadr caaddr) form) (lists->string form (or (not (eq? (eq? head '<=) (eqv? (caddr form) 0))) `(= ,@(cdr form)))))) ((and (eqv? (caddr form) 256) (pair? (cadr form)) ; (< (char->integer key) 256) -> #t (eq? (caadr form) 'char->integer)) (lint-format "perhaps ~A" caller (lists->string form (and (memq head '(< <=)) #t)))) ((or (and (eqv? (cadr form) 0) ; (> (numerator x) 0) -> (> x 0) (pair? (caddr form)) (eq? (caaddr form) 'numerator)) (and (eqv? (caddr form) 0) (pair? (cadr form)) (eq? (caadr form) 'numerator))) (lint-format "perhaps ~A" caller (lists->string form (if (eqv? (cadr form) 0) `(,head ,(cadr form) ,(cadr (caddr form))) `(,head ,(cadadr form) ,(caddr form))))))) (check-char-cmp caller head form)) ;; could change (> x 0) to (positive? x) and so on, but the former is clear and ubiquitous (for-each (lambda (f) (hash-special f sp-<)) '(< > <= >=))) ; '= handled above ;; ---------------- char< char> etc ---------------- (let () (define (sp-char< caller head form env) ;; only once: (char<=? #\0 c #\1) (let ((cleared-form (cons head ; keep operator (remove-if (lambda (x) (not (char? x))) (cdr form))))) (if (and (> (length cleared-form) 2) ; (char>? x #\a #\b y) (not (checked-eval cleared-form))) (lint-format "this comparison can't be true: ~A" caller (truncated-list->string form)))) (if (and (eq? head 'char-ci=?) ; (char-ci=? x #\return) (pair? (cdr form)) (pair? (cddr form)) (null? (cdddr form)) ; (char-ci=? x #\return) (or (and (char? (cadr form)) (char=? (cadr form) (other-case (cadr form)))) (and (char? (caddr form)) (char=? (caddr form) (other-case (caddr form)))))) (lint-format "char-ci=? could be char=? here: ~A" caller form) (when (and (eq? head 'char=?) ; (char=? #\a (char-downcase x)) -> (char-ci=? #\a x) (let ((casef (let ((op #f)) (lambda (a) (or (char? a) (and (pair? a) (memq (car a) '(char-downcase char-upcase)) (if op (eq? op (car a)) (set! op (car a))))))))) (every? casef (cdr form)))) (lint-format "perhaps ~A" caller (lists->string form ; (char=? #\a (char-downcase x)) -> (char-ci=? #\a x) `(char-ci=? ,@(map (lambda (a) (if (and (pair? a) (memq (car a) '(char-upcase char-downcase))) (cadr a) a)) (cdr form)))))))) (for-each (lambda (f) (hash-special f sp-char<)) '(char? char<=? char>=? char=? char-ci? char-ci<=? char-ci>=? char-ci=?))) ;; ---------------- string< string> etc ---------------- (let () (define (sp-string< caller head form env) (let ((cleared-form (cons head ; keep operator (remove-if (lambda (x) (not (string? x))) (cdr form))))) (if (and (> (length cleared-form) 2) ; (string>? "a" x "b" y) (not (checked-eval cleared-form))) (lint-format "this comparison can't be true: ~A" caller (truncated-list->string form)))) (if (and (> (length form) 2) (let ((casef (let ((op #f)) ; (string=? x (string-downcase y)) -> (string-ci=? x y) (lambda (a) (and (pair? a) (memq (car a) '(string-downcase string-upcase)) (if op (eq? op (car a)) (set! op (car a)))))))) (every? casef (cdr form)))) (lint-format "perhaps ~A" caller ; (string=? (string-downcase x) (string-downcase y)) -> (string-ci=? x y) (lists->string form (let ((op (case head ((string=?) 'string-ci=?) ((string<=?) 'string-ci<=?) ((string>=?) 'string-ci>=?) ((string?) 'string-ci>?) (else head)))) `(,op ,@(map (lambda (a) (if (and (pair? a) (memq (car a) '(string-upcase string-downcase))) (cadr a) a)) (cdr form))))))) (if (any? (lambda (a) ; string-copy is redundant in arg list (and (pair? a) (memq (car a) '(copy string-copy)) (null? (cddr a)))) (cdr form)) (let cleaner ((args (cdr form)) (new-args ())) ; (string=? "" (string-copy "")) -> (string=? "" "") (if (not (pair? args)) (lint-format "perhaps ~A" caller (lists->string form `(,head ,@(reverse new-args)))) (let ((a (car args))) (cleaner (cdr args) (cons (if (and (pair? a) (memq (car a) '(copy string-copy)) (null? (cddr a))) (cadr a) a) new-args)))))) (when (and (eq? head 'string=?) (= (length form) 3)) ; (string=? (symbol->string a) (symbol->string b)) -> (eq? a b) (if (and (pair? (cadr form)) (eq? (caadr form) 'symbol->string) (pair? (caddr form)) (eq? (caaddr form) 'symbol->string)) (lint-format "perhaps ~A" caller (lists->string form `(eq? ,(cadadr form) ,(cadr (caddr form))))) (let ((s1 #f) (s2 #f)) (if (and (string? (cadr form)) (= (length (cadr form)) 1)) (begin (set! s1 (cadr form)) (set! s2 (caddr form))) (if (and (string? (caddr form)) (= (length (caddr form)) 1)) (begin (set! s1 (caddr form)) (set! s2 (cadr form))))) (if (and s1 ; (string=? (substring r 0 1) "S") (pair? s2) (eq? (car s2) 'substring) (= (length s2) 4) (eqv? (list-ref s2 2) 0) (eqv? (list-ref s2 3) 1)) (lint-format "perhaps ~A" caller (lists->string form `(char=? (string-ref ,(cadr s2) 0) ,(string-ref s1 0)))))))) (if (every? (lambda (a) ; (string=? "#" (string (string-ref s 0))) -> (char=? #\# (string-ref s 0)) (or (and (string? a) (= (length a) 1)) (and (pair? a) (eq? (car a) 'string)))) (cdr form)) (lint-format "perhaps ~A" caller (lists->string form `(,(symbol "char" (substring (symbol->string head) 6)) ,@(map (lambda (a) (if (string? a) (string-ref a 0) (cadr a))) (cdr form))))))) (for-each (lambda (f) (hash-special f sp-string<)) '(string? string<=? string>=? string=? string-ci? string-ci<=? string-ci>=? string-ci=?))) ;; ---------------- length ---------------- (let () (define (sp-length caller head form env) (when (pair? (cdr form)) (if (pair? (cadr form)) (let ((arg (cadr form)) (arg-args (cdadr form))) (case (car arg) ((string->list vector->list) (if (null? (cdr arg-args)) ; string->list has start:end etc ; (length (string->list x)) -> (length x) (lint-format "perhaps ~A" caller (lists->string form `(length ,(car arg-args)))) (if (pair? (cddr arg-args)) (if (and (integer? (caddr arg-args)) ; (length (vector->list x 1)) -> (- (length x) 1) (integer? (cadr arg-args))) (lint-format "perhaps ~A -> ~A" caller (truncated-list->string form) (max 0 (- (caddr arg-args) (cadr arg-args)))) (lint-format "perhaps ~A" caller (lists->string form `(- ,(caddr arg-args) ,(cadr arg-args))))) (lint-format "perhaps ~A" caller (lists->string form `(- (length ,(car arg-args)) ,(cadr arg-args))))))) ((reverse reverse! list->vector list->string let->list) (lint-format "perhaps ~A" caller (lists->string form `(length ,(car arg-args))))) ((cons) ; (length (cons item items)) -> (+ (length items) 1) (lint-format "perhaps ~A" caller (lists->string form `(+ (length ,(cadr arg-args)) 1)))) ((make-list) ; (length (make-list 3)) -> 3 (lint-format "perhaps ~A" caller (lists->string form (car arg-args)))) ((list) ; (length (list 'a 'b 'c)) -> 3 (lint-format "perhaps ~A" caller (lists->string form (- (length arg) 1)))) ((append) ; (length (append x y)) -> (+ (length x) (length y)) (if (= (length arg) 3) (lint-format "perhaps ~A" caller (lists->string form `(+ (length ,(car arg-args)) (length ,(cadr arg-args))))))) ((quote) ; (length '(1 2 3)) -> 3 (if (list? (car arg-args)) (lint-format "perhaps ~A" caller (lists->string form (length (car arg-args)))))))) ;; not pair cadr (if (code-constant? (cadr form)) ; (length 0) -> #f (lint-format "perhaps ~A -> ~A" caller (truncated-list->string form) (length ((if (and (pair? (cadr form)) (eq? (caadr form) 'quote)) cadadr cadr) form))))))) (hash-special 'length sp-length)) ;; ---------------- zero? positive? negative? ---------------- (let () (define (sp-zero? caller head form env) (when (pair? (cdr form)) (let ((arg (cadr form))) (if (and (real? arg) ; (zero? 0) -> #t (null? (cddr form)) (not (var-member head env))) (lint-format "perhaps ~A" caller (lists->string form (eval form)))) (when (pair? arg) (if (and (eq? head 'negative?) ; (negative? (string-length s))") (hash-table-ref non-negative-ops (car arg))) (lint-format "~A can't be negative: ~A" caller (caadr form) (truncated-list->string form))) (case (car arg) ((-) (lint-format "perhaps ~A" caller ; (zero? (- x)) -> (zero? x) (lists->string form (let ((op '((zero? = zero?) (positive? > negative?) (negative? < positive?)))) (if (null? (cddr arg)) `(,(caddr (assq head op)) ,(cadr arg)) (if (null? (cdddr arg)) `(,(cadr (assq head op)) ,(cadr arg) ,(caddr arg)) `(,(cadr (assq head op)) ,(cadr arg) (+ ,@(cddr arg))))))))) ((numerator) ; (negative? (numerator x)) -> (negative? x) (lint-format "perhaps ~A" caller (lists->string form `(,head ,(cadadr form))))) ((denominator) ; (zero? (denominator x)) -> error (if (eq? head 'zero) (lint-format "denominator can't be zero: ~A" caller form))) ((string-length) ; (zero? (string-length x)) -> (string=? x "") (if (eq? head 'zero?) (lint-format "perhaps ~A" caller (lists->string form `(string=? ,(cadadr form) ""))))) ((vector-length) ; (zero? (vector-length c)) -> (equal? c #()) (if (eq? head 'zero?) (lint-format "perhaps ~A" caller (lists->string form `(equal? ,(cadadr form) #()))))) ((length) ; (zero? (length routes)) -> (null? routes) (if (eq? head 'zero?) (lint-format "perhaps (assuming ~A is list) use null? instead of length: ~A" caller (cadr arg) (lists->string form `(null? ,(cadr arg))))))))))) ;; (zero? (logand...)) is nearly always preceded by not and handled elsewhere (for-each (lambda (f) (hash-special f sp-zero?)) '(zero? positive? negative?))) ;; ---------------- / ---------------- (let () (define (sp-/ caller head form env) (cond ((not (pair? (cdr form)))) ((and (null? (cddr form)) (number? (cadr form)) (zero? (cadr form))) ; (/ 0) (lint-format "attempt to invert zero: ~A" caller (truncated-list->string form))) ((and (pair? (cddr form)) ; (/ x y 2 0) (memv 0 (cddr form))) (lint-format "attempt to divide by 0: ~A" caller (truncated-list->string form))) (else (let ((len (assq 'length (cdr form)))) (if len (lint-format "~A will cause division by 0 if ~A is empty" caller len (cadr len))))))) (hash-special '/ sp-/)) ;; ---------------- copy ---------------- (let () (define (sp-copy caller head form env) (cond ((and (pair? (cdr form)) ; (copy (copy x)) could be (copy x) (or (number? (cadr form)) (boolean? (cadr form)) (char? (cadr form)) (and (pair? (cadr form)) (memq (caadr form) '(copy string-copy))) ; or any maker? (and (pair? (cddr form)) (equal? (cadr form) (caddr form))))) (lint-format "~A could be ~A" caller (truncated-list->string form) (cadr form))) ((and (pair? (cdr form)) ; (copy (owlet)) could be (owlet) (equal? (cadr form) '(owlet))) (lint-format "~A could be (owlet): owlet is copied internally" caller form)) ((= (length form) 5) (check-start-and-end caller 'copy (cdddr form) form env)))) (hash-special 'copy sp-copy)) ;; ---------------- string-copy ---------------- (let () (define (sp-string-copy caller head form env) (if (and (pair? (cdr form)) ; (string-copy (string-copy x)) could be (string-copy x) (pair? (cadr form)) (memq (caadr form) '(copy string-copy string make-string string-upcase string-downcase string-append list->string symbol->string number->string))) (lint-format "~A could be ~A" caller (truncated-list->string form) (cadr form)))) (hash-special 'string-copy sp-string-copy)) ;; ---------------- string-down|upcase ---------------- (let () (define (sp-string-upcase caller head form env) (if (and (pair? (cdr form)) (string? (cadr form))) ; (string-downcase "SPEAK") -> "speak" (lint-format "perhaps ~A" caller (lists->string form ((if (eq? head 'string-upcase) string-upcase string-downcase) (cadr form)))))) (hash-special 'string-upcase sp-string-upcase) (hash-special 'string-downcase sp-string-upcase)) ;; ---------------- string ---------------- (let () (define (sp-string caller head form env) (if (every? (lambda (x) (and (char? x) (char<=? #\space x #\~))) ; #\0xx chars here look dumb (cdr form)) (lint-format "~A could be ~S" caller (truncated-list->string form) (apply string (cdr form))) (if (and (pair? (cdr form)) ; (string (string-ref x 0)) -> (substring x 0 1) (pair? (cadr form))) (if (and (eq? (caadr form) 'string-ref) (null? (cddr form))) (let ((arg (cdadr form))) (if (integer? (cadr arg)) ; (string (string-ref x 0)) -> (substring x 0 1) (lint-format "perhaps ~A" caller (lists->string form `(substring ,(car arg) ,(cadr arg) ,(+ 1 (cadr arg))))))) (if (and (not (null? (cddr form))) (memq (caadr form) '(char-upcase char-downcase)) (every? (lambda (p) (eq? (caadr form) (car p))) (cddr form))) ;; (string (char-downcase (string-ref x 1)) (char-downcase (string-ref x 2))) -> ;; (string-downcase (string (string-ref x 1) (string-ref x 2))) (lint-format "perhaps ~A" caller (lists->string form `(,(if (eq? (caadr form) 'char-upcase) 'string-upcase 'string-downcase) (string ,@(map cadr (cdr form))))))))))) ;; repeated args as in vector/list (sp-list below) got no hits (hash-special 'string sp-string)) ;; ---------------- string? ---------------- (let () (define (sp-string? caller head form env) (if (and (pair? (cdr form)) (pair? (cadr form)) (memq (caadr form) '(format number->string))) (if (eq? (caadr form) 'format) ; (string? (number->string x)) -> #t (lint-format "format returns either #f or a string, so ~A" caller (lists->string form (cadr form))) (lint-format "number->string always returns a string, so ~A" caller (lists->string form #t))) (check-boolean-affinity caller form env))) (hash-special 'string? sp-string?)) ;; ---------------- number? ---------------- (let () (define (sp-number? caller head form env) (if (and (pair? (cdr form)) (pair? (cadr form)) (eq? (caadr form) 'string->number)) ; (number? (string->number x)) -> (string->number x) (lint-format "string->number returns either #f or a number, so ~A" caller (lists->string form (cadr form))) (check-boolean-affinity caller form env))) (hash-special 'number? sp-number?)) ;; ---------------- exact? inexact? infinite? nan? ---------------- (let () (define (sp-exact? caller head form env) (if (and (pair? (cdr form)) (number? (cadr form))) (check-boolean-affinity caller form env))) (for-each (lambda (f) (hash-special f sp-exact?)) '(exact? inexact? infinite? nan?))) ;; ---------------- symbol? etc ---------------- (let () (define (sp-symbol? caller head form env) (check-boolean-affinity caller form env)) (for-each (lambda (f) (hash-special f sp-symbol?)) '(symbol? rational? real? complex? float? keyword? gensym? byte-vector? proper-list? sequence? constant? char? boolean? float-vector? int-vector? vector? let? hash-table? input-port? c-object? output-port? iterator? continuation? dilambda? procedure? macro? random-state? eof-object? c-pointer?))) ;; ---------------- pair? list? ---------------- (let () (define (sp-pair? caller head form env) (check-boolean-affinity caller form env) (if (and (pair? (cdr form)) ; (pair? (member x y)) -> (member x y) (pair? (cadr form)) (memq (caadr form) '(memq memv member assq assv assoc procedure-signature))) (lint-format "~A returns either #f or a pair, so ~A" caller (caadr form) (lists->string form (cadr form))))) (for-each (lambda (f) (hash-special f sp-pair?)) '(pair? list?))) ;; ---------------- integer? ---------------- (let () (define (sp-integer? caller head form env) (check-boolean-affinity caller form env) (if (and (pair? (cdr form)) ; (integer? (char-position x y)) -> (char-position x y) (pair? (cadr form)) (memq (caadr form) '(char-position string-position))) (lint-format "~A returns either #f or an integer, so ~A" caller (caadr form) (lists->string form (cadr form))))) (hash-special 'integer? sp-integer?)) ;; ---------------- null? ---------------- (let () (define (sp-null? caller head form env) (check-boolean-affinity caller form env) (if (and (pair? (cdr form)) ; (null? (string->list x)) -> (zero? (length x)) (pair? (cadr form)) (memq (caadr form) '(vector->list string->list let->list))) (lint-format "perhaps ~A" caller (lists->string form `(zero? (length ,(cadadr form))))))) (hash-special 'null? sp-null?)) ;; ---------------- odd? even? ---------------- (let () (define (sp-odd? caller head form env) (if (and (pair? (cdr form)) ; (odd? (- x 1)) -> (even? x) (pair? (cadr form)) (memq (caadr form) '(+ -)) (= (length (cadr form)) 3)) (let* ((arg1 (cadadr form)) (arg2 (caddr (cadr form))) (int-arg (or (and (integer? arg1) arg1) (and (integer? arg2) arg2)))) (if int-arg (lint-format "perhaps ~A" caller (lists->string form (if (and (integer? arg1) (integer? arg2)) (eval form) `(,(if (eq? (eq? head 'even?) (even? int-arg)) 'even? 'odd?) ,(if (integer? arg1) arg2 arg1))))))))) (hash-special 'odd? sp-odd?) (hash-special 'even? sp-odd?)) ;; ---------------- string-ref ---------------- (let () (define (sp-string-ref caller head form env) (when (= (length form) 3) (if (equal? (cadr form) "") (lint-format "~A is an error" caller form) (when (every? code-constant? (cdr form)) ; (string-ref "abc" 0) -> #\a (catch #t (lambda () (let ((val (eval form))) (lint-format "perhaps ~A" caller (lists->string form val)))) (lambda args (lint-format "~A: ~A" caller (object->string form) (apply format #f (cadr args))))))) (when (pair? (cadr form)) (let ((target (cadr form))) (case (car target) ((substring) ; (string-ref (substring x 1) 2) -> (string-ref x (+ 2 1)) (if (= (length target) 3) (lint-format "perhaps ~A" caller (lists->string form `(string-ref ,(cadr target) (+ ,(caddr form) ,(caddr target))))))) ((symbol->string) ; (string-ref (symbol->string 'abs) 1) -> #\b (if (and (integer? (caddr form)) (pair? (cadr target)) (eq? (caadr target) 'quote) (symbol? (cadadr target))) (lint-format "perhaps ~A" caller (lists->string form (string-ref (symbol->string (cadadr target)) (caddr form)))))) ((make-string) ; (string-ref (make-string 3 #\a) 1) -> #\a (if (and (integer? (cadr target)) (integer? (caddr form)) (> (cadr target) (caddr form))) (lint-format "perhaps ~A" caller (lists->string form (if (= (length target) 3) (caddr target) #\space)))))))))) (hash-special 'string-ref sp-string-ref)) ;; ---------------- vector-ref etc ---------------- (let () (define (sp-vector-ref caller head form env) (unless (= line-number last-checker-line-number) (when (= (length form) 3) (let ((seq (cadr form))) (when (code-constant? (cadr form)) (if (eqv? (length (cadr form)) 0) (lint-format "~A is an error" caller form) (when (every? code-constant? (cddr form)) ; (vector-ref #(1 2) 0) -> 1 (catch #t (lambda () (let ((val (eval form))) (lint-format "perhaps ~A -> ~A~A" caller (truncated-list->string form) (if (or (pair? val) (symbol? val)) "'" "") (object->string val)))) (lambda args (lint-format "~A: ~A" caller (object->string form) (apply format #f (cadr args)))))))) (when (pair? seq) (if (and (memq (car seq) '(vector-ref int-vector-ref float-vector-ref list-ref hash-table-ref let-ref)) (= (length seq) 3)) ; (vector-ref (vector-ref x i) j) -> (x i j) (let ((seq1 (cadr seq))) ; x (lint-format "perhaps ~A" caller (lists->string form (if (and (pair? seq1) ; (vector-ref (vector-ref (vector-ref x i) j) k) -> (x i j k) (memq (car seq1) '(vector-ref int-vector-ref float-vector-ref list-ref hash-table-ref let-ref)) (= (length seq1) 3)) `(,(cadr seq1) ,(caddr seq1) ,(caddr seq) ,(caddr form)) `(,seq1 ,(caddr seq) ,(caddr form)))))) (if (memq (car seq) '(make-vector make-list vector list make-float-vector make-int-vector float-vector int-vector make-hash-table hash-table hash-table* inlet)) (lint-format "this doesn't make much sense: ~A" caller form))) (when (eq? head 'list-ref) (if (eq? (car seq) 'quote) (if (proper-list? (cadr seq)) ; (list-ref '(#t #f) (random 2)) -> (vector-ref #(#t #f) (random 2)) (lint-format "perhaps use a vector: ~A" caller (lists->string form `(vector-ref ,(apply vector (cadr seq)) ,(caddr form))))) (let ((index (caddr form))) ; (list-ref (cdddr f) 2) -> (list-ref f 5) (if (and (memq (car seq) '(cdr cddr cdddr)) (or (integer? index) (and (pair? index) (eq? (car index) '-) (integer? (caddr index))))) (let ((offset (cdr (assq (car seq) '((cdr . 1) (cddr . 2) (cdddr . 3)))))) (lint-format "perhaps ~A" caller (lists->string form `(list-ref ,(cadr seq) ,(if (integer? index) (+ index offset) (let ((noff (- (caddr index) offset))) (if (zero? noff) (cadr index) `(- ,(cadr index) ,noff))))))))))))))) (set! last-checker-line-number line-number))) (for-each (lambda (f) (hash-special f sp-vector-ref)) '(vector-ref list-ref hash-table-ref let-ref int-vector-ref float-vector-ref))) ;; ---------------- vector-set! etc ---------------- (let () (define (sp-vector-set! caller head form env) (when (= (length form) 4) (let ((target (cadr form)) (index (caddr form)) (val (cadddr form))) (cond ((and (pair? val) ; (vector-set! x 0 (vector-ref x 0)) (= (length val) 3) (eq? target (cadr val)) (equal? index (caddr val)) (memq (car val) '(vector-ref list-ref hash-table-ref string-ref let-ref float-vector-ref int-vector-ref))) (lint-format "redundant ~A: ~A" caller head (truncated-list->string form))) ((code-constant? target) ; (vector-set! #(0 1 2) 1 3)?? (lint-format "~A is a constant that is discarded; perhaps ~A" caller target (lists->string form val))) ((not (pair? target))) ((and (not (eq? head 'string-set!)) ; (vector-set! (vector-ref x 0) 1 2) -- vector within vector (memq (car target) '(vector-ref list-ref hash-table-ref let-ref float-vector-ref int-vector-ref))) (lint-format "perhaps ~A" caller (lists->string form `(set! (,@(cdr target) ,index) ,val)))) ((memq (car target) '(make-vector vector make-string string make-list list append cons vector-append inlet sublet copy vector-copy string-copy list-copy)) ;list-copy is from r7rs (lint-format "~A is simply discarded; perhaps ~A" caller (truncated-list->string target) ; (vector-set! (make-vector 3) 1 1) -- does this ever happen? (lists->string form val))) ((and (eq? head 'list-set!) (memq (car target) '(cdr cddr cdddr cddddr)) (integer? (caddr form))) ; (list-set! (cdr x) 0 y) -> (list-set! x 1 y) (lint-format "perhaps ~A" caller (lists->string form `(list-set! ,(cadr target) ,(+ (caddr form) (cdr-count (car target))) ,(cadddr form))))))))) (for-each (lambda (f) (hash-special f sp-vector-set!)) '(vector-set! list-set! hash-table-set! float-vector-set! int-vector-set! string-set! let-set!))) ;; ---------------- object->string ---------------- (let () (define (sp-object->string caller head form env) (when (pair? (cdr form)) (if (and (pair? (cadr form)) ; (object->string (object->string x)) could be (object->string x) (eq? (caadr form) 'object->string)) (lint-format "~A could be ~A" caller (truncated-list->string form) (cadr form)) (if (pair? (cddr form)) (let ((arg2 (caddr form))) (if (and (code-constant? arg2) ; (object->string x :else) (not (memq arg2 '(#f #t :readable)))) ; #f and #t are display|write choice, :readable = ~W (lint-format "bad second argument: ~A" caller arg2))))))) (hash-special 'object->string sp-object->string)) (define (all-caps-warning arg) (and (string? arg) (or (string-position "ERROR" arg) (string-position "WARNING" arg)))) ;; ---------------- display ---------------- (let () (define (sp-display caller head form env) (when (pair? (cdr form)) (let ((arg (cadr form)) (port (if (pair? (cddr form)) (caddr form) ()))) (cond ((all-caps-warning arg) (lint-format "There's no need to shout: ~A" caller (truncated-list->string form))) ((not (and (pair? arg) (pair? (cdr arg))))) ((and (eq? (car arg) 'format) ; (display (format #f str x)) -> (format () str x) (not (cadr arg))) (lint-format "perhaps ~A" caller (lists->string form `(format ,port ,@(cddr arg))))) ((and (eq? (car arg) 'apply) ; (display (apply format #f str x) p) -> (apply format p str x) (eq? (cadr arg) 'format) (pair? (cddr arg)) (not (caddr arg))) (lint-format "perhaps ~A" caller (lists->string form `(apply format ,port ,@(cdddr arg))))) ((and (pair? port) (eq? (car port) 'current-output-port)) (lint-format "(current-output-port) is the default port for display: ~A" caller form)))))) (hash-special 'display sp-display)) ;; ---------------- flush-output-port, newline, close-output-port ---------------- (let () (define (sp-flush-output-port caller head form env) (if (and (pair? (cdr form)) (pair? (cadr form)) (eq? (caadr form) 'current-output-port)) (lint-format "(current-output-port) is the default port for ~A: ~A" caller head form))) (hash-special 'flush-output-port sp-flush-output-port) (hash-special 'close-output-port sp-flush-output-port) (hash-special 'newline sp-flush-output-port)) ;; ---------------- write-char, write-byte, write ---------------- (let () (define (sp-write-char caller head form env) (when (pair? (cdr form)) (if (and (pair? (cddr form)) (pair? (caddr form)) (eq? (caaddr form) 'current-output-port)) (lint-format "(current-output-port) is the default port for ~A: ~A" caller head form)) (if (and (eq? head 'write-byte) (integer? (cadr form)) (not (<= 0 (cadr form) 255))) (lint-format "write-byte argument must be (<= 0 byte 255): ~A" caller form) (if (and (eq? head 'write-char) (eqv? (cadr form) #\newline)) (lint-format "perhaps ~A" caller (lists->string form `(newline ,@(cddr form)))))))) (hash-special 'write-char sp-write-char) (hash-special 'write-byte sp-write-char) (hash-special 'write sp-write-char)) ;; ---------------- read, port-filename, port-line-number, read-char, read-byte ---------------- (let () (define (sp-read caller head form env) (when (and (pair? (cdr form)) (null? (cddr form))) (if (and (pair? (cadr form)) (eq? (caadr form) 'current-input-port)) (lint-format "(current-input-port) is the default port for ~A: ~A" caller head form) (if (and (eq? head 'port-filename) (memq (cadr form) '(*stdin* *stdout* *stderr*))) (lint-format "~A: ~S" caller form (case (cadr form) ((*stdin*) "*stdin*") ((*stdout*) "*stdout*") ((*stderr*) "*stderr*"))))))) (for-each (lambda (c) (hash-special c sp-read)) '(read port-filename port-line-number read-char read-byte peek-char close-input-port))) ;; ---------------- char-alphabetic? char-lower-case? char-numeric? char-upper-case? char-whitespace? etc ---------------- (let () (define (sp-char-numeric caller head form env) (if (and (not (var-member (car form) env)) (pair? (cdr form)) (null? (cddr form)) (char? (cadr form))) (lint-format "perhaps ~A" caller (lists->string form (eval form))))) (for-each (lambda (c) (hash-special c sp-char-numeric)) '(char-alphabetic? char-lower-case? char-numeric? char-upper-case? char-whitespace? char-upcase char-downcase))) ;; ---------------- make-vector etc ---------------- (let () (define (sp-make-vector caller head form env) ;; type of initial value (for make-float|int-vector) is checked elsewhere (if (and (= (length form) 4) (eq? head 'make-vector)) ; (make-vector 3 0 #t) (lint-format "make-vector no longer has a fourth argument: ~A" caller form)) (if (>= (length form) 3) (case (caddr form) ((#) (if (eq? head 'make-vector) ; (make-vector 3 #) (lint-format "# is the default initial value in ~A" caller form))) ((0) (if (not (eq? head 'make-vector)) (lint-format "0 is the default initial value in ~A" caller form))) ((0.0) (if (eq? head 'make-float-vector) (lint-format "0.0 is the default initial value in ~A" caller form))))) (when (and (pair? (cdr form)) (integer? (cadr form)) (zero? (cadr form))) (if (pair? (cddr form)) ; (make-vector 0 0.0) (lint-format "initial value is pointless here: ~A" caller form)) (lint-format "perhaps ~A" caller (lists->string form #())))) (for-each (lambda (f) (hash-special f sp-make-vector)) '(make-vector make-int-vector make-float-vector))) ;; ---------------- make-string make-byte-vector ---------------- (let () (define (sp-make-string caller head form env) (when (and (pair? (cdr form)) (integer? (cadr form)) (zero? (cadr form))) (if (pair? (cddr form)) ; (make-byte-vector 0 0) (lint-format "initial value is pointless here: ~A" caller form)) (lint-format "perhaps ~A" caller (lists->string form "")))) ; #u8() but (equal? #u8() "") -> #t so lint combines these clauses! (for-each (lambda (f) (hash-special f sp-make-string)) '(make-string make-byte-vector))) ;; ---------------- make-list ---------------- (let () (define (sp-make-list caller head form env) (when (and (pair? (cdr form)) (integer? (cadr form)) (zero? (cadr form))) (if (pair? (cddr form)) ; (make-list 0 #f) (lint-format "initial value is pointless here: ~A" caller form)) (lint-format "perhaps ~A" caller (lists->string form ())))) (hash-special 'make-list sp-make-list)) ;; ---------------- reverse string->list etc ---------------- (let () (define (sp-reverse caller head form env) ;; not string->number -- no point in copying a number and it's caught below (when (pair? (cdr form)) (if (code-constant? (cadr form)) (let ((seq (checked-eval form))) (if (not (eq? seq :checked-eval-error)) ; (symbol->string 'abs) -> "abs" (lint-format "perhaps ~A -> ~A~A" caller (truncated-list->string form) (if (pair? seq) "'" "") (if (symbol? seq) (object->string seq :readable) (object->string seq)))))) (when (and (pair? (cadr form)) (pair? (cdadr form))) (let ((inverses '((reverse . reverse) (reverse! . reverse!) ;; reverse and reverse! are not completely interchangable: ;; (reverse (cons 1 2)): (2 . 1) ;; (reverse! (cons 1 2)): error: reverse! argument, (1 . 2), is a pair but should be a proper list (list->vector . vector->list) (vector->list . list->vector) (symbol->string . string->symbol) (string->symbol . symbol->string) (list->string . string->list) (string->list . list->string) (number->string . string->number)))) (let ((inv-op (assq head inverses)) (arg (cadr form)) (arg-args (cdadr form)) (arg-of-arg (cadadr form)) (func-of-arg (caadr form))) (if (pair? inv-op) (set! inv-op (cdr inv-op))) (cond ((eq? func-of-arg inv-op) ; (vector->list (list->vector x)) -> x (if (eq? head 'string->symbol) (lint-format "perhaps ~A" caller (lists->string form arg-of-arg)) (lint-format "~A could be (copy ~S)" caller form arg-of-arg))) ((and (eq? head 'list->string) ; (list->string (vector->list x)) -> (copy x (make-string (length x))) (eq? func-of-arg 'vector->list)) (lint-format "perhaps ~A" caller (lists->string form `(copy ,arg-of-arg (make-string (length ,arg-of-arg)))))) ((and (eq? head 'list->string) ; (list->string (make-list x y)) -> (make-string x y) (eq? func-of-arg 'make-list)) (lint-format "perhaps ~A" caller (lists->string form `(make-string ,@arg-args)))) ((and (eq? head 'string->list) ; (string->list (string x y)) -> (list x y) (eq? func-of-arg 'string)) (lint-format "perhaps ~A" caller (lists->string form `(list ,@arg-args)))) ((and (eq? head 'list->vector) ; (list->vector (make-list ...)) -> (make-vector ...) (eq? func-of-arg 'make-list)) (lint-format "perhaps ~A" caller (lists->string form `(make-vector ,@arg-args)))) ((and (eq? head 'list->vector) ; (list->vector (string->list x)) -> (copy x (make-vector (length x))) (eq? func-of-arg 'string->list)) (lint-format "perhaps ~A" caller (lists->string form `(copy ,arg-of-arg (make-vector (length ,arg-of-arg)))))) ((and (eq? head 'list->vector) ; (list->vector (append (vector->list v1) ...)) -> (append v1 ...) (eq? func-of-arg 'append) (every? (lambda (a) (and (pair? a) (eq? (car a) 'vector->list))) (cdadr form))) (lint-format "perhaps ~A" caller (lists->string form `(append ,@(map cadr (cdadr form)))))) ((and (eq? head 'vector->list) ; (vector->list (make-vector ...)) -> (make-list ...) (eq? func-of-arg 'make-vector)) (lint-format "perhaps ~A" caller (lists->string form `(make-list ,@arg-args)))) ((and (eq? head 'vector->list) ; (vector->list (vector ...)) -> (list ...) (eq? func-of-arg 'vector)) (lint-format "perhaps ~A" caller (lists->string form `(list ,@arg-args)))) ((and (eq? head 'vector->list) ; (vector->list (vector-copy ...)) -> (vector->list ...) (eq? func-of-arg 'vector-copy)) (lint-format "perhaps ~A" caller (lists->string form `(vector->list ,@arg-args)))) ((and (memq func-of-arg '(reverse reverse! copy)) (pair? arg-of-arg) ; (list->string (reverse (string->list x))) -> (reverse x) (eq? (car arg-of-arg) inv-op)) (lint-format "perhaps ~A" caller (lists->string form `(,(if (eq? func-of-arg 'reverse!) 'reverse func-of-arg) ,(cadr arg-of-arg))))) ((and (memq head '(reverse reverse!)) ; (reverse (string->list x)) -> (string->list (reverse x)) -- often redundant (memq func-of-arg '(string->list vector->list sort!))) (cond ((not (eq? func-of-arg 'sort!)) (if (null? (cdr arg-args)) (lint-format "perhaps less consing: ~A" caller (lists->string form `(,func-of-arg (reverse ,arg-of-arg)))))) ((and (pair? arg-args) ; (reverse (sort! x <)) -> (sort x >) (pair? (cdr arg-args)) (hash-table-ref reversibles (cadr arg-args))) => (lambda (op) (lint-format "possibly ~A" caller (lists->string form `(sort! ,arg-of-arg ,op))))))) ((and (pair? arg-of-arg) (memq func-of-arg '(cdr cddr cdddr cddddr list-tail)) (case head ((list->string) (eq? (car arg-of-arg) 'string->list)) ((list->vector) (eq? (car arg-of-arg) 'vector->list)) (else #f))) (let ((len-diff (if (eq? func-of-arg 'list-tail) (cadr arg-args) (cdr-count func-of-arg)))) (lint-format "perhaps ~A" caller ; (list->string (cdr (string->list x))) -> (substring x 1) (lists->string form (if (eq? head 'list->string) `(substring ,(cadr arg-of-arg) ,len-diff) `(copy ,(cadr arg-of-arg) (make-vector (- (length ,(cadr arg-of-arg)) ,len-diff)))))))) ((and (memq head '(list->vector list->string)) (eq? func-of-arg 'sort!) ; (list->vector (sort! (vector->list x) y)) -> (sort! x y) (pair? arg-of-arg) (eq? (car arg-of-arg) (if (eq? head 'list->vector) 'vector->list 'string->list))) (lint-format "perhaps ~A" caller (lists->string form `(sort! ,(cadr arg-of-arg) ,(cadr arg-args))))) ((and (memq head '(list->vector list->string)) (or (memq func-of-arg '(list cons)) (quoted-undotted-pair? arg))) (let ((maker (if (eq? head 'list->vector) 'vector 'string))) (case func-of-arg ((list) (if (var-member maker env) ; (list->string (list x y z)) -> (string x y z) (lint-format "~A could be simplified, but you've shadowed '~A" caller (truncated-list->string form) maker) (lint-format "perhaps ~A" caller (lists->string form `(,maker ,@arg-args))))) ((cons) (if (any-null? (cadr arg-args)) (if (var-member maker env) ; (list->string (cons x ())) -> (string x) (lint-format "~A could be simplified, but you've shadowed '~A" caller (truncated-list->string form) maker) (lint-format "perhaps ~A" caller (lists->string form `(,maker ,arg-of-arg))))))))) ((and (memq head '(list->string list->vector)) ; (list->string (reverse x)) -> (reverse (apply string x)) (memq func-of-arg '(reverse reverse!))) (lint-format "perhaps ~A" caller (lists->string form `(reverse (,head ,arg-of-arg))))) ((and (eq? head 'string->symbol) ; (string->symbol (string-append...)) -> (symbol ...) (or (memq func-of-arg '(string-append append)) (and (eq? func-of-arg 'apply) (memq arg-of-arg '(string-append append))))) (lint-format "perhaps ~A" caller (lists->string form (if (eq? func-of-arg 'apply) `(apply symbol ,@(cdr arg-args)) `(symbol ,@arg-args))))) ((and (eq? head 'string->symbol) ; (string->symbol (if (not (null? x)) x "abc")) -> (if (not (null? x)) (string->symbol x) 'abc) (eq? func-of-arg 'if) (or (string? (cadr arg-args)) (string? (caddr arg-args))) (not (or (equal? (cadr arg-args) "") ; this is actually an error -- should we complain? (equal? (caddr arg-args) "")))) (lint-format "perhaps ~A" caller (lists->string form (if (string? (cadr arg-args)) (if (string? (caddr arg-args)) `(if ,arg-of-arg ',(string->symbol (cadr arg-args)) ',(string->symbol (caddr arg-args))) `(if ,arg-of-arg ',(string->symbol (cadr arg-args)) (string->symbol ,(caddr arg-args)))) `(if ,arg-of-arg (string->symbol ,(cadr arg-args)) ',(string->symbol (caddr arg-args))))))) ((case head ; (reverse (reverse! x)) could be (copy x) ((reverse) (eq? func-of-arg 'reverse!)) ((reverse!) (eq? func-of-arg 'reverse)) (else #f)) (lint-format "~A could be (copy ~S)" caller form arg-of-arg)) ((and (pair? arg-of-arg) ; (op (reverse (inv-op x))) -> (reverse x) (eq? func-of-arg 'reverse) (eq? inv-op (car arg-of-arg))) (lint-format "perhaps ~A" caller (lists->string form `(reverse ,(cadr arg-of-arg))))))))) (when (pair? (cddr form)) ; (string->list x y y) is () (when (and (memq head '(vector->list string->list)) (pair? (cdddr form))) (check-start-and-end caller head (cddr form) form env)) (when (and (eq? head 'number->string) ; (number->string saturation 10) (eqv? (caddr form) 10)) (lint-format "10 is the default radix for number->string: ~A" caller (truncated-list->string form)))) (when (memq head '(reverse reverse!)) (if (and (eq? head 'reverse!) (symbol? (cadr form))) (let ((v (var-member (cadr form) env))) (if (and (var? v) (eq? (var-definer v) 'parameter)) (lint-format "if ~A (a function argument) is a pair, ~A is ill-advised" caller (cadr form) (truncated-list->string form)))) (when (pair? (cadr form)) (let ((arg (cadr form)) (arg-op (caadr form)) (arg-args (cdadr form)) (arg-arg (and (pair? (cdadr form)) (cadadr form)))) (when (and (pair? arg-args) (pair? arg-arg)) (if (and (memq arg-op '(cdr list-tail)) ; (reverse (cdr (reverse lst))) = all but last of lst -> copy to len-1 (memq (car arg-arg) '(reverse reverse!)) (symbol? (cadr arg-arg))) (lint-format "perhaps ~A" caller (lists->string form `(copy ,(cadr arg-arg) (make-list (- (length ,(cadr arg-arg)) ,(if (eq? arg-op 'cdr) 1 (cadr arg-args)))))))) (if (and (eq? arg-op 'append) ; (reverse (append (reverse b) res)) = (append (reverse res) b) (eq? (car arg-arg) 'reverse) (pair? (cdr arg-args)) (null? (cddr arg-args))) (lint-format "perhaps ~A" caller (lists->string form `(append (reverse ,(cadr arg-args)) ,(cadr arg-arg)))))) (when (and (= (length arg) 3) (pair? (cadr arg-args))) (cond ((and (eq? arg-op 'map) ; (reverse (map abs (sort! x <))) -> (map abs (sort! x >)) (eq? (caadr arg-args) 'sort!) (hash-table-ref reversibles (caddr (cadr arg-args)))) => (lambda (op) (lint-format "possibly ~A" caller (lists->string form `(,arg-op ,arg-arg (sort! ,(cadadr arg-args) ,op))))))) ;; (reverse (apply vector (sort! x <))) doesn't happen (nor does this map case, but it's too pretty to leave out) (if (and (eq? arg-op 'cons) ; (reverse (cons x (reverse lst))) -- adds x to end -- (append lst (list x)) (memq (caadr arg-args) '(reverse reverse!))) (lint-format "perhaps ~A" caller (lists->string form `(append ,(cadadr arg-args) (list ,arg-arg)))))))))))) (for-each (lambda (f) (hash-special f sp-reverse)) '(reverse reverse! list->vector vector->list list->string string->list symbol->string string->symbol number->string))) ;; ---------------- char->integer string->number etc ---------------- (let () (define (sp-char->integer caller head form env) (when (pair? (cdr form)) (let ((inverses '((char->integer . integer->char) (integer->char . char->integer) (symbol->keyword . keyword->symbol) (keyword->symbol . symbol->keyword) (string->number . number->string))) (arg (cadr form))) (if (and (pair? arg) (pair? (cdr arg)) ; (string->number (number->string x)) could be x (eq? (car arg) (cond ((assq head inverses) => cdr)))) (lint-format "~A could be ~A" caller (truncated-list->string form) (cadr arg)) (case head ((integer->char) (if (let walk ((tree (cdr form))) (if (pair? tree) (and (walk (car tree)) (walk (cdr tree))) (or (code-constant? tree) (not (side-effect? tree env))))) (let ((chr (checked-eval form))) ; (integer->char (+ (char->integer #\space) 215)) -> #\xf7 (if (char? chr) (lint-format "perhaps ~A" caller (lists->string form chr)))))) ((string->number) (if (and (pair? (cddr form)) (integer? (caddr form)) ; type error is checked elsewhere (not (<= 2 (caddr form) 16))) ; (string->number "123" 21) (lint-format "string->number radix should be between 2 and 16: ~A" caller form) (if (and (pair? arg) (eq? (car arg) 'string) (pair? (cdr arg)) (null? (cddr form)) (null? (cddr arg))) ; (string->number (string num-char)) -> (- (char->integer num-char) (char->integer #\0)) (lint-format "perhaps ~A" caller (lists->string form `(- (char->integer ,(cadr arg)) (char->integer #\0))))))) ((symbol->keyword) (if (and (pair? arg) ; (symbol->keyword (string->symbol x)) -> (make-keyword x) (eq? (car arg) 'string->symbol)) (lint-format "perhaps ~A" caller (lists->string form `(make-keyword ,(cadr arg)))) (if (quoted-symbol? arg) (lint-format "perhaps ~A" caller (lists->string form (symbol->keyword (cadr arg))))))) ((keyword->symbol) (if (keyword? arg) (lint-format "perhaps ~A -> '~A" caller (object->string form) (object->string (keyword->symbol arg)))))))))) (for-each (lambda (f) (hash-special f sp-char->integer)) '(char->integer integer->char symbol->keyword keyword->symbol string->number))) ;; ---------------- string-append ---------------- (let () (define (sp-string-append caller head form env) (unless (= line-number last-checker-line-number) (let ((args (remove-all "" (splice-if (lambda (x) (eq? x 'string-append)) (cdr form)))) (combined #f)) (when (or (any? string? args) (member 'string args (lambda (a b) (and (pair? b) (eq? (car b) a))))) (do ((nargs ()) ; look for (string...) (string...) in the arg list and combine (p args (cdr p))) ((null? p) (set! args (reverse nargs))) (cond ((not (pair? (cdr p))) (set! nargs (cons (car p) nargs))) ((and (pair? (car p)) (eq? (caar p) 'string) (pair? (cadr p)) (eq? (caadr p) 'string)) (set! nargs (cons `(string ,@(cdar p) ,@(cdadr p)) nargs)) (set! combined #t) (set! p (cdr p))) ((and (string? (car p)) (string? (cadr p))) (set! nargs (cons (string-append (car p) (cadr p)) nargs)) (set! combined #t) (set! p (cdr p))) (else (set! nargs (cons (car p) nargs)))))) (cond ((null? args) ; (string-append) -> "" (lint-format "perhaps ~A" caller (lists->string form ""))) ((null? (cdr args)) ; (string-append a) -> a (if (not (tree-memq 'values (cdr form))) (lint-format "perhaps ~A~A" caller (lists->string form (car args)) (if combined "" ", or use copy")))) ; (string-append x "") appears to be a common substitute for string-copy ((every? string? args) ; (string-append "a" "b") -> "ab" (lint-format "perhaps ~A" caller (lists->string form (apply string-append args)))) ((every? (lambda (a) ; (string-append "a" (string #\b)) -> "ab" (or (string? a) (and (pair? a) (eq? (car a) 'string) (char? (cadr a))))) args) (catch #t (lambda () ; (string-append (string #\C) "ZLl*()def") -> "CZLl*()def" (let ((val (if (not (any? pair? args)) (apply string-append args) (eval (cons 'string-append args))))) (lint-format "perhaps ~A -> ~S" caller (truncated-list->string form) val))) (lambda args #f))) ((every? (lambda (c) ; (string-append (make-string 3 #\a) (make-string 2 #\b)) -> (format #f "~NC~NC" 3 #\a 2 #\b) (and (pair? c) (eq? (car c) 'make-string) (pair? (cdr c)) (pair? (cddr c)))) (cdr form)) (lint-format "perhaps ~A" caller (lists->string form `(format #f ,(apply string-append (make-list (abs (length (cdr form))) "~NC")) ,@(map (lambda (c) (values (cadr c) (caddr c))) (cdr form)))))) ((not (equal? args (cdr form))) ; (string-append x (string-append y z)) -> (string-append x y z) (lint-format "perhaps ~A" caller (lists->string form `(string-append ,@args))))) (set! last-checker-line-number line-number)))) (hash-special 'string-append sp-string-append)) ;; ---------------- vector-append ---------------- (let () (define (sp-vector-append caller head form env) (unless (= line-number last-checker-line-number) (let ((args (remove-all #() (splice-if (lambda (x) (eq? x 'vector-append)) (cdr form))))) (cond ((null? args) ; (vector-append) -> #() (lint-format "perhaps ~A" caller (lists->string form #()))) ((null? (cdr args)) ; (vector-append x) -> (copy x) (lint-format "perhaps ~A" caller (lists->string form `(copy ,(car args))))) ((every? vector? args) ; (vector-append #(1 2) (vector-append #(3))) -> #(1 2 3) (lint-format "perhaps ~A" caller (lists->string form (apply vector-append args)))) ((not (equal? args (cdr form))) ; (vector-append x (vector-append y z)) -> (vector-append x y z) (lint-format "perhaps ~A" caller (lists->string form `(vector-append ,@args))))) (set! last-checker-line-number line-number)))) (hash-special 'vector-append sp-vector-append)) ;; ---------------- cons ---------------- (let () (define (sp-cons caller head form env) (cond ((or (not (= (length form) 3)) (= last-cons-line-number line-number)) #f) ((and (pair? (caddr form)) (or (eq? (caaddr form) 'list) ; (cons x (list ...)) -> (list x ...) (and (eq? (caaddr form) #_{list}) (not (tree-member #_{apply_values} (cdaddr form)))))) (lint-format "perhaps ~A" caller (lists->string form `(list ,(cadr form) ,@(un_{list} (cdaddr form)))))) ((any-null? (caddr form)) ; (cons x '()) -> (list x) (lint-format "perhaps ~A" caller (lists->string form `(list ,(cadr form))))) ((not (pair? (caddr form)))) ((and (pair? (cadr form)) ; (cons (car x) (cdr x)) -> (copy x) (let ((x (assq (caadr form) '((car cdr #t) (caar cdar car) (cadr cddr cdr) (caaar cdaar caar) (caadr cdadr cadr) (caddr cdddr cddr) (cadar cddar cdar) (cadddr cddddr cdddr) (caaaar cdaaar caaar) (caaadr cdaadr caadr) (caadar cdadar cadar) (caaddr cdaddr caddr) (cadaar cddaar cdaar) (cadadr cddadr cdadr) (caddar cdddar cddar))))) (and x (eq? (cadr x) (caaddr form)) (caddr x)))) => (lambda (cfunc) (if (and cfunc (equal? (cadadr form) (cadr (caddr form))) (not (side-effect? (cadadr form) env))) (lint-format "perhaps ~A" caller (lists->string form (if (symbol? cfunc) `(copy (,cfunc ,(cadadr form))) `(copy ,(cadadr form)))))))) ((eq? (caaddr form) 'cons) ; list handled above ; (cons a (cons b (cons ...))) -> (list a b ...), input ending in nil of course (let loop ((args (list (cadr form))) (chain (caddr form))) (if (pair? chain) (if (eq? (car chain) 'list) (begin (lint-format "perhaps ~A" caller (lists->string form `(list ,@(reverse args) ,@(cdr chain)))) (set! last-cons-line-number line-number)) (if (and (eq? (car chain) 'cons) (pair? (cdr chain)) (pair? (cddr chain))) (if (any-null? (caddr chain)) (begin (lint-format "perhaps ~A" caller (lists->string form `(list ,@(reverse args) ,(cadr chain)))) (set! last-cons-line-number line-number)) (if (and (pair? (caddr chain)) (memq (caaddr chain) '(cons list))) (loop (cons (cadr chain) args) (caddr chain))))))))))) (hash-special 'cons sp-cons)) ;; ---------------- append ---------------- (let () (define (sp-append caller head form env) (unless (= line-number last-checker-line-number) (set! last-checker-line-number line-number) (letrec ((splice-append (lambda (lst) (cond ((null? lst) ()) ((not (pair? lst)) lst) ((and (pair? (car lst)) (eq? (caar lst) 'append)) (if (null? (cdar lst)) (if (null? (cdr lst)) ; (append) at end -> () to keep copy intact? (list ()) (splice-append (cdr lst))) (append (splice-append (cdar lst)) (splice-append (cdr lst))))) ((and (pair? (car lst)) (eq? (caar lst) 'copy) (pair? (cdr lst)) (null? (cddar lst))) (cons (cadar lst) (splice-append (cdr lst)))) ((or (null? (cdr lst)) (not (or (any-null? (car lst)) (and (pair? (car lst)) (eq? (caar lst) 'list) (null? (cdar lst)))))) (cons (car lst) (splice-append (cdr lst)))) (else (splice-append (cdr lst))))))) (let ((new-args (splice-append (cdr form)))) ; (append '(1) (append '(2) '(3))) -> (append '(1) '(2) '(3)) (let ((len1 (length new-args)) (suggestion made-suggestion)) (if (and (> len1 2) (null? (list-ref new-args (- len1 1))) (pair? (list-ref new-args (- len1 2))) (memq (car (list-ref new-args (- len1 2))) '(list cons append map string->list vector->list make-list))) (begin (set-cdr! (list-tail new-args (- len1 2)) ()) (set! len1 (- len1 1)))) (define (append->list . items) (let ((lst (list 'list))) (for-each (lambda (item) (set! lst (append lst (if (eq? (car item) 'list) (cdr item) (distribute-quote (cadr item)))))) items) lst)) (if (positive? len1) (let ((last (list-ref new-args (- len1 1)))) ;; (define (f) (append '(1) '(2))) (define a (f)) (set! (a 1) 32) (f) -> '(1 32) (if (and (pair? last) (eq? (car last) 'quote) (pair? (cdr last)) (pair? (cadr last))) (lint-format "append does not copy its last argument, so ~A is dangerous" caller form)))) (case len1 ((0) ; (append) -> () (lint-format "perhaps ~A" caller (lists->string form ()))) ((1) ; (append x) -> x (lint-format "perhaps ~A" caller (lists->string form (car new-args)))) ((2) ; (append (list x) ()) -> (list x) (let ((arg2 (cadr new-args)) (arg1 (car new-args))) (cond ((or (any-null? arg2) (equal? arg2 '(list))) ; (append x ()) -> (copy x) (lint-format "perhaps clearer: ~A" caller (lists->string form `(copy ,arg1)))) ((null? arg1) ; (append () x) -> x (lint-format "perhaps ~A" caller (lists->string form arg2))) ((not (pair? arg1))) ((and (pair? arg2) ; (append (list x y) '(z)) -> (list x y z) or extensions thereof (or (eq? (car arg1) 'list) (quoted-undotted-pair? arg1)) (or (eq? (car arg2) 'list) (quoted-undotted-pair? arg2))) (lint-format "perhaps ~A" caller (lists->string form (apply append->list new-args)))) ((and (eq? (car arg1) 'list) ; (append (list x) y) -> (cons x y) (pair? (cdr arg1)) (null? (cddr arg1))) (lint-format "perhaps ~A" caller (lists->string form `(cons ,(cadr arg1) ,arg2)))) ((and (eq? (car arg1) 'list) ; (append (list x y) z) -> (cons x (cons y z)) (pair? (cdr arg1)) (pair? (cddr arg1)) (null? (cdddr arg1))) (lint-format "perhaps ~A" caller (lists->string form `(cons ,(cadr arg1) (cons ,(caddr arg1) ,arg2))))) ;; not sure about this: reports the un-qq'd form (and never happens) ((and (eq? (car arg1) #_{list}) (not (qq-tree? arg1))) (set! last-checker-line-number -1) (sp-append caller 'append `(append ,(un_{list} arg1) ,arg2) env)) ((and (eq? (car arg1) 'vector->list) (pair? arg2) (eq? (car arg2) 'vector->list)) (lint-format "perhaps ~A" caller (lists->string form `(vector->list (append ,(cadr arg1) ,(cadr arg2)))))) ((and (eq? (car arg1) 'quote) ; (append '(x) y) -> (cons 'x y) (pair? (cadr arg1)) (null? (cdadr arg1))) (lint-format "perhaps ~A" caller (lists->string form (if (or (symbol? (caadr arg1)) (pair? (caadr arg1))) `(cons ',(caadr arg1) ,arg2) `(cons ,(caadr arg1) ,arg2))))) ((not (equal? (cdr form) new-args)) ; (append () '(1 2) 1) -> (append '(1 2) 1) (lint-format "perhaps ~A" caller (lists->string form `(append ,@new-args))))))) (else (cond ((every? (lambda (item) (and (pair? item) (or (eq? (car item) 'list) (quoted-undotted-pair? item)))) new-args) ; (append '(1) (append '(2) '(3)) '(4)) -> (list 1 2 3 4) (lint-format "perhaps ~A" caller (lists->string form (apply append->list new-args)))) ((and (pair? (car new-args)) ; (append (list x) y (list z)) -> (cons x (append y (list z)))? (eq? (caar new-args) 'list) (null? (cddar new-args))) (lint-format "perhaps ~A" caller (lists->string form `(cons ,(cadar new-args) (append ,@(cdr new-args)))))) ((let ((n-1 (list-ref new-args (- len1 2)))) (and (pair? n-1) (eq? (car n-1) 'list) (pair? (cdr n-1)) (null? (cddr n-1)))) ; (append x (list y) z) -> (append x (cons y z)) (lint-format "perhaps ~A" caller (lists->string form `(append ,@(copy new-args (make-list (- len1 2))) (cons ,(cadr (list-ref new-args (- len1 2))) ,(list-ref new-args (- len1 1))))))) ((not (equal? (cdr form) new-args)) ; (append x y (append)) -> (append x y ()) (lint-format "perhaps ~A" caller (lists->string form `(append ,@new-args))))))) (if (and (= made-suggestion suggestion) (not (equal? (cdr form) new-args))) (lint-format "perhaps ~A" caller (lists->string form `(append ,@new-args))))))))) (hash-special 'append sp-append)) ;; ---------------- apply ---------------- (let () (define (sp-apply caller head form env) (when (pair? (cdr form)) (let ((len (length form)) (suggestion made-suggestion)) (if (= len 2) ; (apply f) -> (f) (lint-format "perhaps ~A" caller (lists->string form (list (cadr form)))) (if (not (or (<= len 2) ; it might be (apply)... (symbol? (cadr form)) (applicable? (cadr form)))) (lint-format "~S is not applicable: ~A" caller (cadr form) (truncated-list->string form)) (let ((happy #f) (f (cadr form))) (unless (or (<= len 2) (any-macro? f env) (eq? f 'macroexpand)) ; handled specially (syntactic, not a macro) (when (and (symbol? f) (not (var-member f env))) (let ((func (symbol->value f *e*))) (if (procedure? func) (let ((ary (arity func))) (when (pair? ary) ; (apply real? 1 3 rest) (if (> (- len 3) (cdr ary)) ; last apply arg might be var=() (lint-format "too many arguments for ~A: ~A" caller f form)) (if (and (= len 3) (= (car ary) 1) (= (cdr ary) 1)) ; (apply car x) -> (car (car x)) (lint-format "perhaps ~A" caller (lists->string form `(,f (car ,(caddr form))))))))))) (let ((last-arg (form (- len 1)))) (if (and (not (list? last-arg)) (code-constant? last-arg)) ; (apply + 1) (lint-format "last argument should be a list: ~A" caller (truncated-list->string form)) (if (= len 3) (let ((args (caddr form)) (cdr-args (and (pair? (caddr form)) (cdaddr form)))) (if (identity? f) ; (apply (lambda (x) x) y) -> (car y) (lint-format "perhaps (assuming ~A is a list of one element) ~A" caller args (lists->string form `(car ,args))) (if (simple-lambda? f) ; (apply (lambda (x) (f x)) y) -> (f (car y)) (lint-format "perhaps (assuming ~A is a list of one element) ~A" caller args (lists->string form (tree-subst (list 'car args) (caadr f) (caddr f)))))) (cond ((eq? f 'list) ; (apply list x) -> x? (lint-format "perhaps ~A" caller (lists->string form args))) ((any-null? args) ; (apply f ()) -> (f) (lint-format "perhaps ~A" caller (lists->string form (list f)))) ((or (not (pair? args)) (case (car args) ((list) ; (apply f (list a b)) -> (f a b) (lint-format "perhaps ~A" caller (lists->string form `(,f ,@cdr-args)))) ((quote) ; (apply eq? '(a b)) -> (eq? 'a 'b) (and (= suggestion made-suggestion) (lint-format "perhaps ~A" caller (lists->string form `(,f ,@(distribute-quote (car cdr-args))))))) ((cons) ; (apply f (cons a b)) -> (apply f a b) (lint-format "perhaps ~A" caller (lists->string form (if (and (pair? (cadr cdr-args)) (eq? (caadr cdr-args) 'cons)) `(apply ,f ,(car cdr-args) ,@(cdadr cdr-args)) `(apply ,f ,@cdr-args))))) ((append) ; (apply f (append (list ...)...)) -> (apply f ... ...) (and (pair? (car cdr-args)) (eq? (caar cdr-args) 'list) (lint-format "perhaps ~A" caller (lists->string form `(apply ,f ,@(cdar cdr-args) ,(if (null? (cdr cdr-args)) () (if (null? (cddr cdr-args)) (cadr cdr-args) `(append ,@(cdr cdr-args))))))))) ((reverse reverse!) ; (apply vector (reverse x)) -> (reverse (apply vector x)) (and (memq f '(string vector int-vector float-vector)) (lint-format "perhaps ~A" caller (lists->string form `(reverse (apply ,f ,(car cdr-args))))))) ((make-list) ; (apply string (make-list x y)) -> (make-string x y) (if (memq f '(string vector)) (lint-format "perhaps ~A" caller (lists->string form `(,(if (eq? f 'string) 'make-string 'make-vector) ,@cdr-args))))) ((map) (case f ((string-append) ; (apply string-append (map ...)) (if (eq? (car cdr-args) 'symbol->string) (lint-format "perhaps ~A" caller ; (apply string-append (map symbol->string ...)) (lists->string form `(format #f "~{~A~}" ,(cadr cdr-args)))) (if (simple-lambda? (car cdr-args)) (let ((body (caddar cdr-args))) (if (and (pair? body) (eq? (car body) 'string-append) (= (length body) 3) (or (and (string? (cadr body)) (eq? (caddr body) (caadar cdr-args))) (and (string? (caddr body)) (eq? (cadr body) (caadar cdr-args))))) (let ((str (string-append "~{" (if (string? (cadr body)) (cadr body) "~A") (if (string? (caddr body)) (caddr body) "~A") "~}"))) (lint-format "perhaps ~A" caller (lists->string form `(format #f ,str ,(cadr cdr-args)))))))))) ((string) ; (apply string (map char-downcase x)) -> (string-downcase (apply string x)) (if (memq (car cdr-args) '(char-upcase char-downcase)) (lint-format "perhaps, assuming ~A is a list, ~A" caller (cadr cdr-args) (lists->string form `(,(if (eq? (car cdr-args) 'char-upcase) 'string-upcase 'string-downcase) (apply string ,(cadr cdr-args))))))) ((append) ; (apply append (map vector->list args)) -> (vector->list (apply append args)) (and (eq? (car cdr-args) 'vector->list) (lint-format "perhaps ~A" caller (lists->string form `(vector->list (apply append ,@(cdr cdr-args))))))) (else #f))) ;; (apply append (map...)) is very common but changing it to ;; (map (lambda (x) (apply values (f x))) ...) from (apply append (map f ...)) ;; is not an obvious win. The code is more complicated, and currently apply values ;; copies its args (as do apply and append -- how many copies are there here?! ;; need to check for only one apply values ((#_{list}) ; (apply f `(,x ,@z)) -> (apply f x z) (let ((last-arg (list-ref args (- (length args) 1)))) (if (and (pair? last-arg) (eq? (car last-arg) #_{apply_values}) (= (tree-count1 #_{apply_values} args 0) 1)) (lint-format "perhaps ~A" caller (lists->string form `(apply ,f ,@(copy args (make-list (- (length args) 2)) 1) ,(cadr last-arg)))) (if (not (tree-member #_{apply_values} cdr-args)) (lint-format "perhaps ~A" caller (lists->string form `(,f ,@(un_{list} cdr-args))))))))))))) (begin ; len > 3 (when (and (pair? last-arg) (eq? (car last-arg) 'list) ; (apply f y z (list a b)) -> (f y z a b) (not (hash-table-ref syntaces f))) ; also not any-macro I presume (lint-format "perhaps ~A" caller (lists->string form (append (copy (cdr form) (make-list (- len 2))) (cdr last-arg))))) ;; can't cleanly go from (apply write o p) to (write o (car p)) since p can be () (when (and (not happy) (not (memq f '(define define* define-macro define-macro* define-bacro define-bacro* lambda lambda*))) (any-null? last-arg)) ; (apply f ... ()) -> (f ...) (lint-format "perhaps ~A" caller (lists->string form `(,f ,@(copy (cddr form) (make-list (- len 3))))))))))))))) (if (and (= suggestion made-suggestion) (symbol? (cadr form))) (let ((ary (arg-arity (cadr form) env))) (if (and (pair? ary) ; (apply make-string tcnt initializer) -> (make-string tcnt (car initializer)) (= (cdr ary) (- len 2))) (lint-format "perhaps ~A" caller (lists->string form `(,@(copy (cdr form) (make-list (- len 2))) (car ,(list-ref form (- len 1)))))))))))) (hash-special 'apply sp-apply)) ;; ---------------- format ---------------- (let () (define (sp-format caller head form env) (if (< (length form) 3) (begin (cond ((< (length form) 2) ; (format) (lint-format "~A has too few arguments: ~A" caller head (truncated-list->string form))) ((and (pair? (cadr form)) ; (format (format #f str)) (eq? (caadr form) 'format)) (lint-format "redundant format: ~A" caller (truncated-list->string form))) ((and (code-constant? (cadr form)) ; (format 1) (not (string? (cadr form)))) (lint-format "format with one argument takes a string: ~A" caller (truncated-list->string form))) ((and (string? (cadr form)) ; (format "str") -> str (eq? head 'format) ; not snd-display (not (char-position #\~ (cadr form)))) (lint-format "perhaps ~A" caller (lists->string form (cadr form))))) env) (let ((control-string ((if (string? (cadr form)) cadr caddr) form)) (args ((if (string? (cadr form)) cddr cdddr) form))) (define count-directives (let ((format-control-char (let ((chars (make-vector 256 #f))) (for-each (lambda (c) (vector-set! chars (char->integer c) #t)) '(#\A #\S #\C #\F #\E #\G #\O #\D #\B #\X #\P #\N #\W #\, #\{ #\} #\* #\@ #\a #\s #\c #\f #\e #\g #\o #\d #\b #\x #\p #\n #\w #\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9)) chars))) (lambda (str caller form) (let ((curlys 0) (dirs 0) (pos (char-position #\~ str))) (when pos (do ((len (length str)) (tilde-time #t) (i (+ pos 1) (+ i 1))) ((>= i len) (if tilde-time ; (format #f "asdf~") (lint-format "~A control string ends in tilde: ~A" caller head (truncated-list->string form)))) (if tilde-time (let ((c (string-ref str i))) (when (and (= curlys 0) (not (memv c '(#\~ #\T #\t #\& #\% #\^ #\| #\newline #\}))) ; ~* consumes an arg (not (call-with-exit (lambda (return) (do ((k i (+ k 1))) ((= k len) #f) ;; this can be confused by pad chars in ~T (if (not (or (char-numeric? (string-ref str k)) (char=? (string-ref str k) #\,))) (return (char-ci=? (string-ref str k) #\t)))))))) ;; the possibilities are endless, so I'll stick to the simplest (if (not (vector-ref format-control-char (char->integer c))) ; (format #f "~H" 1) (lint-format "unrecognized format directive: ~C in ~S, ~S" caller c str form)) (set! dirs (+ dirs 1)) ;; ~n so try to figure out how many args are needed (this is not complete) (when (char-ci=? c #\n) (let ((j (+ i 1))) (if (>= j len) ; (format p "~A~A" x) (lint-format "missing format directive: ~S" caller str) (begin ;; if ,n -- add another, if then not T, add another (cond ((not (char=? (string-ref str j) #\,))) ((>= (+ j 1) len) (lint-format "missing format directive: ~S" caller str)) ((char-ci=? (string-ref str (+ j 1)) #\n) (set! dirs (+ dirs 1)) (set! j (+ j 2))) ((char-numeric? (string-ref str (+ j 1))) (set! j (+ j 2))) (else (set! j (+ j 1)))) (if (>= j len) (lint-format "missing format directive: ~S" caller str) (if (not (char-ci=? (string-ref str j) #\t)) (set! dirs (+ dirs 1))))))))) (set! tilde-time #f) (case c ((#\{) (set! curlys (+ curlys 1))) ((#\}) (set! curlys (- curlys 1))) ((#\^ #\|) (if (zero? curlys) ; (format #f "~^") (lint-format "~A has ~~~C outside ~~{~~}?" caller str c)))) (if (and (< (+ i 2) len) (member (substring str i (+ i 3)) '("%~&" "^~^" "|~|" "&~&" "\n~\n") string=?)) (lint-format "~A in ~A could be ~A" caller ; (format #f "~%~&") (substring str (- i 1) (+ i 3)) str (substring str (- i 1) (+ i 1))))) (begin (set! pos (char-position #\~ str i)) (if pos (begin (set! tilde-time #t) (set! i pos)) (set! i len)))))) (if (not (= curlys 0)) ; (format #f "~{~A" 1) (lint-format "~A has ~D unmatched ~A~A: ~A" caller head (abs curlys) (if (positive? curlys) "{" "}") (if (> curlys 1) "s" "") (truncated-list->string form))) dirs)))) (when (and (eq? head 'format) (string? (cadr form))) ; (format "s") (lint-format "please include the port argument to format, perhaps ~A" caller `(format () ,@(cdr form)))) (if (any? all-caps-warning (cdr form)) (lint-format "There's no need to shout: ~A" caller (truncated-list->string form))) (if (and (eq? (cadr form) 't) ; (format t " ") (not (var-member 't env))) (lint-format "'t in ~A should probably be #t" caller (truncated-list->string form))) (if (not (string? control-string)) (if (not (proper-list? args)) (lint-format "~S looks suspicious" caller form)) (let ((ndirs (count-directives control-string caller form)) (nargs (if (list? args) (length args) 0))) (let ((pos (char-position #\null control-string))) (if (and pos (< pos (length control-string))) ; (format #f "~a\x00b" x) (lint-format "#\\null in a format control string will confuse both lint and format: ~S in ~A" caller control-string form))) (if (not (or (= ndirs nargs) (tree-memq 'values form))) (lint-format "~A has ~A arguments: ~A" ; (format #f "~nT" 1 2) caller head (if (> ndirs nargs) "too few" "too many") (truncated-list->string form)) (if (and (not (cadr form)) ; (format #f "123") (zero? ndirs) (not (char-position #\~ control-string))) (lint-format "~A could be ~S, (format is a no-op here)" caller (truncated-list->string form) (caddr form)))))) (when (pair? args) (for-each (lambda (a) (if (pair? a) (case (car a) ((number->string) (if (null? (cddr a)) ; (format #f "~A" (number->string x)) (lint-format "format arg ~A could be ~A" caller a (cadr a)) (if (and (pair? (cddr a)) (integer? (caddr a)) (memv (caddr a) '(2 8 10 16))) (if (= (caddr a) 10) (lint-format "format arg ~A could be ~A" caller a (cadr a)) (lint-format "format arg ~A could use the format directive ~~~A and change the argument to ~A" caller a (case (caddr a) ((2) "B") ((8) "O") (else "X")) (cadr a)))))) ((symbol->string) ; (format #f "~A" (symbol->string 'x)) (lint-format "format arg ~A could be ~A" caller a (cadr a))) ((make-string) ; (format #f "~A" (make-string len c)) (lint-format "format arg ~A could use the format directive ~~NC and change the argument to ... ~A ~A ..." caller a (cadr a) (if (char? (caddr a)) (format #f "~W" (caddr a)) (caddr a)))) ((string-append) ; (format #f "~A" (string-append x y)) (lint-format "format appends strings, so ~A seems wasteful" caller a))))) args))))) (hash-special 'format sp-format)) ;; ---------------- error ---------------- (let () (define (sp-error caller head form env) (if (any? all-caps-warning (cdr form)) (lint-format "There's no need to shout: ~A" caller (truncated-list->string form)))) (hash-special 'error sp-error)) ;; ---------------- sort! ---------------- (let () (define (sp-sort caller head form env) (if (= (length form) 3) (let ((func (caddr form))) (if (memq func '(= eq? eqv? equal? string=? char=? string-ci=? char-ci=?)) (lint-format "sort! with ~A may hang: ~A" caller func (truncated-list->string form)) (if (symbol? func) (let ((sig (procedure-signature (symbol->value func)))) (if (and (pair? sig) (not (eq? 'boolean? (car sig))) (not (and (pair? (car sig)) (memq 'boolean? (car sig))))) ; (sort! x abs) (lint-format "~A is a questionable sort! function" caller func)))))))) (hash-special 'sort! sp-sort)) ;; ---------------- substring ---------------- (let () (define (sp-substring caller head form env) (if (every? code-constant? (cdr form)) (catch #t (lambda () (let ((val (eval form))) ; (substring "abracadabra" 2 7) -> "racad" (lint-format "perhaps ~A -> ~S" caller (truncated-list->string form) val))) (lambda (type info) (lint-format "~A -> ~A" caller (truncated-list->string form) (apply format #f info)))) (let ((str (cadr form))) (when (string? str) ; (substring "++++++" 0 2) -> (make-string 2 #\+) (let ((len (length str))) (when (and (> len 0) (string=? str (make-string len (string-ref str 0)))) (lint-format "perhaps ~A" caller (lists->string form (let ((chars (if (null? (cddr form)) len (if (pair? (cdddr form)) (if (eqv? (caddr form) 0) (cadddr form) `(- ,(cadddr form) ,(caddr form))) `(- ,len ,(caddr form)))))) `(make-string ,chars ,(string-ref str 0)))))))) (when (pair? (cddr form)) (when (null? (cdddr form)) (when (and (pair? str) ; (substring (substring x 1) 2) -> (substring x 3) (eq? (car str) 'substring) (null? (cdddr str))) (lint-format "perhaps ~A" caller (lists->string form (if (and (integer? (caddr form)) (integer? (caddr str))) `(substring ,(cadr str) ,(+ (caddr str) (caddr form))) `(substring ,(cadr str) (+ ,(caddr str) ,(caddr form))))))) ;; end indices are complicated -- since this rarely happens, not worth the trouble (if (eqv? (caddr form) 0) ; (substring x 0) -> (copy x) (lint-format "perhaps clearer: ~A" caller (lists->string form `(copy ,str))))) (when (pair? (cdddr form)) (let ((end (cadddr form))) (if (equal? (caddr form) end) ; (substring x (+ y 1) (+ y 1)) is "" (lint-format "leaving aside errors, ~A is \"\"" caller form)) (when (and (pair? str) (eqv? (caddr form) 0) (eq? (car str) 'string-append) (= (length str) 3)) (let ((in-arg2 (caddr str))) (if (and (pair? in-arg2) ; (substring (string-append str (make-string len #\space)) 0 len) -> (copy str (make-string len #\space)) (eq? (car in-arg2) 'make-string) (equal? (cadddr form) (cadr in-arg2))) (lint-format "perhaps ~A" caller (lists->string form `(copy ,(cadr str) (make-string ,(cadddr form) ,(caddr in-arg2)))))))) (if (and (pair? end) ; (substring x start (length|string-length x)) -> (substring s start) (memq (car end) '(string-length length)) (equal? (cadr end) str)) (lint-format "perhaps ~A" caller (lists->string form (copy form (make-list 3)))) (when (symbol? end) (let ((v (var-member end env))) (if (and (var? v) (equal? `(string-length ,str) (var-initial-value v)) (not (any? (lambda (p) (set!? p env)) (var-history v)))) ; if len is still (string-length x), (substring x 1 len) -> (substring x 1) (lint-format "perhaps, if ~A is still ~A, ~A" caller end (var-initial-value v) (lists->string form (copy form (make-list 3)))))))))))))) (hash-special 'substring sp-substring)) ;; ---------------- list, *vector ---------------- (let ((seq-maker (lambda (seq) (cdr (assq seq '((list . make-list) (vector . make-vector) (float-vector . make-float-vector) (int-vector . make-int-vector) (byte-vector . make-byte-vector)))))) (seq-default (lambda (seq) (cdr (assq seq '((list . #f) (vector . #) (float-vector . 0.0) (int-vector . 0) (byte-vector . 0))))))) (define (sp-list caller head form env) (let ((len (length form)) (val (and (pair? (cdr form)) (cadr form)))) (when (and (> len 4) (every? (lambda (a) (equal? a val)) (cddr form))) (if (code-constant? val) ; (vector 12 12 12 12 12 12) -> (make-vector 6 12) (lint-format "perhaps ~A~A" caller (lists->string form (if (eqv? (seq-default head) val) `(,(seq-maker head) ,(- len 1)) `(,(seq-maker head) ,(- len 1) ,val))) (if (and (sequence? val) (not (null? val))) (format #f "~%~NCor wrap (copy ~S) in a function and call that ~A times" lint-left-margin #\space val (- len 1)) "")) (if (pair? val) (if (or (side-effect? val env) (hash-table-ref makers (car val))) (if (> (tree-leaves val) 3) ;; I think we need to laboriously repeat the function call here: ;; (let ((a 1) (b 2) (c 3)) ;; (define f (let ((ctr 0)) (lambda (x y z) (set! ctr (+ ctr 1)) (+ x y ctr (* 2 z))))) ;; (list (f a b c) (f a b c) (f a b c) (f a b c)) ;; so (apply list (make-list 4 (_1_))) or variants thereof fail ;; (eval (append '(list) (make-list 4 '(_1_)))) ;; works, but it's too ugly. (lint-format "perhaps ~A" caller (lists->string form `(let ((_1_ (lambda () ,val))) (,head ,@(make-list (- len 1) '(_1_))))))) ;; if seq copy else (lint-format "perhaps ~A" caller ; (vector (car x) (car x) (car x) (car x)) -> (make-vector 4 (car x)) (lists->string form `(,(seq-maker head) ,(- len 1) ,val))))))))) (for-each (lambda (f) (hash-special f sp-list)) '(list vector int-vector float-vector byte-vector))) ;; ---------------- list-tail ---------------- (let () (define (sp-list-tail caller head form env) (if (= (length form) 3) (if (eqv? (caddr form) 0) ; (list-tail x 0) -> x (lint-format "perhaps ~A" caller (lists->string form (cadr form))) (if (and (pair? (cadr form)) (eq? (caadr form) 'list-tail)) (lint-format "perhaps ~A" caller ; (list-tail (list-tail x 1) 2) -> (list-tail x 3) (lists->string form (if (and (integer? (caddr form)) (integer? (caddr (cadr form)))) `(list-tail ,(cadadr form) ,(+ (caddr (cadr form)) (caddr form))) `(list-tail ,(cadadr form) (+ ,(caddr (cadr form)) ,(caddr form)))))))))) (hash-special 'list-tail sp-list-tail)) ;; ---------------- eq? ---------------- (let () (define (sp-eq? caller head form env) (if (< (length form) 3) ; (eq?) (lint-format "eq? needs 2 arguments: ~A" caller (truncated-list->string form)) (let* ((arg1 (cadr form)) (arg2 (caddr form)) (eq1 (eqf arg1 env)) (eq2 (eqf arg2 env)) (specific-op (and (eq? (cadr eq1) (cadr eq2)) (not (memq (cadr eq1) '(eqv? equal?))) (cadr eq1)))) (eval-constant-expression caller form) (if (or (eq? (car eq1) 'equal?) (eq? (car eq2) 'equal?)) ; (eq? #(0) #(0)) (lint-format "eq? should be equal?~A in ~S" caller (if specific-op (format #f " or ~A" specific-op) "") form) (if (or (eq? (car eq1) 'eqv?) (eq? (car eq2) 'eqv?)) ; (eq? x 1.5) (lint-format "eq? should be eqv?~A in ~S" caller (if specific-op (format #f " or ~A" specific-op) "") form))) (let ((expr 'unset)) (cond ((or (not arg1) ; (eq? #f x) -> (not x) (quoted-not? arg1)) (set! expr (simplify-boolean `(not ,arg2) () () env))) ((or (not arg2) ; (eq? x #f) -> (not x) (quoted-not? arg2)) (set! expr (simplify-boolean `(not ,arg1) () () env))) ((and (any-null? arg1) ; (eq? () x) -> (null? x) (not (code-constant? arg2))) (set! expr (or (equal? arg2 '(list)) ; (eq? () (list)) -> #t `(null? ,arg2)))) ((and (any-null? arg2) ; (eq? x ()) -> (null? x) (not (code-constant? arg1))) (set! expr (or (equal? arg1 '(list)) `(null? ,arg1)))) ((and (eq? arg1 #t) ; (eq? #t ) -> boolean-expr (pair? arg2) (eq? (return-type (car arg2) env) 'boolean?)) (set! expr arg2)) ((and (eq? arg2 #t) ; (eq? #t) -> boolean-expr (pair? arg1) (eq? (return-type (car arg1) env) 'boolean?)) (set! expr arg1))) (if (not (eq? expr 'unset)) ; (eq? x '()) -> (null? x) (lint-format "perhaps ~A" caller (lists->string form expr))))))) (hash-special 'eq? sp-eq?)) ;; ---------------- eqv? equal? ---------------- (let () (define (sp-eqv? caller head form env) (define (useless-copy? a) (and (pair? a) (memq (car a) '(copy string-copy vector-copy list-copy)) (null? (cddr a)))) (if (< (length form) 3) (lint-format "~A needs 2 arguments: ~A" caller head (truncated-list->string form)) (let* ((arg1 (cadr form)) (arg2 (caddr form)) (eq1 (eqf arg1 env)) (eq2 (eqf arg2 env)) (specific-op (and (eq? (cadr eq1) (cadr eq2)) (not (memq (cadr eq1) '(eq? eqv? equal?))) (cadr eq1)))) (eval-constant-expression caller form) (if (or (useless-copy? arg1) (useless-copy? arg2)) ; (equal? (vector-copy #(a b c)) #(a b c)) -> (equal? #(a b c) #(a b c)) (lint-format "perhaps ~A" caller (lists->string form `(,head ,(if (useless-copy? arg1) (cadr arg1) arg1) ,(if (useless-copy? arg2) (cadr arg2) arg2))))) (if (and (string? (cadr form)) (= (length (cadr form)) 1)) (let ((s2 (caddr form))) (if (pair? s2) (if (eq? (car s2) 'string) ; (equal? "[" (string r)) -> (char=? #\[ r) (lint-format "perhaps ~A" caller (lists->string form `(char=? ,(string-ref (cadr form) 0) ,(cadr s2)))) (if (and (eq? (car s2) 'substring) (= (length s2) 4) ; (equal? "^" (substring s 0 1)) -> (char=? #\^ (string-ref s 0)) (eqv? (list-ref s2 2) 0) (eqv? (list-ref s2 3) 1)) (lint-format "perhaps ~A" caller (lists->string form `(char=? ,(string-ref (cadr form) 0) (string-ref ,(cadr s2) 0))))))))) (if (and (not (eq? (cadr eq1) (cadr eq2))) ; (eqv? ":" (string-ref s 0)) (memq (cadr eq1) '(char=? string=?)) (memq (cadr eq2) '(char=? string=?))) (lint-format "this can't be right: ~A" caller form)) ;; (equal? a (list b)) and equivalents happens a lot, but is the extra consing worse than ;; (and (pair? a) (equal? (car a) b) (null? (cdr a))) -- code readability seems more important here (cond ((or (eq? (car eq1) 'equal?) (eq? (car eq2) 'equal?)) (if (eq? head 'equal?) (if specific-op ; equal? could be string=? in (equal? (string x) (string-append y z)) (lint-format "~A could be ~A in ~S" caller head specific-op form)) (lint-format "~A should be equal?~A in ~S" caller head (if specific-op (format #f " or ~A" specific-op) "") form))) ((or (eq? (car eq1) 'eqv?) (eq? (car eq2) 'eqv?)) (if (eq? head 'eqv?) (if specific-op ; (eqv? (integer->char x) #\null) (lint-format "~A could be ~A in ~S" caller head specific-op form)) (lint-format "~A ~A be eqv?~A in ~S" caller head (if (eq? head 'eq?) "should" "could") (if specific-op (format #f " or ~A" specific-op) "") form))) ((not (or (eq? (car eq1) 'eq?) (eq? (car eq2) 'eq?)))) ((not (and arg1 arg2)) ; (eqv? x #f) -> (not x) (lint-format "~A could be not: ~A" caller head (lists->string form `(not ,(or arg1 arg2))))) ((or (any-null? arg1) (any-null? arg2)) ; (eqv? x ()) -> (null? x) (lint-format "~A could be null?: ~A" caller head (lists->string form (if (any-null? arg1) `(null? ,arg2) `(null? ,arg1))))) (else ; (eqv? x 'a) (lint-format "~A could be eq?~A in ~S" caller head (if specific-op (format #f " or ~A" specific-op) "") form)))))) (hash-special 'eqv? sp-eqv?) (hash-special 'equal? sp-eqv?)) ;; ---------------- map for-each ---------------- (let () (define (sp-map caller head form env) (let* ((len (length form)) (args (- len 2))) (if (< len 3) ; (map (lambda (v) (vector-ref v 0))) (lint-format "~A missing argument~A in: ~A" caller head (if (= len 2) "" "s") (truncated-list->string form)) (let ((func (cadr form)) (ary #f)) ;; if zero or one args, the map/for-each is either a no-op or a function call (if (any? any-null? (cddr form)) ; (map abs ()) (lint-format "this ~A has no effect (null arg)" caller (truncated-list->string form)) (if (and (not (tree-memq 'values form)) ; e.g. flatten in s7.html (any? (lambda (p) (and (pair? p) (case (car p) ((quote) (and (pair? (cadr p)) (null? (cdadr p)))) ((list) (null? (cddr p))) ((cons) (any-null? (caddr p))) (else #f)))) (cddr form))) ; (for-each display (list a)) -> (display a) (lint-format "perhaps ~A" caller (lists->string form (let ((args (map (lambda (a) (if (pair? a) (case (car a) ((list cons) (cadr a)) ; slightly inaccurate ((quote) (caadr a)) (else `(,a 0))) ; not car -- might not be a list `(,a 0))) ; but still not right -- arg might be a hash-table (cddr form)))) (if (eq? head 'for-each) `(,(cadr form) ,@args) `(list (,(cadr form) ,@args)))))))) ;; 2 happens a lot, but introduces evaluation order quibbles ;; we used to check for values if list arg -- got 4 hits! (if (and (symbol? func) (procedure? (symbol->value func *e*))) (begin (set! ary (arity (symbol->value func *e*))) (if (and (eq? head 'map) (hash-table-ref no-side-effect-functions func) (= len 3) (pair? (caddr form)) (or (eq? (caaddr form) 'quote) (and (eq? (caaddr form) 'list) (every? code-constant? (cdaddr form))))) (catch #t (lambda () ; (map symbol->string '(a b c d)) -> '("a" "b" "c" "d") (let ((val (eval form))) (lint-format "perhaps ~A" caller (lists->string form (list 'quote val))))) (lambda args #f)))) (when (and (pair? func) (memq (car func) '(lambda lambda*))) (if (pair? (cadr func)) (let ((arglen (length (cadr func)))) (set! ary (if (eq? (car func) 'lambda) (if (negative? arglen) (cons (abs arglen) 512000) (cons arglen arglen)) (cons 0 (if (or (negative? arglen) (memq :rest (cadr func))) 512000 arglen)))))) (if (= len 3) (let ((body (cddr func))) ; (map (lambda (a) #f) x) -> (make-list (abs (length x)) #f) (if (and (null? (cdr body)) (code-constant? (car body))) (lint-format "perhaps ~A" caller (lists->string form `(make-list (abs (length ,(caddr form))) ,(car body))))))))) (if (pair? ary) (if (< args (car ary)) ; (map (lambda (a b) a) '(1 2)) (lint-format "~A has too few arguments in: ~A" caller head (truncated-list->string form)) (if (> args (cdr ary)) ; (map abs '(1 2) '(3 4)) (lint-format "~A has too many arguments in: ~A" caller head (truncated-list->string form))))) (for-each (lambda (obj) (if (and (pair? obj) (memq (car obj) '(vector->list string->list let->list))) (lint-format* caller ; (vector->list #(1 2)) could be simplified to: #(1 2) (truncated-list->string obj) " could be simplified to: " (truncated-list->string (cadr obj)) (string-append " ; (" (symbol->string head) " accepts non-list sequences)")))) (cddr form)) (when (eq? head 'map) (when (and (memq func '(char-downcase char-upcase)) (pair? (caddr form)) ; (map char-downcase (string->list str)) -> (string->list (string-downcase str)) (eq? (caaddr form) 'string->list)) (lint-format "perhaps ~A" caller (lists->string form `(string->list (,(if (eq? func 'char-upcase) 'string-upcase 'string-downcase) ,(cadr (caddr form))))))) (when (identity? func) ; to check f here as var is more work ; (map (lambda (x) x) lst) -> lst (lint-format "perhaps ~A" caller (lists->string form (caddr form))))) (let ((arg1 (caddr form))) (when (and (pair? arg1) (memq (car arg1) '(cdr cddr cdddr cddddr list-tail)) (pair? (cdr arg1)) (pair? (cadr arg1)) (memq (caadr arg1) '(string->list vector->list))) (let ((string-case (eq? (caadr arg1) 'string->list)) (len-diff (if (eq? (car arg1) 'list-tail) (caddr arg1) (cdr-count (car arg1))))) ; (cdr (vector->list v)) -> (make-shared-vector v (- (length v) 1) 1) (lint-format "~A accepts ~A arguments, so perhaps ~A" caller head (if string-case 'string 'vector) (lists->string arg1 (if string-case `(substring ,(cadadr arg1) ,len-diff) `(make-shared-vector ,(cadadr arg1) (- (length ,(cadadr arg1)) ,len-diff) ,len-diff))))))) (when (and (eq? head 'for-each) (pair? (cadr form)) (eq? (caadr form) 'lambda) (pair? (cdadr form)) ; (for-each (lambda (x) (+ (abs x) 1)) lst) (not (any? (lambda (x) (side-effect? x env)) (cddadr form)))) (lint-format "pointless for-each: ~A" caller (truncated-list->string form))) (when (= args 1) (let ((seq (caddr form))) (when (pair? seq) (case (car seq) ((cons) ; (for-each display (cons msgs " ")) (if (and (pair? (cdr seq)) (pair? (cddr seq)) (code-constant? (caddr seq))) (lint-format "~A will ignore ~S in ~A" caller head (caddr seq) seq))) ((map) (when (= (length seq) 3) ;; a toss-up -- probably faster to combine funcs here, and easier to read? ;; but only if first arg is only used once in first func, and everything is simple (one-line or symbol) (let* ((seq-func (cadr seq)) (arg-name (find-unique-name func seq-func))) (if (symbol? func) ; (map f (map g h)) -> (map (lambda (_1_) (f (g _1_))) h) -- dubious (if (symbol? seq-func) (lint-format "perhaps ~A" caller (lists->string form `(,head (lambda (,arg-name) (,func (,seq-func ,arg-name))) ,(caddr seq)))) (if (simple-lambda? seq-func) ;; (map f (map (lambda (x) (g x)) h)) -> (map (lambda (x) (f (g x))) h) (lint-format "perhaps ~A" caller (lists->string form `(,head (lambda (,arg-name) (,func ,(tree-subst arg-name (caadr seq-func) (caddr seq-func)))) ,(caddr seq)))))) (if (less-simple-lambda? func) (if (symbol? seq-func) ;; (map (lambda (x) (f x)) (map g h)) -> (map (lambda (x) (f (g x))) h) (lint-format "perhaps ~A" caller (lists->string form `(,head (lambda (,arg-name) ,@(tree-subst (list seq-func arg-name) (caadr func) (cddr func))) ,(caddr seq)))) (if (simple-lambda? seq-func) ;; (map (lambda (x) (f x)) (map (lambda (x) (g x)) h)) -> (map (lambda (x) (f (g x))) h) (lint-format "perhaps ~A" caller (lists->string form `(,head (lambda (,arg-name) ,@(tree-subst (tree-subst arg-name (caadr seq-func) (caddr seq-func)) (caadr func) (cddr func))) ,(caddr seq))))))))))))) ;; repetitive code... (when (eq? head 'for-each) ; args = 1 above ; (for-each display (list a)) -> (format () "~A" a) (let ((func (cadr form))) (if (memq func '(display write newline write-char write-string)) (lint-format "perhaps ~A" caller (if (and (pair? seq) (memq (car seq) '(list quote))) (let ((op (if (eq? func 'write) "~S" "~A")) (len (- (length seq) 1))) (lists->string form `(format () ,(do ((i 0 (+ i 1)) (str "")) ((= i len) str) (set! str (string-append str op))) ,@(cdr seq)))) (let ((op (if (eq? func 'write) "~{~S~}" "~{~A~}"))) (lists->string form `(format () ,op ,seq))))) (when (and (pair? func) (eq? (car func) 'lambda)) (let ((body (cddr func))) (let ((op (write-port (car body))) (larg (and (pair? (cadr func)) (caadr func)))) (when (and (symbol? larg) (null? (cdadr func)) ; just one arg (one sequence to for-each) for now (every? (lambda (x) (and (pair? x) (memq (car x) '(display write newline write-char write-string)) (or (eq? (car x) 'newline) (eq? (cadr x) larg) (string? (cadr x)) (eqv? (cadr x) #\space) (and (pair? (cadr x)) (pair? (cdadr x)) (eq? (caadr x) 'number->string) (eq? (cadadr x) larg))) (eq? (write-port x) op))) body)) ;; (for-each (lambda (x) (display x) (write-char #\space)) msg) ;; (for-each (lambda (elt) (display elt)) lst) (let ((ctrl-string "") (arg-ctr 0)) (define* (gather-format str (arg :unset)) (set! ctrl-string (string-append ctrl-string str))) (for-each (lambda (d) (if (or (memq larg d) (and (pair? (cdr d)) (pair? (cadr d)) (memq larg (cadr d)))) (set! arg-ctr (+ arg-ctr 1))) (gather-format (display->format d))) body) (when (= arg-ctr 1) ; (for-each (lambda (x) (display x)) args) -> (format () "~{~A~}" args) (lint-format "perhaps ~A" caller (lists->string form `(format ,op ,(string-append "~{" ctrl-string "~}") ,seq))))))))) ))))))))) (for-each (lambda (f) (hash-special f sp-map)) '(map for-each))) ;; ---------------- magnitude ---------------- (let () (define (sp-magnitude caller head form env) (if (and (= (length form) 2) ; (magnitude 2/3) (memq (->lint-type (cadr form)) '(integer? rational? real?))) (lint-format "perhaps use abs here: ~A" caller form))) (hash-special 'magnitude sp-magnitude)) ;; ---------------- open-input-file open-output-file ---------------- (let () (define (sp-open-input-file caller head form env) (if (and (pair? (cdr form)) (pair? (cddr form)) (string? (caddr form)) ; (open-output-file x "fb+") (not (memv (string-ref (caddr form) 0) '(#\r #\w #\a)))) ; b + then e m c x if gcc (lint-format "unexpected mode: ~A" caller form))) (for-each (lambda (f) (hash-special f sp-open-input-file)) '(open-input-file open-output-file))) ;; ---------------- values ---------------- (let () (define (sp-values caller head form env) (cond ((member 'values (cdr form) (lambda (a b) (and (pair? b) ; (values 2 (values 3 4) 5) -> (values 2 3 4 5) (eq? (car b) 'values)))) (lint-format "perhaps ~A" caller (lists->string form `(values ,@(splice-if (lambda (x) (eq? x 'values)) (cdr form)))))) ((= (length form) 2) (lint-format "perhaps ~A" caller (lists->string form ; (values ({list} 'x ({apply_values} y))) -> (cons 'x y) (if (and (pair? (cadr form)) (eq? (caadr form) #_{list}) (not (qq-tree? (cadr form)))) (un_{list} (cadr form)) (cadr form))))) ((and (assq #_{list} (cdr form)) (not (any? (lambda (a) (and (pair? a) (memq (car a) '(#_{list} #_{apply_values})) (qq-tree? a))) (cdr form)))) (lint-format "perhaps ~A" caller (lists->string form ; (values ({list} 'x y) a) -> (values (list 'x y) a) `(values ,@(map (lambda (a) (if (and (pair? a) (eq? (car a) #_{list})) (un_{list} a) a)) (cdr form)))))))) (hash-special 'values sp-values)) ;; ---------------- call-with-values ---------------- (let () (define (sp-call/values caller head form env) ; (call/values p c) -> (c (p)) (when (= (length form) 3) (let ((producer (cadr form)) (consumer (caddr form))) (let* ((produced-values (mv-range producer env)) (consumed-values (and produced-values (or (and (symbol? consumer) (arg-arity consumer env)) (and (pair? consumer) (eq? (car consumer) 'lambda) (pair? (cadr consumer)) (let ((len (length (cadr consumer)))) (if (negative? len) (cons (abs len) (cdr (arity +))) ; 536870912 = MAX_ARITY in s7.c (cons len len)))))))) (if (and consumed-values (or (> (car consumed-values) (car produced-values)) (< (cdr consumed-values) (cadr produced-values)))) (let ((clen ((if (> (car consumed-values) (car produced-values)) car cdr) consumed-values))) (lint-format "call-with-values consumer ~A wants ~D value~P, but producer ~A returns ~A" caller (truncated-list->string consumer) clen clen (truncated-list->string producer) ((if (> (car consumed-values) (car produced-values)) car cadr) produced-values))))) (cond ((not (pair? producer)) ; (call-with-values log c) (if (and (symbol? producer) (not (memq (return-type producer ()) '(#t #f values)))) (lint-format "~A does not return multiple values" caller producer) (lint-format "perhaps ~A" caller (lists->string form `(,consumer (,producer)))))) ((not (eq? (car producer) 'lambda)) ; (call-with-values (eval p env) (eval c env)) -> ((eval c env) ((eval p env))) (lint-format "perhaps ~A" caller (lists->string form `(,consumer (,producer))))) ((pair? (cadr producer)) ; (call-with-values (lambda (x) 0) list) (lint-format "~A requires too many arguments" caller (truncated-list->string producer))) ((symbol? (cadr producer)) ; (call-with-values (lambda x 0) list) (lint-format "~A's parameter ~A will always be ()" caller (truncated-list->string producer) (cadr producer))) ((and (pair? (cddr producer)) ; (call-with-values (lambda () (read-char p)) cons) (null? (cdddr producer))) ; (call-with-values (lambda () (values 1 2 3)) list) -> (list 1 2 3) (let ((body (caddr producer))) (if (or (code-constant? body) (and (pair? body) (symbol? (car body)) (not (memq (return-type (car body) ()) '(#t #f values))))) (lint-format "~A does not return multiple values" caller body) (lint-format "perhaps ~A" caller (lists->string form (if (and (pair? body) (eq? (car body) 'values)) `(,consumer ,@(cdr body)) `(,consumer ,body))))))) (else (lint-format "perhaps ~A" caller (lists->string form `(,consumer (,producer))))))))) (hash-special 'call-with-values sp-call/values)) ;; ---------------- multiple-value-bind ---------------- (let () (define (sp-mvb caller head form env) (when (>= (length form) 4) (let ((vars (cadr form)) (producer (caddr form)) (body (cdddr form))) (if (null? vars) (lint-format "this multiple-value-bind is pointless; perhaps ~A" caller (lists->string form (if (side-effect? producer env) `(begin ,producer ,@body) (if (null? (cdr body)) (car body) `(begin ,@body))))) (unless (symbol? vars) ; else any number of values is ok (let ((vals (mv-range producer env)) ; (multiple-value-bind (a b) (values 1 2 3) b) (args (length vars))) (if (and (pair? vals) (not (<= (car vals) args (cadr vals)))) (lint-format "multiple-value-bind wants ~D values, but ~A returns ~A" caller args (truncated-list->string producer) ((if (< args (car vals)) car cadr) vals))) (if (and (pair? producer) ; (multiple-value-bind (a b) (f) b) -> ((lambda (a b) b) (f)) (symbol? (car producer)) (not (memq (return-type (car producer) ()) '(#t #f values)))) (lint-format "~A does not return multiple values" caller (car producer)) (lint-format "perhaps ~A" caller (lists->string form (if (and (null? (cdr body)) (pair? (car body)) (equal? vars (cdar body)) (defined? (caar body)) (equal? (arity (symbol->value (caar body))) (cons args args))) `(,(caar body) ,producer) `((lambda ,vars ,@body) ,producer))))))))))) (hash-special 'multiple-value-bind sp-mvb)) ;; ---------------- let-values ---------------- (let () (define (sp-let-values caller head form env) (if (and (pair? (cdr form)) (pair? (cadr form))) (if (null? (cdadr form)) ; just one set of vars (let ((call (caadr form))) (if (and (pair? call) (pair? (cdr call))) (lint-format "perhaps ~A" caller ; (let-values (((x) (values 1))) x) -> ((lambda (x) x) (values 1)) (lists->string form `((lambda ,(car call) ,@(cddr form)) ,(cadr call)))))) (if (every? pair? (cadr form)) (lint-format "perhaps ~A" caller ; (let-values (((x) (values 1)) ((y) (values 2))) (list x y)) ... (lists->string form `(with-let (apply sublet (curlet) (list ,@(map (lambda (v) `((lambda ,(car v) (values ,@(map (lambda (name) (values (symbol->keyword name) name)) (args->proper-list (car v))))) ,(cadr v))) (cadr form)))) ,@(cddr form)))))))) (hash-special 'let-values sp-let-values)) ;; ---------------- let*-values ---------------- (hash-special 'let*-values (lambda (caller head form env) (if (and (pair? (cdr form)) (pair? (cadr form))) (lint-format "perhaps ~A" caller (lists->string form ; (let*-values (((a) (f x))) (+ a b)) -> (let ((a (f x))) (+ a b)) (let loop ((var-data (cadr form))) (let ((v (car var-data))) (if (and (pair? (car v)) ; just one var (null? (cdar v))) (if (null? (cdr var-data)) `(let ((,(caar v) ,(cadr v))) ,@(cddr form)) `(let ((,(caar v) ,(cadr v))) ,(loop (cdr var-data)))) (if (null? (cdr var-data)) `((lambda ,(car v) ,@(cddr form)) ,(cadr v)) `((lambda ,(car v) ,(loop (cdr var-data))) ,(cadr v))))))))))) ;; ---------------- define-values ---------------- (hash-special 'define-values (lambda (caller head form env) (when (pair? (cdr form)) (if (null? (cadr form)) (lint-format "~A is pointless" caller (truncated-list->string form)) (when (pair? (cddr form)) (lint-format "perhaps ~A" caller ; (define-values (x y) (values 3 2)) -> (varlet (curlet) ((lambda (x y) (curlet)) (values 3 2))) (cond ((symbol? (cadr form)) (lists->string form `(define ,(cadr form) (list ,(caddr form))))) ((and (pair? (cadr form)) (null? (cdadr form))) (lists->string form `(define ,(caadr form) ,(caddr form)))) (else (let-temporarily ((target-line-length 120)) (truncated-lists->string form `(varlet (curlet) ((lambda ,(cadr form) (curlet)) ,(caddr form))))))))))))) ;; ---------------- eval ---------------- (let () (define (sp-eval caller head form env) (case (length form) ((2) (let ((arg (cadr form))) (if (not (pair? arg)) (if (not (symbol? arg)) ; (eval 32) (lint-format "this eval is pointless; perhaps ~A" caller (lists->string form arg))) (case (car arg) ((quote) ; (eval 'x) (lint-format "perhaps ~A" caller (lists->string form (cadr arg)))) ((string->symbol) ; (eval (string->symbol "x")) -> x (if (string? (cadr arg)) (lint-format "perhaps ~A" caller (lists->string form (string->symbol (cadr arg)))))) ((with-input-from-string call-with-input-string) (if (and (pair? (cdr arg)) ; (eval (call-with-input-string port read)) -> (eval-string port) (pair? (cddr arg)) (eq? (caddr arg) 'read)) (lint-format "perhaps ~A" caller (lists->string form `(eval-string ,(cadr arg)))))) ((read) (if (and (= (length arg) 2) ; (eval (read (open-input-string expr))) -> (eval-string expr) (pair? (cadr arg)) (eq? (caadr arg) 'open-input-string)) (lint-format "perhaps ~A" caller (lists->string form `(eval-string ,(cadadr arg)))))) ((list) (if (every? (lambda (p) ; (eval (list '* 2 x)) -> (* 2 (eval x)) (or (symbol? p) (code-constant? p))) (cdr arg)) (lint-format "perhaps ~A" caller (lists->string form (map (lambda (p) (if (and (pair? p) (eq? (car p) 'quote)) (cadr p) (if (code-constant? p) p (list 'eval p)))) (cdr arg)))))))))) ((3) (let ((arg (cadr form)) (e (caddr form))) (if (and (pair? arg) (eq? (car arg) 'quote)) (lint-format "perhaps ~A" caller ; (eval 'x env) -> (env 'x) (lists->string form (if (symbol? (cadr arg)) `(,e ,arg) `(with-let ,e ,@(unbegin (cadr arg))))))))))) (hash-special 'eval sp-eval)) ;; ---------------- fill! etc ---------------- (let () (define (sp-fill! caller head form env) (if (= (length form) 5) (check-start-and-end caller head (cdddr form) form env))) (for-each (lambda (f) (hash-special f sp-fill!)) '(fill! string-fill! list-fill! vector-fill!))) ;; ---------------- write-string ---------------- (let () (define (sp-write-string caller head form env) (cond ((= (length form) 4) (check-start-and-end caller 'write-string (cddr form) form env)) ((and (pair? (cdr form)) (pair? (cddr form)) (pair? (caddr form)) (eq? (caaddr form) 'current-output-port)) (lint-format "(current-output-port) is the default port for ~A: ~A" caller head form)) ((equal? (cadr form) (string #\newline)) (lint-format "perhaps ~A" caller (lists->string form `(newline ,@(cddr form))))))) (hash-special 'write-string sp-write-string)) ;; ---------------- read-line ---------------- (let () (define (sp-read-line caller head form env) (if (and (= (length form) 3) (code-constant? (caddr form)) (not (boolean? (caddr form)))) ; (read-line in-port 'concat) (lint-format "the third argument should be boolean (#f=default, #t=include trailing newline): ~A" caller form) (if (and (pair? (cdr form)) (pair? (cadr form)) (eq? (caadr form) 'current-input-port)) (lint-format "(current-input-port) is the default port for ~A: ~A" caller head form)))) (hash-special 'read-line sp-read-line)) ;; ---------------- string-length ---------------- (let () (define (sp-string-length caller head form env) (when (= (length form) 2) (if (string? (cadr form)) ; (string-length "asdf") -> 4 (lint-format "perhaps ~A -> ~A" caller (truncated-list->string form) (string-length (cadr form))) (if (and (pair? (cadr form)) ; (string-length (make-string 3)) -> 3 (eq? (caadr form) 'make-string)) (lint-format "perhaps ~A" caller (lists->string form (cadadr form))))))) (hash-special 'string-length sp-string-length)) ;; ---------------- vector-length ---------------- (let () (define (sp-vector-length caller head form env) (when (= (length form) 2) (if (vector? (cadr form)) (lint-format "perhaps ~A -> ~A" caller (truncated-list->string form) (vector-length (cadr form))) (let ((arg (cadr form))) (if (pair? arg) (if (eq? (car arg) 'make-vector) ; (vector-length (make-vector 10)) -> 10 (lint-format "perhaps ~A" caller (lists->string form (cadr arg))) (if (memq (car arg) '(copy vector-copy)) (lint-format "perhaps ~A" caller (lists->string form ; (vector-length (vector-copy arr start end)) -> (- end start) (if (null? (cddr arg)) `(vector-length ,(cadr arg)) (if (eq? (car arg) 'copy) `(vector-length ,(caddr arg)) (let ((start (caddr arg)) (end (if (null? (cdddr arg)) `(vector-length ,(cadr arg)) (cadddr arg)))) `(- ,end ,start))))))))))))) (hash-special 'vector-length sp-vector-length)) ;; ---------------- dynamic-wind ---------------- (let () (define (sp-dw caller head form env) (when (= (length form) 4) (let ((init (cadr form)) (body (caddr form)) (end (cadddr form)) (empty 0)) ;; (equal? init end) as a mistake doesn't seem to happen (when (and (pair? init) (eq? (car init) 'lambda)) (if (not (null? (cadr init))) (lint-format "dynamic-wind init function should be a thunk: ~A" caller init)) (if (pair? (cddr init)) (let ((last-expr (list-ref init (- (length init) 1)))) (if (not (pair? last-expr)) (if (null? (cdddr init)) (set! empty 1)) (unless (side-effect? last-expr env) (if (null? (cdddr init)) (set! empty 1)) ; (dynamic-wind (lambda () (s7-version)) (lambda () (list)) (lambda () #f)) (lint-format "this could be omitted: ~A in ~A" caller last-expr init)))))) (if (and (pair? body) (eq? (car body) 'lambda)) (if (not (null? (cadr body))) (lint-format "dynamic-wind body function should be a thunk: ~A" caller body)) (set! empty 3)) ; don't try to access body below (when (and (pair? end) (eq? (car end) 'lambda)) (if (not (null? (cadr end))) (lint-format "dynamic-wind end function should be a thunk: ~A" caller end)) (if (pair? (cddr end)) (let ((last-expr (list-ref end (- (length end) 1)))) (if (not (pair? last-expr)) (if (null? (cdddr end)) (set! empty (+ empty 1))) (unless (side-effect? last-expr env) ; or if no side-effects in any (also in init) (if (null? (cdddr end)) (set! empty (+ empty 1))) (lint-format "this could be omitted: ~A in ~A" caller last-expr end))) (if (= empty 2) ; (dynamic-wind (lambda () #f) (lambda () #()) (lambda () #f)) -> #() (lint-format "this dynamic-wind is pointless, ~A" caller (lists->string form (if (null? (cdddr body)) (caddr body) `(begin ,@(cddr body)))))))))))) (hash-special 'dynamic-wind sp-dw)) ;; ---------------- *s7* ---------------- (hash-special '*s7* (let ((s7-fields (let ((h (make-hash-table))) (for-each (lambda (f) (hash-table-set! h f #t)) '(print-length safety cpu-time heap-size free-heap-size gc-freed max-string-length max-list-length max-vector-length max-vector-dimensions default-hash-table-length initial-string-port-length gc-protected-objects file-names rootlet-size c-types stack-top stack-size stacktrace-defaults max-stack-size stack catches exits float-format-precision bignum-precision default-rationalize-error default-random-state morally-equal-float-epsilon hash-table-float-epsilon undefined-identifier-warnings gc-stats symbol-table-locked? c-objects history-size profile-info)) h))) (lambda (caller head form env) (if (= (length form) 2) (let ((arg (cadr form))) (if (and (pair? arg) (eq? (car arg) 'quote) (symbol? (cadr arg)) ; (*s7* 'vector-print-length) (not (hash-table-ref s7-fields (cadr arg)))) (lint-format "unknown *s7* field: ~A" caller arg))))))) ;; ---------------- throw ---------------- (hash-special 'throw (lambda (caller head form env) (if (pair? (cdr form)) (let* ((tag (cadr form)) (eq (eqf tag env))) (if (not (member eq '((eq? eq?) (#t #t)))) (lint-format "~A tag ~S is unreliable (catch uses eq? to match tags)" caller 'throw tag)))))) ;; ---------------- make-hash-table ---------------- (hash-special 'make-hash-table (lambda (caller head form env) (if (= (length form) 3) (let ((func (caddr form))) (if (and (symbol? func) ; (make-hash-table eq? symbol-hash) (not (memq func '(eq? eqv? equal? morally-equal? char=? char-ci=? string=? string-ci=? =)))) (lint-format "make-hash-table function, ~A, is not a hash function" caller func)))))) ;; ---------------- deprecated funcs ---------------- (let ((deprecated-ops '((global-environment . rootlet) (current-environment . curlet) (make-procedure-with-setter . dilambda) (procedure-with-setter? . dilambda?) (make-random-state . random-state)))) (define (sp-deprecate caller head form env) ; (make-random-state 123 432) (lint-format "~A is deprecated; use ~A" caller head (cond ((assq head deprecated-ops) => cdr)))) (for-each (lambda (op) (hash-special (car op) sp-deprecate)) deprecated-ops)) ;; ---------------- eq null eqv equal ---------------- (let () (define (sp-null caller head form env) (if (not (var-member head env)) ; (if (null (cdr x)) 0) (lint-format "misspelled '~A? in ~A?" caller head form))) (for-each (lambda (f) (hash-special f sp-null)) '(null eq eqv equal))) ; (null (cdr...)) ;; ---------------- set-car set-cdr list-set vector-set string-set ---------------- (let () (define (sp-set caller head form env) (if (not (var-member head env)) ; (list-set x 1 y) (lint-format "misspelled '~A! in ~A?" caller head form))) (for-each (lambda (f) (hash-special f sp-set)) '(set-car set-cdr list-set vector-set string-set))) ;; set and sort occur a million times, but aren't interesting ;; ---------------- string-index ---------------- (let () (define (sp-string-index caller head form env) (if (and (pair? (cdr form)) (pair? (cddr form)) (not (var-member 'string-index env)) (or (char? (caddr form)) (let ((sig (arg-signature (caddr form) env))) (and (pair? sig) (eq? (car sig) 'char?))))) (lint-format "perhaps ~A" caller ; (string-index path #\/) -> (char-position #\/ path) (lists->string form `(char-position ,(caddr form) ,(cadr form) ,@(cdddr form)))))) (hash-special 'string-index sp-string-index)) ;; ---------------- cons* ---------------- (let () (define (sp-cons* caller head form env) (unless (var-member 'cons env) (case (length form) ((2) (lint-format "perhaps ~A" caller (lists->string form (cadr form)))) ((3) (lint-format "perhaps ~A" caller (lists->string form ; cons* x y) -> (cons x y) (if (any-null? (caddr form)) `(list ,(cadr form)) `(cons ,@(cdr form)))))) ((4) (lint-format "perhaps ~A" caller (lists->string form ; (cons* (symbol->string v) " | " (w)) -> (cons (symbol->string v) (cons " | " (w))) (if (any-null? (cadddr form)) `(list ,(cadr form) ,(caddr form)) `(cons ,(cadr form) (cons ,@(cddr form)))))))))) (hash-special 'cons* sp-cons*)) ;; ---------------- the-environment etc ---------------- (let ((other-names '((the-environment . curlet) (interaction-environment . curlet) (system-global-environment . rootlet) (user-global-environment . rootlet) (user-initial-environment . rootlet) (procedure-environment . funclet) (environment? . let?) (environment-set! . let-set!) (environment-ref . let-ref) (fluid-let . let-temporarily) (unquote-splicing apply values ...) (bitwise-and . logand) (bitwise-ior . logior) (bitwise-xor . logxor) (bitwise-not . lognot) (bit-and . logand) (bit-or . logior) (bit-xor . logxor) (bit-not . lognot) (arithmetic-shift . ash) (vector-for-each . for-each) (string-for-each . for-each) (list-copy . copy) (bytevector? . byte-vector?) (bytevector . byte-vector) (make-bytevector . make-byte-vector) (bytevector-u8-ref . byte-vector-ref) (bytevector-u8-set! . byte-vector-set!) (bytevector-length . length) (write-bytevector . write-string) (hash-set! . hash-table-set!) ; Guile (hash-ref . hash-table-ref) (hashq-set! . hash-table-set!) (hashq-ref . hash-table-ref) (hashv-set! . hash-table-set!) (hashv-ref . hash-table-ref) (hash-table-get . hash-table-ref) ; Gauche (hash-table-put! . hash-table-set!) (hash-table-num-entries . hash-table-entries) (hashtable? . hash-table?) ; Bigloo (hashtable-size . hash-table-entries) (hashtable-get . hash-table-ref) (hashtable-set! . hash-table-set!) (hashtable-put! . hash-table-set!) (hash-for-each . for-each) (exact-integer? . integer?) (truncate-quotient . quotient) (truncate-remainder . remainder) (floor-remainder . modulo) (read-u8 . read-byte) (write-u8 . write-byte) (write-simple . write) (peek-u8 . peek-char) (u8-ready? . char-ready?) (open-input-bytevector . open-input-string) (open-output-bytevector . open-output-string) (raise . error) (raise-continuable . error)))) (define (sp-other-names caller head form env) (if (not (var-member head env)) (let ((counts (or (hash-table-ref other-names-counts head) 0))) (when (< counts 2) (hash-table-set! other-names-counts head (+ counts 1)) (lint-format "~A is probably ~A in s7" caller head (cdr (assq head other-names))))))) (for-each (lambda (f) (hash-special (car f) sp-other-names)) other-names)) (hash-special '1+ (lambda (caller head form env) (if (not (var-member '1+ env)) (lint-format "perhaps ~A" caller (lists->string form `(+ ,(cadr form) 1)))))) (let () (define (sp-1- caller head form env) (if (not (var-member '-1+ env)) (lint-format "perhaps ~A" caller (lists->string form `(- ,(cadr form) 1))))) (hash-special '-1+ sp-1-) (hash-special '1- sp-1-)) ;; ---------------- push! pop! ---------------- (hash-special 'push! (lambda (caller head form env) ; not predefined (if (= (length form) 3) (set-set (caddr form) caller form env)))) (hash-special 'pop! (lambda (caller head form env) ; also not predefined (if (= (length form) 2) (set-set (cadr form) caller form env)))) ;; ---------------- receive ---------------- (hash-special 'receive (lambda (caller head form env) ; this definition comes from Guile (if (and (> (length form) 3) (not (var-member 'receive env))) ((hash-table-ref special-case-table 'call-with-values) caller 'call-with-values `(call-with-values (lambda () ,(caddr form)) (lambda ,(cadr form) ,@(cdddr form))) env)))) ;; ---------------- and=> ---------------- (hash-special 'and=> (lambda (caller head form env) ; (and=> (ref w k) v) -> (cond ((ref w k) => v) (else #f)) (when (and (= (length form) 3) (not (var-member 'and=> env))) (lint-format "perhaps ~A" caller (lists->string form `(cond (,(cadr form) => ,(caddr form)) (else #f))))))) ;; ---------------- and-let* ---------------- (let () (define (sp-and-let caller head form env) (when (and (> (length form) 2) (not (var-member 'and-let* env))) (let loop ((bindings (cadr form))) (cond ((pair? bindings) (if (binding-ok? caller 'and-let* (car bindings) env #f) (loop (cdr bindings)))) ((not (null? bindings)) (lint-format "~A variable list is not a proper list? ~S" caller 'and-let* bindings)) ((and (pair? (cadr form)) ; (and-let* ((x (f y))) (abs x)) -> (cond ((f y) => abs)) (null? (cdadr form)) (pair? (cddr form))) (lint-format "perhaps ~A" caller (lists->string form ; (and-let* ((x (f y))) (abs x)) -> (cond ((f y) => abs)) (if (and (null? (cdddr form)) (pair? (caddr form)) (pair? (cdaddr form)) (null? (cddr (caddr form))) (eq? (caaadr form) (cadr (caddr form)))) `(cond (,(cadar (cadr form)) => ,(caaddr form))) `(cond (,(cadar (cadr form)) => (lambda (,(caaadr form)) ,@(cddr form)))))))))))) (hash-special 'and-let* sp-and-let)) special-case-table)) ;; end special-case-functions ;; ---------------------------------------- (define (unused-parameter? x) #t) (define (unused-set-parameter? x) #t) (define (check-args caller head form checkers env max-arity) ;; check for obvious argument type problems ;; caller = overall caller, head = current caller, checkers = proc or list of procs for checking args (define (every-compatible? type1 type2) (if (symbol? type1) (if (symbol? type2) (compatible? type1 type2) (and (pair? type2) ; here everything has to match (compatible? type1 (car type2)) (every-compatible? type1 (cdr type2)))) (and (pair? type1) ; here any match is good (or (compatible? (car type1) type2) (any-compatible? (cdr type1) type2))))) (define (check-checker checker at-end) (if (eq? checker 'integer:real?) (if at-end 'real? 'integer?) (if (eq? checker 'integer:any?) (or at-end 'integer?) checker))) (define (any-checker? types arg) (if (and (symbol? types) (not (eq? types 'values))) ((symbol->value types *e*) arg) (and (pair? types) (or (any-checker? (car types) arg) (any-checker? (cdr types) arg))))) (define (report-arg-trouble caller form head arg-number checker arg uop) (define (prettify-arg-number argn) (if (or (not (= argn 1)) (pair? (cddr form))) (format #f "~D " argn) "")) (when (and (or arg (not (eq? checker 'output-port?))) (not (and (eq? checker 'string?) (pair? arg) (eq? (car arg) 'format) (not (null? (cadr arg))))) ; other case involves a symbol that is an output-port (not (and (pair? arg) (eq? (car arg) 'length)))) ; same for length (let ((op (if (and (eq? checker 'real?) (eq? uop 'number?)) 'complex? uop))) (if (and (pair? op) (member checker op any-compatible?)) (if (and *report-sloppy-assoc* (not (var-member :catch env))) (lint-format* caller ; (round (char-position #\a "asb")) (string-append "in " (truncated-list->string form) ", ") (string-append (symbol->string head) "'s argument " (prettify-arg-number arg-number)) (string-append "should be " (prettify-checker-unq checker) ", ") (string-append "but " (truncated-list->string arg) " might also be " (object->string (car (remove-if (lambda (o) (any-compatible? checker o)) op)))))) (lint-format* caller ; (string-ref (char-position #\a "asb") 1) (string-append "in " (truncated-list->string form) ", ") (string-append (symbol->string head) "'s argument " (prettify-arg-number arg-number)) (string-append "should be " (prettify-checker-unq checker) ", ") (string-append "but " (truncated-list->string arg) " is " (prettify-checker op))))))) (when *report-func-as-arg-arity-mismatch* (let ((v (var-member head env))) (when (and (var? v) (memq (var-ftype v) '(define define* lambda lambda*)) (zero? (var-set v)) ; perhaps this needs to wait for report-usage? (pair? (var-arglist v))) (let ((source (var-initial-value v))) (when (and (pair? source) (pair? (cdr source)) (pair? (cddr source))) (let ((vhead (cddr source)) (head-arglist (var-arglist v)) (arg-number 1)) (when (pair? vhead) (for-each (lambda (arg) ;; only check func if head is var-member and has procedure-source (var-[initial-]value?) ;; and arg has known arity, and check only if arg(par) is car, not (for example) cadr of apply (let ((ari (if (symbol? arg) (arg-arity arg env) (and (pair? arg) (eq? (car arg) 'lambda) (let ((len (length (cadr arg)))) (and (integer? len) (cons (abs len) (if (negative? len) 500000 len))))))) (par (and (> (length head-arglist) (- arg-number 1)) (list-ref head-arglist (- arg-number 1))))) (when (and (symbol? par) (pair? ari) (or (> (car ari) 0) (< (cdr ari) 20))) ;; fwalk below needs to be smart about tree walking so that ;; it does not confuse (c) in (lambda (c)...) with a call on the function c. ;; check only if current parameter name is not shadowed (let fwalk ((sym par) (tree vhead)) (when (pair? tree) (if (eq? (car tree) sym) (let ((args (- (length tree) 1))) (if (> (car ari) args) (lint-format "~A's parameter ~A is passed ~A and called ~A, but ~A needs ~A argument~P" caller head par (truncated-list->string arg) (truncated-list->string tree) (truncated-list->string arg) (car ari) (car ari)) (if (> args (cdr ari)) (lint-format "~A's parameter ~A is passed ~A and called ~A, but ~A takes only ~A argument~P" caller head par (truncated-list->string arg) (truncated-list->string tree) (truncated-list->string arg) (cdr ari) (cdr ari))))) (case (car tree) ((let let*) (if (and (pair? (cdr tree)) (pair? (cddr tree))) (let ((vs ((if (symbol? (cadr tree)) caddr cadr) tree))) (if (not (any? (lambda (a) (or (not (pair? a)) (eq? sym (car a)))) vs)) (fwalk sym ((if (symbol? (cadr tree)) cdddr cddr) tree)))))) ((do letrec letrec*) (if (and (pair? (cdr tree)) (pair? (cddr tree)) (not (any? (lambda (a) (or (not (pair? a)) (eq? sym (car a)))) (cadr tree)))) (fwalk sym (cddr tree)))) ((lambda lambda*) (if (and (pair? (cdr tree)) (pair? (cddr tree)) (not (any? (lambda (a) (eq? sym a)) (args->proper-list (cadr tree))))) (fwalk sym (cddr tree)))) ((define define-constant) (if (and (not (eq? sym (cadr tree))) (pair? (cadr tree)) (not (any? (lambda (a) (eq? sym a)) (args->proper-list (cdadr tree))))) (fwalk sym (cddr tree)))) ((define* define-macro define-macro* define-expansion define-bacro define-bacro*) (if (and (pair? (cdr tree)) (pair? (cddr tree)) (not (any? (lambda (a) (eq? sym a)) (args->proper-list (cdadr tree))))) (fwalk sym (cddr tree)))) ((quote) #f) ((case) (if (and (pair? (cdr tree)) (pair? (cddr tree))) (for-each (lambda (c) (fwalk sym (cdr c))) (cddr tree)))) (else (if (pair? (car tree)) (fwalk sym (car tree))) (if (pair? (cdr tree)) (for-each (lambda (p) (fwalk sym p)) (cdr tree)))))))))) (set! arg-number (+ arg-number 1))) (cdr form))))))))) (when (pair? checkers) (let ((arg-number 1) (flen (- (length form) 1))) (call-with-exit (lambda (done) (for-each (lambda (arg) (let ((checker (check-checker (if (pair? checkers) (car checkers) checkers) (= arg-number flen)))) ;; check-checker only fixes up :at-end cases (define (check-arg expr) (unless (symbol? expr) (let ((op (->lint-type expr))) (if (not (or (memq op '(#f #t values)) (every-compatible? checker op))) (report-arg-trouble caller form head arg-number checker expr op))))) (define (check-cond-arg expr) (unless (symbol? expr) (let ((op (->lint-type expr))) (when (pair? op) (set! op (remove 'boolean? op)) ; this is for cond test, no result -- returns test if not #f, so it can't be #f! (if (null? (cdr op)) (set! op (car op)))) (if (not (or (memq op '(#f #t values)) (every-compatible? checker op))) (report-arg-trouble caller form head arg-number checker expr op))))) ;; special case checker? (if (and (symbol? checker) (not (memq checker '(unused-parameter? unused-set-parameter?))) (not (hash-table-ref built-in-functions checker))) (let ((chk (symbol->value checker))) (if (and (procedure? chk) (equal? (arity chk) '(2 . 2))) (catch #t (lambda () (let ((res (chk form arg-number))) (set! checker #t) (if (symbol? res) (set! checker res) (if (string? res) (lint-format "~A's argument, ~A, should be ~A" caller head arg res))))) (lambda (type info) (set! checker #t)))))) (if (and (pair? arg) (pair? (car arg))) (let ((rtn (return-type (caar arg) env))) (if (memq rtn '(boolean? real? integer? rational? number? complex? float? keyword? symbol? null? char?)) (lint-format* caller ; (cons ((pair? x) 2) y) (string-append (symbol->string head) "'s argument ") (string-append (truncated-list->string arg) " looks odd: ") (string-append (object->string (caar arg)) " returns " (symbol->string rtn)) " which is not applicable")))) (when (or (pair? checker) (symbol? checker)) ; otherwise ignore type check on this argument (#t -> anything goes) (if arg (if (eq? checker 'unused-parameter?) (lint-format* caller ; (define (f5 a . b) a) (f5 1 2) (string-append (symbol->string head) "'s parameter " (number->string arg-number)) " is not used, but a value is passed: " (truncated-list->string arg)) (if (eq? checker 'unused-set-parameter?) (lint-format* caller ; (define (f21 x y) (set! x 3) (+ y 1)) (f21 (+ z 1) z) (string-append (symbol->string head) "'s parameter " (number->string arg-number)) "'s value is not used, but a value is passed: " (truncated-list->string arg))))) (if (not (pair? arg)) (let ((val (cond ((not (symbol? arg)) arg) ((constant? arg) (symbol->value arg)) ((and (hash-table-ref built-in-functions arg) (not (var-member :with-let env)) (not (var-member arg env))) (symbol->value arg *e*)) (else arg)))) (if (not (or (and (symbol? val) (not (keyword? val))) (any-checker? checker val))) (let ((op (->lint-type val))) (unless (memq op '(#f #t values)) (report-arg-trouble caller form head arg-number checker arg op))))) (case (car arg) ((quote) ; '1 -> 1 (let ((op (if (pair? (cadr arg)) 'list? (if (symbol? (cadr arg)) 'symbol? (->lint-type (cadr arg)))))) ;; arg is quoted expression (if (not (or (memq op '(#f #t values)) (every-compatible? checker op))) (report-arg-trouble caller form head arg-number checker arg op)))) ;; arg is an expression ((begin let let* letrec letrec* with-let) (check-arg (and (pair? (cdr arg)) (list-ref arg (- (length arg) 1))))) ((if) (if (and (pair? (cdr arg)) (pair? (cddr arg))) (let ((t (caddr arg)) (f (if (pair? (cdddr arg)) (cadddr arg)))) (check-arg t) (when (and f (not (symbol? f))) (check-arg f))))) ((dynamic-wind catch) (if (= (length arg) 4) (let ((f (caddr arg))) (if (and (pair? f) (eq? (car f) 'lambda)) (let ((len (length f))) (if (> len 2) (check-arg (list-ref f (- len 1))))))))) ((do) (if (and (pair? (cdr arg)) (pair? (cddr arg))) (let ((end+res (caddr arg))) (check-arg (if (pair? (cdr end+res)) (list-ref end+res (- (length end+res) 1)) ()))))) ((case) (for-each (lambda (clause) (if (and (pair? clause) (pair? (cdr clause)) (not (eq? (cadr clause) '=>))) (check-arg (list-ref clause (- (length clause) 1))))) (cddr arg))) ((cond) (for-each (lambda (clause) (if (pair? clause) (if (pair? (cdr clause)) (if (not (eq? (cadr clause) '=>)) (check-arg (list-ref clause (- (length clause) 1)))) (check-cond-arg (car clause))))) (cdr arg))) ((call/cc call-with-exit call-with-current-continuation) ;; find func in body (as car of list), check its arg as return value (when (and (pair? (cdr arg)) (pair? (cadr arg)) (eq? (caadr arg) 'lambda)) (let ((f (cdadr arg))) (when (and (pair? f) (pair? (car f)) (symbol? (caar f)) (null? (cdar f))) (define c-walk (let ((rtn (caar f))) (lambda (tree) (if (pair? tree) (if (eq? (car tree) rtn) (check-arg (if (null? (cdr tree)) () (cadr tree))) (begin (c-walk (car tree)) (for-each (lambda (x) (if (pair? x) (c-walk x))) (cdr tree)))))))) (for-each c-walk (cdr f)))))) ((values) (cond ((not (positive? (length arg)))) ((null? (cdr arg)) ; # (if (not (any-checker? checker #)) (report-arg-trouble caller form head arg-number checker arg 'unspecified?))) ((null? (cddr arg)) (check-arg (cadr arg))) (else (for-each (lambda (expr rest) (check-arg expr) (set! arg-number (+ arg-number 1)) (if (> arg-number max-arity) (done)) (if (list? checkers) (if (null? (cdr checkers)) (done) (set! checkers (cdr checkers))))) (cdr arg) (cddr arg)) (check-arg (list-ref arg (- (length arg) 1)))))) (else (let ((op (return-type (car arg) env))) (let ((v (var-member (car arg) env))) (if (and (var? v) (not (memq form (var-history v)))) (set! (var-history v) (cons form (var-history v))))) ;; checker is arg-type, op is expression type (can also be a pair) (if (and (not (memq op '(#f #t values))) (not (memq checker '(unused-parameter? unused-set-parameter?))) (or (not (every-compatible? checker op)) (and (just-constants? arg env) ; try to eval the arg (catch #t (lambda () (not (any-checker? checker (eval arg)))) (lambda ignore-catch-error-args #f))))) (report-arg-trouble caller form head arg-number checker arg op))))))) (if (list? checkers) (if (null? (cdr checkers)) (done) (set! checkers (cdr checkers))) (if (memq checker '(unused-parameter? unused-set-parameter?)) (set! checker #t))) (set! arg-number (+ arg-number 1)) (if (> arg-number max-arity) (done)))) (cdr form))))))) (define check-unordered-exprs (let ((changers (let ((h (make-hash-table))) (for-each (lambda (s) (hash-table-set! h s #t)) '(set! read read-byte read-char read-line read-string write write-byte write-char write-string format display newline reverse! set-cdr! sort! string-fill! vector-fill! fill! emergency-exit exit error throw)) h))) (lambda (caller form vals env) (define (report-trouble) (lint-format* caller ; (let ((x (read-byte)) (y (read-byte))) (- x y)) (string-append "order of evaluation of " (object->string (car form)) "'s ") (string-append (if (memq (car form) '(let letrec do)) "bindings" "arguments") " is unspecified, ") (string-append "so " (truncated-list->string form) " is trouble"))) (let ((reads ()) (writes ()) (jumps ())) (call-with-exit (lambda (return) (for-each (lambda (p) (when (and (pair? p) (not (var-member (car p) env)) (hash-table-ref changers (car p))) (if (pair? jumps) (return (report-trouble))) (case (car p) ((read read-char read-line read-byte) (if (null? (cdr p)) (if (memq () reads) (return (report-trouble)) (set! reads (cons () reads))) (if (memq (cadr p) reads) (return (report-trouble)) (set! reads (cons (cadr p) reads))))) ((read-string) (if (or (null? (cdr p)) (null? (cddr p))) (if (memq () reads) (return (report-trouble)) (set! reads (cons () reads))) (if (memq (caddr p) reads) (return (report-trouble)) (set! reads (cons (caddr p) reads))))) ((display write write-char write-string write-byte) (if (null? (cddr p)) (if (memq () writes) (return (report-trouble)) (set! writes (cons () writes))) (if (memq (caddr p) writes) (return (report-trouble)) (set! writes (cons (caddr p) writes))))) ((newline) (if (null? (cdr p)) (if (memq () writes) (return (report-trouble)) (set! writes (cons () writes))) (if (memq (cadr p) writes) (return (report-trouble)) (set! writes (cons (cadr p) writes))))) ((format) (if (and (pair? (cdr p)) (not (string? (cadr p))) (cadr p)) ; i.e. not #f (if (memq (cadr p) writes) (return (report-trouble)) (set! writes (cons (cadr p) writes))))) ((fill! string-fill! vector-fill! reverse! sort! set! set-cdr!) ;; here there's trouble if cadr used anywhere -- but we need to check for shadowing (if (any? (lambda (np) (and (not (eq? np p)) (tree-memq (cadr p) np))) vals) (return (report-trouble)))) ((throw error exit emergency-exit) (if (or (pair? reads) ; jumps already checked above (pair? writes)) (return (report-trouble)) (set! jumps (cons p jumps))))))) vals))))))) (define check-call (let ((repeated-args-table (let ((h (make-hash-table))) (for-each (lambda (op) (set! (h op) #t)) '(= / max min < > <= >= - quotient remainder modulo rationalize and or string=? string<=? string>=? string? string-ci=? string-ci<=? string-ci>=? string-ci? char=? char<=? char>=? char? char-ci=? char-ci<=? char-ci>=? char-ci? boolean=? symbol=?)) h)) (repeated-args-table-2 (let ((h (make-hash-table))) (for-each (lambda (op) (set! (h op) #t)) '(= max min < > <= >= and or string=? string<=? string>=? string? string-ci=? string-ci<=? string-ci>=? string-ci? char=? char<=? char>=? char? char-ci=? char-ci<=? char-ci>=? char-ci? boolean=? symbol=?)) h))) (lambda (caller head form env) (let ((data (var-member head env))) (if (and (pair? (cdr form)) (pair? (cddr form)) (any-procedure? head env)) (check-unordered-exprs caller form (cdr form) env)) (if (var? data) (let ((fdata (cdr data))) ;; a local var (when (symbol? (fdata 'ftype)) (let ((args (fdata 'arglist)) (ary (and (not (eq? (fdata 'decl) 'error)) (arity (fdata 'decl)))) (sig (var-signature data))) (when (pair? ary) (let ((req (car ary)) (opt (cdr ary)) (pargs (if (pair? args) (proper-list args) (if (symbol? args) (list args) ())))) (let ((call-args (- (length form) 1))) (if (< call-args req) (begin (for-each (lambda (p) (if (pair? p) (let ((v (var-member (car p) env))) (if (var? v) (let ((vals (let-ref (cdr v) 'values))) (if (pair? vals) (set! call-args (+ call-args -1 (cadr vals))))))))) (cdr form)) (if (not (or (>= call-args req) (tree-memq 'values (cdr form)) (tree-memq 'dilambda (fdata 'initial-value)))) (lint-format "~A needs ~D argument~A: ~A" caller head req (if (> req 1) "s" "") (truncated-list->string form)))) (if (> (- call-args (keywords (cdr form))) opt) ; multiple-values can make this worse, (values)=nothing doesn't apply here (lint-format "~A has too many arguments: ~A" caller head (truncated-list->string form))))) (unless (fdata 'allow-other-keys) (let ((last-was-key #f) (have-keys 0) (warned #f) (rest (if (and (pair? form) (pair? (cdr form))) (cddr form) ()))) (for-each (lambda (arg) (if (and (keyword? arg) (not last-was-key)) ; keyarg might have key value (begin (set! have-keys (+ have-keys 1)) (if (not (member (keyword->symbol arg) pargs (lambda (a b) (eq? a (if (pair? b) (car b) b))))) (lint-format "~A keyword argument ~A (in ~A) does not match any argument in ~S" caller head arg (truncated-list->string form) pargs)) (if (memq arg rest) (lint-format "~W is repeated in ~A" caller arg (cdr form))) (set! last-was-key #t)) (begin (when (and (positive? have-keys) (not last-was-key) (not warned)) (set! warned #t) (lint-format "non-keyword argument ~A follows previous keyword~P" caller arg have-keys)) (set! last-was-key #f))) (if (pair? rest) (set! rest (cdr rest)))) (cdr form)))) (check-args caller head form (if (pair? sig) (cdr sig) ()) env opt) ;; for a complete var-history, we could run through the args here even if no type info ;; also if var passed to macro -- what to do? ;; look for problematic macro expansion (when (memq (fdata 'ftype) '(define-macro define-macro* defmacro defmacro*)) (unless (list? (fdata 'macro-ops)) (let ((syms (list () ()))) (tree-symbol-walk ((if (memq (fdata 'ftype) '(define-macro define-macro*)) cddr cdddr) (fdata 'initial-value)) syms) (varlet fdata 'macro-locals (car syms) 'macro-ops (cadr syms)))) (when (or (pair? (fdata 'macro-locals)) (pair? (fdata 'macro-ops))) (let ((bad-locals ()) (bad-quoted-locals ())) (for-each (lambda (local) (if (tree-unquoted-member local (cdr form)) (set! bad-locals (cons local bad-locals)))) (fdata 'macro-locals)) (when (null? bad-locals) (for-each (lambda (local) (if (tree-member local (cdr form)) (set! bad-quoted-locals (cons local bad-quoted-locals)))) (fdata 'macro-locals))) (let ((bad-ops ())) (for-each (lambda (op) (let ((curf (var-member op env)) (oldf (var-member op (fdata 'env)))) (if (and (not (eq? curf oldf)) (or (pair? (fdata 'env)) (defined? op (rootlet)))) (set! bad-ops (cons op bad-ops))))) (fdata 'macro-ops)) (when (or (pair? bad-locals) (pair? bad-quoted-locals) ;; (define-macro (mac8 b) `(let ((a 12)) (+ (symbol->value ,b) a))) ;; (let ((a 1)) (mac8 'a)) ;; far-fetched! (pair? bad-ops)) (lint-format "possible problematic macro expansion:~% ~A ~A collide with subsequently defined ~A~A~A" caller (truncated-list->string form) (if (or (pair? bad-locals) (pair? bad-ops)) "may" "could conceivably") (if (pair? bad-locals) (format #f "~{'~A~^, ~}" bad-locals) (if (pair? bad-quoted-locals) (format #f "~{'~A~^, ~}" bad-quoted-locals) "")) (if (and (pair? bad-locals) (pair? bad-ops)) ", " "") (if (pair? bad-ops) (format #f "~{~A~^, ~}" bad-ops) ""))))))) ))))) ;; not local var (when (symbol? head) (let ((head-value (symbol->value head *e*))) ; head might be "arity"! (when (or (procedure? head-value) (macro? head-value)) ;; check arg number (let ((ary (arity head-value))) (let ((args (- (length form) 1)) (min-arity (car ary)) (max-arity (cdr ary))) (if (< args min-arity) (lint-format "~A needs ~A~D argument~A: ~A" caller head (if (= min-arity max-arity) "" "at least ") min-arity (if (> min-arity 1) "s" "") (truncated-list->string form)) (if (and (not (procedure-setter head-value)) (> (- args (keywords (cdr form))) max-arity)) (lint-format "~A has too many arguments: ~A" caller head (truncated-list->string form)))) (when (and (procedure? head-value) (pair? (cdr form))) ; there are args (the not-enough-args case is checked above) (if (zero? max-arity) (lint-format "too many arguments: ~A" caller (truncated-list->string form)) (begin (for-each (lambda (arg) (if (pair? arg) (if (negative? (length arg)) (lint-format "missing quote? ~A in ~A" caller arg form) (if (eq? (car arg) 'unquote) (lint-format "stray comma? ~A in ~A" caller arg form))))) (cdr form)) ;; if keywords, check that they are acceptable ;; this only applies to lambda*'s that have been previously loaded (lint doesn't create them) (let ((source (procedure-source head-value))) (if (and (pair? source) (eq? (car source) 'lambda*)) (let ((decls (cadr source))) (if (not (memq :allow-other-keys decls)) (for-each (lambda (arg) (if (and (keyword? arg) (not (eq? arg :rest)) (not (member arg decls (lambda (a b) (eq? (keyword->symbol a) (if (pair? b) (car b) b)))))) (lint-format "~A keyword argument ~A (in ~A) does not match any argument in ~S" caller head arg (truncated-list->string form) decls))) (cdr form)))))) ;; we've already checked for head in the current env above (if (and (or (memq head '(eq? eqv?)) (and (= (length form) 3) (hash-table-ref repeated-args-table head))) (repeated-member? (cdr form) env)) (lint-format "this looks odd: ~A" caller ;; sigh (= a a) could be used to check for non-finite numbers, I suppose, ;; and (/ 0 0) might be deliberate (as in gmp) ;; also (min (random x) (random x)) is not pointless (truncated-list->string form)) (if (and (hash-table-ref repeated-args-table-2 head) (repeated-member? (cdr form) env)) (lint-format "it looks odd to have repeated arguments in ~A" caller (truncated-list->string form)))) (when (memq head '(eq? eqv?)) (define (repeated-member-with-not? lst env) (and (pair? lst) (let ((this-repeats (and (not (and (pair? (car lst)) (side-effect? (car lst) env))) (or (member (list 'not (car lst)) (cdr lst)) (and (pair? (car lst)) (eq? (caar lst) 'not) (= (length (car lst)) 2) (member (cadar lst) (cdr lst))))))) (or this-repeats (repeated-member-with-not? (cdr lst) env))))) (if (repeated-member-with-not? (cdr form) env) (lint-format "this looks odd: ~A" caller (truncated-list->string form)))) ;; now try to check arg types (let ((arg-data (cond ((procedure-signature (symbol->value head *e*)) => cdr) (else #f)))) (if (pair? arg-data) (check-args caller head form arg-data env max-arity)) )))))))))))))) (define (indirect-set? vname func arg1) (case func ((set-car! set-cdr! vector-set! list-set! string-set!) (eq? arg1 vname)) ((set!) (and (pair? arg1) (eq? (car arg1) vname))) (else #f))) (define (env-difference name e1 e2 lst) (if (or (null? e1) (null? e2) (eq? (car e1) (car e2))) (reverse lst) (env-difference name (cdr e1) e2 (if (eq? name (var-name (car e1))) lst (cons (car e1) lst))))) (define report-usage (let ((unwrap-cxr (hash-table '(caar car) '(cadr cdr) '(cddr cdr) '(cdar car) '(caaar caar car) '(caadr cadr cdr) '(caddr cddr cdr) '(cdddr cddr cdr) '(cdaar caar car) '(cddar cdar car) '(cadar cadr car) '(cdadr cadr cdr) '(cadddr cdddr cddr cdr) '(cddddr cdddr cddr cdr) '(caaaar caaar caar car) '(caaadr caadr cadr cdr) '(caadar cadar cdar car) '(caaddr caddr cddr cdr) '(cadaar cdaar caar car) '(cadadr cdadr cadr cdr) '(caddar cddar cdar car) '(cdaaar caaar caar car) '(cdaadr caadr cadr cdr) '(cdadar cadar cdar car) '(cdaddr caddr cddr cdr) '(cddaar cdaar caar car) '(cddadr cdadr cadr cdr) '(cdddar cddar cdar car)))) (lambda (caller head vars env) ;; report unused or set-but-unreferenced variables, then look at the overall history ;; vars used before defined are kind of a mess -- history has #f for the (unknown) enclosing form ;; and any definition wipes out the accumulated pre-def uses -- this should be by closed-body and ;; ignore local defines (i.e. really only define[x] propagates backwards) -- changing this is ;; tricky (fools current unused func arg + value message for example). (define (all-types-agree v) (let ((base-type (->lint-type (var-initial-value v))) (vname (var-name v))) (let ((typef (lambda (p) (or (not (and (pair? p) (eq? (car p) 'set!) (eq? vname (cadr p)))) (let ((nt (->lint-type (caddr p)))) (or (subsumes? base-type nt) (and (subsumes? nt base-type) (set! base-type nt)) (and (memq nt '(pair? null? proper-list?)) (memq base-type '(pair? null? proper-list?)) (set! base-type 'list?)))))))) (and (every? typef (var-history v)) base-type)))) (when (and (not (eq? head 'begin)) ; begin can redefine = set a variable (pair? vars) (proper-list? vars)) (do ((cur vars (cdr cur)) (rst (cdr vars) (cdr rst))) ((null? rst)) (let ((vn (var-name (car cur)))) (if (not (memq vn '(:lambda :dilambda))) (let ((repeat (var-member vn rst))) (when repeat (let ((type (if (eq? (var-definer repeat) 'parameter) 'parameter 'variable))) (if (eq? (var-definer (car cur)) 'define) (lint-format "~A ~A ~A is redefined ~A" caller head type vn (if (equal? head "") (if (not (tree-memq vn (var-initial-value (car cur)))) "at the top level." (format #f "at the top level. Perhaps use set! instead: ~A" (truncated-list->string `(set! ,vn ,(var-initial-value (car cur)))))) (format #f "in the ~A body. Perhaps use set! instead: ~A" head (truncated-list->string `(set! ,vn ,(var-initial-value (car cur))))))) (lint-format "~A ~A ~A is declared twice" caller head type vn))))))))) (let ((old-line-number line-number) (outer-form (cond ((var-member :let env) => var-initial-value) (else #f)))) (for-each (lambda (local-var) (let ((vname (var-name local-var)) (otype (if (eq? (var-definer local-var) 'parameter) 'parameter 'variable))) ;; (let ((x 0)...) ... (set! x 1)...) -> move the set! value to let init value ;; car body as set! is handled in let-walker etc (when (and (pair? outer-form) (positive? (var-set local-var)) (memq (car outer-form) '(let let*)) (list? (cadr outer-form)) (not (side-effect? (var-initial-value local-var) env))) (let ((nxt (let ((len (length (var-history local-var)))) (and (> len 1) (list-ref (var-history local-var) (- len 2)))))) (when (and (pair? nxt) (eq? (car nxt) 'set!) (eq? (cadr nxt) vname) (code-constant? (caddr nxt)) ; so vname is not involved etc (not (tree-memq vname (caddr outer-form))) ; not redundant with next -- need to exclude this case (let ((f (member vname (cdddr outer-form) tree-memq))) (and (pair? f) (eq? (car f) nxt)))) (lint-format "perhaps change ~A's initial value to ~A, and remove ~A in ~A" caller vname (caddr nxt) nxt (truncated-list->string outer-form))))) ;; if's possible for an unused function to have ref=1, null cdr history, but it appears to ;; always involve curlet exports and the like. ;; do all refs to an unset var go through the same function (at some level) (when (and (zero? (var-set local-var)) (> (var-ref local-var) 1)) (let ((hist (var-history local-var))) (when (and (pair? hist) (pair? outer-form) ; if outer-form is #f, local-var is probably a top-level var (not (and (memq (car outer-form) '(let let*)) ; not a named-let parameter (symbol? (cadr outer-form))))) (let ((first (car hist))) ; all but the initial binding have to match this (when (pair? first) (let ((op (car first))) (when (and (symbol? op) (not (eq? op 'unquote)) (not (hash-table-ref makers op)) (not (eq? vname op)) ; not a function (this kind if repetition is handled elsewhere) (pair? (cdr hist)) (pair? (cddr hist)) (pair? (cdr first)) (not (side-effect? first env)) (every? (lambda (a) (or (eq? a vname) (code-constant? a))) (cdr first)) (or (code-constant? (var-initial-value local-var)) (= (tree-count1 vname first 0) 1)) (every? (lambda (a) (and (pair? a) (or (equal? first a) (and (eq? (hash-table-ref reversibles (car first)) (car a)) (equal? (cdr first) (reverse (cdr a)))) (set! op (match-cxr op (car a)))))) (if (eq? otype 'parameter) (cdr hist) (copy (cdr hist) (make-list (- (length hist) 2)))))) (let* ((new-op (or op (car first))) (set-target (let walker ((tree outer-form)) ; check for new-op dilambda as target of set! (and (pair? tree) (or (and (eq? (car tree) 'set!) (pair? (cdr tree)) (pair? (cadr tree)) (eq? (caadr tree) new-op)) (walker (car tree)) (walker (cdr tree))))))) (unless set-target (if (eq? otype 'parameter) (if (> (var-ref local-var) 2) (lint-format "parameter ~A is always accessed (~A times) via ~S" caller vname (var-ref local-var) `(,new-op ,@(cdr first)))) (lint-format* caller (symbol->string vname) " is not set, and is always accessed via " (object->string `(,new-op ,@(cdr first))) " so its binding could probably be " ;; "probably" here because the accesses could have hidden protective assumptions ;; i.e. full accessor is not valid at point of let binding (object->string `(,vname (,new-op ,@(tree-subst (var-initial-value local-var) vname (cdr first))))) " in " (truncated-list->string outer-form)))))))))))) ;; translate to dilambda fixing arg if necessary and mention generic set! (let ((init (var-initial-value local-var))) (when (and (pair? init) (eq? (car init) 'define) (pair? (cadr init))) (let* ((vstr (symbol->string vname)) (len (length vstr))) (when (> len 4) (let ((setv #f) (newv #f)) (if (string=? (substring vstr 0 4) "get-") (let ((sv (symbol "set-" (substring vstr 4)))) (set! setv (or (var-member sv vars) (var-member sv env))) (set! newv (string->symbol (substring vstr 4)))) (if (string=? (substring vstr (- len 4)) "-ref") (let ((sv (symbol (substring vstr 0 (- len 4)) "-set!"))) (set! setv (or (var-member sv vars) (var-member sv env))) (set! newv (string->symbol (substring vstr 0 (- len 4))))) (let ((pos (string-position "-get-" vstr))) (when pos ; this doesn't happen very often, others: Get-, -ref-, -set!- are very rare (let ((sv (let ((s (copy vstr))) (set! (s (+ pos 1)) #\s) (string->symbol s)))) (set! setv (or (var-member sv vars) (var-member sv env))) (set! newv (symbol (substring vstr 0 pos) (substring vstr (+ pos 4))))))))) ; +4 to include #\- (when (and setv (not (var-member newv vars)) (not (var-member newv env))) (let ((getter init) (setter (var-initial-value setv))) (when (and (pair? setter) (eq? (car setter) 'define) (pair? (cadr setter))) (let ((getargs (cdadr getter)) (setargs (cdadr setter))) (unless (null? setargs) (if (or (eq? newv getargs) (and (pair? getargs) (memq newv getargs))) (let ((unique (find-unique-name getter newv))) (set! getter (tree-subst unique newv getter)) (set! getargs (cdadr getter)))) (if (or (eq? newv setargs) (and (pair? setargs) (memq newv setargs))) (let ((unique (find-unique-name setter newv))) (set! setter (tree-subst unique newv setter)) (set! setargs (cdadr setter)))) (let ((getdots (if (null? getargs) "" " ...")) (setdots (if (or (not (pair? setargs)) (null? (cdr setargs))) "" " ...")) (setvalue (and (proper-list? setargs) (list-ref setargs (- (length setargs) 1))))) (if setvalue (format outport "~NC~A: perhaps use dilambda and generalized set! for ~A and ~A:~%~ ~NCreplace (~A~A) with (~A~A) and (~A~A ~A) with (set! (~A~A) ~A)~%~ ~NC~A~%" lint-left-margin #\space caller vname (var-name setv) (+ lint-left-margin 4) #\space vname getdots newv getdots (var-name setv) setdots setvalue newv setdots setvalue (+ lint-left-margin 4) #\space (lint-pp `(define ,newv (dilambda (lambda ,getargs ,@(cddr getter)) (lambda ,setargs ,@(cddr setter)))))))))))))))))) ;; bad variable names (cond ((hash-table-ref syntaces vname) (lint-format "~A ~A named ~A is asking for trouble" caller head otype vname)) ((eq? vname 'l) (lint-format "\"l\" is a really bad variable name" caller)) ((and *report-built-in-functions-used-as-variables* (hash-table-ref built-in-functions vname)) (lint-format "~A ~A named ~A is asking for trouble" caller (if (and (pair? (var-scope local-var)) (null? (cdr (var-scope local-var))) (symbol? (car (var-scope local-var)))) (car (var-scope local-var)) head) otype vname)) (else (check-for-bad-variable-name caller vname))) (unless (memq vname '(:lambda :dilambda)) (if (and (eq? otype 'variable) (or *report-unused-top-level-functions* (not (eq? caller top-level:)))) (let ((scope (var-scope local-var))) ; might be #? (if (pair? scope) (set! scope (remove vname scope))) (when (and (pair? scope) (null? (cdr scope)) (symbol? (car scope)) (not (var-member (car scope) (let search ((e env)) (if (null? e) env (if (eq? (caar e) vname) e (search (cdr e)))))))) (format outport "~NC~A~A is ~A only in ~A~%" lint-left-margin #\space (if (eq? caller top-level:) "top-level: " "") vname (if (memq (var-ftype local-var) '(define lambda define* lambda*)) "called" "used") (car scope))))) (if (and (eq? (var-ftype local-var) 'define-expansion) (not (eq? caller top-level:))) (format outport "~NCdefine-expansion for ~A is not at the top-level, so it is ignored~%" lint-left-margin #\space vname)) ;; look for port opened but not closed ;; (let ((p (open-output-file str))) (display 32 p) x) (when (and (pair? outer-form) (not (tree-memq vname (list-ref outer-form (- (length outer-form) 1))))) ; vname never returned from outer-form?? (let ((hist (var-history local-var)) (open-set '(open-input-string open-input-file open-output-string open-output-file)) (open-form #f)) (when (and (any? (lambda (tree) (and (pair? tree) (or (and (memq (car tree) open-set) (pair? (cdr tree)) (not (memq vname (cdr tree)))) (and (eq? (car tree) 'set!) (pair? (cdr tree)) (eq? (cadr tree) vname) (pair? (cddr tree)) (pair? (caddr tree)) (memq (caaddr tree) open-set))) (set! open-form tree))) hist) (not (tree-set-member '(close-input-port close-output-port close-port close current-output-port current-input-port) hist))) (lint-format "in ~A~% perhaps ~A is opened via ~A, but never closed" caller (truncated-list->string outer-form) vname open-form)))) ;; redundant vars are hard to find -- tons of false positives (if (zero? (var-ref local-var)) (when (and (or (not (equal? head "")) *report-unused-top-level-functions*) (or *report-unused-parameters* (not (eq? otype 'parameter)))) (if (positive? (var-set local-var)) (let ((sets (map (lambda (call) (if (and (pair? call) (not (eq? (var-definer local-var) 'do)) (eq? (car call) 'set!) (eq? (cadr call) vname)) call (values))) (var-history local-var)))) (if (pair? sets) (if (null? (cdr sets)) (lint-format "~A set, but not used: ~A" caller vname (truncated-list->string (car sets))) (lint-format "~A set, but not used: ~{~S~^ ~}" caller vname sets)) (lint-format "~A set, but not used: ~A from ~A" caller vname (truncated-list->string (var-initial-value local-var)) (var-definer local-var)))) ;; not ref'd or set (if (not (memq vname '(documentation signature iterator? defanimal))) (let ((val (if (pair? (var-history local-var)) (car (var-history local-var)) (var-initial-value local-var))) (def (var-definer local-var))) (let-temporarily ((line-number (if (eq? caller top-level:) -1 line-number))) ;; eval confuses this message (eval '(+ x 1)), no other use of x [perhaps check :let initial-value = outer-form] ;; so does let-ref syntax: (apply (*e* 'g1)...) will miss this reference to g1 (if (symbol? def) (if (eq? otype 'parameter) (lint-format "~A not used" caller vname) (lint-format* caller (string-append (object->string vname) " not used, initially: ") (string-append (truncated-list->string val) " from " (symbol->string def)))) (lint-format* caller (string-append (object->string vname) " not used, value: ") (truncated-list->string val)))))))) ;; not zero var-ref (let ((arg-type #f)) (when (and (not (memq (var-definer local-var) '(parameter named-let named-let*))) (pair? (var-history local-var)) (or (zero? (var-set local-var)) (set! arg-type (all-types-agree local-var)))) (let ((vtype (or arg-type ; this can't be #f unless no sets so despite appearances there's no contention here (eq? caller top-level:) ; might be a global var where init value is largely irrelevant (->lint-type (var-initial-value local-var)))) (lit? (code-constant? (var-initial-value local-var)))) (do ((clause (var-history local-var) (cdr clause))) ((null? (cdr clause))) ; ignore the initial value which depends on a different env (let ((call (car clause))) (if (pair? call) (set! line-number (pair-line-number call))) (when (pair? call) (let ((func (car call)) (call-arg1 (and (pair? (cdr call)) (cadr call)))) ;; check for assignments into constants (if (and lit? (indirect-set? vname func call-arg1)) (lint-format "~A's value, ~A, is a literal constant, so this set! is trouble: ~A" caller vname (var-initial-value local-var) (truncated-list->string call))) (when (symbol? vtype) (when (and (not (eq? caller top-level:)) (not (memq vtype '(boolean? #t values))) (memq func '(if when unless)) ; look for (if x ...) where x is never #f, this happens a dozen or so times (or (eq? (cadr call) vname) (and (pair? (cadr call)) (eq? (caadr call) 'not) (eq? (cadadr call) vname)))) (lint-format "~A is never #f, so ~A" caller vname (lists->string call (if (eq? vname (cadr call)) (case func ((if) (caddr call)) ((when) (if (pair? (cdddr call)) `(begin ,@(cddr call)) (caddr call))) ((unless) #)) (case func ((if) (if (pair? (cdddr call)) (cadddr call))) ((when) #) ((unless) (if (pair? (cdddr call)) `(begin ,@(cddr call)) (caddr call)))))))) ;; check for incorrect types in function calls (unless (memq vtype '(boolean? null?)) ; null? here avoids problems with macros that call set! (let ((p (memq vname (cdr call)))) (when (pair? p) (let ((sig (arg-signature func env)) (pos (- (length call) (length p)))) (when (and (pair? sig) (< pos (length sig))) (let ((desired-type (list-ref sig pos))) (if (not (compatible? vtype desired-type)) (lint-format "~A is ~A, but ~A in ~A wants ~A" caller vname (prettify-checker-unq vtype) func (truncated-list->string call) (prettify-checker desired-type)))))))) (let ((suggest made-suggestion)) ;; check for pointless vtype checks (when (and (hash-table-ref bools func) (not (eq? vname func))) (when (or (eq? vtype func) (and (compatible? vtype func) (not (subsumes? vtype func)))) (lint-format "~A is ~A, so ~A is #t" caller vname (prettify-checker-unq vtype) call)) (unless (compatible? vtype func) (lint-format "~A is ~A, so ~A is #f" caller vname (prettify-checker-unq vtype) call))) (case func ;; need a way to mark exported variables so they won't be checked in this process ;; case can happen here, but it never seems to trigger a type error ((eq? eqv? equal?) ;; (and (pair? x) (eq? x #\a)) etc (when (or (and (code-constant? call-arg1) (not (compatible? vtype (->lint-type call-arg1)))) (and (code-constant? (caddr call)) (not (compatible? vtype (->lint-type (caddr call)))))) (lint-format "~A is ~A, so ~A is #f" caller vname (prettify-checker-unq vtype) call))) ((and or) (when (let amidst? ((lst call)) (and (pair? lst) (pair? (cdr lst)) (or (eq? (car lst) vname) (amidst? (cdr lst))))) ; don't clobber possible trailing vname (returned by expression) (lint-format "~A is ~A, so ~A" caller ; (let ((x 1)) (and x (< x 1))) -> (< x 1) vname (prettify-checker-unq vtype) (lists->string call (simplify-boolean (remove vname call) () () vars))))) ((not) (if (eq? vname (cadr call)) (lint-format "~A is ~A, so ~A" caller vname (prettify-checker-unq vtype) (lists->string call #f)))) ((/) (if (and (number? (var-initial-value local-var)) (zero? (var-initial-value local-var)) (zero? (var-set local-var)) (memq vname (cddr call))) (lint-format "~A is ~A, so ~A is an error" caller vname (var-initial-value local-var) call)))) ;; the usual eqx confusion (when (and (= suggest made-suggestion) (memq vtype '(char? number? integer? real? float? rational? complex?))) (if (memq func '(eq? equal?)) (lint-format "~A is ~A, so ~A ~A be eqv? in ~A" caller vname (prettify-checker-unq vtype) func (if (eq? func 'eq?) "should" "could") call)) ;; check other boolean exprs (when (and (zero? (var-set local-var)) (number? (var-initial-value local-var)) (eq? vname call-arg1) (null? (cddr call)) (hash-table-ref booleans func)) (let ((val (catch #t (lambda () ((symbol->value func (rootlet)) (var-initial-value local-var))) (lambda args 'error)))) (if (boolean? val) (lint-format "~A is ~A, so ~A is ~A" caller vname (var-initial-value local-var) call val)))))) ;; implicit index checks -- these are easily fooled by macros (when (and (memq vtype '(vector? float-vector? int-vector? string? list? byte-vector?)) (pair? (cdr call))) (when (eq? func vname) (let ((init (var-initial-value local-var))) (if (not (compatible? 'integer? (->lint-type call-arg1))) (lint-format "~A is ~A, but the index ~A is ~A" caller vname (prettify-checker-unq vtype) call-arg1 (prettify-checker (->lint-type call-arg1)))) (if (integer? call-arg1) (if (negative? call-arg1) (lint-format "~A's index ~A is negative" caller vname call-arg1) (if (zero? (var-set local-var)) (let ((lim (cond ((code-constant? init) (length init)) ((memq (car init) '(vector float-vector int-vector string list byte-vector)) (- (length init) 1)) (else (and (pair? (cdr init)) (integer? (cadr init)) (memq (car init) '(make-vector make-float-vector make-int-vector make-string make-list make-byte-vector)) (cadr init)))))) (if (and (real? lim) (>= call-arg1 lim)) (lint-format "~A has length ~A, but index is ~A" caller vname lim call-arg1)))))))) (when (eq? func 'implicit-set) ;; ref is already checked in other history entries (let ((ref-type (case vtype ((float-vector?) 'real?) ; not 'float? because ints are ok here ((int-vector? byte-vector?) 'integer) ((string?) 'char?) (else #f)))) (if ref-type (let ((val-type (->lint-type (caddr call)))) (if (not (compatible? val-type ref-type)) (lint-format "~A wants ~A, but the value in ~A is ~A" caller vname (prettify-checker-unq ref-type) `(set! ,@(cdr call)) (prettify-checker val-type))))) )))))) ))) ; do loop through clauses ;; check for duplicated calls involving local-var (when (and (> (var-ref local-var) 8) (zero? (var-set local-var)) (eq? (var-ftype local-var) #)) (let ((h (make-hash-table))) (for-each (lambda (call) (when (and (pair? call) (not (eq? (car call) vname)) ; ignore functions for now (not (side-effect? call env))) (hash-table-set! h call (+ 1 (or (hash-table-ref h call) 0))) (cond ((hash-table-ref unwrap-cxr (car call)) => (lambda (lst) (for-each (lambda (c) (hash-table-set! h (cons c (cdr call)) (+ 1 (or (hash-table-ref h (cons c (cdr call))) 0)))) lst)))))) (var-history local-var)) (let ((repeats ())) (for-each (lambda (call) (if (and (> (cdr call) (max 3 (/ 20 (tree-leaves (car call))))) ; was 5 (not (memq (caar call) '(make-vector make-float-vector))) (or (null? (cddar call)) (every? (lambda (p) (or (not (symbol? p)) (eq? p vname))) (cdar call)))) (set! repeats (cons (string-append (truncated-list->string (car call)) " occurs ") (cons (string-append (object->string (cdr call)) " times" (if (pair? repeats) ", " "")) repeats))))) h) (if (pair? repeats) (apply lint-format* caller (string-append (object->string vname) " is not set, but ") repeats))))) ;; check for function parameters whose values never change and are not just symbols (when (and (> (var-ref local-var) 3) (zero? (var-set local-var)) (memq (var-ftype local-var) '(define lambda)) (pair? (var-arglist local-var)) (let loop ((calls (var-history local-var))) ; if func passed as arg, ignore it (or (null? calls) (null? (cdr calls)) (and (pair? (car calls)) (not (memq (var-name local-var) (cdar calls))) (loop (cdr calls)))))) (let ((pars (map list (proper-list (var-arglist local-var))))) (do ((clauses (var-history local-var) (cdr clauses))) ((null? (cdr clauses))) ; ignore the initial value (if (and (pair? (car clauses)) (eq? (caar clauses) (var-name local-var))) (for-each (lambda (arg par) ; collect all arguments for each parameter (if (not (member arg (cdr par))) ; we haven't seen this argument yet, so (set-cdr! par (cons arg (cdr par))))) ; add it to the list for this parameter (cdar clauses) pars))) (for-each (lambda (p) (if (and (pair? (cdr p)) (null? (cddr p)) ; so all calls, this parameter has the same value (not (symbol? (cadr p)))) (lint-format "~A's '~A parameter is always ~S (~D calls)" caller (var-name local-var) (car p) (cadr p) (var-ref local-var)))) pars))) )))) ; end (if zero var-ref) ;; vars with multiple incompatible ascertainable types don't happen much and obvious type errors are extremely rare (when (and *report-clobbered-function-return-value* (positive? (var-set local-var))) (let ((start (var-initial-value local-var))) (let ((func #f) (retcons? (and (pair? start) (let ((v (var-member (car start) env))) (and (var? v) (eq? (var-retcons v) #t)))))) (for-each (lambda (f) (when (pair? f) (case (car f) ((set!) (set! retcons? (and (pair? (cdr f)) (eq? (cadr f) vname) (pair? (cddr f)) (pair? (caddr f)) (let ((v (var-member (caaddr f) env))) (and (var? v) (eq? #t (var-retcons v)) (set! func f)))))) ((string-set! list-set! vector-set! set-car! set-cdr!) (if (and retcons? (eq? (cadr f) vname)) (lint-format "~A returns a constant sequence, but ~A appears to clobber it" caller func f)))))) (reverse (var-history local-var)))))) ))) vars) (set! line-number old-line-number))))) (define (find-call sym body) (call-with-exit (lambda (return) (let tree-call ((tree body)) (if (and (pair? tree) (not (eq? (car tree) 'quote))) (begin (if (eq? (car tree) sym) (return tree)) (if (memq (car tree) '(let let* letrec letrec* do lambda lambda* define)) (return #f)) ; possible shadowing -- not worth the infinite effort to corroborate (if (pair? (car tree)) (tree-call (car tree))) (if (pair? (cdr tree)) (do ((p (cdr tree) (cdr p))) ((not (pair? p)) #f) (tree-call (car p)))))))))) (define (check-returns caller f env) ; f is not the last form in the body (if (not (or (side-effect? f env) (eq? '=> f))) (lint-format "this could be omitted: ~A" caller (truncated-list->string f)) (when (pair? f) (case (car f) ((if) (when (and (pair? (cdr f)) (pair? (cddr f))) (let ((true (caddr f)) (false (if (pair? (cdddr f)) (cadddr f) 'no-false))) (let ((true-ok (side-effect? true env)) (false-ok (or (eq? false 'no-false) (side-effect? false env)))) (if true-ok (if (pair? true) (check-returns caller true env)) (lint-format "this branch is pointless~A: ~A in ~A" caller (local-line-number true) (truncated-list->string true) (truncated-list->string f))) (if false-ok (if (pair? false) (check-returns caller false env)) (lint-format "this branch is pointless~A: ~A in ~A" caller (local-line-number false) (truncated-list->string false) (truncated-list->string f))))))) ((cond case) ;; here all but last result exprs are already checked ;; redundant begin can confuse this, but presumably we'll complain about that elsewhere ;; also even in mid-body, if else clause has a side-effect, an earlier otherwise pointless clause might be avoiding that (let ((has-else (let ((last-clause (list-ref f (- (length f) 1)))) (and (pair? last-clause) (memq (car last-clause) '(else #t)) (any? (lambda (c) (side-effect? c env)) (cdr last-clause)))))) (for-each (lambda (c) (if (and (pair? c) (pair? (cdr c)) (not (memq '=> (cdr c)))) (let ((last-expr (list-ref c (- (length c) 1)))) (cond ((side-effect? last-expr env) (if (pair? last-expr) (check-returns caller last-expr env))) (has-else (if (or (pair? (cddr c)) (eq? (car f) 'cond)) (lint-format "this ~A clause's result could be omitted" caller (truncated-list->string c)) (if (not (memq last-expr '(#f #t #))) ; it's not already obvious (lint-format "this ~A clause's result could be simply #f" caller (truncated-list->string c))))) ((and (eq? (car f) 'case) (or (eq? last-expr (cadr c)) (not (any? (lambda (p) (side-effect? p env)) (cdr c))))) (lint-format "this case clause can be omitted: ~A" caller (truncated-list->string c))) (else (lint-format "this is pointless: ~A in ~A" caller (truncated-list->string last-expr) (truncated-list->string c))))))) ((if (eq? (car f) 'cond) cdr cddr) f)))) ((let let*) (if (and (pair? (cdr f)) (not (symbol? (cadr f))) (pair? (cddr f))) (let ((last-expr (list-ref f (- (length f) 1)))) (if (side-effect? last-expr env) (if (pair? last-expr) (check-returns caller last-expr env)) (lint-format "this is pointless~A: ~A in ~A" caller (local-line-number last-expr) (truncated-list->string last-expr) (truncated-list->string f)))))) ;; perhaps use truncated-lists->string here?? ((and) (let ((len (length f))) (case len ((1) (lint-format "this ~A is pointless" caller f)) ((2) (lint-format "perhaps ~A" caller (lists->string f (cadr f)))) ((3) (lint-format "perhaps ~A" caller (lists->string f `(if ,(cadr f) ,(caddr f))))) ; (begin (and x (display y)) (log z)) -> (if x (display y)) (else (lint-format "perhaps ~A" caller (lists->string f `(if ,(cadr f) (and ,@(cddr f))))))))) ((or) (let ((len (length f))) (case len ((1) (lint-format "this ~A is pointless" caller f)) ((2) (lint-format "perhaps ~A" caller (lists->string f (cadr f)))) ((3) (lint-format "perhaps ~A" caller (lists->string f `(if (not ,(cadr f)) ,(caddr f))))) (else (lint-format "perhaps ~A" caller (lists->string f `(if (not ,(cadr f)) (or ,@(cddr f))))))))) ((not) (lint-format "this ~A is pointless" caller f)) ((letrec letrec* with-let unless when begin with-baffle) (if (and (pair? (cdr f)) (pair? (cddr f))) (let ((last-expr (list-ref f (- (length f) 1)))) (if (side-effect? last-expr env) (if (pair? last-expr) (check-returns caller last-expr env)) ;; (begin (if x (begin (display x) z)) z) (lint-format "this is pointless~A: ~A in ~A" caller (local-line-number last-expr) (truncated-list->string last-expr) (truncated-list->string f)))))) ((do) (let ((returned (if (and (pair? (cdr f)) (pair? (cddr f))) (let ((end+res (caddr f))) (if (pair? (cdr end+res)) (list-ref end+res (- (length end+res) 1))))))) (if (or (eq? returned #) (and (pair? returned) (side-effect? returned env))) (if (pair? returned) (check-returns caller returned env)) ;; (begin (do ((i 0 (+ i 1))) ((= i 10) i) (display i)) x) (lint-format "~A: result ~A~A is not used" caller (truncated-list->string f) (truncated-list->string returned) (local-line-number returned))))) ((call-with-exit) (if (and (pair? (cdr f)) (pair? (cadr f)) (eq? (caadr f) 'lambda) (pair? (cdadr f)) (pair? (cadadr f))) (let ((return (car (cadadr f)))) (let walk ((tree (cddadr f))) (if (pair? tree) (if (eq? (car tree) return) (if (and (pair? (cdr tree)) (or (not (boolean? (cadr tree))) (pair? (cddr tree)))) ;; (begin (call-with-exit (lambda (quit) (if (< x 0) (quit (+ x 1))) (display x))) (+ x 2)) (lint-format "th~A call-with-exit return value~A will be ignored: ~A" caller (if (pair? (cddr tree)) (values "ese" "s") (values "is" "")) tree)) (for-each walk tree))))))) ((map) (if (pair? (cdr f)) ; (begin (map g123 x) x) (lint-format "map could be for-each: ~A" caller (truncated-list->string `(for-each ,@(cdr f)))))) ((reverse!) (if (pair? (cdr f)) ; (let ((x (list 23 1 3))) (reverse! x) x) (lint-format "~A might leave ~A in an undefined state; perhaps ~A" caller (car f) (cadr f) `(set! ,(cadr f) ,f)))) ((format) (if (and (pair? (cdr f)) (eq? (cadr f) #t)) ; (let () (format #t "~A" x) x) (lint-format "perhaps use () with format since the string value is discarded:~% ~A" caller `(format () ,@(cddr f))))))))) (define lint-current-form #f) (define lint-mid-form #f) (define (escape? form env) (and (pair? form) (let ((v (var-member (car form) env))) (if (var? v) (memq (var-definer v) '(call/cc call-with-current-continuation call-with-exit)) (memq (car form) '(error throw)))))) (define (lint-walk-body caller head body env) (when (pair? body) (when (and (pair? (car body)) (pair? (cdar body))) (when (and (not (eq? last-rewritten-internal-define (car body))) ; we already rewrote this (pair? (cdr body)) ; define->named let, but this is only ok in a "closed" situation, not (begin (define...)) for example (pair? (cadr body)) (memq (caar body) '(define define*)) (pair? (cadar body))) (let ((fname (caadar body)) (fargs (cdadar body)) (fbody (cddar body))) (when (and (symbol? fname) (proper-list? fargs) (= (tree-count1 fname (cdr body) 0) 1) (not (any? keyword? fargs))) (let ((call (find-call fname (cdr body)))) (when (pair? call) (let ((new-args (if (eq? (caar body) 'define) (map list fargs (cdr call)) (let loop ((pars fargs) (vals (cdr call)) (args ())) (if (null? pars) (reverse args) (loop (cdr pars) (if (pair? vals) (values (cdr vals) (cons (list ((if (pair? (car pars)) caar car) pars) (car vals)) args)) (values () (cons (if (pair? (car pars)) (car pars) (list (car pars) #f)) args)))))))) (new-let (if (eq? (caar body) 'define) 'let 'let*))) (if (and (pair? fbody) (pair? (cdr fbody)) (string? (car fbody))) (set! fbody (cdr fbody))) ;; (... (define* (f1 a b) (+ a b)) (f1 :c 1)) -> (... (let ((a :c) (b 1)) (+ a b))) (lint-format "perhaps ~A" caller (lists->string `(... ,@body) (if (= (tree-count2 fname body 0) 2) (if (null? fargs) (if (null? (cdr fbody)) `(... ,@(tree-subst (car fbody) call (cdr body))) `(... ,@(tree-subst `(let () ,@fbody) call (cdr body)))) `(... ,@(tree-subst `(let ,new-args ,@fbody) call (cdr body)))) `(... ,@(tree-subst `(,new-let ,fname ,new-args ,@fbody) call (cdr body)))))))))))) ;; look for non-function defines at the start of the body and use let(*) instead ;; we're in a closed body here, so the define can't propagate backwards (let ((first-expr (car body))) ;; another case: f(args) (let(...)set! arg < no let>) (when (and (eq? (car first-expr) 'define) (symbol? (cadr first-expr)) (pair? (cddr first-expr)) ;;(not (tree-car-member (cadr first-expr) (caddr first-expr))) ;;(not (tree-set-car-member '(lambda lambda*) (caddr first-expr))) (not (and (pair? (caddr first-expr)) (memq (caaddr first-expr) '(lambda lambda*)))) (> (length body) 2)) ;; this still is not ideal -- we need to omit let+lambda as well (do ((names ()) (letx 'let) (vars&vals ()) (p body (cdr p))) ((not (and (pair? p) (let ((expr (car p))) (and (pair? expr) (eq? (car expr) 'define) (symbol? (cadr expr)) ; not (define (f ...)) (pair? (cddr expr)) (not (and (pair? (caddr expr)) ; not (define f (lambda...)) (memq (caaddr expr) '(lambda let lambda* let* letrec letrec*)))))))) ;; (... (define x 3) 32) -> (... (let ((x 3)) ...)) (if (pair? vars&vals) (lint-format "perhaps ~A" caller (lists->string `(... ,@body) `(... (,letx ,(reverse vars&vals) ...)))))) ;; define acts like letrec(*), not let -- reference to name in lambda body is current name (let ((expr (cdar p))) (set! vars&vals (cons (if (< (tree-leaves (cdr expr)) 12) expr (list (car expr) '...)) vars&vals)) (if (tree-set-member names (cdr expr)) (set! letx 'let*)) (set! names (cons (car expr) names))))))) (let ((len (length body))) (when (> len 2) ; ... (define (x...)...) (x ...) -> (let (...) ...) or named let -- this happens a lot! (let ((n-1 (list-ref body (- len 2))) ; or (define (x ...)...) (some expr calling x once) -> named let etc (n (list-ref body (- len 1)))) (when (and (pair? n-1) (eq? (car n-1) 'define) (pair? (cadr n-1)) (symbol? (caadr n-1)) (proper-list? (cdadr n-1)) (pair? n) (or (and (eq? (car n) (caadr n-1)) (eqv? (length (cdadr n-1)) (length (cdr n)))) ; not values -> let! (and (< (tree-leaves n-1) 12) (tree-car-member (caadr n-1) (cdr n)) ; skip car -- see preceding (= (tree-count1 (caadr n-1) n 0) 1)))) (let ((outer-form (cond ((var-member :let env) => var-initial-value) (else #f))) (new-var (caadr n-1))) (when (and (pair? outer-form) (not (let walker ((tree outer-form)) ; check even the enclosing env -- define in do body back ref'd in stepper for example (or (eq? new-var tree) (and (pair? tree) (not (eq? n tree)) (not (eq? n-1 tree)) (not (eq? (car tree) 'quote)) (or (walker (car tree)) (walker (cdr tree)))))))) (let ((named (if (tree-memq new-var (cddr n-1)) (list new-var) ()))) (if (eq? (car n) (caadr n-1)) (lint-format "perhaps change ~A to a ~Alet: ~A" caller new-var (if (pair? named) "named " "") (lists->string outer-form `(... (let ,@named ,(map list (cdadr n-1) (cdr n)) ...)))) (let ((call (find-call new-var n))) (when (and (pair? call) (eqv? (length (cdadr n-1)) (length (cdr call)))) (let ((new-call `(let ,@named ,(map list (cdadr n-1) (cdr call)) ,@(cddr n-1)))) (lint-format "perhaps embed ~A: ~A" caller new-var (lists->string outer-form `(... ,(tree-subst new-call call n))))))))))))) (let ((suggest made-suggestion)) (unless (tree-memq 'curlet (list-ref body (- len 1))) (do ((q body (cdr q)) (k 0 (+ k 1))) ((null? q)) (let ((expr (car q))) (when (and (pair? expr) (eq? (car expr) 'define) (pair? (cdr expr)) (pair? (cddr expr)) (null? (cdddr expr))) (let ((name (and (symbol? (cadr expr)) (cadr expr)))) (when name (do ((last-ref k) (p (cdr q) (cdr p)) (i (+ k 1) (+ i 1))) ((null? p) (if (and (< k last-ref (+ k 2)) (pair? (list-ref body (+ k 1)))) (let ((end-dots (if (< last-ref (- len 1)) '(...) ())) (letx (if (tree-member name (cddr expr)) 'letrec 'let)) (use-expr (list-ref body (+ k 1))) (seen-earlier (or (var-member name env) (do ((s body (cdr s))) ((or (eq? s q) (and (pair? (car s)) (tree-memq name (car s)))) (not (eq? s q))))))) (cond (seen-earlier) ((not (eq? (car use-expr) 'define)) (let-temporarily ((target-line-length 120)) ;; (... (define f14 (lambda (x y) (if (positive? x) (+ x y) y))) (+ (f11 1 2) (f14 1 2))) -> ;; (... (let ((f14 (lambda (x y) (if (positive? x) (+ x y) y)))) (+ (f11 1 2) (f14 1 2)))) (lint-format "the scope of ~A could be reduced: ~A" caller name (truncated-lists->string `(... ,expr ,use-expr ,@end-dots) `(... (,letx ((,name ,(caddr expr))) ,use-expr) ,@end-dots))))) ((eq? (cadr use-expr) name) ;; (let () (display 33) (define x 2) (define x (+ x y)) (display 43)) -> ;; (... (set! x (+ x y)) ...) (lint-format "use set! to redefine ~A: ~A" caller name (lists->string `(... ,use-expr ,@end-dots) `(... (set! ,name ,(caddr use-expr)) ,@end-dots)))) ((pair? (cadr use-expr)) (if (symbol? (caadr use-expr)) (let-temporarily ((target-line-length 120)) ;; (let () (display 32) (define x 2) (define (f101 y) (+ x y)) (display 41) (f101 2)) -> ;; (... (define f101 (let ((x 2)) (lambda (y) (+ x y)))) ...) (lint-format "perhaps move ~A into ~A's closure: ~A" caller name (caadr use-expr) (truncated-lists->string `(... ,expr ,use-expr ,@end-dots) `(... (define ,(caadr use-expr) (,letx ((,name ,(caddr expr))) (lambda ,(cdadr use-expr) ,@(cddr use-expr)))) ,@end-dots)))))) ((and (symbol? (cadr use-expr)) (pair? (cddr use-expr))) (let-temporarily ((target-line-length 120)) (if (and (pair? (caddr use-expr)) (eq? (caaddr use-expr) 'lambda)) ;; (let () (display 34) (define x 2) (define f101 (lambda (y) (+ x y))) (display 41) (f101 2)) ;; (... (define f101 (let ((x 2)) (lambda (y) (+ x y)))) ...) (lint-format "perhaps move ~A into ~A's closure: ~A" caller name (cadr use-expr) (truncated-lists->string `(... ,expr ,use-expr ,@end-dots) `(... (define ,(cadr use-expr) (,letx ((,name ,(caddr expr))) ,(caddr use-expr))) ,@end-dots))) ;; (... (define lib (r file)) (define exports (caddr lib)) ...) -> ;; (... (define exports (let ((lib (r file))) (caddr lib))) ...) (lint-format "the scope of ~A could be reduced: ~A" caller name (truncated-lists->string `(... ,expr ,use-expr ,@end-dots) `(... (define ,(cadr use-expr) (,letx ((,name ,(caddr expr))) ,(caddr use-expr))) ,@end-dots)))))))) (when (and (> len 3) (< k last-ref (+ k 3)) ; larger cases happen very rarely -- 3 or 4 altogether (pair? (list-ref body (+ k 1))) (pair? (list-ref body (+ k 2)))) (let ((end-dots (if (< last-ref (- len 1)) '(...) ())) (letx (if (tree-member name (cddr expr)) 'letrec 'let)) (seen-earlier (or (var-member name env) (do ((s body (cdr s))) ((or (eq? s q) (and (pair? (car s)) (tree-memq name (car s)))) (not (eq? s q))))))) (unless seen-earlier (let ((use-expr1 (list-ref body (+ k 1))) (use-expr2 (list-ref body (+ k 2)))) (if (not (or (tree-set-member '(define lambda) use-expr1) (tree-set-member '(define lambda) use-expr2))) ;; (... (define f101 (lambda (y) (+ x y))) (display 41) (f101 2)) -> ;; (... (let ((f101 (lambda (y) (+ x y)))) (display 41) (f101 2))) (lint-format "the scope of ~A could be reduced: ~A" caller name (let-temporarily ((target-line-length 120)) (truncated-lists->string `(... ,expr ,use-expr1 ,use-expr2 ,@end-dots) `(... (,letx ((,name ,(caddr expr))) ,use-expr1 ,use-expr2) ,@end-dots))))))))))) (when (tree-memq name (car p)) (set! last-ref i))))))))) (when (= suggest made-suggestion) ;; look for define+binding-expr at end and combine (do ((prev-f #f) (fs body (cdr fs))) ((not (pair? fs))) (let ((f (car fs))) ;; define can come after the use, and in an open body can be equivalent to set!: ;; (let () (if x (begin (define y 12) (do ((i 0 (+ i 1))) ((= i y)) (f i))) (define y 21)) y) ;; (let () (define (f x) (+ y x)) (if z (define y 12) (define y 1)) (f 12)) ;; so we can't do this check in walk-open-body ;; ;; define + do -- if cadr prev-f not used in do inits, fold into do, else use let ;; the let case is semi-redundant (it's already reported elsewhere) (when (and (pair? prev-f) (pair? f) (eq? (car prev-f) 'define) (symbol? (cadr prev-f)) (not (hash-table-ref other-identifiers (cadr prev-f))) ; (cadr prev-f) already ref'd, so it's a member of env (or (null? (cdr fs)) (not (tree-memq (cadr prev-f) (cdr fs))))) (if (eq? (car f) 'do) ;; (... (define z (f x)) (do ((i z (+ i 1))) ((= i 3)) (display (+ z i))) ...) -> (do ((i (f x) (+ i 1))) ((= i 3)) (display (+ z i))) (lint-format "perhaps ~A" caller (lists->string `(... ,prev-f ,f ...) (if (any? (lambda (p) (tree-memq (cadr prev-f) (cadr p))) (cadr f)) (if (and (eq? (cadr prev-f) (cadr (caadr f))) (null? (cdadr f))) `(do ((,(caaadr f) ,(caddr prev-f) ,(caddr (caadr f)))) ,@(cddr f)) `(let (,(cdr prev-f)) ,f)) `(do (,(cdr prev-f) ,@(cadr f)) ,@(cddr f))))) ;; just changing define -> let seems officious, though it does reduce (cadr prev-f)'s scope (if (and (or (and (eq? (car f) 'let) (not (tree-memq (cadr prev-f) (cadr f)))) (eq? (car f) 'let*)) (not (symbol? (cadr f)))) (lint-format "perhaps ~A" caller (lists->string `(... ,prev-f ,f ,@(if (null? (cdr fs)) () '(...))) `(... (,(car f) (,(cdr prev-f) ,@(cadr f)) ...) ,@(if (null? (cdr fs)) () '(...)))))))) (set! prev-f f)))))))) ;; definer as last in body is rare outside let-syntax, and tricky -- only one clear optimizable case found (lint-walk-open-body caller head body env)) (define (lint-walk-open-body caller head body env) ;; walk a body (a list of forms, the value of the last of which might be returned) (if (not (proper-list? body)) (lint-format "stray dot? ~A" caller (truncated-list->string body)) (let ((prev-f #f) (old-current-form lint-current-form) (old-mid-form lint-mid-form) (prev-len 0) (f-len 0) (repeats 0) (start-repeats body) (repeat-arg 0) (dpy-f #f) (dpy-start #f) (rewrote-already #f) (len (length body))) (if (eq? head 'do) (set! len (+ len 1))) ; last form in do body is not returned (do ((fs body (cdr fs)) (ctr 0 (+ ctr 1))) ((not (pair? fs))) (let* ((f (car fs)) (f-func (and (pair? f) (car f)))) (when (and (pair? f) (pair? (cdr f))) (if (eq? f-func 'define) (let ((vname (if (symbol? (cadr f)) (cadr f) (and (pair? (cadr f)) (symbol? (caadr f)) (caadr f))))) ;; if already in env, check shadowing request (if (and *report-shadowed-variables* (var-member vname env)) ;; (let ((f33 33)) (define f33 4) (g f33 1)) (lint-format "~A variable ~A in ~S shadows an earlier declaration" caller head vname f)))) ;; mid-body defines happen by the million, so resistance is futile ;; -------- repeated if/when etc -------- (when (and (pair? prev-f) ; (if A ...) (if A ...) -> (when A ...) or equivalents (memq (car prev-f) '(if when unless)) (memq f-func '(if when unless)) (pair? (cdr prev-f)) (pair? (cddr f)) ; possible broken if statement (pair? (cddr prev-f))) (define (tree-change-member set tree) (and (pair? tree) (not (eq? (car tree) 'quote)) (or (and (eq? (car tree) 'set!) (memq (cadr tree) set)) (tree-change-member set (car tree)) (tree-change-member set (cdr tree))))) (let ((test1 (cadr prev-f)) (test2 (cadr f))) ;; (if A...) (if (not A)...) happens very rarely -- only two rewritable hits (let ((equal-tests ; test1 = test2 [check for side-effects already] (lambda () (if (and (pair? (caddr prev-f)) (escape? (caddr prev-f) env)) ;; (begin (if x (error 'oops)) (if x y)) -> begin: x is #f in (if x y) -- this never happens (lint-format "~A is #f in ~A" caller test2 (truncated-list->string f))) ;; (... (if (and A B) (f C)) (if (and B A) (g E) (h F)) ...) -> (... (if (and A B) (begin (f C) (g E)) (begin (h F))) ...) (lint-format "perhaps ~A" caller (lists->string `(... ,prev-f ,f ...) (if (eq? f-func 'if) (if (and (null? (cdddr prev-f)) (null? (cdddr f))) ;; if (null (cdr fs)) we have to make sure the returned value is not changed by our rewrite ;; but when/unless return their last value in s7 (or #), so I think this is ok (if (and (pair? test1) (eq? (car test1) 'not)) `(... (unless ,(cadr test1) ,@(unbegin (caddr prev-f)) ,@(unbegin (caddr f))) ...) `(... (when ,test1 ,@(unbegin (caddr prev-f)) ,@(unbegin (caddr f))) ...)) `(... (if ,test1 (begin ,@(unbegin (caddr prev-f)) ,@(unbegin (caddr f))) (begin ,@(if (pair? (cdddr prev-f)) (unbegin (cadddr prev-f)) ()) ,@(if (pair? (cdddr f)) (unbegin (cadddr f)) ()))) ...)) `(,f-func ,test1 ; f-func = when|unless ,@(cddr prev-f) ,@(cddr f))))))) (test1-in-test2 (lambda () (if (null? (cddr test2)) (set! test2 (cadr test2))) ;; (... (if A (f B)) (when (and A C) (g D) (h E)) ...) -> (... (when A (f B) (when C (g D) (h E))) ...) (lint-format "perhaps ~A" caller (lists->string `(... ,prev-f ,f ...) (if (or (null? (cdddr prev-f)) (eq? (car prev-f) 'when)) ; so prev-f is when or 1-arm if (as is f) `(... (when ,test1 ,@(cddr prev-f) (when ,test2 ,@(cddr f))) ,@(if (null? (cdr fs)) () '(...))) ;; prev-f is 2-arm if and f is when or 1-arm if (the other case is too ugly) `(... (if ,test1 (begin ,(caddr prev-f) (when ,test2 ,@(cddr f))) ,@(cdddr prev-f)) ...)))))) (test2-in-test1 (lambda () (if (null? (cddr test1)) (set! test1 (cadr test1))) ;; (... (if (and A B) (f C)) (if A (g E)) ...) -> (... (when A (when B (f C)) (g E))) (lint-format "perhaps ~A" caller (lists->string `(... ,prev-f ,f ...) (if (or (null? (cdddr f)) (eq? f-func 'when)) ; so f is when or 1-arm if (as is prev-f) `(... (when ,test2 (when ,test1 ,@(cddr prev-f)) ,@(cddr f)) ,@(if (null? (cdr fs)) () '(...))) ;; f is 2-arm if and prev-f is when or 1-arm if `(... (if ,test2 (begin (when ,test1 ,@(cddr prev-f)) ,(caddr f)) ,(cadddr f)) ,@(if (null? (cdr fs)) () '(...))))))))) (cond ((equal? test1 test2) (if (and (eq? f-func (car prev-f)) (not (side-effect? test1 env)) (not (tree-change-member (gather-symbols test1) (cdr prev-f)))) (equal-tests))) ((or (eq? f-func 'unless) (eq? (car prev-f) 'unless))) ; too hard! ;; look for test1 as member of test2 (so we can use test1 as the outer test) ((and (pair? test2) (eq? (car test2) 'and) (member test1 (cdr test2)) (or (eq? f-func 'when) ; f has to be when or 1-arm if (null? (cdddr f))) (or (pair? (cdr fs)) ; if prev-f has false branch, we have to ignore the return value of f (eq? (car prev-f) 'when) (null? (cdddr prev-f))) (not (side-effect? test2 env)) (not (tree-change-member (gather-symbols test1) (cddr prev-f)))) (set! test2 (remove test1 test2)) (test1-in-test2)) ;; look for test2 as member of test1 ((and (pair? test1) (eq? (car test1) 'and) (member test2 (cdr test1)) (or (eq? (car prev-f) 'when) ; prev-f has to be when or 1-arm if (null? (cdddr prev-f))) (not (side-effect? test1 env)) (not (tree-change-member (gather-symbols test2) (cddr prev-f)))) (set! test1 (remove test2 test1)) (test2-in-test1)) ;; look for some intersection of test1 and test2 ((and (pair? test1) (pair? test2) (eq? (car test1) 'and) (eq? (car test2) 'and) (not (side-effect? test1 env)) (not (side-effect? test2 env)) (not (tree-change-member (gather-symbols test2) (cddr prev-f)))) (let ((intersection ()) (new-test1 ()) (new-test2 ())) (for-each (lambda (tst) (if (member tst test2) (set! intersection (cons tst intersection)) (set! new-test1 (cons tst new-test1)))) (cdr test1)) (for-each (lambda (tst) (if (not (member tst test1)) (set! new-test2 (cons tst new-test2)))) (cdr test2)) (when (pair? intersection) (if (null? new-test1) (if (null? new-test2) (begin (set! test1 `(and ,@(reverse intersection))) (equal-tests)) (when (and (or (eq? f-func 'when) (null? (cdddr f))) (or (pair? (cdr fs)) (eq? (car prev-f) 'when) (null? (cdddr prev-f)))) (set! test1 `(and ,@(reverse intersection))) (set! test2 `(and ,@(reverse new-test2))) (test1-in-test2))) (if (null? new-test2) (when (or (eq? (car prev-f) 'when) (null? (cdddr prev-f))) (set! test2 `(and ,@(reverse intersection))) (set! test1 `(and ,@(reverse new-test1))) (test2-in-test1)) (when (and (or (eq? f-func 'when) (null? (cdddr f))) (or (eq? (car prev-f) 'when) (null? (cdddr prev-f)))) ;; (... (if (and A B) (f C)) (when (and B C) (g E)) ...) -> (... (when B (when A (f C)) (when C (g E)))) (lint-format "perhaps ~A" caller (let ((outer-test (if (null? (cdr intersection)) (car intersection) `(and ,@(reverse intersection))))) (set! new-test1 (if (null? (cdr new-test1)) (car new-test1) `(and ,@(reverse new-test1)))) (set! new-test2 (if (null? (cdr new-test2)) (car new-test2) `(and ,@(reverse new-test2)))) (lists->string `(... ,prev-f ,f ...) `(... (when ,outer-test (when ,new-test1 ,@(cddr prev-f)) (when ,new-test2 ,@(cddr f))) ,@(if (null? (cdr fs)) () '(...))))))))))))))))) ;; -------- ;; check for repeated calls, but only one arg currently can change (more args = confusing separation in code) (let ((feq (and (pair? prev-f) (pair? f) (eq? f-func (car prev-f)) (or (equal? (cdr f) (cdr prev-f)) (do ((fp (cdr f) (cdr fp)) (pp (cdr prev-f) (cdr pp)) (i 1 (+ i 1))) ((or (and (null? pp) (null? fp)) (not (pair? pp)) (not (pair? fp)) (if (= i repeat-arg) ; ignore the arg that's known to be changing (side-effect? (car pp) env) (and (not (equal? (car pp) (car fp))) (or (positive? repeat-arg) (and (set! repeat-arg i) ; call this one the changer #f))))) (and (null? pp) (null? fp)))))))) (if feq (set! repeats (+ repeats 1))) (when (or (not feq) (= ctr (- len 1))) ; this assumes we're not returning the last value? (when (and (> repeats 2) (not (hash-table-ref syntaces (car prev-f)))) ; macros should be ok here if args are constants (if (zero? repeat-arg) ; simple case -- all exprs are identical (let ((step 'i)) (if (tree-member step prev-f) (set! step (find-unique-name prev-f))) (lint-format "perhaps ~A... ->~%~NC(do ((~A 0 (+ ~A 1))) ((= ~A ~D)) ~A)" caller (truncated-list->string prev-f) pp-left-margin #\space step step step (+ repeats 1) prev-f)) (let ((fs-end (if (not feq) fs (cdr fs))) (args ()) (constants? #t) (func-name (car prev-f)) (new-arg (if (tree-member 'arg prev-f) (find-unique-name prev-f) 'arg))) (do ((p start-repeats (cdr p))) ((eq? p fs-end)) (set! args (cons (list-ref (car p) repeat-arg) args)) (if constants? (set! constants? (code-constant? (car args))))) (let ((func (if (and (= repeat-arg 1) (null? (cddar start-repeats))) func-name `(lambda (,new-arg) ,(let ((call (copy prev-f))) (list-set! call repeat-arg new-arg) call))))) (if constants? (lint-format "perhaps ~A... ->~%~NC(for-each ~S '(~{~S~^ ~}))" caller (truncated-list->string (car start-repeats)) pp-left-margin #\space func (map unquoted (reverse args))) (let ((v (var-member func-name env))) (if (or (and (var? v) (memq (var-ftype v) '(define define* lambda lambda*))) (procedure? (symbol->value func-name *e*))) ;; (let () (write-byte 0) (write-byte 1) (write-byte 2) (write-byte 3) (write-byte 4)) -> ;; (for-each write-byte '(0 1 2 3 4)) (lint-format "perhaps ~A... ->~%~NC(for-each ~S (vector ~{~S~^ ~}))" caller ;; vector rather than list because it is easier on the GC (list copies in s7) (truncated-list->string (car start-repeats)) pp-left-margin #\space func (reverse args)) (if (not (or (var? v) (macro? (symbol->value func-name *e*)))) ;; (let () (writ 0) (writ 1) (writ 2) (writ 3) (writ (* x 2))) -> (for-each writ (vector 0 1 2 3 (* x 2))) (lint-format "assuming ~A is not a macro, perhaps ~A" caller func-name (lists->string (list '... (car start-repeats) '...) `(for-each ,func (vector ,@(reverse args))))))))))))) (set! repeats 0) (set! repeat-arg 0) (set! start-repeats fs))) ;; -------- (if (pair? f) (begin (set! f-len (length f)) (if (eq? f-func 'begin) (lint-format "redundant begin: ~A" caller (truncated-list->string f)))) (begin (if (symbol? f) (set-ref f caller f env)) (set! f-len 0))) ;; set-car! + set-cdr! here is usually "clever" code assuming eq?ness, so we can't rewrite it using cons ;; but copy does not create a new cons... [if at end of body, the return values will differ] (when (= f-len prev-len 3) (when (and (memq f-func '(set-car! set-cdr!)) ; ...(set-car! x (car y)) (set-cdr! x (cdr y))... -> (copy y x) (memq (car prev-f) '(set-car! set-cdr!)) (not (eq? (car prev-f) f-func)) (equal? (cadr f) (cadr prev-f))) (let ((ncar (caddr (if (eq? f-func 'set-car!) f prev-f))) (ncdr (caddr (if (eq? f-func 'set-car!) prev-f f)))) (if (and (pair? ncar) (eq? (car ncar) 'car) (pair? ncdr) (eq? (car ncdr) 'cdr) (equal? (cadr ncar) (cadr ncdr))) (lint-format "perhaps ~A~A ~A~A -> ~A" caller (if (= ctr 0) "" "...") (truncated-list->string prev-f) (truncated-list->string f) (if (= ctr (- len 1)) "" "...") `(copy ,(cadr ncar) ,(cadr f)))))) ;; successive if's that can be combined into case ;; else in last if could be accommodated as well (when (and (not rewrote-already) (eq? f-func 'if) (eq? (car prev-f) 'if) (pair? (cadr f)) (pair? (cadr prev-f)) (= (length f) 3) (= (length prev-f) 3) (memq (caadr prev-f) '(eq? eqv? = char=?)) ; not memx (memq (caadr f) '(eq? eqv? = char=?))) (let ((a1 (cadadr prev-f)) (a2 (caddr (cadr prev-f))) (b1 (cadadr f)) (b2 (caddr (cadr f)))) ; other possibilities are never hit (when (and (equal? a1 b1) (code-constant? a2) (code-constant? b2) (not (tree-change-member (list a1) (cddr prev-f)))) ; or any symbol in a1? (set! rewrote-already #t) ;; (... (if (= x 1) (display y)) (if (= x 2) (f y)) ...) -> (case x ((1) (display y)) ((2) (f y)) ((3) (display z))) (lint-format "perhaps ~A" caller (lists->string `(... ,prev-f ,f ...) `(case ,a1 ((,(unquoted a2)) ,@(unbegin (caddr prev-f))) ((,(unquoted b2)) ,@(unbegin (caddr f))) ,@(do ((more ()) (nfs (cdr fs) (cdr nfs))) ((let ((nf (if (pair? nfs) (car nfs) ()))) (not (and (pair? nf) (eq? (car nf) 'if) (= (length nf) 3) (pair? (cadr nf)) (memq (caadr nf) '(eq? eqv? = char=?)) (equal? a1 (cadadr nf)) (code-constant? (caddr (cadr nf)))))) ;; maybe add (not (tree-change-member (list a1) (cddr last-f))) ;; but it never is needed (reverse more)) (if (pair? nfs) (set! more (cons (cons (list (unquoted (caddr (cadar nfs)))) (unbegin (caddar nfs))) more)))))))))) (when (and (eq? f-func 'set!) (eq? (car prev-f) 'set!)) (let ((arg1 (caddr prev-f)) (arg2 (caddr f)) (settee (cadr f))) (if (and (or (and (equal? settee arg1) ; (set! x y) (set! y x) (equal? arg2 (cadr prev-f))) (and (equal? settee (cadr prev-f)) ; (set! x y) (set! x y) (equal? arg1 arg2))) (not (tree-equal-member settee arg2))) (lint-format "this pair of set!s looks odd: ~A" caller `(... ,prev-f ,f ...))) (cond ((not (eq? settee (cadr prev-f))) (if (and (symbol? (cadr prev-f)) ; (set! x (A)) (set! y (A)) -> (set! x (A)) (set! y x) (pair? arg1) ; maybe more trouble than it's worth (equal? arg1 arg2) (not (eq? (car arg1) 'quote)) (hash-table-ref no-side-effect-functions (car arg1)) (not (tree-unquoted-member (cadr prev-f) arg1)) (not (side-effect? arg1 env)) (not (maker? arg1))) (lint-format "perhaps ~A" caller (lists->string f `(set! ,settee ,(cadr prev-f)))))) ((not (and (pair? arg2) ; (set! x 0) (set! x 1) -> "this could be omitted: (set! x 0)" (tree-unquoted-member settee arg2))) (if (not (or (side-effect? arg1 env) (side-effect? arg2 env))) (lint-format "this could be omitted: ~A" caller prev-f))) ((and (pair? arg1) ; (set! x (cons 1 z)) (set! x (cons 2 x)) -> (set! x (cons 2 (cons 1 z))) (pair? arg2) (eq? (car arg1) 'cons) (eq? (car arg2) 'cons) (eq? settee (caddr arg2)) (not (eq? settee (cadr arg2)))) (lint-format "perhaps ~A ~A -> ~A" caller prev-f f `(set! ,settee (cons ,(cadr arg2) (cons ,@(cdr arg1)))))) ((and (pair? arg1) ; (set! x (append x y)) (set! x (append x z)) -> (set! x (append x y z)) (pair? arg2) (eq? (car arg1) 'append) (eq? (car arg2) 'append) (eq? settee (cadr arg1)) (eq? settee (cadr arg2)) (not (tree-memq settee (cddr arg1))) (not (tree-memq settee (cddr arg2)))) (lint-format "perhaps ~A ~A -> ~A" caller prev-f f `(set! ,settee (append ,settee ,@(cddr arg1) ,@(cddr arg2))))) ((and (= (tree-count1 settee arg2 0) 1) ; (set! x y) (set! x (+ x 1)) -> (set! x (+ y 1)) (or (not (pair? arg1)) (< (tree-leaves arg1) 5))) (lint-format "perhaps ~A ~A ->~%~NC~A" caller prev-f f pp-left-margin #\space (object->string `(set! ,settee ,(tree-subst arg1 settee arg2))))))))) (if (< ctr (- len 1)) (begin ; f is not the last form, so its value is ignored (if (and (escape? f env) (pair? (cdr fs)) ; do special case (every? (lambda (arg) (not (and (symbol? arg) (let ((v (var-member arg env))) (and (var? v) (eq? (var-initial-value v) :call/cc)))))) (cdr f))) (if (= ctr (- len 2)) ;; (let () (error 'oops "an error") #t) (lint-format "~A makes this pointless: ~A" caller (truncated-list->string f) (truncated-list->string (cadr fs))) ;; (begin (stop) (exit 6) (print 4) (stop)) (lint-format "~A makes the rest of the body unreachable: ~A" caller (truncated-list->string f) (truncated-list->string (list '... (cadr fs) '...))))) (check-returns caller f env)) ;; here f is the last form in the body (when (and (pair? prev-f) (pair? (cdr prev-f))) (case (car prev-f) ((display write write-char write-byte) (if (and (equal? f (cadr prev-f)) (not (side-effect? f env))) ;; (cond ((= x y) y) (else (begin (display x) x))) (lint-format "~A returns its first argument, so this could be omitted: ~A" caller (car prev-f) (truncated-list->string f)))) ((vector-set! float-vector-set! int-vector-set! byte-vector-set! string-set! list-set! hash-table-set! let-set! set-car! set-cdr!) (if (equal? f (list-ref prev-f (- (length prev-f) 1))) ;; (begin (vector-set! x 0 (* y 2)) (* y 2)) (lint-format "~A returns the new value, so this could be omitted: ~A" caller (car prev-f) (truncated-list->string f))) (if (and (pair? f) (pair? (cdr f)) (eq? (cadr prev-f) (cadr f)) (not (code-constant? (cadr f))) (case (car prev-f) ((vector-set! float-vector-set! int-vector-set!) (memq f-func '(vector-ref float-vector-ref int-vector-ref))) ((list-set!) (eq? f-func 'list-ref)) ((string-set!) (eq? f-func 'string-ref)) ((set-car!) (eq? f-func 'car)) ((set-cdr!) (eq? f-func 'cdr)) (else #f)) (or (memq f-func '(car cdr)) ; no indices (and (pair? (cddr f)) ; for the others check that indices match (equal? (caddr f) (caddr prev-f)) (pair? (cdddr prev-f)) (not (pair? (cddddr prev-f))) (not (pair? (cdddr f))) (not (side-effect? (caddr f) env))))) ;; (let ((x (list 1 2))) (set-car! x 3) (car x)) (lint-format "~A returns the new value, so this could be omitted: ~A" caller (car prev-f) (truncated-list->string f)))) ((copy) (if (or (and (null? (cddr prev-f)) (equal? (cadr prev-f) f)) (and (pair? (cddr prev-f)) (null? (cdddr prev-f)) (equal? (caddr prev-f) f))) (lint-format "~A returns the new value, so ~A could be omitted" caller (truncated-list->string prev-f) (truncated-list->string f)))) ((set! define define* define-macro define-constant define-macro* defmacro defmacro* define-expansion define-bacro define-bacro*) (cond ((not (and (pair? (cddr prev-f)) ; (set! ((L 1) 2)) an error, but lint should keep going (or (and (equal? (caddr prev-f) f) ; (begin ... (set! x (...)) (...)) (not (side-effect? f env))) (and (symbol? f) ; (begin ... (set! x ...) x) (eq? f (cadr prev-f))) ; also (begin ... (define x ...) x) (and (not (eq? (car prev-f) 'set!)) (pair? (cadr prev-f)) ; (begin ... (define (x...)...) x) (eq? f (caadr prev-f))))))) ((not (memq (car prev-f) '(define define*))) (lint-format "~A returns the new value, so this could be omitted: ~A" caller (car prev-f) (truncated-list->string f))) ((symbol? (cadr prev-f)) (lint-format "perhaps omit ~A and return ~A" caller (cadr prev-f) (caddr prev-f))) ((= (tree-count2 f body 0) 2) ;; (let () (define (f1 x) (+ x 1)) f1) -> (lambda (x) ...) (lint-format "perhaps omit ~A, and change ~A" caller f (lists->string `(,(car prev-f) ,(cadr prev-f) ...) `(,(if (eq? (car prev-f) 'define) 'lambda 'lambda*) ,(cdadr prev-f) ...)))) (else (lint-format "~A returns the new value, so this could be omitted: ~A" caller (car prev-f) f))))))) ; possibly still not right if letrec? ;; needs f fs prev-f dpy-f dpy-start ctr len ;; trap lint-format (let ((dpy-case (and (pair? f) (memq f-func '(display write newline write-char write-string))))) ; flush-output-port? (when (and dpy-case (not dpy-start)) (set! dpy-f fs) (set! dpy-start ctr)) (when (and (integer? dpy-start) (> (- ctr dpy-start) (if dpy-case 1 2)) (or (= ctr (- len 1)) (not dpy-case))) ;; display sequence starts at dpy-start, goes to ctr (prev-f) unless not dpy-case (let ((ctrl-string "") (args ()) (dctr 0) (dpy-last (if (not dpy-case) prev-f f)) (op (write-port (car dpy-f))) (exprs (make-list (if dpy-case (- ctr dpy-start -1) (- ctr dpy-start)) ()))) (define* (gather-format str (arg :unset)) (set! ctrl-string (string-append ctrl-string str)) (unless (eq? arg :unset) (set! args (cons arg args)))) (call-with-exit (lambda (done) (for-each (lambda (d) (if (not (equal? (write-port d) op)) (begin (lint-format "unexpected port change: ~A -> ~A in ~A" caller op (write-port d) d) ; ?? (done))) (list-set! exprs dctr d) (set! dctr (+ dctr 1)) (gather-format (display->format d)) (when (eq? d dpy-last) ; op can be null => send to (current-output-port), return #f or # ;; (begin (display x) (newline) (display y) (newline)) -> (format () "~A~%~A~%" x y) (lint-format "perhaps ~A" caller (lists->string `(... ,@exprs) `(format ,op ,ctrl-string ,@(reverse args)))) (done))) dpy-f)))) (set! dpy-start #f)) (unless dpy-case (set! dpy-start #f))) (if (and (pair? f) (memq head '(defmacro defmacro* define-macro define-macro* define-bacro define-bacro*)) (tree-member 'unquote f)) (lint-format "~A probably has too many unquotes: ~A" caller head (truncated-list->string f))) (set! prev-f f) (set! prev-len f-len) (set! lint-current-form f) (if (= ctr (- len 1)) (set! env (lint-walk caller f env)) (begin (set! lint-mid-form f) (let ((e (lint-walk caller f env))) (if (and (pair? e) (not (memq (var-name (car e)) '(:lambda :dilambda)))) (set! env e))))) (set! lint-current-form #f) (set! lint-mid-form #f) ;; need to put off this ref tick until we have a var for it (lint-walk above) (when (and (= ctr (- len 1)) (pair? f) (pair? (cdr f))) (if (and (pair? (cadr f)) (memq f-func '(define define* define-macro define-constant define-macro* define-expansion define-bacro define-bacro*))) (set-ref (caadr f) caller f env) (if (memq f-func '(defmacro defmacro*)) (set-ref (cadr f) caller f env)))) )) (set! lint-mid-form old-mid-form) (set! lint-current-form old-current-form))) env) (define (return-walker last func) (if (not (pair? last)) (func last) (case (car last) ((begin let let* letrec letrec* when unless with-baffle with-let) (when (pair? (cdr last)) (let ((len (length last))) (when (positive? len) (return-walker (list-ref last (- len 1)) func))))) ((if) (when (and (pair? (cdr last)) (pair? (cddr last))) (return-walker (caddr last) func) (if (pair? (cdddr last)) (return-walker (cadddr last) func)))) ((cond) (for-each (lambda (c) (if (and (pair? c) (pair? (cdr c))) (return-walker (list-ref c (- (length c) 1)) func))) (cdr last))) ((case) (when (and (pair? (cdr last)) (pair? (cddr last))) (for-each (lambda (c) (if (and (pair? c) (pair? (cdr c))) (return-walker (list-ref c (- (length c) 1)) func))) (cddr last)))) ((do) (if (and (pair? (cdr last)) (pair? (cddr last)) (pair? (caddr last)) (pair? (cdaddr last))) (return-walker (list-ref (caddr last) (- (length (caddr last)) 1)) func))) ((set!) (if (and (pair? (cdr last)) (pair? (cddr last))) (func (caddr last)))) (else (func last)) ; includes quote ;; call-with-exit et al also or|and ;; or|and -- call return-walker on each entry? ;; call-with-exit: walker on last on body, and scan for return func, walker on arg(s...)->values? ))) (define (check-sequence-constant function-name last) (return-walker last (lambda (in-seq) (when (or (not (pair? in-seq)) (eq? (car in-seq) 'quote)) (let ((seq (if (and (pair? in-seq) (pair? (cdr in-seq))) ; (quote . 1)?? (cadr in-seq) in-seq))) (when (and (sequence? seq) (not (zero? (length seq)))) (lint-format "returns ~A constant: ~A~S" function-name ; (define-macro (m a) `(+ 1 a)) (if (pair? seq) (values "a list" "'" seq) (values (prettify-checker-unq (->lint-type in-seq)) "" seq))) (throw 'sequence-constant-done))))))) ; just report one constant -- the full list is annoying (define lint-function-body #f) ; a momentary kludge?? (define (lint-walk-function-body definer function-name args body env) ;; walk function body, with possible doc string at the start (when (and (pair? body) (pair? (cdr body)) (string? (car body))) (if *report-doc-strings* (lint-format "old-style doc string: ~S, in s7 use 'documentation:~%~NC~A" function-name (car body) (+ lint-left-margin 4) #\space (lint-pp `(define ,function-name (let ((documentation ,(car body))) (,(if (eq? definer 'define) 'lambda (if (eq? definer 'define*) 'lambda* definer)) ,args ,@(cdr body))))))) (set! body (cdr body))) ; ignore old-style doc-string ;; (set! arg ...) never happens as last in body ;; but as first in body, it happens ca 100 times (if (and (pair? body) (pair? (car body)) (eq? (caar body) 'set!) (or (eq? (cadar body) args) (and (pair? args) (memq (cadar body) args)))) ;; (define (f21 x y) (set! x 3) (+ y 1)) (lint-format "perhaps ~A" function-name (lists->string (car body) `(let ((,(cadar body) ,(caddar body))) ...)))) ;; as first in let of body, maybe a half-dozen (let ((tag 'yup)) (catch 'sequence-constant-done (lambda () (check-sequence-constant function-name (list-ref body (- (length body) 1))) ; some of these are innocuous -- lambda forms in midst of outer body etc (set! tag 'nope)) (lambda args #f)) (if (eq? tag 'yup) (let ((v (var-member function-name env))) (if (var? v) (set! (var-retcons v) #t))))) (set! lint-function-body body) (lint-walk-body function-name definer body env)) (define (lint-walk-function definer function-name args body form env) ;; check out function arguments (adding them to the current env), then walk its body ;; first check for (define (hi...) (ho...)) where ho has no opt args (and try to ignore possible string constant doc string) (when (eq? definer 'define) (let ((bval (if (and (pair? body) (string? (car body))) (cdr body) ; strip away the (old-style) documentation string body))) (cond ((not (and (pair? bval) ; not (define (hi a) . 1)! (pair? (car bval)) (null? (cdr bval)) (symbol? (caar bval))))) ; not (define (hi) ((if #f + abs) 0)) ((or (equal? args (cdar bval)) (and (hash-table-ref reversibles (caar bval)) (equal? args (reverse (cdar bval))))) (let* ((cval (caar bval)) (p (symbol->value cval *e*)) (ary (arity p))) (if (or (procedure? p) (let ((e (var-member cval env) )) (and e (var? e) (symbol? (var-ftype e)) (let ((def (var-initial-value e)) (e-args (var-arglist e))) (and (pair? def) (memq (var-ftype e) '(define lambda)) (or (and (null? args) (null? e-args)) (and (symbol? args) (symbol? e-args)) (and (pair? args) (pair? e-args) (= (length args) (length e-args))))))))) (lint-format "~A~A could be (define ~A ~A)" function-name (if (and (procedure? p) (not (= (car ary) (cdr ary))) (not (= (length args) (cdr ary)))) (format #f "leaving aside ~A's optional arg~P, " cval (- (cdr ary) (length args))) "") function-name function-name (if (equal? args (cdar bval)) cval (hash-table-ref reversibles (caar bval)))) (if (and (null? args) ; perhaps this can be extended to any equal args (null? (cdar bval))) ;; (define (getservent) (getserv)) -> (define getservent getserv) (lint-format "~A could probably be ~A" function-name (truncated-list->string form) (truncated-list->string `(define ,function-name ,cval))))))) ((and (or (symbol? args) (and (pair? args) (negative? (length args)))) (eq? (caar bval) 'apply) (pair? (cdar bval)) (symbol? (cadar bval)) (not (memq (cadar bval) '(and or))) (pair? (cddar bval)) (or (and (eq? args (caddar bval)) (null? (cdddar bval))) (and (pair? args) (equal? (cddar bval) (proper-list args))))) ;; (define (f1 . x) (apply + x)) -> (define f1 +) (lint-format "~A could be (define ~A ~A)" function-name function-name function-name (cadar bval))) ((and (hash-table-ref combinable-cxrs (caar bval)) (pair? (cadar bval))) ((lambda* (cr arg) (and cr (< (length cr) 5) (pair? args) (null? (cdr args)) (eq? (car args) arg) (let ((f (symbol "c" cr "r"))) (if (eq? f function-name) ;; (define (cadddr l) (caddr (cdr l))) (lint-format "this redefinition of ~A is pointless (use (with-let (unlet)...) or #_~A)" definer function-name function-name) ;; (define (f1 x) (cdr (car x))) -> (define f1 cdar) (lint-format "~A could be (define ~A ~A)" function-name function-name function-name f))))) (combine-cxrs (car bval)))) ((not (and (memq (caar bval) '(list-ref list-tail)) (pair? (cdar bval)) (pair? (cddar bval)) (pair? args) (eq? (car args) (cadar bval)) (null? (cdr args))))) ((eq? (caar bval) 'list-ref) (case (caddar bval) ((0) (lint-format "~A could be (define ~A car)" function-name function-name function-name)) ((1) (lint-format "~A could be (define ~A cadr)" function-name function-name function-name)) ((2) (lint-format "~A could be (define ~A caddr)" function-name function-name function-name)) ((3) (lint-format "~A could be (define ~A cadddr)" function-name function-name function-name)))) (else (case (caddar bval) ((1) (lint-format "~A could be (define ~A cdr)" function-name function-name function-name)) ((2) (lint-format "~A could be (define ~A cddr)" function-name function-name function-name)) ((3) (lint-format "~A could be (define ~A cdddr)" function-name function-name function-name)) ((4) (lint-format "~A could be (define ~A cddddr)" function-name function-name function-name))))))) (let ((fvar (and (symbol? function-name) (make-fvar :name (if (memq definer '(lambda lambda*)) :lambda (if (eq? definer 'dilambda) :dilambda function-name)) :ftype definer :initial-value form :env env :arglist (if (memq definer '(lambda lambda*)) (cadr form) ((if (memq definer '(defmacro defmacro*)) caddr cdadr) form)))))) (when fvar (let ((fvar-let (cdr fvar))) (set! (fvar-let 'decl) (catch #t (lambda () (case definer ((lambda) (set! (fvar-let 'allow-other-keys) #t) (eval (list definer (cadr form) #f))) ((lambda*) (set! (fvar-let 'allow-other-keys) (eq? (last-par (cadr form)) :allow-other-keys)) (eval (list definer (copy (cadr form)) #f))) ; eval can remove :allow-other-keys! ((define*) (set! (fvar-let 'allow-other-keys) (eq? (last-par (cdadr form)) :allow-other-keys)) (eval (list definer (cons '_ (copy (cdadr form))) #f))) ((defmacro defmacro*) (set! (fvar-let 'allow-other-keys) (or (not (eq? definer 'defmacro*)) (eq? (last-par (caddr form)) :allow-other-keys))) (eval (list definer '_ (caddr form) #f))) ((define-constant) (set! (fvar-let 'allow-other-keys) #t) (eval (list 'define (cons '_ (cdadr form)) #f))) (else (set! (fvar-let 'allow-other-keys) (or (not (memq definer '(define-macro* define-bacro*))) (eq? (last-par (cdadr form)) :allow-other-keys))) (eval (list definer (cons '_ (cdadr form)) #f))))) (lambda args 'error))))) (if (null? args) (begin (if (memq definer '(define* lambda* defmacro* define-macro* define-bacro*)) (lint-format "~A could be ~A" ; (define* (f1) 32) function-name definer (symbol (substring (symbol->string definer) 0 (- (length (symbol->string definer)) 1))))) (let ((cur-env (if fvar (cons fvar env) env))) (let ((nvars (let ((e (lint-walk-function-body definer function-name args body cur-env))) (and (not (eq? e cur-env)) (env-difference function-name e cur-env ()))))) (if (pair? nvars) (report-usage function-name definer nvars cur-env))) cur-env)) (if (not (or (symbol? args) (pair? args))) (begin (lint-format "strange ~A parameter list ~A" function-name definer args) env) (let ((args-as-vars (if (symbol? args) ; this is getting arg names to add to the environment (list (make-var :name args :definer 'parameter)) (map (lambda (arg) (if (symbol? arg) (if (memq arg '(:rest :allow-other-keys)) (values) ; omit :rest and :allow-other-keys (make-var :name arg :definer 'parameter)) (if (not (and (pair? arg) (= (length arg) 2) (memq definer '(define* lambda* defmacro* define-macro* define-bacro* definstrument define*-public)))) (begin (lint-format "strange parameter for ~A: ~S" function-name definer arg) (values)) (begin (if (not (or (cadr arg) ; (define* (f4 (a #f)) a) (eq? definer 'define*-public))) ; who knows? (lint-format "the default argument value is #f in ~A ~A" function-name definer arg)) (make-var :name (car arg) :definer 'parameter))))) (proper-list args))))) (let* ((cur-env (cons (make-var :name :let :initial-value form :definer definer) (append args-as-vars (if fvar (cons fvar env) env)))) (nvars (let ((e (lint-walk-function-body definer function-name args body cur-env))) (and (not (eq? e cur-env)) (env-difference function-name e cur-env ()))))) (report-usage function-name definer (append (or nvars ()) args-as-vars) cur-env)) (when (and (var? fvar) (memq definer '(define lambda define-macro))) ;; look for unused parameters that are passed a value other than #f (let ((set ()) (unused ())) (for-each (lambda (arg-var) (if (zero? (var-ref arg-var)) (if (positive? (var-set arg-var)) (set! set (cons (var-name arg-var) set)) (if (not (memq (var-name arg-var) '(documentation signature iterator?))) (set! unused (cons (var-name arg-var) unused)))))) args-as-vars) (when (or (pair? set) (pair? unused)) (let ((proper-args (args->proper-list args))) (let ((sig (var-signature fvar)) (len (+ (length proper-args) 1))) (if (not sig) (set! sig (make-list len #t)) (if (< (length sig) len) (set! sig (copy sig (make-list len #t))))) (let ((siglist (cdr sig))) (for-each (lambda (arg) (if (memq arg unused) (set-car! siglist 'unused-parameter?) (if (memq arg set) (set-car! siglist 'unused-set-parameter?))) (set! siglist (cdr siglist))) proper-args)) (set! (var-signature fvar) sig)))))) (if fvar (cons fvar env) env)))))) (define (check-bool-cond caller form c1 c2 env) ;; (cond (x #f) (#t #t)) -> (not x) ;; c1/c2 = possibly combined, so in (cond (x #t) (y #t) (else #f)), c1: ((or x y) #t), so -> (or x y) (and (pair? c1) (= (length c1) 2) (pair? c2) (pair? (cdr c2)) (memq (car c2) '(#t else)) (or (and (boolean? (cadr c1)) (or (and (null? (cddr c2)) (boolean? (cadr c2)) (not (equal? (cadr c1) (cadr c2))) ; handled elsewhere (lint-format "perhaps ~A" caller (lists->string form (if (eq? (cadr c1) #t) (car c1) (simplify-boolean `(not ,(car c1)) () () env))))) (and (not (cadr c1)) ; (cond (x #f) (else y)) -> (and (not x) y) (let ((cc1 (simplify-boolean `(not ,(car c1)) () () env))) (lint-format "perhaps ~A" caller (lists->string form (if (null? (cddr c2)) `(and ,cc1 ,(cadr c2)) `(and ,cc1 (begin ,@(cdr c2)))))))) (and (pair? (car c1)) ; (cond ((null? x) #t) (else y)) -> (or (null? x) y) (eq? (return-type (caar c1) env) 'boolean?) (lint-format "perhaps ~A" caller (lists->string form (if (null? (cddr c2)) `(or ,(car c1) ,(cadr c2)) `(or ,(car c1) (begin ,@(cdr c2))))))))) (and (boolean? (cadr c2)) (null? (cddr c2)) (not (equal? (cadr c1) (cadr c2))) ;; (cond ((= 3 (length eq)) (caddr eq)) (else #f)) -> (and (= 3 (length eq)) (caddr eq)) (lint-format "perhaps ~A" caller (lists->string form (if (cadr c2) `(or (not ,(car c1)) ,(cadr c1)) (if (and (pair? (car c1)) (eq? (caar c1) 'and)) (append (car c1) (cdr c1)) `(and ,@c1))))))))) (define (case-branch test eqv-select exprs) (case (car test) ((eq? eqv? = equal? char=?) (if (equal? eqv-select (cadr test)) `((,(unquoted (caddr test))) ,@exprs) `((,(unquoted (cadr test))) ,@exprs))) ((memq memv member) `(,(unquoted (caddr test)) ,@exprs)) ((not) `((#f) ,@exprs)) ((null?) `((()) ,@exprs)) ((eof-object?) `((#) ,@exprs)) ((zero?) `((0 0.0) ,@exprs)) ((boolean?) `((#t #f) ,@exprs)) ((char-ci=?) (if (equal? eqv-select (cadr test)) `(,(list (caddr test) (other-case (caddr test))) ,@exprs) `(,(list (cadr test) (other-case (cadr test))) ,@exprs))) (else `(,(map (lambda (p) (case (car p) ((eq? eqv? = equal? char=?) (unquoted ((if (equal? eqv-select (cadr p)) caddr cadr) p))) ((memq memv member) (apply values (caddr p))) ((not) #f) ((null?) ()) ((eof-object?) #) ((zero?) (values 0 0.0)) ((boolean?) (values #t #f)) ((char-ci=?) (if (equal? eqv-select (cadr p)) (values (caddr p) (other-case (caddr p))) (values (cadr p) (other-case (cadr p))))) (else (error "oops")))) (cdr test)) ,@exprs)))) (define (cond->case eqv-select new-clauses) `(case ,eqv-select ,@(map (lambda (clause) (let ((test (car clause)) (exprs (cdr clause))) (if (null? exprs) ; cond returns the test result if no explicit results (set! exprs (list #t))) ; but all tests here return a boolean, and we win only if #t?? (memx is an exception) (if (memq test '(else #t)) `(else ,@exprs) (case-branch test eqv-select exprs)))) new-clauses))) (define (eqv-code-constant? x) (or (number? x) (char? x) (and (pair? x) (eq? (car x) 'quote) (or (symbol? (cadr x)) (and (not (pair? (cadr x))) (eqv-code-constant? (cadr x))))) (memq x '(#t #f () # # #)))) (define (cond-eqv? clause eqv-select or-ok) (if (not (pair? clause)) (memq clause '(else #t)) ;; it's eqv-able either directly or via memq/memv, or via (or ... eqv-able clauses) ;; all clauses involve the same (eventual case) selector (case (car clause) ((eq? eqv? = equal? char=? char-ci=?) (if (eqv-code-constant? (cadr clause)) (equal? eqv-select (caddr clause)) (and (eqv-code-constant? (caddr clause)) (equal? eqv-select (cadr clause))))) ((memq memv member) (and (equal? eqv-select (cadr clause)) (pair? (caddr clause)) (eq? (caaddr clause) 'quote) (or (not (eq? (car clause) 'member)) (every? (lambda (x) (or (number? x) (char? x) (symbol? x) (memq x '(#t #f () # # #)))) (cdr (caddr clause)))))) ((or) (and or-ok (every? (lambda (p) (cond-eqv? p eqv-select #f)) (cdr clause)))) ((not null? eof-object? zero? boolean?) (equal? eqv-select (cadr clause))) (else #f)))) (define (find-constant-exprs caller vars body) (if (or (tree-set-member '(call/cc call-with-current-continuation lambda lambda* define define* define-macro define-macro* define-bacro define-bacro* define-constant define-expansion) body) (let set-walk ((tree body)) ; generalized set! causes confusion (and (pair? tree) (or (and (eq? (car tree) 'set!) (pair? (cdr tree)) (pair? (cadr tree))) (set-walk (car tree)) (set-walk (cdr tree)))))) () (let ((refs (let ((vs (out-vars caller vars body))) (remove-if (lambda (v) (or (assq v vars) ; vars = do-loop steppers (memq v (cadr vs)))) ; (cadr vs) = sets (car vs)))) ;; refs are the external variables accessed in the do-loop body ;; that are not set or shadowed or changed (vector-set! etc) (constant-exprs ())) (let expr-walk ((tree body)) (when (pair? tree) (if (let all-ok? ((tree tree)) (if (symbol? tree) (memq tree refs) (or (not (pair? tree)) (eq? (car tree) 'quote) (and (hash-table-ref no-side-effect-functions (car tree)) (or (not (hash-table-ref syntaces (car tree))) (memq (car tree) '(if begin cond or and unless when))) (not (hash-table-ref makers (car tree))) (list? (cdr tree)) (every? all-ok? (cdr tree)))))) (if (not (or (eq? (car tree) 'quote) (member tree constant-exprs))) (set! constant-exprs (cons tree constant-exprs))) (begin (if (pair? (car tree)) (expr-walk (car tree))) (when (pair? (cdr tree)) (let ((f (cdr tree))) (case (car f) ((case) (when (and (pair? (cdr f)) (pair? (cddr f))) (expr-walk (cadr f)) (for-each (lambda (c) (expr-walk (cdr c))) (cddr f)))) ((letrec letrec*) (when (pair? (cddr f)) (for-each (lambda (c) (if (and (pair? c) (pair? (cdr c))) (expr-walk (cadr c)))) (cadr f)) (expr-walk (cddr f)))) ((let let*) (when (pair? (cddr f)) (if (symbol? (cadr f)) (set! f (cdr f))) (for-each (lambda (c) (if (and (pair? c) (pair? (cdr c))) (expr-walk (cadr c)))) (cadr f)) (expr-walk (cddr f)))) ((do) (when (and (list? (cadr f)) (list? (cddr f)) (pair? (cdddr f))) (for-each (lambda (c) (if (pair? (cddr c)) (expr-walk (caddr c)))) (cadr f)) (expr-walk (cdddr f)))) (else (for-each expr-walk f))))))))) (when (pair? constant-exprs) (set! constant-exprs (remove-if (lambda (p) (or (null? (cdr p)) (and (null? (cddr p)) (memq (car p) '(not -)) (symbol? (cadr p))) (tree-unquoted-member 'port-line-number p))) constant-exprs))) constant-exprs))) (define (partition-form start len) (let ((ps (make-vector len)) (qs (make-vector len))) (do ((i 0 (+ i 1)) (p start (cdr p))) ((= i len)) (set! (ps i) (cadar p)) (set! (qs i) (reverse (cadar p)))) (let ((header-len (length (ps 0)))) (let ((trailer-len header-len) (result-min-len header-len)) (do ((i 1 (+ i 1))) ((= i len)) (set! result-min-len (min result-min-len (length (ps i)))) (do ((k 1 (+ k 1)) (p (cdr (ps i)) (cdr p)) (f (cdr (ps 0)) (cdr f))) ((or (= k header-len) (not (pair? p)) (not (equal? (car p) (car f)))) (set! header-len k))) (do ((k 0 (+ k 1)) (q (qs i) (cdr q)) (f (qs 0) (cdr f))) ((or (= k trailer-len) (not (pair? q)) (not (equal? (car q) (car f)))) (set! trailer-len k)))) (if (= result-min-len header-len) (begin (set! header-len (- header-len 1)) (set! trailer-len 0))) (if (<= result-min-len (+ header-len trailer-len)) (set! trailer-len (- result-min-len header-len 1))) (values header-len trailer-len result-min-len))))) (define (one-call-and-dots body) ; body is unchanged here, so it's not interesting (if (< (tree-leaves body) 30) (if (null? (cdr body)) body (list (car body) '...)) (if (pair? (car body)) (list (list (caar body) '...)) (list (car body) '...)))) (define (replace-redundant-named-let caller form outer-name outer-args inner) (when (proper-list? outer-args) ; can be null (let ((inner-name (cadr inner)) (inner-args (caddr inner)) (inner-body (cdddr inner))) (do ((p outer-args (cdr p)) (a inner-args (cdr a))) ((or (null? p) (not (pair? a)) (not (pair? (car a))) (and (not (eq? (car p) (caar a))) (tree-memq (car p) inner-body))) ;; args can be reversed, but rarely match as symbols (when (and (null? p) (or (null? a) (and (null? (cdr a)) (code-constant? (cadar a))))) (let* ((args-match (do ((p outer-args (cdr p)) (a inner-args (cdr a))) ((or (null? p) (not (eq? (car p) (caar a))) (not (eq? (caar a) (cadar a)))) (null? p)))) (args-aligned (and (not args-match) (do ((p outer-args (cdr p)) (a inner-args (cdr a))) ((or (null? p) (not (eq? (car p) (cadar a)))) (null? p)))))) (when (or args-match args-aligned) (let ((definer (if (null? a) 'define 'define*)) (extras (if (and (pair? a) (quoted-null? (cadar a))) (list (list (caar a) ())) a))) ;; (define (f61 x) (let loop ((y x)) (if (positive? y) (loop (- y 1)) 0))) -> (define (f61 y) (if (positive? y) (f61 (- y 1)) 0)) (lint-format "~A ~A" caller (if (null? a) "perhaps" "a toss-up -- perhaps") (lists->string form `(,definer (,outer-name ,@(if args-match outer-args (do ((result ()) (p outer-args (cdr p)) (a inner-args (cdr a))) ((null? p) (reverse result)) (set! result (cons (caar a) result)))) ,@extras) ,@(tree-subst outer-name inner-name inner-body))))))))))))) (define (set!? form env) (and *report-any-!-as-setter* ; (inc! x) when inc! is unknown, assume it sets x (symbol? (car form)) (pair? (cdr form)) (or (symbol? (cadr form)) (and (pair? (cddr form)) (symbol? (caddr form)))) (not (var-member (car form) env)) (not (hash-table-ref built-in-functions (car form))) (let ((str (symbol->string (car form)))) (char=? (string-ref str (- (length str) 1)) #\!)))) (define (set-target name form env) (and (pair? form) (or (and (pair? (cdr form)) (or (eq? (cadr form) name) ; (pop! x) (and (pair? (cddr form)) ; (push! y x) (eq? (caddr form) name))) (or (eq? (car form) 'set!) ; (set! x y) (set!? form env))) (set-target name (car form) env) (set-target name (cdr form) env)))) (define (check-definee caller sym form env) (cond ((keyword? sym) ; (define :x 1) (lint-format "keywords are constants ~A" caller sym)) ((and (eq? sym 'pi) ; (define pi (atan 0 -1)) (member (caddr form) '((atan 0 -1) (acos -1) (* 2 (acos 0)) (* 4 (atan 1)) (* 4 (atan 1 1))))) (lint-format "~A is one of its many names, but pi is a predefined constant in s7" caller (caddr form))) ((constant? sym) ; (define most-positive-fixnum 432) (lint-format "~A is a constant in s7: ~A" caller sym form)) ((eq? sym 'quote) (lint-format "either a stray quote, or a real bad idea: ~A" caller (truncated-list->string form))) ((pair? sym) (check-definee caller (car sym) form env)) ((let ((v (var-member sym env))) (and (var? v) (eq? (var-definer v) 'define-constant) (not (equal? (caddr form) (var-initial-value v))))) => (lambda (v) (let ((line (if (and (pair? (var-initial-value v)) (positive? (pair-line-number (var-initial-value v)))) (format #f "(line ~D): " (pair-line-number (var-initial-value v))) ""))) (lint-format "~A in ~A is already a constant, defined ~A~A" caller sym (truncated-list->string form) line (truncated-list->string (var-initial-value v)))))))) (define binders (let ((h (make-hash-table))) (for-each (lambda (op) (set! (h op) #t)) '(let let* letrec letrec* do lambda lambda* define define* call/cc call-with-current-continuation define-macro define-macro* define-bacro define-bacro* define-constant define-expansion load eval eval-string require)) h)) (define lint-let-reduction-factor 3) ; maybe make this a global switch -- the higher this number, the fewer let-reduction suggestions (define walker-functions (let ((h (make-hash-table))) ;; ---------------- define ---------------- (let () (define (define-walker caller form env) (if (< (length form) 2) (begin (lint-format "~S makes no sense" caller form) env) (let ((sym (cadr form)) (val (cddr form)) (head (car form))) (if (symbol? sym) (begin (check-definee caller sym form env) (if (memq head '(define define-constant define-envelope define-public define*-public defmacro-public define-inlinable define-integrable define^)) (let ((len (length form))) (if (not (= len 3)) ; (define a b c) (lint-format "~A has ~A value~A?" caller (truncated-list->string form) (if (< len 3) (values "no" "") (values "too many" "s"))))) (lint-format "~A is messed up" caller (truncated-list->string form))) (if (not (pair? val)) env (begin (if (and (null? (cdr val)) (equal? sym (car val))) ; (define a a) (lint-format "this ~A is either not needed, or is an error: ~A" caller head (truncated-list->string form))) (if (not (pair? (car val))) (begin (cond ((and (not (memq caller '(module cond-expand))) (hash-table-ref other-identifiers sym)) => (lambda (p) (lint-format "~A is used before it is defined: ~A" caller sym form)))) (cons (make-var :name sym :initial-value (car val) :definer head) env)) (let ((e (lint-walk (if (and (pair? (car val)) (eq? (caar val) 'letrec)) 'define sym) (car val) env))) (if (or (not (pair? e)) (eq? e env) (not (memq (var-name (car e)) '(:lambda :dilambda)))) ; (define x (lambda ...)) (cons (make-var :name sym :initial-value (car val) :definer head) env) (begin (set! (var-name (car e)) sym) (let ((val (caddr form))) (when (and (eq? (car val) 'lambda) ; (define sym (lambda args (let name...))), let here happens rarely (proper-list? (cadr val)) (pair? (caddr val)) (null? (cdddr val)) (eq? (caaddr val) 'let) (symbol? (cadr (caddr val)))) (replace-redundant-named-let caller form sym (cadr val) (caddr val)))) ;; (define x (letrec ((y (lambda...))) (lambda (...) (y...)))) -> (define (x...)...) (let* ((let-form (cdaddr form)) (var (and (pair? (car let-form)) (null? (cdar let-form)) ; just one var in let/rec (caar let-form)))) ;; let-form here can be cdr of (lambda...) or (let|letrec ... lambda) (when (and (pair? var) (symbol? (car var)) (pair? (cdr let-form)) (pair? (cadr let-form)) (null? (cddr let-form)) ; just one form in the let/rec (pair? (cdr var)) (pair? (cadr var)) (pair? (cdadr var)) (eq? (caadr var) 'lambda) ; var is lambda (proper-list? (cadadr var))) ; it has no rest arg (let ((body (cadr let-form))) (when (and (eq? (car body) 'lambda) ; let/rec body is lambda calling var (proper-list? (cadr body)) ; rest args are a headache (pair? (caddr body))) ; (lambda (...) (...) where car is letrec func name (if (eq? (caaddr body) (car var)) (lint-format "perhaps ~A" caller (lists->string form `(define (,sym ,@(cadr body)) (let ,(car var) ,(map list (cadadr var) (cdaddr body)) ,@(cddadr var))))) (let ((call (find-call (car var) (caddr body)))) (when (and (pair? call) ; inner lambda body is (...some-expr...(sym...) ...) (= (tree-count1 (car var) (caddr body) 0) 1)) (let ((new-call `(let ,(car var) ,(map list (cadadr var) (cdr call)) ,@(cddadr var)))) (lint-format "perhaps ~A" caller (lists->string form `(define (,sym ,@(cadr body)) ,(tree-subst new-call call (caddr body))))))))))))) e))))))) ; symbol? sym ;; not (symbol? sym) (if (and (pair? sym) ; cadr form (pair? val) ; cddr form (not (pair? (car sym)))) ; pair would indicate a curried func or something equally stupid (let ((outer-args (cdr sym)) (outer-name (car sym))) (cond ((not *report-forward-functions*)) ;; need to ignore macro usages here -- this happens ca 20000 times! ((hash-table-ref other-identifiers (car sym)) => (lambda (p) (lint-format "~A is used before it is defined" caller (car sym))))) (if (and *report-boolean-functions-misbehaving* (symbol? (car sym)) (not (memq head '(lambda lambda*))) ; how to catch this case? -- this appears to be ignored (char=? #\? ((reverse (symbol->string (car sym))) 0))) (catch 'one-is-enough (lambda () (return-walker (list-ref val (- (length val) 1)) (lambda (last) (when (or (and (code-constant? last) (not (boolean? last)) (not (and (pair? last) (eq? (car last) 'quote) (boolean? (cadr last))))) (and (pair? last) (let ((sig (arg-signature (car last) env))) (and (pair? sig) (if (pair? (car sig)) (not (tree-set-member '(boolean? #t values) (car sig))) (not (memq (car sig) '(boolean? #t values)))))))) (lint-format "~A looks boolean, but it can return ~A" caller (car sym) (truncated-list->string last)) (throw 'one-is-enough))))) (lambda args #f))) (check-definee caller (car sym) form env) (when (pair? (car val)) (when (eq? (caar val) 'let) (when (pair? (cadar val)) (do ((inner-vars (cadar val)) (p outer-args (cdr p))) ((not (pair? p))) (cond ((assq (car p) inner-vars) => (lambda (v) (if (eq? (cadr v) (car p)) ;; (define (f70 a b) (let ((a a) (b b)) (+ a b))) (lint-format "in ~A this let binding is pointless: ~A" caller (truncated-list->string form) v))))))) ;; define + redundant named-let -- sometimes rewrites to define* (when (and (symbol? (cadar val)) (null? (cdr val))) (replace-redundant-named-let caller form outer-name outer-args (car val)))) ;; perhaps this block should be on a *report-* switch -- ;; it translates some internal defines into named lets ;; (or just normal lets, etc) ;; this is not redundant given the walk-body translations because here ;; we have the outer parameters and can check those against the inner ones ;; leading (sometimes) to much nicer rewrites. (when (and (eq? (caar val) 'define) ; define* does not happen here (pair? (cdr val)) (pair? (cadar val))) ; inner define (name ...) (let ((inner-name (caadar val)) (inner-args (cdadar val)) (inner-body (cddar val)) (outer-body (cdddr form))) (when (and (symbol? inner-name) (proper-list? inner-args) (pair? (car outer-body)) (= (tree-count1 inner-name outer-body 0) 1)) (let ((call (find-call inner-name outer-body))) (when (pair? call) (set! last-rewritten-internal-define (car val)) (let ((new-call (if (tree-memq inner-name inner-body) (if (and (null? inner-args) (null? outer-args)) (if (null? (cdr inner-body)) (car (tree-subst outer-name inner-name inner-body)) `(begin ,@(tree-subst outer-name inner-name inner-body))) `(let ,inner-name ,(if (null? inner-args) () (map list inner-args (cdr call))) ,@inner-body)) (if (or (null? inner-args) (and (equal? inner-args outer-args) (equal? inner-args (cdr call)))) (if (null? (cdr inner-body)) (car (tree-subst outer-name inner-name inner-body)) `(begin ,@(tree-subst outer-name inner-name inner-body))) `(let ,(map list inner-args (cdr call)) ,@inner-body))))) ;; (define (f11 a b) (define (f12 a b) (if (positive? a) (+ a b) b)) (f12 a b)) -> ;; (define (f11 a b) (if (positive? a) (+ a b) b)) (lint-format "perhaps ~A" caller (lists->string form `(,head ,sym ,@(let ((p (tree-subst new-call call outer-body))) (if (and (pair? p) (pair? (car p)) (eq? (caar p) 'begin)) (cdar p) p)))))))))))) (when (pair? outer-args) (if (repeated-member? (proper-list outer-args) env) (lint-format "~A parameter is repeated: ~A" caller head (truncated-list->string sym))) (cond ((memq head '(define* define-macro* define-bacro* define*-public)) (check-star-parameters outer-name outer-args env)) ((list-any? keyword? outer-args) (lint-format "~A parameter can't be a keyword: ~A" caller outer-name sym)) ((memq 'pi outer-args) (lint-format "~A parameter can't be a constant: ~A" caller outer-name sym))) ;; look for built-in names used as parameter names and used as functions internally(!) ;; this requires a tree walker to ignore (for example) (let loop ((string string))...) (for-each (lambda (p) (let ((par (if (pair? p) (car p) p))) (when (or (hash-table-ref built-in-functions par) (hash-table-ref syntaces par)) (let ((call (call-with-exit (lambda (return) (let loop ((tree (cddr form))) (if (pair? tree) (if (eq? (car tree) par) (return tree) (case (car tree) ((quote) #f) ((let let*) (if (pair? (cdr tree)) (if (symbol? (cadr tree)) (if (not (tree-memq par (caddr tree))) (loop (cdddr tree))) (if (not (tree-memq par (cadr tree))) (loop (cddr tree)))))) ((letrec letrec*) (if (and (pair? (cdr tree)) (not (tree-memq par (cadr tree)))) (loop (cddr tree)))) ((do) (if (and (pair? (cdr tree)) (pair? (cddr tree)) (not (tree-memq par (cadr tree)))) (loop (cdddr tree)))) (else (if (pair? (cdr tree)) (for-each loop (cdr tree))) (if (pair? (car tree)) (loop (car tree)))))))))))) (if (and (pair? call) (pair? (cdr call)) (not (eq? par (cadr call)))) (lint-format* caller ; (define (f50 abs) (abs -1)) (string-append (object->string outer-name) "'s parameter " (symbol->string par)) (string-append " is called " (truncated-list->string call)) ": find a less confusing parameter name!")))))) outer-args)) (when (and (eq? head 'define-macro) (pair? val) (null? (cdr val))) (let ((body (car val))) (if (and (null? outer-args) ; look for C macros translated as define-macro! -- this happens a lot sad to say (or (not (symbol? body)) (keyword? body)) (or (not (pair? body)) (and (eq? (car body) 'quote) (not (symbol? (cadr body))) (or (not (pair? (cadr body))) (eq? (caadr body) 'quote))) (not (or (memq (car body) '(quote quasiquote list cons append)) (tree-set-member '(#_{list} #_{apply_values} #_{append}) body))))) (lint-format "perhaps ~A or ~A" caller (lists->string form `(define ,outer-name ,(unquoted (car val)))) (truncated-list->string `(define (,outer-name) ,(unquoted (car val)))))) (when (pair? body) (case (car body) ((#_{list}) (when (and (quoted-symbol? (cadr body)) (proper-list? outer-args)) (if (and (equal? (cddr body) outer-args) (or (not (hash-table-ref syntaces (cadadr body))) ; (define-macro (x y) `(lambda () ,y)) (memq (cadadr body) '(set! define)))) (lint-format "perhaps ~A" caller (lists->string form `(define ,outer-name ,(cadadr body)))) (if (and (not (hash-table-ref syntaces (cadadr body))) (not (any-macro? (cadadr body) env)) (every? (lambda (a) (or (code-constant? a) (and (memq a outer-args) (= (tree-count1 a (cddr body) 0) 1)))) (cddr body))) ;; marginal -- there are many debatable cases here (lint-format "perhaps ~A" caller (lists->string form `(define (,outer-name ,@outer-args) (,(cadadr body) ,@(map unquoted (cddr body))))))))) (let ((pargs (args->proper-list outer-args))) (for-each (lambda (p) (if (and (pair? p) (eq? (car p) 'quote) (pair? (cdr p)) (pair? (cadr p)) (tree-set-member pargs (cadr p))) (lint-format "missing comma? ~A" caller form))) (cdr body)))) ((quote) ;; extra comma (unquote) is already caught elsewhere (if (and (pair? (cdr body)) (pair? (cadr body)) (tree-set-member (args->proper-list outer-args) (cadr body))) (lint-format "missing comma? ~A" caller form))))))) (if (and (eq? head 'definstrument) (string? (car val))) (set! val (cdr val))) (if (keyword? outer-name) env (lint-walk-function head outer-name outer-args val form env))) (begin ; not (and (pair? sym)...) (lint-format "strange form: ~A" head (truncated-list->string form)) (when (and (pair? sym) (pair? (car sym))) (let ((outer-args (cdr sym)) (outer-name (if (eq? head 'define*) (remove :optional (car sym)) (car sym)))) (if (symbol? (car outer-name)) ;; perhaps a curried definition -- as a public service, we'll rewrite the dumb thing (begin (lint-format "perhaps ~A" caller (lists->string form `(,head ,outer-name (lambda ,outer-args ,@(cddr form))))) (lint-walk-function head (car outer-name) (cdr outer-name) val form env)) ;val=(cddr form) I think (when (pair? (car outer-name)) (if (symbol? (caar outer-name)) (begin (lint-format "perhaps ~A" caller (lists->string form `(,head ,(car outer-name) (lambda ,(cdr outer-name) (lambda ,outer-args ,@(cddr form)))))) (lint-walk-function head (caar outer-name) (cdar outer-name) val form env)) (when (and (pair? (caar outer-name)) (symbol? (caaar outer-name))) (lint-format "perhaps ~A" caller (lists->string form `(,head ,(caar outer-name) (lambda ,(cdar outer-name) (lambda ,(cdr outer-name) (lambda ,outer-args ,@(cddr form))))))) (lint-walk-function head (caaar outer-name) (cdaar outer-name) val form env))))))) env)))))) (for-each (lambda (op) (hash-table-set! h op define-walker)) '(define define* define-constant define-macro define-macro* define-bacro define-bacro* define-expansion definstrument defanimal define-envelope ; for clm define-public define*-public defmacro-public define-inlinable define-integrable define^))) ; these give more informative names in Guile and scmutils (MIT-scheme)) ;; ---------------- dilambda ---------------- (let () (define (dilambda-walker caller form env) ;(format *stderr* "~A~%" form) (let ((len (length form))) (if (not (= len 3)) (begin (lint-format "dilambda takes two arguments: ~A" caller (truncated-list->string form)) env) (let ((getter (cadr form)) (setter (caddr form))) (check-call caller 'dilambda form env) (lint-walk caller setter env) (let ((e (lint-walk caller getter env))) ; goes to lint-walk-function -> :lambda as first in e (if (and (pair? e) (eq? (var-name (car e)) :lambda)) (set! (var-name (car e)) :dilambda)) e))))) (hash-table-set! h 'dilambda dilambda-walker)) ;; ---------------- lambda ---------------- (let () (define (lambda-walker caller form env) (let ((len (length form)) (head (car form))) (if (< len 3) (begin (lint-format "~A is messed up in ~A" caller head (truncated-list->string form)) env) (let ((args (cadr form))) (when (list? args) (let ((arglen (length args))) (if (null? args) (if (eq? head 'lambda*) ; (lambda* ()...) -> (lambda () ...) (lint-format "lambda* could be lambda ~A" caller form)) (begin ; args is a pair ; (lambda (a a) ...) (let ((val (caddr form))) (if (and (pair? val) (eq? (car val) 'let) (pair? (cadr val))) (do ((inner-vars (cadr val)) (p (cadr form) (cdr p))) ((not (pair? p))) (cond ((assq (car p) inner-vars) => (lambda (v) (if (eq? (cadr v) (car p)) (lint-format "in ~A this let binding is pointless: ~A" caller (truncated-list->string form) v)))))))) (if (repeated-member? (proper-list args) env) (lint-format "~A parameter is repeated: ~A" caller head (truncated-list->string args))) (if (eq? head 'lambda*) ; (lambda* (a :b) ...) (check-star-parameters head args env) (if (list-any? keyword? args) ; (lambda (:key) ...) (lint-format "lambda arglist can't handle keywords (use lambda*)" caller))))) (when (and (eq? head 'lambda) ; (lambda () (f)) -> f, (lambda (a b) (f a b)) -> f (not (eq? caller 'case-lambda)) (= len 3) (>= arglen 0)) ; not a dotted list (let ((body (caddr form))) (cond ((not (and (pair? body) (symbol? (car body)) (not (memq (car body) '(and or)))))) ((equal? args (cdr body)) ;; (lambda (a b) (> a b)) -> > (lint-format "perhaps ~A" caller (lists->string form (car body)))) ((equal? (reverse args) (cdr body)) (let ((rf (hash-table-ref reversibles (car body)))) ;; (lambda (a b) (> b a)) -> < (if rf (lint-format "perhaps ~A" caller (lists->string form rf))))) ((and (= arglen 1) (hash-table-ref combinable-cxrs (car body))) ((lambda* (cr arg) ; lambda* not lambda because combine-cxrs might return just #f (and cr (< (length cr) 5) (eq? (car args) arg) ;; (lambda (x) (cdr (cdr (car x)))) -> cddar (lint-format "perhaps ~A" caller (lists->string form (symbol "c" cr "r"))))) (combine-cxrs body)))))))) (if (and (or (symbol? args) ; (lambda args (apply f args)) -> f (and (pair? args) ; (lambda #\a ...) ! (negative? (length args)))) (eq? head 'lambda) (not (eq? caller 'case-lambda)) (= len 3)) (let ((body (caddr form))) (if (and (pair? body) (eq? (car body) 'apply) (pair? (cdr body)) (symbol? (cadr body)) (not (memq (cadr body) '(and or))) (pair? (cddr body)) (or (eq? args (caddr body)) (and (pair? args) (equal? (cddr body) (proper-list args))))) ;; (lambda args (apply + args)) -> + (lint-format "perhaps ~A" caller (lists->string form (cadr body)))))) (lint-walk-function head caller args (cddr form) form env) ;; not env as return value here -- return the lambda+old env via lint-walk-function )))) (hash-table-set! h 'lambda lambda-walker) (hash-table-set! h 'lambda* lambda-walker)) ;; ---------------- set! ---------------- (let () (define (set-walker caller form env) (if (not (= (length form) 3)) (begin (lint-format "set! has too ~A arguments: ~S" caller (if (> (length form) 3) "many" "few") form) env) (let ((settee (cadr form)) (setval (caddr form))) (if (symbol? setval) (set-ref setval caller form env)) (let ((result (lint-walk caller setval env))) (if (symbol? settee) (if (constant? settee) ; (set! pi 3) (lint-format "can't set! ~A (it is a constant)" caller (truncated-list->string form)) (let ((v (var-member settee env))) (if (and (var? v) (eq? (var-definer v) 'define-constant)) (let ((line (if (and (pair? (var-initial-value v)) (positive? (pair-line-number (var-initial-value v)))) (format #f "(line ~D): " (pair-line-number (var-initial-value v))) ""))) (lint-format "can't set! ~A in ~A (it is a constant: ~A~A)" caller settee (truncated-list->string form) line (truncated-list->string (var-initial-value v))))))) (if (not (pair? settee)) ; (set! 3 1) (lint-format "can't set! ~A" caller (truncated-list->string form)) (begin (if (memq (car settee) '(vector-ref list-ref string-ref hash-table-ref)) ;; (set! (vector-ref v 0) 3) (lint-format "~A as target of set!~A" caller (car settee) (truncated-list->string form))) (lint-walk caller settee env) ; this counts as a reference since it's by reference so to speak ;; try type check (dilambda signatures) (when (symbol? (car settee)) (let ((f (symbol->value (car settee) *e*))) (when (dilambda? f) (let ((sig (procedure-signature (procedure-setter f))) (settee-len (length settee))) (when (and (pair? sig) (positive? settee-len) (pair? (list-tail sig settee-len))) (let ((checker (list-ref sig settee-len)) (arg-type (->lint-type setval))) (when (and (symbol? checker) (not (compatible? checker arg-type))) ;; (set! (print-length) "asd") (lint-format "~A: new value should be a~A ~A: ~S: ~A" caller (car settee) (if (char=? (string-ref (format #f "~A" checker) 0) #\i) "n" "") checker arg-type (truncated-list->string form))))))))) (set! settee (do ((sym (car settee) (car sym))) ((not (pair? sym)) sym)))))) (if (symbol? (cadr form)) ; see do directly above -- sets settee so we have to go back to (cadr form) (set-set (cadr form) caller form env) (if (and (pair? (cadr form)) (symbol? settee)) (set-ref settee caller `(implicit-set ,@(cdr form)) env))) (if (equal? (cadr form) setval) ; not settee here! ; (set! a a) (lint-format "pointless set! ~A" caller (truncated-list->string form))) (when (and (pair? setval) (symbol? settee)) (case (car setval) ((if) ; (set! x (if y x 1)) -> (if (not y) (set! x 1)) (if (= (length setval) 4) (if (eq? settee (caddr setval)) (lint-format "perhaps ~A" caller (lists->string form `(if (not ,(cadr setval)) (set! ,settee ,(cadddr setval))))) (if (eq? settee (cadddr setval)) (lint-format "perhaps ~A" caller (lists->string form `(if ,(cadr setval) (set! ,settee ,(caddr setval))))))))) ((cond) ; (set! x (cond (z w) (else x))) -> (if z (set! x w)) -- this never happens (if (and (= (length setval) 3) (memq (caaddr setval) '(#t else)) (null? (cddr (caddr setval))) (null? (cddadr setval))) (if (eq? (cadr (caddr setval)) (cadr form)) (lint-format "perhaps ~A" caller (lists->string form `(if ,(caadr setval) (set! ,(cadr form) ,(cadadr setval))))) (if (eq? (cadadr setval) (cadr form)) (lint-format "perhaps ~A" caller (lists->string form `(if (not ,(caadr setval)) (set! ,(cadr form) ,(cadr (caddr setval)))))))))) ((or) ; (set! x (or x y)) -> (if (not x) (set! x y)) (if (and (= (length setval) 3) ; the other case here is not improved by using 'if (eq? settee (cadr setval))) (lint-format "perhaps ~A" caller (lists->string form `(if (not ,settee) (set! ,settee ,(caddr setval))))))) ((and) (if (= (length setval) 3) ; (set! x (and x y)) -> (if x (set! x y)) (if (eq? settee (cadr setval)) (lint-format "perhaps ~A" caller (lists->string form `(if ,settee (set! ,settee ,(caddr setval))))) (if (eq? settee (caddr setval)) (lint-format "perhaps ~A" caller (lists->string form `(if (not ,(cadr setval)) (set! ,settee #f)))))))))) result)))) (hash-table-set! h 'set! set-walker)) ;; ---------------- quote ---------------- (let () (define (quote-walker caller form env) (let ((len (length form))) (if (negative? len) (lint-format "stray dot in quote's arguments? ~S" caller form) (if (not (= len 2)) (lint-format "quote has too ~A arguments: ~S" caller (if (> len 2) "many" "few") form) (let ((arg (cadr form))) (if (pair? arg) (if (> (length arg) 8) (hash-table-set! big-constants arg (+ 1 (or (hash-table-ref big-constants arg) 0)))) (unless (or (>= quote-warnings 20) (and (symbol? arg) (not (keyword? arg)))) (set! quote-warnings (+ quote-warnings 1)) ; (char? '#\a) (lint-format "quote is not needed here: ~A~A" caller ; this is by far the most common message from lint (truncated-list->string form) (if (= quote-warnings 20) "; will ignore this error henceforth." "")))))))) env) (hash-table-set! h 'quote quote-walker)) ;; ---------------- if ---------------- (let () (define definers (let ((h (make-hash-table))) (for-each (lambda (d) (hash-table-set! h d #t)) '(define define* define-constant lambda lambda* curlet require load eval eval-string define-macro define-macro* define-bacro define-bacro* define-expansion definstrument defanimal define-envelope define-values define-module define-method define-syntax define-public define-inlinable define-integrable define^)) h)) (define (if-walker caller form env) (let ((len (length form))) (if (> len 4) (lint-format "if has too many clauses: ~A" caller (truncated-list->string form)) (if (< len 3) (lint-format "if has too few clauses: ~A" caller (truncated-list->string form)) (let ((test (cadr form)) (true (caddr form)) (false (if (= len 4) (cadddr form) 'no-false)) (expr (simplify-boolean (cadr form) () () env)) (suggestion made-suggestion) (true-op (and (pair? (caddr form)) (caaddr form))) (true-rest (and (pair? (caddr form)) (cdaddr form))) (false-op (and (= len 4) (pair? (cadddr form)) (car (cadddr form)))) (false-rest (and (= len 4) (pair? (cadddr form)) (cdr (cadddr form))))) (if (eq? false #) (lint-format "this # is redundant: ~A" caller form)) (if (and (symbol? test) (pair? true) (memq test true)) (and-incomplete form 'if test true env) (when (pair? test) (if (and (eq? (car test) 'not) (symbol? (cadr test)) (pair? false) (memq (cadr test) false)) (and-incomplete form 'if2 (cadr test) false env)) (if (and (hash-table-ref bools (car test)) (pair? true)) (if (member (cadr test) true) (and-forgetful form 'if test true env) (do ((p true (cdr p))) ((or (not (pair? p)) (and (pair? (car p)) (member (cadr test) (car p)))) (if (pair? p) (and-forgetful form 'if test (car p) env))))) (if (and (eq? (car test) 'not) (pair? (cadr test)) (pair? false) (hash-table-ref bools (caadr test))) (if (member (cadadr test) false) (and-forgetful form 'if2 (cadr test) false env) (do ((p false (cdr p))) ((or (not (pair? p)) (and (pair? (car p)) (member (cadadr test) (car p)))) (if (pair? p) (and-forgetful form 'if2 (cadr test) (car p) env))))))))) (when (and (pair? true) (pair? false) (not (memq true-op (list 'quote {list}))) (not (any-macro? true-op env)) (or (not (hash-table-ref syntaces true-op)) (memq true-op '(let let* set! and or begin))) (pair? true-rest)) (define (tree-subst-eq new old tree) ;; tree-subst above substitutes every occurence of 'old with 'new, so we check ;; in advance that 'old only occurs once in the tree (via tree-count1). Here ;; 'old may occur any number of times, but we want to change it only once, ;; so we keep the actual pointer to it and use eq?. (This assumes no shared code?) (cond ((eq? old tree) (cons new (cdr tree))) ((not (pair? tree)) tree) ((eq? (car tree) 'quote) (copy-tree tree)) (else (cons (tree-subst-eq new old (car tree)) (tree-subst-eq new old (cdr tree)))))) ;; maybe move the unless before this (let ((diff (let differ-in-one ((p true) (q false)) (and (pair? p) (pair? q) (if (equal? (car p) (car q)) (differ-in-one (cdr p) (cdr q)) (and (equal? (cdr p) (cdr q)) (or (and (pair? (car p)) (not (eq? (caar p) 'quote)) (pair? (car q)) (not (eq? (caar q) 'quote)) (differ-in-one (car p) (car q))) (list p (list (car p) (car q)))))))))) (if (pair? diff) (unless (or (and (equal? true-op (caadr diff)) ; (if x (+ y 1) (- y 1)) -- are we trying to keep really simple stuff out? (or (hash-table-ref syntaces true-op) (hash-table-ref syntaces false-op)) (any? pair? true-rest)) ; (if x (set! y (+ x 1)) (set! y 1)) (and (eq? true-op 'set!) ; (if x (set! y w) (set! z w)) (equal? (caar diff) (car true-rest)))) (let ((subst-loc (car diff))) ;; for let/let* if tree-subst position can't affect the test, just subst, else save test first ;; named let diff in args gets no hits (if (memq true-op '(let let*)) (if (not (or (symbol? (car true-rest)) ; assume named let is moving an if outside the loop (eq? subst-loc true-rest))) ; avoid confusion about the vars list (let ((vars (car true-rest))) ;; (if x (let ((y (abs x))) (display z) y) (let ((y (log x))) (display z) y)) -> (let ((y ((if x abs log) x))) (display z) y) (lint-format "perhaps ~A" caller (lists->string form (if (and (pair? vars) (case true-op ((let) (tree-memq subst-loc vars)) ((let*) (tree-memq subst-loc (car vars))) (else #f))) (tree-subst-eq `(if ,expr ,@(cadr diff)) subst-loc true) `(let ((_1_ ,expr)) ,(tree-subst-eq `(if _1_ ,@(cadr diff)) subst-loc true))))))) ;; also not any-macro? (car true|false) probably ;; (if x (set! y #t) (set! y #f)) -> (set! y x) (lint-format "perhaps ~A" caller (lists->string form (cond ((eq? true-op (caadr diff)) ; very common! ;; (if x (f y) (g y)) -> ((if x f g) y) ;; but f and g can't be or/and unless there are no expressions ;; I now like all of these -- originally found them odd: CL influence! (if (equal? true-op test) `((or ,test ,false-op) ,@true-rest) `((if ,test ,true-op ,false-op) ,@true-rest))) ((and (eq? (caadr diff) #t) (not (cadadr diff))) ;; (if x (set! y #t) (set! y #f)) -> (set! y x) (tree-subst-eq test subst-loc true)) ((and (not (caadr diff)) (eq? (cadadr diff) #t)) ;; (if x (set! y #f) (set! y #t)) -> (set! y (not x)) (tree-subst-eq (simplify-boolean `(not ,expr) () () env) subst-loc true)) ((equal? (caadr diff) test) ;; (if x (set! y x) (set! y 21)) -> (set! y (or x 21)) (tree-subst-eq (simplify-boolean `(or ,@(cadr diff)) () () env) subst-loc true)) ((or (memq true-op '(set! begin and or)) (let list-memq ((a subst-loc) (lst true)) (and (pair? lst) (or (eq? a lst) (list-memq a (cdr lst)))))) ;; (if x (set! y z) (set! y w)) -> (set! y (if x z w)) ;; true op moved out, if expr moved in ;; (if A (and B C) (and B D)) -> (and B (if A C D)) ;; here differ-in-one means that preceding/trailing stuff must subst-loc exactly (tree-subst-eq `(if ,expr ,@(cadr diff)) subst-loc true)) ;; paranoia... normally the extra let is actually not needed, ;; but it's very hard to distinguish the bad cases (else `(let ((_1_ ,expr)) ,(tree-subst-eq `(if _1_ ,@(cadr diff)) subst-loc true))))))))) ;; else not pair? diff (unless (memq true-op '(let let*)) ;; differ-in-trailers can (sometimes) take advantage of values (let ((enddiff (let differ-in-trailers ((p true) (q false) (c 0)) (and (pair? p) (pair? q) (if (equal? (car p) (car q)) (differ-in-trailers (cdr p) (cdr q) (+ c 1)) (and (> c 1) (let ((op (if (memq true-op '(and or + * begin max min)) true-op 'values))) (list p (if (null? (cdr p)) (car p) `(,op ,@p)) (if (null? (cdr q)) (car q) `(,op ,@q)))))))))) ;; (if A (+ B C E) (+ B D)) -> (+ B (if A (+ C E) D)) ;; if p/q null, don't change because for example ;; (if A (or B C) (or B C D F)) can't be (or B C (if A ...)) ;; but if this were not and/or, it could be (+ B (if A C (values C D F))) (if (pair? enddiff) (lint-format "perhaps ~A" caller (lists->string form (tree-subst `((if ,expr ,@(cdr enddiff))) (car enddiff) true))) ;; differ-in-headers looks for equal trailers ;; (if A (+ B B E C) (+ D D E C)) -> (+ (if A (+ B B) (+ D D)) E C) ;; these are not always (read: almost never) an improvement (when (and (eq? true-op false-op) (not (eq? true-op 'values)) (or (not (eq? true-op 'set!)) (equal? (car true-rest) (car false-rest)))) (let ((headdiff (let differ-in-headers ((p true-rest) (q false-rest) (c 0) (rp ()) (rq ())) (and (pair? p) (pair? q) (if (equal? p q) (and (> c 0) ; once case is handled elsewhere? (list p (reverse rp) (reverse rq))) (differ-in-headers (cdr p) (cdr q) (+ c 1) (cons (car p) rp) (cons (car q) rq))))))) (when (pair? headdiff) (let ((op (if (memq true-op '(and or + * begin max min)) true-op 'values))) (let ((tp (if (null? (cdadr headdiff)) (caadr headdiff) `(,op ,@(cadr headdiff)))) (tq (if (null? (cdaddr headdiff)) (caaddr headdiff) `(,op ,@(caddr headdiff))))) ;; (if A (+ B B E C) (+ D D E C)) -> (+ (if A (+ B B) (+ D D)) E C) (lint-format "perhaps ~A" caller (lists->string form `(,true-op (if ,expr ,tp ,tq) ,@(car headdiff))))))))))))))) ;; (when (and (pair? true)...) ;; end tree-subst section (unless (= last-if-line-number line-number) (do ((iff form (cadddr iff)) (iffs 0 (+ iffs 1))) ((not (and (<= iffs 2) (pair? iff) (= (length iff) 4) (eq? (car iff) 'if))) (when (or (> iffs 2) (and (= iffs 2) (pair? iff) (= (length iff) 3) (eq? (car iff) 'if))) (set! last-if-line-number line-number) ;; (if a b (if c d (if e f g))) -> (cond (a b) (c d) (e f) (else g)) (lint-format "perhaps use cond: ~A" caller (lists->string form `(cond ,@(do ((iff form (cadddr iff)) (clauses ())) ((not (and (pair? iff) (= (length iff) 4) (eq? (car iff) 'if))) (append (reverse clauses) (if (and (pair? iff) (= (length iff) 3) (eq? (car iff) 'if)) `((,(cadr iff) ,@(unbegin (caddr iff)))) `((else ,@(unbegin iff)))))) (set! clauses (cons (cons (cadr iff) (unbegin (caddr iff))) clauses)))))))))) (if (never-false test) (lint-format "if test is never false: ~A" caller (truncated-list->string form)) (if (and (never-true test) true) ; complain about (if #f #f) later ;; (if #f x y) (lint-format "if test is never true: ~A" caller (truncated-list->string form)))) (cond ((side-effect? test env)) ((or (equal? test true) ; (if x x y) -> (or x y) (equal? expr true)) (lint-format "perhaps ~A" caller (lists->string form (simplify-boolean (if (eq? false 'no-false) `(or ,expr #) `(or ,expr ,false)) () () env)))) ((or (equal? test `(not ,true)) ; (if x (not x) y) -> (and (not x) y) (equal? `(not ,test) true)) ; (if (not x) x y) -> (and x y) (lint-format "perhaps ~A" caller (lists->string form (simplify-boolean (if (eq? false 'no-false) `(and ,true #) `(and ,true ,false)) () () env)))) ((or (equal? test false) ; (if x y x) -> (and x y) (equal? expr false)) (lint-format "perhaps ~A" caller (lists->string form (simplify-boolean `(and ,expr ,true) () () env)))) ((or (equal? `(not ,test) false) ; (if x y (not x)) -> (or (not x) y) (equal? test `(not ,false))) ; (if (not x) y x) -> (or x y) (lint-format "perhaps ~A" caller (lists->string form (simplify-boolean `(or ,false ,true) () () env))))) (when (and (pair? true) (eq? true-op 'cond) (not (eq? false-op 'cond)) (not (boolean? false))) ; these cases are handled elsewhere via or/and ;; (if A (cond...) B) -> (cond ((not A) B) ...) ;; if no false and cond is one-shot => this can be optimized to (cond ((and (not A) C) => ...)) (lint-format "perhaps ~A" caller (let ((nexpr (simplify-boolean (list 'not expr) () () env)) (nfalse (if (eq? false 'no-false) (if (eq? form lint-mid-form) () '(#)) (list (if (and (pair? false) (> (tree-leaves false) 100)) (if (pair? (car false)) (list (list (caar false) '...)) (list (car false) '...)) false))))) (lists->string form `(cond (,nexpr ,@nfalse) ,@true-rest))))) ;; true-op = case happens a lot, but never in a way that (not expr)->false can be combined in the case (when (= len 4) (when (and (pair? true) (eq? true-op 'if)) (let ((true-test (car true-rest)) (true-true (cadr true-rest))) (if (= (length true) 4) (let ((true-false (caddr true-rest))) (if (equal? expr (simplify-boolean `(not ,true-test) () () env)) ;; (if a (if (not a) B C) A) -> (if a C A) (lint-format "perhaps ~A" caller (lists->string form `(if ,expr ,true-false ,false)))) (if (equal? expr true-test) ;; (if x (if x z w) y) -> (if x z y) (lint-format "perhaps ~A" caller (lists->string form `(if ,expr ,true-true ,false)))) (if (equal? false true-false) ;; (if a (if b B A) A) -> (if (and a b) B A) (lint-format "perhaps ~A" caller (lists->string form (simplify-boolean (if (not false) `(and ,expr ,true-test ,true-true) `(if (and ,expr ,true-test) ,true-true ,false)) () () env))) (if (equal? false true-true) ;; (if a (if b A B) A) -> (if (and a (not b)) B A) (lint-format "perhaps ~A" caller (lists->string form (simplify-boolean (if (not false) `(and ,expr (not ,true-test) ,true-false) `(if (and ,expr (not ,true-test)) ,true-false ,false)) () () env))))) ;; (if a (if b d e) (if c d e)) -> (if (if a b c) d e)? reversed does not happen. ;; (if a (if b d) (if c d)) -> (if (if a b c) d) ;; (if a (if b d e) (if (not b) d e)) -> (if (eq? (not a) (not b)) d e) (when (and (pair? false) (eq? false-op 'if) (= (length false) 4) (not (equal? true-test (car false-rest))) (equal? (cdr true-rest) (cdr false-rest))) (let ((false-test (car false-rest))) (lint-format "perhaps ~A" caller (lists->string form (cond ((and (pair? true-test) (eq? (car true-test) 'not) (equal? (cadr true-test) false-test)) `(if (not (eq? (not ,expr) ,true-test)) ,@(cdr true-rest))) ((and (pair? false-test) (eq? (car false-test) 'not) (equal? true-test (cadr false-test))) `(if (eq? (not ,expr) ,false-test) ,@(cdr true-rest))) ((> (+ (tree-leaves expr) (tree-leaves true-test) (tree-leaves false-test)) 12) `(let ((_1_ (if ,expr ,true-test ,false-test))) (if _1_ ,@(cdr true-rest)))) (else `(if (if ,expr ,true-test ,false-test) ,@(cdr true-rest))))))))) (begin ; (length true) != 4 (if (equal? expr (simplify-boolean `(not ,true-test) () () env)) (lint-format "perhaps ~A" caller ; (if a (if (not a) B) A) -> (if (not a) A) (lists->string form `(if (not ,expr) ,false)))) (if (equal? expr true-test) ; (if x (if x z) w) -> (if x z w) (lint-format "perhaps ~A" caller (lists->string form `(if ,expr ,true-true ,false)))) (if (equal? false true-true) ; (if a (if b A) A) (lint-format "perhaps ~A" caller (let ((nexpr (simplify-boolean `(or (not ,expr) ,true-test) () () env))) (lists->string form `(if ,nexpr ,false))))))))) (when (pair? false) (case false-op ((cond) ; (if a A (cond...)) -> (cond (a A) ...) (lint-format "perhaps ~A" caller (lists->string form `(cond (,expr ,true) ,@false-rest)))) ((if) (when (= (length false) 4) (let ((false-test (car false-rest)) (false-true (cadr false-rest)) (false-false (caddr false-rest))) (if (equal? true false-true) ;; (if a A (if b A B)) -> (if (or a b) A B) (lint-format "perhaps ~A" caller (if (and (pair? false-false) (eq? (car false-false) 'if) (equal? true (caddr false-false))) (lists->string form (let ((nexpr (simplify-boolean `(or ,expr ,false-test ,(cadr false-false)) () () env))) `(if ,nexpr ,true ,@(cdddr false-false)))) (if true (let ((nexpr (simplify-boolean `(or ,expr ,false-test) () () env))) (lists->string form `(if ,nexpr ,true ,false-false))) (lists->string form (simplify-boolean `(and (not (or ,expr ,false-test)) ,false-false) () () env))))) (if (equal? true false-false) ;; (if a A (if b B A)) -> (if (or a (not b)) A B) (lint-format "perhaps ~A" caller (if true (let ((nexpr (simplify-boolean `(or ,expr (not ,false-test)) () () env))) (lists->string form `(if ,nexpr ,true ,false-true))) (lists->string form (simplify-boolean `(and (not (or ,expr (not ,false-test))) ,false-true) () () env)))))))) (if (and (pair? true) (eq? true-op 'if) (= (length true) 3) (= (length false) 3) (equal? (cdr true-rest) (cdr false-rest))) ;; (if a (if b d) (if c d)) -> (if (if a b c) d) (lint-format "perhaps ~A" caller (lists->string form (if (> (+ (tree-leaves expr) (tree-leaves (car true-rest)) (tree-leaves (car false-rest))) 12) `(let ((_1_ (if ,expr ,(car true-rest) ,(car false-rest)))) (if _1_ ,@(cdr true-rest))) `(if (if ,expr ,(car true-rest) ,(car false-rest)) ,@(cdr true-rest))))))) ((map) ; (if (null? x) () (map abs x)) -> (map abs x) (if (and (pair? test) (eq? (car test) 'null?) (or (null? true) (equal? true (cadr test))) (equal? (cadr test) (cadr false-rest)) (or (null? (cddr false-rest)) (not (side-effect? (cddr false-rest) env)))) (lint-format "perhaps ~A" caller (lists->string form false)))) ((case) (if (and (pair? expr) (cond-eqv? expr (car false-rest) #t)) ;; (if (eof-object? x) 32 (case x ((#\a) 3) (else 4))) -> (case x ((#) 32) ((#\a) 3) (else 4)) (lint-format "perhaps ~A" caller (lists->string form `(case ,(car false-rest) ,(case-branch expr (car false-rest) (list true)) ,@(cdr false-rest)))))))) ) ; (= len 4) (if (pair? false) (let ((false-test (and (pair? false-rest) (car false-rest)))) (if (and (eq? false-op 'if) ; (if x 3 (if (not x) 4)) -> (if x 3 4) (pair? false-rest) (not (side-effect? test env))) (if (or (equal? test false-test) (equal? expr false-test)) (lint-format "perhaps ~A" caller (lists->string form `(if ,expr ,true ,@(cddr false-rest)))) (if (and (pair? false-test) (eq? (car false-test) 'not) (or (equal? test (cadr false-test)) (equal? expr (cadr false-test)))) (lint-format "perhaps ~A" caller (lists->string form `(if ,expr ,true ,(cadr false-rest))))))) (if (and (eq? false-op 'if) ; (if test0 expr (if test1 expr)) -> if (or test0 test1) expr) (null? (cddr false-rest)) ; other case is dealt with above (equal? true (cadr false-rest))) (let ((test1 (simplify-boolean `(or ,expr ,false-test) () () env))) (lint-format "perhaps ~A" caller (lists->string form `(if ,test1 ,true ,@(cddr false-rest))))))) (when (and (eq? false 'no-false) ; no false branch (pair? true)) (when (pair? test) (let ((test-op (car test))) ;; the min+max case is seldom hit, and takes about 50 lines (when (and (memq test-op '(< > <= >=)) (null? (cdddr test))) (let ((rel-arg1 (cadr test)) (rel-arg2 (caddr test))) ;; (if (< x y) (set! x y) -> (set! x (max x y)) (if (eq? true-op 'set!) (let ((settee (car true-rest)) (setval (cadr true-rest))) (if (and (member settee test) (member setval test)) ; that's all there's room for (let ((f (if (equal? settee (if (memq test-op '(< <=)) rel-arg1 rel-arg2)) 'max 'min))) (lint-format "perhaps ~A" caller (lists->string form `(set! ,settee (,f ,@true-rest))))))) ;; (if (<= (list-ref ind i) 32) (list-set! ind i 32)) -> (list-set! ind i (max (list-ref ind i) 32)) (if (memq true-op '(list-set! vector-set!)) (let ((settee (car true-rest)) (index (cadr true-rest)) (setval (caddr true-rest))) (let ((mx-op (if (and (equal? setval rel-arg1) (eqv? (length rel-arg2) 3) (equal? settee (cadr rel-arg2)) (equal? index (caddr rel-arg2))) (if (memq test-op '(< <=)) 'min 'max) (and (equal? setval rel-arg2) (eqv? (length rel-arg1) 3) (equal? settee (cadr rel-arg1)) (equal? index (caddr rel-arg1)) (if (memq test-op '(< <=)) 'max 'min))))) (if mx-op (lint-format "perhaps ~A" caller (lists->string form `(,true-op ,settee ,index (,mx-op ,@(cdr test)))))))))))))) (cond ((not (eq? (car true) 'if)) ; (if test0 (if test1 expr)) -> (if (and test0 test1) expr) (if (memq true-op '(when unless)) ; (if test0 (when test1 expr...)) -> (when (and test0 test1) expr...) (let ((test1 (simplify-boolean (if (eq? true-op 'when) `(and ,expr ,(car true-rest)) `(and ,expr (not ,(car true-rest)))) () () env))) ;; (if (and (< x 1) y) (when z (display z) x)) -> (when (and (< x 1) y z) (display z) x) (lint-format "perhaps ~A" caller (lists->string form (if (and (pair? test1) (eq? (car test1) 'not)) `(unless ,(cadr test1) ,@(cdr true-rest)) `(when ,test1 ,@(cdr true-rest)))))))) ((null? (cddr true-rest)) (let ((test1 (simplify-boolean `(and ,expr ,(car true-rest)) () () env))) (lint-format "perhaps ~A" caller (lists->string form `(if ,test1 ,(cadr true-rest)))))) ((equal? expr (car true-rest)) (lint-format "perhaps ~A" caller (lists->string form true))) ((equal? (car true-rest) `(not ,expr)) (lint-format "perhaps ~A" caller (lists->string form (caddr true-rest))))))) (if (and (pair? test) (memq (car test) '(< <= > >= =)) ; (if (< x y) x y) -> (min x y) (null? (cdddr test)) (member false test) (member true test)) (if (eq? (car test) '=) ; (if (= x y) y x) -> y [this never happens] (lint-format "perhaps ~A" caller (lists->string form false)) (let ((f (if (equal? (cadr test) (if (memq (car test) '(< <=)) true false)) 'min 'max))) (lint-format "perhaps ~A" caller (lists->string form `(,f ,true ,false)))))) (cond ((eq? expr #t) ; (if #t #f) -> #f (lint-format "perhaps ~A" caller (lists->string form true))) ((not expr) (if (eq? false 'no-false) (if true ; (if #f x) as a kludgey # (lint-format "perhaps ~A" caller (lists->string form #))) ;; (if (negative? (gcd x y)) a b) -> b (lint-format "perhaps ~A" caller (lists->string form false)))) ((not (equal? true false)) (if (boolean? true) (if (boolean? false) ; ! (if expr #t #f) turned into something less verbose ;; (if x #f #t) -> (not x) (lint-format "perhaps ~A" caller (lists->string form (if true expr (simplify-boolean `(not ,expr) () () env)))) (when (= suggestion made-suggestion) ;; (if x #f y) -> (and (not x) y) (lint-format "perhaps ~A" caller (lists->string form (if true (if (eq? false 'no-false) expr (simplify-boolean `(or ,expr ,false) () () env)) (simplify-boolean (if (eq? false 'no-false) `(not ,expr) `(and (not ,expr) ,false)) () () env)))))) (if (and (boolean? false) (= suggestion made-suggestion)) ;; (if x y #t) -> (or (not x) y) (lint-format "perhaps ~A" caller (let ((nexpr (if false (if (and (pair? expr) (eq? (car expr) 'not)) `(or ,(cadr expr) ,true) `(or (not ,expr) ,true)) `(and ,expr ,true)))) (lists->string form (simplify-boolean nexpr () () env))))))) ((= len 4) ;; (if x (+ y 1) (+ y 1)) -> (+ y 1) (lint-format "if is not needed here: ~A" caller (lists->string form (if (not (side-effect? test env)) true `(begin ,expr ,true)))))) (when (and (= suggestion made-suggestion) (not (equal? expr test))) ; make sure the boolean simplification gets reported ;; (or (not (pair? x)) (not (pair? z))) -> (not (and (pair? x) (pair? z))) (lint-format "perhaps ~A" caller (lists->string test expr))) (when (pair? true) (if (and (pair? test) (pair? true-rest) (null? (cdr true-rest)) (or (equal? test (car true-rest)) (equal? expr (car true-rest)))) (lint-format "perhaps ~A" caller (lists->string form (if (eq? false 'no-false) `(cond (,expr => ,true-op)) `(cond (,expr => ,true-op) (else ,false)))))) (when (and (pair? false) (eq? true-op 'if) (eq? false-op 'if) (= (length true) (length false) 4) (equal? (car true-rest) (car false-rest))) (if (and (equal? (cadr true-rest) (caddr false-rest)) ; (if A (if B a b) (if B b a)) -> (if (eq? (not A) (not B)) a b) (equal? (caddr true-rest) (cadr false-rest))) (let* ((switch #f) (a (if (and (pair? expr) (eq? (car expr) 'not)) (begin (set! switch #t) expr) (simplify-boolean `(not ,expr) () () env))) (b (if (and (pair? (car true-rest)) (eq? (caar true-rest) 'not)) (begin (set! switch (not switch)) (car true-rest)) (simplify-boolean `(not ,(car true-rest)) () () env)))) (lint-format "perhaps ~A" caller (lists->string form (if switch `(if (eq? ,a ,b) ,(cadr false-rest) ,(cadr true-rest)) `(if (eq? ,a ,b) ,(cadr true-rest) ,(cadr false-rest)))))) (unless (or (side-effect? expr env) (equal? (cdr true-rest) (cdr false-rest))) ; handled elsewhere (if (equal? (cadr true-rest) (cadr false-rest)) ; (if A (if B a b) (if B a c)) -> (if B a (if A b c)) (lint-format "perhaps ~A" caller (lists->string form `(if ,(car true-rest) ,(cadr true-rest) (if ,expr ,(caddr true-rest) ,(caddr false-rest))))) (if (equal? (caddr true-rest) (caddr false-rest)) ; (if A (if B a b) (if B c b)) -> (if B (if A a c) b) (lint-format "perhaps ~A" caller (lists->string form `(if ,(car true-rest) (if ,expr ,(cadr true-rest) ,(cadr false-rest)) ,(caddr true-rest)))))))))) ;; -------- (when (and (= suggestion made-suggestion) (not (= line-number last-if-line-number))) ;; unravel complicated if-then-else nestings into a single cond, if possible. ;; ;; The (> new-len *report-nested-if*) below can mean (nearly) all nested ifs are turned into conds. ;; For a long time I thought the if form was easier to read, but now ;; I like cond better. But cond also has serious readability issues: ;; it needs to be clearer where the test separates from the result, ;; and in a stack of these clauses, it's hard to see anything at all. ;; Maybe a different color for the test than the result? ;; ;; Also, the check for tree-leaves being hugely different is taken ;; from C -- I think it is easier to read a large if statement if ;; the shortest clause is at the start -- especially in a nested if where ;; it can be nearly impossible to see which dangling one-liner matches ;; which if (this even in emacs because it unmarks or doesn't remark the matching ;; paren as you're trying to scroll up to it). ;; ;; the cond form is not always an improvement: ;; (if A (if B (if C a b) (if C c d)) (if B (if C e f) (if C g h))) ;; (cond (A (cond (B (cond (C a) (else b))) ... oh forget it ...)))) ;; perhaps: (case (+ (if A 4 0) (if B 2 0) (if C 1 0)) ((#b000)...))! ;; how often (and how deeply nested) does this happen? -- not very, but nesting can be ridiculous. ;; and this assumes all tests are always hit (define (swap-clauses form) (if (not (pair? (cdddr form))) form (let ((expr (cadr form)) (ltrue (caddr form)) (lfalse (cadddr form))) (let ((true-n (tree-leaves ltrue)) (false-n (if (not (pair? lfalse)) 1 (tree-leaves lfalse)))) (if (< false-n (/ true-n 4)) (let ((new-expr (simplify-boolean `(not ,expr) () () env))) (if (and (pair? ltrue) (eq? (car ltrue) 'if)) (set! ltrue (swap-clauses ltrue))) (if (and (pair? ltrue) (eq? (car ltrue) 'cond)) `(cond (,new-expr ,@(unbegin lfalse)) ,@(cdr ltrue)) `(cond (,new-expr ,@(unbegin lfalse)) (else ,@(unbegin ltrue))))) (begin (if (and (pair? lfalse) (eq? (car lfalse) 'if)) (set! lfalse (swap-clauses lfalse))) (if (and (pair? lfalse) (eq? (car lfalse) 'cond)) `(cond (,expr ,@(unbegin ltrue)) ,@(cdr lfalse)) `(cond (,expr ,@(unbegin ltrue)) (else ,@(unbegin lfalse)))))))))) (let ((new-if (swap-clauses form))) (if (eq? (car new-if) 'cond) (if (> (length new-if) *report-nested-if*) (begin (set! last-if-line-number line-number) (lint-format "perhaps ~A" caller (lists->string form new-if))) (when (= len 4) (let ((true-len (tree-leaves (caddr form)))) (if (and (> true-len *report-short-branch*) (< (tree-leaves (cadddr form)) (/ true-len *report-short-branch*))) (let ((new-expr (simplify-boolean `(not ,(cadr form)) () () env))) (lint-format "perhaps place the much shorter branch first~A: ~A" caller (local-line-number (cadr form)) (truncated-lists->string form `(if ,new-expr ,false ,true)))))))))) ;; if+let() -> when: about a dozen hits (let ((ntrue (and (pair? true) ; (if A B (let () (display x))) -> (if A B (begin (display x))) (eq? true-op 'let) (pair? (cdr true)) (null? (cadr true)) (not (tree-table-member definers (cddr true))) (cddr true))) (nfalse (and (pair? false) (eq? false-op 'let) (pair? (cdr false)) (null? (cadr false)) (not (tree-table-member definers (cddr false))) (cddr false)))) (if (or ntrue nfalse) (lint-format "perhaps ~A" caller (lists->string form (if (eq? false 'no-false) `(when ,expr ,@ntrue) (if ntrue (if nfalse `(if ,expr (begin ,@ntrue) (begin ,@nfalse)) `(if ,expr (begin ,@ntrue) ,false)) `(if ,expr ,true (begin ,@nfalse)))))))) (when (= len 4) ;; move repeated test to top, if no inner false branches ;; (if A (if B C) (if B D)) -> (if B (if A C D)) (when (and (pair? true) (pair? false) (eq? true-op 'if) (eq? false-op 'if) (equal? (car true-rest) (car false-rest)) (null? (cddr true-rest)) (null? (cddr false-rest))) (lint-format "perhaps ~A" caller (lists->string form `(if ,(car true-rest) (if ,expr ,(cadr true-rest) ,(cadr false-rest)))))) ;; move repeated start/end statements out of the if (let ((ltrue (if (and (pair? true) (eq? true-op 'begin)) true (list 'begin true))) (lfalse (if (and (pair? false) (eq? false-op 'begin)) false (list 'begin false)))) (let ((true-len (length ltrue)) (false-len (length lfalse))) (let ((start (if (and (equal? (cadr ltrue) (cadr lfalse)) (not (side-effect? expr env))) ; expr might affect start, so we can't pull it ahead (list (cadr ltrue)) ())) (end (if (and (not (= true-len false-len 2)) (equal? (list-ref ltrue (- true-len 1)) (list-ref lfalse (- false-len 1)))) (list (list-ref ltrue (- true-len 1))) ()))) (when (or (pair? start) (pair? end)) (let ((new-true (cdr ltrue)) (new-false (cdr lfalse))) (when (pair? end) (set! new-true (copy new-true (make-list (- true-len 2)))) ; (copy lst ()) -> () (set! new-false (copy new-false (make-list (- false-len 2))))) (when (pair? start) (if (pair? new-true) (set! new-true (cdr new-true))) (if (pair? new-false) (set! new-false (cdr new-false)))) (when (or (pair? end) (and (pair? new-true) (pair? new-false))) ; otherwise the rewrite changes the returned value (if (pair? new-true) (set! new-true (if (null? (cdr new-true)) (car new-true) (cons 'begin new-true)))) (if (pair? new-false) (set! new-false (if (null? (cdr new-false)) (car new-false) (cons 'begin new-false)))) ;; (if x (display y) (begin (set! z y) (display y))) -> (begin (if (not x) (set! z y)) (display y)) (lint-format "perhaps ~A" caller (lists->string form (let ((body (if (null? new-true) `(if (not ,expr) ,new-false) (if (null? new-false) `(if ,expr ,new-true) `(if ,expr ,new-true ,new-false))))) `(begin ,@start ,body ,@end)))))))))) (when (and (= suggestion made-suggestion) ; not redundant -- this will repeat the earlier suggestion in many cases (not (= line-number last-if-line-number)) (pair? expr) ; (if (not a) A B) -> (if a B A) (eq? (car expr) 'not) (> (tree-leaves true) (tree-leaves false))) (lint-format "perhaps ~A" caller (lists->string form `(if ,(cadr expr) ,false ,true)))) ;; this happens occasionally -- scarcely worth this much code! (gather copied vars outside the if) (when (and (pair? true) (pair? false) (eq? true-op 'let) (eq? false-op 'let) (pair? (car true-rest)) (pair? (car false-rest))) (let ((true-vars (map car (car true-rest))) (false-vars (map car (car false-rest))) (shared-vars ())) (for-each (lambda (v) (if (and (memq v false-vars) (equal? (cadr (assq v (car true-rest))) (cadr (assq v (car false-rest))))) (set! shared-vars (cons v shared-vars)))) true-vars) (when (pair? shared-vars) ;; now remake true/false lets (maybe nil) without shared-vars (let ((ntv ()) (nfv ()) (sv ())) (for-each (lambda (v) (if (memq (car v) shared-vars) (set! sv (cons v sv)) (set! ntv (cons v ntv)))) (car true-rest)) (set! ntv (if (or (pair? ntv) (pair? (cddr true-rest))) ; even define is safe here because outer let blocks it just as inner let used to `(let ,(reverse ntv) ,@(cdr true-rest)) (cadr true-rest))) (for-each (lambda (v) (if (not (memq (car v) shared-vars)) (set! nfv (cons v nfv)))) (car false-rest)) (set! nfv (if (or (pair? nfv) (pair? (cddr false-rest))) `(let ,(reverse nfv) ,@(cdr false-rest)) (cadr false-rest))) ;; (if (> (+ a b) 3) (let ((a x) (c y)) (* a (log c))) (let ((b z) (c y)) (+... -> ;; (let ((c y)) (if (> (+ a b) 3) (let ((a x)) (* a (log c))) (let ((b z)) (+ b (log c))))) (lint-format "perhaps ~A" caller (lists->string form (if (not (or (side-effect? expr env) (tree-set-member (map car sv) expr))) `(let ,(reverse sv) (if ,expr ,ntv ,nfv)) (let ((uniq (find-unique-name form))) `(let ((,uniq ,expr)) (let ,(reverse sv) (if ,uniq ,ntv ,nfv))))))))))))) ; (when (and (= suggestion made-suggestion)...)) (when (and *report-one-armed-if* (eq? false 'no-false) (or (not (integer? *report-one-armed-if*)) (> (tree-leaves true) *report-one-armed-if*))) ;; (if a (begin (set! x y) z)) -> (when a (set! x y) z) (lint-format "~A~A~A perhaps ~A" caller (if (integer? *report-one-armed-if*) "this one-armed if is too big" "") (local-line-number test) (if (integer? *report-one-armed-if*) ";" "") (truncated-lists->string form (if (and (pair? expr) (eq? (car expr) 'not)) `(unless ,(cadr expr) ,@(unbegin true)) `(when ,expr ,@(unbegin true)))))) (if (symbol? expr) (set-ref expr caller form env) (lint-walk caller expr env)) (if (symbol? true) (set-ref true caller form env) (set! env (lint-walk caller true env))) (if (symbol? false) (if (not (eq? false 'no-false)) (set-ref false caller form env)) (set! env (lint-walk caller false env)))))) env)) (hash-table-set! h 'if if-walker)) ;; -------- when, unless -------- (let () (define (when-walker caller form env) (if (< (length form) 3) (begin (lint-format "~A is messed up: ~A" caller (car form) (truncated-list->string form)) env) (let ((test (cadr form)) (head (car form))) (if (and (pair? test) (eq? (car test) 'not)) ;; (when (not a) (set! x y)) -> (unless a (set! x y)) (lint-format "perhaps ~A" caller (truncated-lists->string form `(,(if (eq? head 'when) 'unless 'when) ,(cadr test) ,@(cddr form))))) (if (never-false test) (lint-format "~A test is never false: ~A" caller head (truncated-list->string form)) (if (never-true test) ; (unless #f...) (lint-format "~A test is never true: ~A" caller head (truncated-list->string form)))) (if (symbol? test) (begin (set-ref test caller form env) (if (and (eq? head 'when) (pair? (cddr form)) (pair? (caddr form))) (if (memq test (caddr form)) (and-incomplete form head test (caddr form) env) (do ((p (caddr form) (cdr p))) ((or (not (pair? p)) (and (pair? (car p)) (memq test (car p)))) (if (pair? p) (and-incomplete form head test (car p) env))))))) (when (pair? test) (if (and (eq? (car test) 'and) (pair? (cdr test)) (pair? (cddr test)) (null? (cdddr test))) (let ((arg1 (cadr test)) (arg2 (caddr test))) (if (or (and (pair? arg1) (eq? (car arg1) 'not)) (and (pair? arg2) (eq? (car arg2) 'not))) (if (eq? head 'unless) ;; (unless (and x (not y)) (display z)) -> (when (or (not x) y) ...) (lint-format "perhaps ~A" caller (lists->string form `(when ,(simplify-boolean `(not ,test) () () env) ...))) (if (and (pair? arg1) (eq? (car arg1) 'not) (pair? arg2) (eq? (car arg2) 'not)) ;; (when (and (not x) (not y)) (display z)) -> (unless (or x y) ...) (lint-format "perhaps ~A" caller (lists->string form `(unless (or ,(cadr arg1) ,(cadr arg2)) ...)))))))) (lint-walk caller test env))) (when (and (pair? (cddr form)) ; (when t1 (if t2 A)) -> (when (and t1 t2) A) (null? (cdddr form)) (pair? (caddr form))) (let ((body (caddr form))) (if (eq? (car body) 'cond) ; (when (cond ...)) -> (cond ...) (lint-format "perhaps ~A" caller (truncated-lists->string form `(cond (,(if (eq? (car form) 'when) (simplify-boolean `(not ,(cadr form)) () () env) (cadr form)) #f) ,@(cdr body)))) (when (or (memq (car body) '(when unless)) (and (eq? (car body) 'if) (pair? (cdr body)) (pair? (cddr body)) (null? (cdddr body)))) (let ((new-test (let ((inner-test (if (eq? (car body) 'unless) `(not ,(cadr body)) (cadr body))) (outer-test (if (eq? head 'unless) `(not ,test) test))) (simplify-boolean `(and ,outer-test ,inner-test) () () env)))) ;; (when (and (< x 1) y) (if z (display z))) -> (when (and (< x 1) y z) (display z)) (lint-format "perhaps ~A" caller (lists->string form (if (and (pair? new-test) (eq? (car new-test) 'not)) `(unless ,(cadr new-test) ,@(cddr body)) `(when ,new-test ,@(cddr body)))))))))) (lint-walk-open-body caller head (cddr form) env)))) (hash-table-set! h 'when when-walker) (hash-table-set! h 'unless when-walker)) ;; ---------------- cond ---------------- (let () (define (cond-walker caller form env) (let ((ctr 0) (suggest made-suggestion) (len (- (length form) 1))) (if (< len 1) (lint-format "cond is messed up: ~A" caller (truncated-list->string form)) (let ((exprs ()) (result :unset) (has-else #f) (has-combinations #f) (simplifications ()) (prev-clause #f) (all-eqv #t) (eqv-select #f)) ;; (cond (A (and B C)) (else (and B D))) et al never happens ;; also (cond (A C) (B C)) -> (if (or A B) C) [non-pair C] ;; ---------------- ;; if regular cond + else ;; scan all return blocks ;; if all one form, and either header or trailer always match, ;; rewrite as header + cond|if + trailer ;; given values and the presence of else, every possibility is covered ;; at least (car result) has to match across all (when (and (> len 1) ; (cond (else ...)) is handled elsewhere (pair? (cdr form)) (pair? (cadr form)) (not (tree-set-member '(unquote #_{list}) form))) (let ((first-clause (cadr form)) (else-clause (list-ref form len))) (when (and (pair? (cdr first-clause)) (null? (cddr first-clause)) (pair? (cadr first-clause)) (pair? else-clause)) (let ((first-result (cadr first-clause)) (first-func (caadr first-clause))) (if (and (memq (car else-clause) '(#t else)) (pair? (cdr else-clause)) (pair? (cadr else-clause)) (or (equal? (caadr first-clause) (caadr else-clause)) ; there's some hope we'll match (escape? (cadr else-clause) env))) (let ((else-error (escape? (cadr else-clause) env))) (when (and (pair? (cdr first-result)) (not (eq? first-func 'values)) (or (not (hash-table-ref syntaces first-func)) (eq? first-func 'set!)) (every? (lambda (c) (and (pair? c) (pair? (cdr c)) (pair? (cadr c)) (null? (cddr c)) (pair? (cdadr c)) (or (equal? first-func (caadr c)) (and (eq? c else-clause) else-error)))) (cddr form))) ((lambda (header-len trailer-len result-min-len) (when (and (or (not (eq? first-func 'set!)) (> header-len 1)) (or (not (eq? first-func '/)) (> header-len 1) (> trailer-len 0))) (let ((header (copy first-result (make-list header-len))) (trailer (copy first-result (make-list trailer-len) (- (length first-result) trailer-len)))) (if (= len 2) (unless (equal? first-result (cadr else-clause)) ; handled elsewhere (all results equal -> result) ;; (cond (x (for-each (lambda (x) (display (+ x a))) (f y))) (else (for-each... -> ;; (for-each (lambda (x) (display (+ x a))) (if x (f y) (g y))) (lint-format "perhaps ~A" caller (let ((else-result (cadr else-clause))) (let ((first-mid-len (- (length first-result) header-len trailer-len)) (else-mid-len (- (length else-result) header-len trailer-len))) (let ((fmid (if (= first-mid-len 1) (list-ref first-result header-len) `(values ,@(copy first-result (make-list first-mid-len) header-len)))) (emid (if else-error else-result (if (= else-mid-len 1) (list-ref else-result header-len) `(values ,@(copy else-result (make-list else-mid-len) header-len)))))) (lists->string form `(,@header (if ,(car first-clause) ,fmid ,emid) ,@trailer))))))) ;; len > 2 so use cond in the revision (let ((middle (map (lambda (c) (if (and else-error (eq? c else-clause)) else-clause (let ((test (car c)) (result (cadr c))) (let ((mid-len (- (length result) header-len trailer-len))) `(,test ,(if (= mid-len 1) (list-ref result header-len) `(values ,@(copy result (make-list mid-len) header-len)))))))) (cdr form)))) ;; (cond ((< x 1) (+ x 1)) ((< y 1) (+ x 3)) (else (+ x 2))) -> (+ x (cond ((< x 1) 1) ((< y 1) 3) (else 2))) (lint-format "perhaps ~A" caller (lists->string form `(,@header (cond ,@middle) ,@trailer)))))))) (partition-form (cdr form) (if else-error (- len 1) len))))) ;; not escaping else here because the trailing args might be evaluated first (when (and (not (hash-table-ref syntaces (car first-result))) (every? (lambda (c) (and (pair? c) (pair? (cdr c)) (pair? (cadr c)) (null? (cddr c)) (not (hash-table-ref syntaces (caadr c))) (equal? (cdadr c) (cdr first-result)))) (cddr form))) (if (every? (lambda (c) (eq? first-func (caadr c))) ; all result clauses are the same!? (cddr form)) ; possibly no else, so not always a duplicate message ;; (cond (X (f y z)) (Y (f y z)) (Z (f y z))) -> (if (or X Y Z) (f y z)) (lint-format "perhaps ~A" caller (lists->string form `(if (or ,@(map car (cdr form))) ,first-result))) ;; here we need an else clause else (apply # args) (if (memq (car else-clause) '(#t else)) ;; (cond (X (f y z)) (else (g y z))) -> ((cond (X f) (else g)) y z) (lint-format "perhaps ~A" caller (lists->string form `((cond ,@(map (lambda (c) (list (car c) (caadr c))) (cdr form))) ,@(cdr first-result)))))))))))) ;; ---------------- (let ((falses ()) (trues ())) (for-each (lambda (clause) (set! ctr (+ ctr 1)) (if (not (pair? clause)) (begin (set! all-eqv #f) (set! has-combinations #f) ;; ; (cond 1) (lint-format "cond clause ~A in ~A is not a pair?" caller clause (truncated-list->string form))) (begin (when all-eqv (unless eqv-select (set! eqv-select (eqv-selector (car clause)))) (set! all-eqv (and eqv-select (not (and (pair? (cdr clause)) (eq? (cadr clause) '=>))) ; case sends selector, but cond sends test result (cond-eqv? (car clause) eqv-select #t)))) (if (and (pair? prev-clause) (not has-combinations) (> len 2) (equal? (cdr clause) (cdr prev-clause))) (if (memq (car clause) '(else #t)) ; (cond ... (x z) (else z)) -> (cond ... (else z)) (unless (side-effect? (car prev-clause) env) ;; (cond (x y) (z 32) (else 32)) (lint-format* caller "this clause could be omitted: " (truncated-list->string prev-clause))) (set! has-combinations #t))) ; handle these later (set! prev-clause clause) (let ((expr (simplify-boolean (car clause) trues falses env)) (test (car clause)) (sequel (cdr clause)) (first-sequel (and (pair? (cdr clause)) (cadr clause)))) (if (not (equal? expr test)) (set! simplifications (cons (cons clause expr) simplifications))) (if (symbol? test) (if (and (not (eq? test 'else)) (pair? first-sequel)) (if (memq test first-sequel) (and-incomplete form 'cond test first-sequel env) (do ((p first-sequel (cdr p))) ((or (not (pair? p)) (and (pair? (car p)) (memq test (car p)))) (if (pair? p) (and-incomplete form 'cond test (car p) env)))))) (if (and (pair? test) (pair? first-sequel) (hash-table-ref bools (car test))) (if (member (cadr test) first-sequel) (and-forgetful form 'cond test first-sequel env) (do ((p first-sequel (cdr p))) ((or (not (pair? p)) (and (pair? (car p)) (member (cadr test) (car p)))) (if (pair? p) (and-forgetful form 'cond test (car p) env))))))) ;; code here to check every arg against its use in the sequel found no problems?!? (cond ((memq test '(else #t)) (set! has-else #t) (when (pair? sequel) (if (eq? first-sequel #) ;; (cond ((= x y) z) (else #) (lint-format "this # is redundant: ~A" caller clause)) (if (and (pair? first-sequel) ; (cond (a A) (else (cond ...))) -> (cond (a A) ...) (null? (cdr sequel))) ; similarly for if, when, and unless (case (car first-sequel) ((cond) ;; (cond ((< x 1) 2) (else (cond ((< y 3) 2) (#t 4)))) (lint-format "else clause could be folded into the outer cond: ~A" caller (lists->string form (append (copy form (make-list ctr)) (cdr first-sequel))))) ((if) ;; (cond (a A) (else (if b B))) (lint-format "else clause could be folded into the outer cond: ~A" caller (lists->string form (append (copy form (make-list ctr)) (if (= (length first-sequel) 3) (list (cdr first-sequel)) `((,(cadr first-sequel) ,@(unbegin (caddr first-sequel))) (else ,@(unbegin (cadddr first-sequel))))))))) ((when unless) ;; (cond (a A) (else (when b B))) (lint-format "else clause could be folded into the outer cond: ~A" caller (lists->string form (append (copy form (make-list ctr)) (if (eq? (car first-sequel) 'when) `((,(cadr first-sequel) ,@(cddr first-sequel))) `(((not ,(cadr first-sequel)) ,@(cddr first-sequel)))))))))))) ((not (= ctr len))) ((equal? test ''else) ;; (cond (x y) ('else z)) (lint-format "odd cond clause test: is 'else supposed to be else? ~A" caller (truncated-list->string clause))) ((and (eq? test 't) (not (var-member 't env))) ;; (cond ((= x 1) 1) (t 2) (lint-format "odd cond clause test: is t supposed to be #t? ~A" caller (truncated-list->string clause)))) (if (never-false expr) (if (not (= ctr len)) ;; (cond ((getenv s) x) ((= y z) w)) (lint-format "cond test ~A is never false: ~A" caller (car clause) (truncated-list->string form)) (if (not (or (memq expr '(#t else)) (side-effect? test env))) (lint-format "cond last test could be #t: ~A" caller form))) (if (never-true expr) ;; (cond ((< 3 1) 2)) (lint-format "cond test ~A is never true: ~A" caller (car clause) (truncated-list->string form)))) (unless (side-effect? test env) (cond ((or (memq test '(else #t)) (not (pair? sequel)) (pair? (cdr sequel)))) ((equal? test first-sequel) ;; (cond ((= x 0) x) ((= x 1) (= x 1))) (lint-format "no need to repeat the test: ~A" caller (lists->string clause (list test)))) ((and (pair? first-sequel) (pair? (cdr first-sequel)) (null? (cddr first-sequel)) (equal? test (cadr first-sequel))) (if (eq? (car first-sequel) 'not) ;; (cond ((> x 2) (not (> x 2)))) (lint-format "perhaps replace ~A with #f" caller first-sequel) ;; (cond (x (abs x))) (lint-format "perhaps use => here: ~A" caller (lists->string clause (list test '=> (car first-sequel)))))) ((and (eq? first-sequel #t) (pair? test) (not (memq (car test) '(or and))) (eq? (return-type (car test) env) 'boolean?)) ;; (cond ((null? x) #t) (else y)) (lint-format "this #t could be omitted: ~A" caller (truncated-list->string clause)))) (if (member test exprs) ;; (cond ((< x 2) 3) ((> x 0) 4) ((< x 2) 5)) (lint-format "cond test repeated: ~A" caller (truncated-list->string clause)) (set! exprs (cons test exprs)))) (if (boolean? expr) (if (not expr) ;; (cond ((< 3 1) 2)) (lint-format "cond test is always false: ~A" caller (truncated-list->string clause)) (if (not (= ctr len)) ;; (cond (#t 2) (x 3)) (lint-format "cond #t clause is not the last: ~A" caller (truncated-list->string form)))) (if (eq? test 'else) (if (not (= ctr len)) ;; (cond (else 2) (x 3)) (lint-format "cond else clause is not the last: ~A" caller (truncated-list->string form))) (lint-walk caller test env))) (if (and (symbol? expr) (not (var-member expr env)) (procedure? (symbol->value expr *e*))) ;; (cond (< x 1) (else 1)) (lint-format "strange cond test: ~A in ~A is a procedure" caller expr clause)) (if (eq? result :unset) (set! result sequel) (if (not (equal? result sequel)) (set! result :unequal))) (cond ((not (pair? sequel)) (if (not (null? sequel)) ; (not (null?...)) here is correct -- we're looking for stray dots (lint-format "cond clause is messed up: ~A" caller (truncated-list->string clause)))) ((not (eq? first-sequel '=>)) (lint-walk-open-body caller 'cond sequel env)) ((or (not (pair? (cdr sequel))) (pair? (cddr sequel))) ;; (cond (x =>)) (lint-format "cond => target is messed up: ~A" caller (truncated-list->string clause))) (else (let ((f (cadr sequel))) (if (symbol? f) (let ((val (symbol->value f *e*))) (when (procedure? val) (if (not (aritable? val 1)) ; here values might be in test expr ;; (cond (x => expt)) (lint-format "=> target (~A) may be unhappy: ~A" caller f clause)) (let ((sig (procedure-signature val))) (if (and (pair? sig) (pair? (cdr sig))) (let ((from-type (->lint-type expr)) (to-type (cadr sig))) (if (not (or (memq from-type '(#f #t values)) (memq to-type '(#f #t values)) (any-compatible? to-type from-type))) ;; (cond ((> x 0) => abs) (else y)) (lint-format "in ~A, ~A returns a ~A, but ~A expects ~A" caller (truncated-list->string clause) expr (prettify-checker-unq from-type) f to-type))))))) (if (and (pair? f) (eq? (car f) 'lambda) (pair? (cdr f)) (pair? (cadr f)) (not (= (length (cadr f)) 1))) (lint-format "=> target (~A) may be unhappy: ~A" caller f clause))) (lint-walk caller f env)))) (if (side-effect? expr env) (begin (set! falses ()) (set! trues ()) (set! result :unequal)) (begin (if (not (member expr falses)) (set! falses (cons expr falses))) (when (pair? expr) (if (and (eq? (car expr) 'not) (not (member (cadr expr) trues))) (set! trues (cons (cadr expr) trues))) (if (eq? (car expr) 'or) (for-each (lambda (p) (if (not (member p falses)) (set! falses (cons p falses)))) (cdr expr)))))))))) (cdr form))) ; for-each clause (if has-else (if (pair? result) ; all result clauses are the same (and not implicit) ;; (cond (x #t) (else #t)) -> #t (lint-format "perhaps ~A" caller (lists->string form (if (null? (cdr result)) (car result) `(begin ,@result))))) (let* ((last-clause (and (> len 1) (list-ref form len))) (last-res (let ((clen (and (pair? last-clause) (length last-clause)))) (and (integer? clen) (> clen 1) (list-ref last-clause (- clen 1)))))) (if (and (pair? last-res) (memq (car last-res) '(#t else))) ;; (cond (x y) (y z (else 3))) (lint-format "perhaps cond else clause is misplaced: ~A in ~A" caller last-res last-clause)))) (when (and (= len 2) (not (check-bool-cond caller form (cadr form) (caddr form) env)) (pair? (cadr form)) ; (cond 1 2)! (pair? (caddr form))) (let ((c1 (cadr form)) (c2 (caddr form))) (if (equal? (simplify-boolean (car c1) () () env) (simplify-boolean `(not ,(car c2)) () () env)) (lint-format "perhaps ~A" caller ; (cond ((x) y) ((not (x)) z)) -> (cond ((x) y) (else z)) (lists->string form `(cond ,c1 (else ,@(cdr c2))))) (when (and (pair? (car c1)) ; (cond ((not x) y) (else z)) -> (cond (x z) (else y)) (pair? (cdr c1)) ; null case is handled elsewhere (eq? (caar c1) 'not) (memq (car c2) '(else #t))) (let ((c1-len (tree-leaves (cdr c1))) ; try to avoid the dangling short case as in if (c2-len (tree-leaves (cdr c2)))) (when (and (< (+ c1-len c2-len) 100) (> (* c1-len 4) c2-len)) ; maybe 4 is too much (lint-format "perhaps ~A" caller (lists->string form (if (or (pair? (cddr c1)) (pair? (cddr c2))) `(cond (,(cadar c1) ,@(cdr c2)) (else ,@(cdr c1))) `(if ,(cadar c1) ,(cadr c2) ,(cadr c1))))))))))) (when has-combinations (do ((new-clauses ()) (current-clauses ()) (clauses (cdr form) (cdr clauses))) ((null? clauses) (let ((len2 (= (length new-clauses) 2))) (unless (and len2 ; i.e. don't go to check-bool-cond (check-bool-cond caller form (cadr new-clauses) (car new-clauses) env)) ;; (cond ((= x 3) 3) ((= x 2) 4) ((= x 1) 4)) -> (case x ((3) 3) ((2 1) 4)) (lint-format "perhaps ~A" caller (lists->string form (cond (all-eqv (cond->case eqv-select (reverse new-clauses))) ((not (and len2 (pair? (car new-clauses)) (memq (caar new-clauses) '(else #t)) (pair? (cadr new-clauses)) (pair? (caadr new-clauses)) (eq? (caaadr new-clauses) 'or) (null? (cdadr new-clauses)))) `(cond ,@(reverse new-clauses))) ((null? (cddar new-clauses)) ; (cond (A) (B) (else C)) -> (or A B C) `(or ,@(cdaadr new-clauses) ,(cadar new-clauses))) (else `(or ,@(cdaadr new-clauses) (begin ,@(cdar new-clauses)))))))) (set! simplifications ()) (set! all-eqv #f))) (let* ((clause (car clauses)) (result (cdr clause))) ; can be null in which case the test is the result (cond ((and (pair? simplifications) (assq clause simplifications)) => (lambda (e) (set! clause (cons (cdr e) result))))) (if (and (pair? (cdr clauses)) (equal? result (cdadr clauses))) (set! current-clauses (cons clause current-clauses)) (if (pair? current-clauses) (begin (set! current-clauses (cons clause current-clauses)) (set! new-clauses (cons (cons (simplify-boolean `(or ,@(map car (reverse current-clauses))) () () env) result) new-clauses)) (set! current-clauses ())) (set! new-clauses (cons clause new-clauses))))))) (when (and all-eqv (> len (if has-else 2 1))) ; (cond (x y)) -- kinda dumb, but (if x y) isn't much shorter ;; (cond ((= x 0) x) ((= x 1) (= x 1))) -> (case x ((0) x) ((1) (= x 1))) (lint-format "perhaps use case instead of cond: ~A" caller (lists->string form (cond->case eqv-select (cdr form))))) (if (and (= len 2) has-else (null? (cdadr form))) (let ((else-clause (if (null? (cddr (caddr form))) (cadr (caddr form)) `(begin ,@(cdr (caddr form)))))) ;; (cond ((a)) (else A)) -> (or (a) A) (lint-format "perhaps ~A" caller (lists->string form `(or ,(caadr form) ,else-clause))))) ;; -------- (unless (or has-combinations all-eqv) ;; look for repeated ((op x c1) c2) -> ((assoc x '((c1 . c2)...)) => cdr) anywhere in the clause list (let ((nc ()) (op #f) (sym-access #f) (start #f) (changed #f)) ;; extending this to memx possibilities got only 1 hit and involved ca. 20 lines (define (car-with-expr cls) (cond ((and (pair? simplifications) (assq cls simplifications)) => (lambda (e) (set! changed #t) (cons (cdr e) (cdr cls)))) (else cls))) (define (start-search clauses test) (if (code-constant? (cadr test)) (if (memq (car test) '(= string=? string-ci=? eq? eqv? equal? char=? char-ci=?)) (set! sym-access caddr)) (if (code-constant? (caddr test)) (set! sym-access cadr))) (if sym-access (begin (set! start clauses) (set! op (car test))) (set! nc (cons (car-with-expr (car clauses)) nc)))) (do ((clauses (cdr form) (cdr clauses))) ((or (null? clauses) (not (pair? (car clauses)))) (if (and changed (null? clauses)) ;; (cond ((< x 2) 3) ((> x 0) 4) ((< x 2) 5)) -> (cond ((< x 2) 3) ((> x 0) 4)) (lint-format "perhaps ~A" caller (lists->string form `(cond ,@(reverse (map (lambda (c) (if (not (car c)) (values) c)) nc))))))) (let ((test (caar clauses))) (let ((ok-but-at-end #f) (looks-ok (let ((result (cdar clauses))) (and (pair? test) (pair? (cdr test)) (pair? (cddr test)) (null? (cdddr test)) (pair? result) (null? (cdr result)) (not (symbol? (car result))) (or (not (pair? (car result))) ; quoted lists look bad in this context (and (eq? (caar result) 'quote) (not (pair? (cadar result))))))))) (if (not start) (if (and looks-ok (not (null? (cdr clauses)))) (start-search clauses test) (set! nc (cons (car-with-expr (car clauses)) nc))) (unless (and looks-ok (eq? (car test) op) (equal? (sym-access test) (sym-access (caar start))) (code-constant? ((if (eq? sym-access cadr) caddr cadr) test)) (not (set! ok-but-at-end (null? (cdr clauses))))) (if (eq? (cdr start) clauses) ; only one item in the block, or two but it's just two at the end (begin (set! nc (cons (car start) nc)) (if (and looks-ok (not (null? (cdr clauses)))) (start-search clauses test) (begin (set! start #f) (set! nc (cons (car-with-expr (car clauses)) nc))))) ;; multiple hits -- can we combine them? (let ((alist ()) (cc (if (eq? sym-access cadr) caddr cadr))) (set! changed #t) (do ((sc start (cdr sc))) ((if ok-but-at-end (null? sc) (eq? sc clauses)) (case op ((eq?) (set! nc (cons `((assq ,(sym-access (caar start)) ',(reverse alist)) => cdr) nc))) ((eqv? char=?) (set! nc (cons `((assv ,(sym-access (caar start)) ',(reverse alist)) => cdr) nc))) ((equal?) (set! nc (cons `((assoc ,(sym-access (caar start)) ',(reverse alist)) => cdr) nc))) ((string=?) ;; this is probably faster than assoc + string=?, but it creates symbols (let ((nlst (map (lambda (c) (cons (string->symbol (car c)) (cdr c))) alist))) (set! nc (cons `((assq (string->symbol ,(sym-access (caar start))) ',(reverse nlst)) => cdr) nc)))) (else (set! nc (cons `((assoc ,(sym-access (caar start)) ',(reverse alist) ,op) => cdr) nc))))) (set! alist (cons (cons (unquoted (cc (caar sc))) (unquoted (cadar sc))) alist))) (if (and looks-ok (not (null? (cdr clauses)))) (start-search clauses test) (begin (set! start #f) (if (not ok-but-at-end) (set! nc (cons (car-with-expr (car clauses)) nc)))))))))))))) ;; look for case at end (case in the middle is tricky due to #f handling) (when (and (> len 3) (= suggest made-suggestion)) (let ((rform (reverse form)) (eqv-select #f) (elen (if has-else (- len 1) len))) (if has-else (set! rform (cdr rform))) (set! eqv-select (eqv-selector (caar rform))) (when eqv-select (do ((clauses rform (cdr clauses)) (ctr 0 (+ ctr 1))) ((or (null? clauses) (let ((clause (car clauses))) (or (and (pair? (cdr clause)) (eq? (cadr clause) '=>)) ; case sends selector, but cond sends test result (not (cond-eqv? (car clause) eqv-select #t))))) (when (and (pair? clauses) (> ctr 1)) ;; (cond ((pair? x) 3) ((eq? x 'a) z) ((eq? x 'b) (* 2 z)) ((eq? x 'c)... -> ;; (if (pair? x) 3 (case x ((a) z) ((b) (* 2 z)) ((c) (display z)))) (lint-format "possibly use case at the end: ~A" caller (lists->string form (let ((else-case (cond->case eqv-select ; cond->case will handle the else branch (list-tail (cdr form) (- elen ctr))))) (if (= (- elen ctr) 1) (if (equal? (cdadr form) '(#f)) `(and (not ,(caadr form)) ,else-case) `(if ,@(cadr form) ,else-case)) `(cond ,@(copy (cdr form) (make-list (- elen ctr))) (else ,else-case)))))))))))) ;; -------- ;; repeated test exprs handled once (let ((exprs ()) (reps ()) (ctr 0) (pos 0) (head-len 0) (else-leaves 0) (else-result #f)) (for-each (lambda (c) (set! pos (+ pos 1)) (cond ((and (pair? c) (memq (car c) '(#t else))) (set! else-result (cdr c)) (set! else-leaves (tree-leaves else-result))) ((not (and (pair? c) (pair? (car c)) (or (eq? (caar c) 'and) (member (car c) reps)))) (set! exprs ()) (set! reps ()) (set! ctr 0)) ((null? exprs) (set! head-len pos) (set! exprs (cdar c)) (set! reps exprs) (set! ctr 1)) (else (set! ctr (+ ctr 1)) (set! reps (remove-if (lambda (rc) (not (or (equal? rc (car c)) (member rc (cdar c))))) reps))))) (cdr form)) (when (and (pair? reps) (> ctr 1) (< else-leaves (* ctr (length reps) 3))) ;; (cond ((pair? z) 32) ((and (pair? x) (pair? w)) 12) ((pair? x) 2) (else 0)) -> ;; (cond ((pair? z) 32) ((not (pair? x)) 0) ((pair? w) 12) (else 2)) (lint-format "perhaps ~A" caller (lists->string form (let ((not-reps (simplify-boolean (if (null? (cdr reps)) `(not ,(car reps)) `(not (and ,@reps))) () () env))) `(,@(copy form (make-list head-len)) (,not-reps ,@(or else-result '(#))) ,@(let mapper ((clauses (list-tail form head-len)) (lst ())) (if (null? clauses) (reverse lst) (let ((new-clause (let ((c (car clauses))) (if (memq (car c) '(else #t)) c `(,(if (member (car c) reps) 'else (remove-if (lambda (rc) (member rc reps)) (car c))) ,@(cdr c)))))) (if (and (pair? new-clause) (pair? (car new-clause)) (eq? (caar new-clause) 'and) (pair? (cdar new-clause)) (null? (cddar new-clause))) (set-car! new-clause (cadar new-clause))) (if (memq (car new-clause) '(else #t)) (reverse (cons new-clause lst)) (mapper (cdr clauses) (cons new-clause lst)))))))))))) (when (pair? (cadr form)) (if (= len 1) (let ((clause (cadr form))) ; (cond (a)) -> a, (cond (a b)) -> (if a b) etc (if (null? (cdr clause)) (lint-format "perhaps ~A" caller (lists->string form (car clause))) (if (and (not (eq? (cadr clause) '=>)) (or (pair? (cddr clause)) (= suggest made-suggestion))) ;; (cond ((= x 1) 32)) -> (if (= x 1) 32) (lint-format "perhaps ~A" caller (lists->string form (if (null? (cddr clause)) `(if ,(car clause) ,(cadr clause)) (if (and (pair? (car clause)) (eq? (caar clause) 'not)) `(unless ,@(cdar clause) ,@(cdr clause)) `(when ,(car clause) ,@(cdr clause))))))))) (when has-else ; len > 1 here (let ((last-clause (list-ref form (- len 1)))) ; not the else branch! -- just before it. (when (and (= suggest made-suggestion) ; look for all results the same (pair? (cadr form)) (pair? (cdadr form))) (let ((result (list-ref (cadr form) (- (length (cadr form)) 1))) (else-clause (cdr (list-ref form len)))) (when (every? (lambda (c) (and (pair? c) (pair? (cdr c)) (equal? result (list-ref c (- (length c) 1))))) (cddr form)) ;; (cond ((and (display x) x) 32) (#t 32)) -> (begin (and (display x) x) 32) (lint-format "perhaps ~A" caller (lists->string form (if (= len 2) ; one is else -- this case is very common (let* ((c1-len (length (cdr last-clause))) (new-c1 (case c1-len ((1) #f) ((2) (cadr last-clause)) (else `(begin ,@(copy (cdr last-clause) (make-list (- c1-len 1))))))) (else-len (length else-clause)) (new-else (case else-len ((1) #f) ((2) (car else-clause)) (else `(begin ,@(copy else-clause (make-list (- else-len 1)))))))) `(begin ,(if (= c1-len 1) (if new-else `(if (not ,(car last-clause)) ,new-else) (car last-clause)) (if (= else-len 1) (if new-c1 `(if ,(car last-clause) ,new-c1) (car last-clause)) `(if ,(car last-clause) ,new-c1 ,new-else))) ,result)) `(begin ; this almost never happens (cond ,@(map (lambda (c) (let ((len (length c))) (if (= len 2) (if (or (memq (car c) '(else #t)) (not (side-effect? (car c) env))) (values) (car c)) (copy c (make-list (- len 1)))))) (cdr form))) ,result))))))) ;; a few dozen hits here ;; the 'case parallel gets 2 hits, complex selectors ;; len = (- (length form) 1) = number of clauses (when (and (> len 2) (or (null? (cdr last-clause)) (and (pair? (cdr last-clause)) (null? (cddr last-clause)) (boolean? (cadr last-clause))))) (let ((else-clause (cdr (list-ref form len))) (next-clause (cdr (list-ref form (- len 2))))) (when (and (pair? else-clause) (null? (cdr else-clause)) (boolean? (car else-clause)) (not (equal? (cdr last-clause) else-clause)) (pair? next-clause) (null? (cdr next-clause)) (not (boolean? (car next-clause)))) (lint-format "perhaps ~A" caller (lists->string form `(,@(copy form (make-list (- len 1))) (else ,(if (car else-clause) `(not ,(car last-clause)) (car last-clause))))))))) ;; (cond ((= x y) 2) ((= x 2) #f) (else #t)) -> (cond ((= x y) 2) (else (not (= x 2)))) ;; (cond ((= x y) 2) ((= x 2) #t) (else #f)) -> (cond ((= x y) 2) (else (= x 2))) (when (= len 3) (let ((first-clause (cadr form)) (else-clause (cdr (list-ref form len)))) (when (and (or (null? (cdr first-clause)) (and (null? (cddr first-clause)) (boolean? (cadr first-clause)))) (pair? last-clause) (or (null? (cdr last-clause)) (null? (cddr last-clause)))) (if (and (pair? (cdr first-clause)) (not (cadr first-clause)) ; (cond (A #f) (B #t) (else C)) -> (and (not A) (or B C)) (or (null? (cdr last-clause)) (eq? (cadr last-clause) #t))) (lint-format "perhaps ~A" caller (lists->string form (simplify-boolean `(and (not ,(car first-clause)) (or ,(car last-clause) ,@(if (null? (cdr else-clause)) else-clause `(begin ,@else-clause)))) () () env))) (if (and (or (null? (cdr first-clause)) ; (cond (A #t) (B C) (else #f)) -> (or A (and B C)) (eq? (cadr first-clause) #t)) (not (car else-clause)) (null? (cdr else-clause))) (lint-format "perhaps ~A" caller (lists->string form `(or ,(car first-clause) (and ,@last-clause))))))) (when (and (equal? (cdr first-clause) else-clause) ; a = else result (pair? (cdr last-clause)) ; b does exist (not (eq? (cadr last-clause) '=>))) ; no => in b ;; (cond (A a) (B b) (else a)) -> (if (or A (not B)) a b) (lint-format "perhaps ~A" caller (lists->string form (let ((A (car first-clause)) (a (cdr first-clause)) (B (car last-clause)) (b (cdr last-clause))) (let ((nexpr (simplify-boolean `(or ,A (not ,B)) () () env))) (cond ((not (and (null? (cdr a)) (null? (cdr b)))) `(cond (,nexpr ,@a) (else ,@b))) ((eq? (car a) #t) (if (not (car b)) nexpr (simplify-boolean `(or ,nexpr ,(car b)) () () env))) ((car a) ; i.e a is not #f `(if ,nexpr ,(car a) ,(car b))) ((eq? (car b) #t) (simplify-boolean `(not ,nexpr) () () env)) (else (simplify-boolean `(and (not ,nexpr) ,(car b)) () () env)))))))))) (when (> len 3) ;; this is not ideal (let ((e (list-ref form len)) ; (cond (W X) (A B) (C D) (else B)) -> (cond (W X) ((or A (not C)) B) (else D)) (b (list-ref form (- len 1))) (a (list-ref form (- len 2)))) (if (and (pair? a) (pair? (cdr a)) ; is (else) a legal cond clause? -- yes, it returns else... (pair? e) (equal? (cdr a) (cdr e)) (pair? b) (pair? (cdr b)) (not (eq? (cadr b) '=>))) (let ((expr (simplify-boolean `(or ,(car a) (not ,(car b))) () () env))) (lint-format "perhaps ~A" caller (lists->string form `(cond ,(if (> len 4) '... (cadr form)) (,expr ,@(cdr a)) (else ,@(cdr b))))))))) (let ((arg1 (cadr form)) (arg2 (caddr form))) (when (and (pair? arg1) (pair? (car arg1)) (pair? (cdr arg1)) (pair? arg2) (eq? (caar arg1) 'and) (null? (cddr arg1)) (pair? (cdr arg2)) (null? (cddr arg2)) (member (car arg2) (cdar arg1)) (= (length (cdar arg1)) 2)) ;; (cond ((and A B) c) (B d) (else e)) -> (cond (B (if A c d)) (else e)) (lint-format "perhaps ~A" caller (lists->string form `(cond (,(car arg2) (if ,((if (equal? (car arg2) (cadar arg1)) caddar cadar) arg1) ,(cadr arg1) ,(cadr arg2))) ,@(cdddr form)))))) (if (and (pair? last-clause) ; (cond ... ((or ...)) (else ...)) -> (cond ... (else (or ... ...))) (pair? (car last-clause)) (null? (cdr last-clause)) (eq? (caar last-clause) 'or)) (let ((else-clause (let ((e (cdr (list-ref form len)))) (if (null? (cdr e)) (car e) `(begin ,@e))))) ;; (cond ((A) B) ((or C D)) (else E)) -> (cond ((A) B) (else (or C D E))) (lint-format "perhaps ~A" caller (lists->string form `(cond ,@(copy (cdr form) (make-list (- len 2))) (else (or ,@(cdar last-clause) ,else-clause)))))))))) (let ((last-clause (list-ref form (if has-else (- len 1) len)))) ; not the else branch! -- just before it. (if (and (pair? last-clause) ; (cond ... (A (cond ...)) (else B)) -> (cond ... ((not A) B) ...) (pair? (cdr last-clause)) (null? (cddr last-clause)) (pair? (cadr last-clause)) (memq (caadr last-clause) '(if cond))) (let ((new-test (simplify-boolean `(not ,(car last-clause)) () () env)) (new-result (if has-else (cdr (list-ref form len)) (if (eq? form lint-mid-form) () (list #))))) (if (eq? (caadr last-clause) 'cond) ;; (cond (A (cond (B c) (else D))) (else E)) -> (cond ((not A) E) (B c) (else D)) (lint-format "perhaps ~A" caller (lists->string form `(cond ,@(copy (cdr form) (make-list (- len (if has-else 2 1)))) (,new-test ,@new-result) ,@(cdadr last-clause)))) (if (= (length (cadr last-clause)) 4) (let ((if-form (cdadr last-clause))) ;; (cond (A B) (C (if D d E)) (else F)) -> (cond (A B) ((not C) F) (D d) (else E)) (lint-format "perhaps ~A" caller (lists->string form `(cond ,@(copy (cdr form) (make-list (- len (if has-else 2 1)))) (,new-test ,@new-result) (,(car if-form) ,@(unbegin (cadr if-form))) (else ,@(unbegin (caddr if-form)))))))))) (when (> len 2) ; rewrite nested conds as one cond (let ((lim (if has-else (- len 2) len)) (tlen (tree-leaves form))) (when (< tlen 200) (set! tlen (/ tlen 4)) (do ((i 0 (+ i 1)) (k (+ lim 1) (- k 1)) (p (cdr form) (cdr p))) ((or (not (pair? p)) (= i lim))) (let ((nc (car p))) (if (and (pair? nc) (pair? (cdr nc)) (null? (cddr nc)) (pair? (cadr nc)) (eq? (caadr nc) 'cond) (>= (length (cdadr nc)) (* 2 k)) (> (tree-leaves nc) tlen)) (let ((new-test (simplify-boolean `(not ,(car nc)) () () env)) (new-result (if (and has-else (= i (- lim 1)) (null? (cddadr p)) (null? (cddr (caddr p)))) `(if ,(caadr p) ,(cadadr p) ,(cadr (caddr p))) `(cond ,@(cdr p))))) ;; (cond ((= x 0) 1) ((= x 3) (cond ((not y) 1) ((pair? y) 2) ((eq? y 'a) 3) (else 4))) ((< x 200) 2) (else 5)) -> ;; (cond ((= x 0) 1) ((not (= x 3)) (if (< x 200) 2 5)) ((not y) 1) ((pair? y) 2) ((eq? y 'a) 3) (else 4)) (lint-format "perhaps ~A" caller (lists->string form `(cond ,@(copy (cdr form) (make-list i)) (,new-test ,new-result) ,@(cdadr nc)))))))))))))))) env)) (hash-table-set! h 'cond cond-walker)) ;; ---------------- case ---------------- (let () (define case-walker (let ((selector-types '(#t symbol? char? boolean? integer? rational? real? complex? number? null? eof-object?))) (lambda (caller form env) ;; here the keys are not evaluated, so we might have a list like (letrec define ...) ;; also unlike cond, only 'else marks a default branch (not #t) (if (< (length form) 3) ;; (case 3) (lint-format "case is messed up: ~A" caller (truncated-list->string form)) (let ((sel-type #t) (selector (cadr form)) (suggest made-suggestion)) ;; ---------------- ;; if regular case + else -- just like cond above (let ((len (- (length form) 2))) ; number of clauses (when (and (> len 1) ; (case x (else ...)) is handled elsewhere (pair? (cdr form)) (pair? (cddr form)) (pair? (caddr form)) (not (tree-set-member '(unquote #_{list}) form))) (let ((first-clause (caddr form)) (else-clause (list-ref form (+ len 1)))) (when (and (pair? else-clause) (eq? (car else-clause) 'else) (pair? (cdr first-clause)) (pair? (cadr first-clause)) (not (hash-table-ref syntaces (caadr first-clause))) (pair? (cdadr first-clause)) (null? (cddr first-clause)) (every? (lambda (c) (and (pair? c) (pair? (cdr c)) (pair? (cadr c)) (null? (cddr c)) (not (hash-table-ref syntaces (caadr c))) (equal? (cdadr first-clause) (cdadr c)))) (cdddr form))) ;; (case x ((a) (f y z)) (else (g y z))) -> ((if (eq? x 'a) f g) y z) (lint-format "perhaps ~A" caller ; all results share trailing args (lists->string form (if (and (= len 2) (symbol? (caar first-clause)) (null? (cdar first-clause))) `((if (eq? ,(cadr form) ',(caar first-clause)) ,(caadr first-clause) ,(caadr else-clause)) ,@(cdadr first-clause)) `((case ,(cadr form) ,@(map (lambda (c) (list (car c) (caadr c))) (cddr form))) ,@(cdadr first-clause)))))) (when (and (pair? (cdr first-clause)) (null? (cddr first-clause)) (pair? (cadr first-clause)) (pair? else-clause) (eq? (car else-clause) 'else) (pair? (cdr else-clause)) (pair? (cadr else-clause)) (or (equal? (caadr first-clause) (caadr else-clause)) ; there's some hope we'll match (escape? (cadr else-clause) env))) (let ((first-result (cadr first-clause)) (first-func (caadr first-clause)) (else-error (escape? (cadr else-clause) env))) (when (and (pair? (cdr first-result)) (not (eq? first-func 'values)) (or (not (hash-table-ref syntaces first-func)) (eq? first-func 'set!)) (every? (lambda (c) (and (pair? c) (pair? (cdr c)) (pair? (cadr c)) (null? (cddr c)) (pair? (cdadr c)) (or (equal? first-func (caadr c)) (and (eq? c else-clause) else-error)))) (cdddr form))) ((lambda (header-len trailer-len result-mid-len) (when (and (or (not (eq? first-func 'set!)) (> header-len 1)) (or (not (eq? first-func '/)) (> header-len 1) (> trailer-len 0))) (let ((header (copy first-result (make-list header-len))) (trailer (copy first-result (make-list trailer-len) (- (length first-result) trailer-len)))) (if (= len 2) (unless (equal? first-result (cadr else-clause)) ; handled elsewhere (all results equal -> result) ;; (case x ((1) (+ x 1)) (else (+ x 3))) -> (+ x (if (eqv? x 1) 1 3)) (lint-format "perhaps ~A" caller (let ((else-result (cadr else-clause))) (let ((first-mid-len (- (length first-result) header-len trailer-len)) (else-mid-len (- (length else-result) header-len trailer-len))) (let* ((fmid (if (= first-mid-len 1) (list-ref first-result header-len) `(values ,@(copy first-result (make-list first-mid-len) header-len)))) (emid (if else-error else-result (if (= else-mid-len 1) (list-ref else-result header-len) `(values ,@(copy else-result (make-list else-mid-len) header-len))))) (middle (if (= (length (car first-clause)) 1) `(eqv? ,(cadr form) ,(caar first-clause)) `(memv ,(cadr form) ',(car first-clause))))) (lists->string form `(,@header (if ,middle ,fmid ,emid) ,@trailer))))))) ;; len > 2 so use case in the revision (let ((middle (map (lambda (c) (if (and else-error (eq? c else-clause)) else-clause (let ((test (car c)) (result (cadr c))) (let ((mid-len (- (length result) header-len trailer-len))) `(,test ,(if (= mid-len 1) (list-ref result header-len) `(values ,@(copy result (make-list mid-len) header-len)))))))) (cddr form)))) ;; (case x ((0) (log x 2)) ((1) (log x 3)) (else (error 'oops))) -> (log x (case x ((0) 2) ((1) 3) (else (error 'oops)))) (lint-format "perhaps ~A" caller (lists->string form `(,@header (case ,(cadr form) ,@middle) ,@trailer)))))))) (partition-form (cddr form) (if else-error (- len 1) len))))))))) ;; ---------------- (if (every? (lambda (c) ; (case x ((a) a) ((b) b)) -> (symbol->value x) (and (pair? c) (pair? (car c)) (symbol? (caar c)) (null? (cdar c)) (pair? (cdr c)) (null? (cddr c)) (eq? (caar c) (cadr c)))) ; the quoted case happens only in test suites (cddr form)) (lint-format "perhaps (ignoring the unmatched case) ~A" caller (lists->string form `(symbol->value ,(cadr form))))) (when (= suggest made-suggestion) (let ((clauses (cddr form))) ; (case x ((a) #t) (else #f)) -> (eq? x 'a) -- this stuff actually happens! (if (null? (cdr clauses)) (let ((clause (car clauses))) (when (and (pair? clause) (pair? (car clause)) (pair? (cdr clause))) (let ((keys (car clause))) ;; (case 3 ((0) #t)) -> (if (eqv? 3 0) #t) ;; (case x ((#(0)) 2)) -> (if (eqv? x #(0)) 2) (lint-format "perhaps ~A" caller (lists->string form (let ((test (cond ((pair? (cdr keys)) `(memv ,(cadr form) ',keys)) ((and (symbol? (car keys)) (not (keyword? (car keys)))) `(eq? ,(cadr form) ',(car keys))) ((or (keyword? (car keys)) (null? (car keys))) `(eq? ,(cadr form) ,(car keys))) ((not (boolean? (car keys))) `(eqv? ,(cadr form) ,(car keys))) ((car keys) (cadr form)) (else `(not ,(cadr form))))) (op (if (and (pair? (cdr clause)) (pair? (cddr clause))) 'when 'if))) `(,op ,test ,@(cdr clause)))))))) (when (and (null? (cddr clauses)) (pair? (car clauses)) (pair? (cadr clauses)) (eq? (caadr clauses) 'else) (pair? (cdar clauses)) (pair? (cdadr clauses)) (null? (cddar clauses)) (null? (cddadr clauses)) (not (equal? (cadadr clauses) (cadar clauses)))) (let* ((akey (null? (cdaar clauses))) (keylist ((if akey caaar caar) clauses)) (quoted (or (not akey) (symbol? keylist))) (op (if (every? symbol? (caar clauses)) (if akey 'eq? 'memq) (if akey 'eqv? 'memv)))) ;; can't use '= or 'char=? here because the selector may return anything ;; (case x ((#\a) 3) (else 4)) -> (if (eqv? x #\a) 3 4) ;; (case x ((a) #t) (else #f)) -> (eq? x 'a) (lint-format "perhaps ~A" caller (lists->string form (cond ((and (boolean? (cadar clauses)) (boolean? (cadadr clauses))) (if (cadadr clauses) (if quoted `(not (,op ,selector ',keylist)) `(not (,op ,selector ,keylist))) (if quoted `(,op ,selector ',keylist) `(,op ,selector ,keylist)))) ((not (cadadr clauses)) ; (else #f) happens a few times (simplify-boolean (if quoted `(and (,op ,selector ',keylist) ,(cadar clauses)) `(and (,op ,selector ,keylist) ,(cadar clauses))) () () env)) (quoted `(if (,op ,selector ',keylist) ,(cadar clauses) ,(cadadr clauses))) (else (let ((select-expr (if (and (eq? op 'eqv?) (boolean? keylist) (or (and (symbol? selector) (not keylist)) (and (pair? selector) (symbol? (car selector)) (let ((sig (arg-signature (car selector) env))) (and (pair? sig) (eq? (car sig) 'boolean?)))))) (if keylist selector `(not ,selector)) `(,op ,selector ,keylist)))) `(if ,select-expr ,(cadar clauses) ,(cadadr clauses)))))))))))) (if (and (not (pair? selector)) (constant? selector)) ;; (case 3 ((0) #t)) (lint-format "case selector is a constant: ~A" caller (truncated-list->string form))) (if (symbol? selector) (set-ref selector caller form env) (lint-walk caller selector env)) (if (and (pair? selector) (symbol? (car selector))) (begin (set! sel-type (return-type (car selector) env)) (if (and (symbol? sel-type) (not (memq sel-type selector-types))) ;; (case (list 1) ((0) #t)) (lint-format "case selector may not work with eqv: ~A" caller (truncated-list->string selector))))) (let ((all-keys ()) (all-exprs ()) (ctr 0) (result :unset) (exprs-repeated #f) (else-foldable #f) (has-else #f) (len (length (cddr form)))) (for-each (lambda (clause) (set! ctr (+ ctr 1)) (if (not (pair? clause)) (lint-format "case clause should be a list: ~A" caller (truncated-list->string clause)) (let ((keys (car clause)) (exprs (cdr clause))) (if (null? exprs) ;; (case x (0)) (lint-format "clause result is missing: ~A" caller clause)) (if (eq? result :unset) (set! result exprs) (if (not (equal? result exprs)) (set! result :unequal))) (if (member exprs all-exprs) (set! exprs-repeated exprs) (set! all-exprs (cons exprs all-exprs))) (if (and (pair? exprs) (null? (cdr exprs)) (pair? (car exprs)) (pair? (cdar exprs)) (null? (cddar exprs)) (equal? selector (cadar exprs))) (if (and (eq? (caar exprs) 'not) (not (memq #f keys))) ;; (case x ((0) (f x)) ((1) (not x))) (lint-format "in ~A, perhaps replace ~A with #f" caller clause (car exprs)) ;; (case x ((0 1) (abs x))) (lint-format "perhaps use => here: ~A" caller (lists->string clause (list keys '=> (caar exprs)))))) (if (pair? keys) (if (not (proper-list? keys)) ;; (case x ((0) 1) ((1) 2) ((3 . 0) 4)) (lint-format (if (null? keys) "null case key list: ~A" "stray dot in case case key list: ~A") caller (truncated-list->string clause)) (for-each (lambda (key) (if (or (vector? key) (string? key) (pair? key)) ;; (case x ((#(0)) 2)) (lint-format "case key ~S in ~S is unlikely to work (case uses eqv? but it is a ~A)" caller key clause (cond ((vector? key) 'vector) ((pair? key) 'pair) (else 'string)))) (if (member key all-keys) ;; (case x ((0) 1) ((1) 2) ((3 0) 4)) (lint-format "repeated case key ~S in ~S" caller key clause) (set! all-keys (cons key all-keys))) ;; unintentional quote here, as in (case x ('a b)...) never happens and ;; is hard to distinguish from (case x ((quote a) b)...) which happens a lot (if (not (compatible? sel-type (->lint-type key))) ;; (case (string->symbol x) ((a) 1) ((2 3) 3)) (lint-format "case key ~S in ~S is pointless" caller key clause))) keys)) (if (not (eq? keys 'else)) ;; (case ((1) 1) (t 2)) (lint-format "bad case key ~S in ~S" caller keys clause) (begin (set! has-else clause) ;; exprs: (res) or if case, ((case ...)...) (if (not (= ctr len)) ;; (case x (else 2) ((0) 1)) (lint-format "case else clause is not the last: ~A" caller (truncated-list->string (cddr form))) (when (and (pair? exprs) (pair? (car exprs)) (null? (cdr exprs))) (let ((expr (car exprs))) (case (car expr) ((case) ; just the case statement in the else clause (when (and (equal? selector (cadr expr)) (not (side-effect? selector env))) (set! else-foldable (cddr expr)))) ((if) ; just if -- if foldable, make it look like it came from case (when (and (equal? selector (eqv-selector (cadr expr))) (cond-eqv? (cadr expr) selector #t) (not (side-effect? selector env))) ;; else-foldable as (((keys-from-test) true-branch) (else false-branch)) (set! else-foldable (if (pair? (cdddr expr)) `(,(case-branch (cadr expr) selector (list (caddr expr))) (else ,(car (cdddr expr)))) (list (case-branch (cadr expr) selector (cddr expr)))))))))))))) (lint-walk-open-body caller (car form) exprs env)))) (cddr form)) (if (and has-else (pair? result) (not else-foldable)) (begin ;; (case x (else (case x (else 1)))) -> 1 (lint-format "perhaps ~A" caller (lists->string form (if (null? (cdr result)) (car result) `(begin ,@result)))) (set! exprs-repeated #f))) ;; repeated result (but not all completely equal) and with else never happens (when (or exprs-repeated else-foldable) (let ((new-keys-and-exprs ()) (mergers ()) (else-clause (if else-foldable (call-with-exit (lambda (return) (for-each (lambda (c) (if (eq? (car c) 'else) (return c))) else-foldable) ())) (or has-else ())))) (let ((merge-case-keys (let ((else-exprs (and (pair? else-clause) (cdr else-clause)))) (define (a-few lst) (if (> (length lst) 3) (copy lst (make-list 4 '...) 0 3) lst)) (lambda (clause) (let ((keys (car clause)) (exprs (cdr clause))) (when (and (pair? exprs) ; ignore clauses that are messed up (not (eq? keys 'else)) (not (equal? exprs else-exprs))) (let ((prev (member exprs new-keys-and-exprs (lambda (a b) (equal? a (cdr b)))))) (if prev (let* ((cur-clause (car prev)) (cur-keys (car cur-clause))) (when (pair? cur-keys) (set! mergers (cons (list (a-few keys) (a-few cur-keys)) mergers)) (set-car! cur-clause (append cur-keys (map (lambda (key) (if (memv key cur-keys) (values) key)) keys))))) (set! new-keys-and-exprs (cons (cons (copy (car clause)) (cdr clause)) new-keys-and-exprs)))))))))) (for-each merge-case-keys (cddr form)) (if (pair? else-foldable) (for-each merge-case-keys else-foldable))) (if (null? new-keys-and-exprs) (lint-format "perhaps ~A" caller ;; (case x (else (case x (else 1)))) -> 1 (lists->string form (if (or (null? else-clause) ; can this happen? (it's caught above as an error) (null? (cdr else-clause))) () (if (null? (cddr else-clause)) (cadr else-clause) `(begin ,@(cdr else-clause)))))) (begin ;; (null? (cdr new-keys-and-exprs)) is rare and kinda dumb -- cases look like test suite entries (for-each (lambda (clause) (if (and (pair? (car clause)) (pair? (cdar clause))) (if (every? integer? (car clause)) (set-car! clause (sort! (car clause) <)) (if (every? char? (car clause)) (set-car! clause (sort! (car clause) char (case x ((0 1) 32)) (lint-format "perhaps ~A" caller (if (pair? mergers) (format #f "merge keys ~{~{~A with ~A~}~^, ~}: ~A" (reverse mergers) (lists->string form new-form)) (lists->string form new-form))))))))))) env))) (hash-table-set! h 'case case-walker)) ;; ---------------- do ---------------- (let ((cxars (hash-table '(car . ()) '(caar . car) '(cdar . cdr) '(caaar . caar) '(cdaar . cdar) '(cddar . cddr) '(cadar . cadr) '(caaaar . caaar) '(caadar . caadr) '(cadaar . cadar) '(caddar . caddr) '(cdaaar . cdaar) '(cdadar . cdadr) '(cddaar . cddar) '(cdddar . cdddr)))) (define (car-subst sym new-sym tree) (cond ((or (not (pair? tree)) (eq? (car tree) 'quote)) tree) ((not (and (symbol? (car tree)) (pair? (cdr tree)) (null? (cddr tree)) (eq? sym (cadr tree)))) (cons (car-subst sym new-sym (car tree)) (car-subst sym new-sym (cdr tree)))) ((hash-table-ref cxars (car tree)) => (lambda (f) (if (symbol? f) (list f new-sym) new-sym))) (else tree))) (define (cadr-subst sym new-sym tree) ;(format *stderr* "subst: ~A ~A ~A~%" sym new-sym tree) (cond ((or (not (pair? tree)) (eq? (car tree) 'quote)) tree) ((and (memq (car tree) '(vector-ref string-ref list-ref)) (pair? (cdr tree)) (pair? (cddr tree)) (null? (cdddr tree)) (equal? sym (cadr tree))) new-sym) (else (cons (cadr-subst sym new-sym (car tree)) (cadr-subst sym new-sym (cdr tree)))))) (define (var-step v) ((cdr v) 'step)) (define (do-walker caller form env) (let ((vars ())) (if (not (and (>= (length form) 3) (proper-list? (cadr form)) (proper-list? (caddr form)))) (lint-format "do is messed up: ~A" caller (truncated-list->string form)) (let ((step-vars (cadr form)) (inner-env #f)) ;; do+lambda in body with stepper as free var never happens (unless (side-effect? form env) ;; a much more permissive check here (allowing sets of locals etc) got only a half-dozen hits (let ((end+result (caddr form))) (if (or (not (pair? end+result)) (null? (cdr end+result))) ;; (do ((i 0 (+ i 1))) ((= i 1))) (lint-format "this do-loop could be replaced by (): ~A" caller (truncated-list->string form)) (if (and (null? (cddr end+result)) (code-constant? (cadr end+result))) ;; (begin (z 1) (do ((i 0 (+ i 1))) ((= i n) 32))): 32 (lint-format "this do-loop could be replaced by ~A: ~A" caller (cadr end+result) (truncated-list->string form)))))) ;; walk the init forms before adding the step vars to env (do ((bindings step-vars (cdr bindings))) ((not (pair? bindings)) (if (not (null? bindings)) (lint-format "do variable list is not a proper list? ~S" caller step-vars))) (when (binding-ok? caller 'do (car bindings) env #f) (for-each (lambda (v) (if (not (or (eq? (var-initial-value v) (var-name v)) (not (tree-memq (var-name v) (cadar bindings))) (hash-table-ref built-in-functions (var-name v)) (tree-table-member binders (cadar bindings)))) (if (not (var-member (var-name v) env)) ;; (let ((xx 0)) (do ((x 1 (+ x 1)) (y x (- y 1))) ((= x 3) xx) (display y)): x (lint-format "~A in ~A does not appear to be defined in the calling environment" caller (var-name v) (car bindings)) ;; (let ((x 0)) (do ((x 1 (+ x 1)) (y x (- y 1))) ((= x 3)) (display y))): y (lint-format "~A in ~A refers to the caller's ~A, not the do-loop variable" caller (var-name v) (car bindings) (var-name v))))) vars) (lint-walk caller (cadar bindings) env) (let ((new-var (let ((v (make-var :name (caar bindings) :definer 'do :initial-value (cadar bindings)))) (let ((stepper (and (pair? (cddar bindings)) (caddar bindings)))) (varlet (cdr v) :step stepper) (if stepper (set! (var-history v) (cons (list 'set! (caar bindings) stepper) (var-history v))))) v))) (set! vars (cons new-var vars))))) (set! inner-env (append vars env)) ;; walk the step exprs (let ((baddies ())) ; these are step vars (with step exprs) used within other step vars step expressions (for-each (lambda (stepper) (when (and (binding-ok? caller 'do stepper env #t) (pair? (cddr stepper))) (let ((data (var-member (car stepper) vars))) (let ((old-ref (var-ref data))) (lint-walk caller (caddr stepper) inner-env) (set! (var-ref data) old-ref)) (if (eq? (car stepper) (caddr stepper)) (lint-format "perhaps ~A" caller (lists->string stepper (list (car stepper) (cadr stepper))))) (set! (var-set data) (+ (var-set data) 1))) (when (and (pair? (caddr stepper)) (not (eq? (car stepper) (cadr stepper))) (eq? (car (caddr stepper)) 'cdr) (eq? (cadr stepper) (cadr (caddr stepper)))) (lint-format "this looks suspicious: ~A" caller stepper)) (for-each (lambda (v) (if (and (var-step v) (not (eq? (var-name v) (car stepper))) (or (eq? (var-name v) (caddr stepper)) (and (pair? (caddr stepper)) (tree-unquoted-member (var-name v) (caddr stepper))))) (set! baddies (cons (car stepper) baddies)))) vars))) step-vars) (check-unordered-exprs caller form (map var-initial-value vars) env) (when (pair? baddies) ;; (do ((i 0 j) (j ...))...) is unreadable -- which (binding of) j is i set to? ;; but this is tricky if there is more than one such variable -- if cross links, we'll need named let ;; and if no step expr, there's no confusion. ;; (do ((i 0 j) (j 1 i) (k 0 (+ k 1))) ((= k 4)) (format *stderr* "~A ~A~%" i j)) ;; (let __1__ ((i 0) (j 1) (k 0)) (if (= k 4) () (begin (format *stderr* "~A ~A~%" i j) (__1__ j i (+ k 1))))) (let ((new-steppers (map (lambda (stepper) (if (memq (car stepper) baddies) `(,(car stepper) ,(cadr stepper)) stepper)) step-vars)) (new-sets (map (lambda (stepper) (if (memq (car stepper) baddies) `(set! ,(car stepper) ,(caddr stepper)) (values))) step-vars))) (if (or (null? (cdr baddies)) (let ((trails new-sets)) (not (any? (lambda (v) ; for each baddy, is it used in any following set!? (and (pair? (cdr trails)) (set! trails (cdr trails)) (tree-unquoted-member v trails))) (reverse baddies))))) (lint-format "perhaps ~A" caller (lists->string form `(do ,new-steppers ,(caddr form) ,@(cdddr form) ,@new-sets))) ;; (do ((i 0 (+ i j)) (j 0 (+ k 1)) (k 1)) ((= i 10)) (display (+ i j k))) -> ;; (do ((i 0) (j 0 (+ k 1)) (k 1)) ((= i 10)) (display (+ i j k)) (set! i (+ i j))) (let* ((loop (find-unique-name form)) (new-body (let ((let-loop `(,loop ,@(map (lambda (s) ((if (pair? (cddr s)) caddr car) s)) step-vars)))) (if (pair? (cdddr form)) `(begin ,@(cdddr form) ,let-loop) let-loop)))) (let ((test (if (pair? (caddr form)) (caaddr form) ())) (result (if (not (and (pair? (caddr form)) (pair? (cdaddr form)))) () (if (null? (cdr (cdaddr form))) (car (cdaddr form)) `(begin ,@(cdaddr form)))))) ;; (do ((i 0 j) (j 1 i) (k 0 (+ k 1))) ((= k 5) (set! x k) (+ k 1)) (display (+ i j)) -> use named let (lint-format "this do loop is unreadable; perhaps ~A" caller (lists->string form `(let ,loop ,(map (lambda (s) (list (car s) (cadr s))) step-vars) (if ,test ,result ,new-body)))))))))) ;; walk the body and end stuff (it's too tricky to find infinite do loops) (when (pair? (caddr form)) (let ((end+result (caddr form))) (when (pair? end+result) (let ((end (car end+result))) (lint-walk caller end inner-env) ; this will call simplify-boolean (if (pair? (cdr end+result)) (if (null? (cddr end+result)) (begin (if (any-null? (cadr end+result)) ;; (do ((i 0 (+ i 1))) ((= i 3) ()) (display i)) (lint-format "nil return value is redundant: ~A" caller end+result)) (lint-walk caller (cadr end+result) inner-env)) (lint-walk-open-body caller 'do-result (cdr end+result) inner-env))) (if (and (symbol? end) (memq end '(= > < >= <= null? not))) ;; (do ((i 0 (+ i 1))) (= i 10) (display i)) (lint-format "perhaps missing parens: ~A" caller end+result)) (cond ((never-false end) ;; (do ((i 0 (+ i 1))) ((+ i 10) i)) (lint-format "end test is never false: ~A" caller end)) (end ; it's not #f (if (never-true end) (lint-format "end test is never true: ~A" caller end) (let ((v (and (pair? end) (memq (car end) '(< > <= >=)) (pair? (cdr end)) (symbol? (cadr end)) (var-member (cadr end) vars)))) ;; if found, v is the var info (when (pair? v) (let ((step (var-step v))) (when (pair? step) (let ((inc (and (memq (car step) '(+ -)) (pair? (cdr step)) (pair? (cddr step)) (or (and (real? (cadr step)) (cadr step)) (and (real? (caddr step)) (caddr step)))))) (when (and (real? inc) (case (car step) ((+) (and (positive? inc) (memq (car end) '(< <=)))) ((-) (and (positive? inc) (memq (car end) '(> >=)))) (else #f))) ;; (do ((i 0 (+ i 1))) ((< i len)) (display i) ;; (do ((i 0 (- i 1))) ((> i len)) (display i)) (lint-format "do step looks like it doesn't match end test: ~A" caller (lists->string step end)))))))))) ((pair? (cdr end+result)) ;; (do ((i 0 (+ i 1))) (#f i)) (lint-format "result is unreachable: ~A" caller end+result))) (if (and (symbol? end) (not (var-member end env)) (procedure? (symbol->value end *e*))) ;; (do ((i 0 (+ i 1))) (abs i) (display i)) (lint-format "strange do end-test: ~A in ~A is a procedure" caller end end+result)))))) (lint-walk-body caller 'do (cdddr form) (cons (make-var :name :let :initial-value form :definer 'do) inner-env)) ;; before report-usage, check for unused variables, and don't complain about them if ;; they are referenced in an earlier step expr. (do ((v vars (cdr v))) ((null? v)) (let ((var (car v))) (when (zero? (var-ref var)) ;; var was not seen in the end+result/body or any subsequent step exprs ;; vars is reversed order, so we need only scan var-step of the rest (if (side-effect? (var-step var) env) (set! (var-ref var) (+ (var-ref var) 1)) (for-each (lambda (nv) (if (or (eq? (var-name var) (var-step nv)) (and (pair? (var-step nv)) (tree-unquoted-member (var-name var) (var-step nv)))) (set! (var-ref var) (+ (var-ref var) 1)))) (cdr v)))))) (report-usage caller 'do vars inner-env) ;; look for constant expressions in the do body (when *report-constant-expressions-in-do* (let ((constant-exprs (find-constant-exprs 'do (map var-name vars) (cdddr form)))) (when (pair? constant-exprs) (if (null? (cdr constant-exprs)) ;; (do ((p (list 1) (cdr p))) ((null? p)) (set! y (log z 2)) (display x)) (lint-format "in ~A, ~A appears to be constant" caller (truncated-list->string form) (car constant-exprs)) (lint-format "in ~A, the following expressions appear to be constant:~%~NC~A" caller (truncated-list->string form) (+ lint-left-margin 4) #\space (format #f "~{~A~^, ~}" constant-exprs)))))) ;; if simple lambda expanded and exists only for the loop, remove let as well? ;; this can sometimes be simplified (let ((body (cdddr form))) (when (and (pair? body) (null? (cdr body)) (pair? (car body))) ;; do+let: tons of hits but how to distinguish the rewritable ones? ;; very tricky if val is not a constant (if (and (eq? (caar body) 'let) (not (symbol? (cadar body))) (every? (lambda (c) (code-constant? (cadr c))) (cadar body))) ;; (do ((i 0 (+ i 1))) ((= i 3)) (let ((a 12)) (set! a (+ a i)) (display a))) -> ;; (do ((i 0 (+ i 1)) (a 12 12)) ((= i 3)) (set! a (+ a i)) ...) (lint-format "perhaps ~A" caller (lists->string form `(do (,@(cadr form) ,@(map (lambda (c) (list (car c) (cadr c) (cadr c))) (cadar body))) ,(caddr form) ,@(one-call-and-dots (cddar body)))))) (let ((v (var-member (caar body) env))) (when (and (var? v) (memq (var-ftype v) '(define lambda))) (let* ((vfunc (var-initial-value v)) (vbody (cddr vfunc))) ;; we already detect a do body with no side-effects (walk-body) (when (and (proper-list? ((if (eq? (var-ftype v) 'define) cdadr cadr) vfunc)) (null? (cdr vbody)) (< (tree-leaves vbody) 16)) (do ((pp (var-arglist v) (cdr pp))) ((or (null? pp) (> (tree-count1 (car pp) vbody 0) 1)) (when (null? pp) (let ((new-body (copy vbody))) (for-each (lambda (par arg) (if (not (eq? par arg)) (set! new-body (tree-subst arg par new-body)))) (var-arglist v) (cdar body)) ;; (do ((i 0 (+ i 1))) ((= i 10)) (f i)) -> (do ((i 0 (+ i 1))) ((= i 10)) (abs (* 2 i))) (lint-format "perhaps ~A" caller (lists->string form `(do ,(cadr form) ,(caddr form) ,@new-body))))))))))))) ;; do -> for-each (when (and (pair? step-vars) (null? (cdr step-vars))) (let ((var (car step-vars))) (when (and (pair? (cdr var)) (pair? (cddr var)) (pair? (caddr var)) (pair? (caddr form)) (pair? (caaddr form)) (null? (cdaddr form)) (pair? (cdaddr var)) (eq? (car var) (cadr (caddr var)))) (let ((vname (car var)) (end (caaddr form))) (case (caaddr var) ((cdr) (when (and (case (car end) ((null?) (eq? (cadr end) vname)) ((not) (and (pair? (cadr end)) (eq? (caadr end) 'pair?) (eq? (cadadr end) vname))) (else #f)) (not (let walker ((tree (cdddr form))) ; since only (cxar sym) is accepted, surely sym can't be shadowed? (or (eq? tree vname) (and (pair? tree) (or (and (match-cxr 'cdr (car tree)) (pair? (cdr tree)) (eq? vname (cadr tree))) (and (not (hash-table-ref cxars (car tree))) (or (walker (car tree)) (walker (cdr tree)))))))))) ;; this assumes slightly more than the do-loop if (not (pair? var)) is the end-test ;; for-each wants a sequence, but the do loop checks that in advance. ;; (do ((p lst (cdr p))) ((null? p)) (display (car p))) -> (for-each (lambda ([p]) (display [p])) lst) (lint-format "perhaps ~A" caller (lists->string form (let ((new-sym (symbol "[" (symbol->string vname) "]"))) `(for-each (lambda (,new-sym) ,@(car-subst vname new-sym (cdddr form))) ,(cadr var))))))) ((+) (when (and (eqv? (cadr var) 0) (pair? (cddr (caddr var))) (eqv? (caddr (caddr var)) 1) (null? (cdddr (caddr var)))) (let ((end-var ((if (eq? vname (cadr end)) caddr cadr) end))) (if (and (pair? end-var) (memq (car end-var) '(length string-length vector-length))) (set! end-var (cadr end-var)) (let ((v (var-member end-var env))) (if (and (var? v) (pair? (var-initial-value v)) (memq (car (var-initial-value v)) '(length string-length vector-length))) (set! end-var (cadr (var-initial-value v)))))) (when (and (memq (car end) '(= >=)) (memq vname end) (tree-memq vname (cdddr form)) (not (let walker ((tree (cdddr form))) (if (and (pair? tree) (memq vname tree) (memq (car tree) '(string-ref list-ref vector-ref)) (eq? (caddr tree) vname)) (not (equal? (cadr tree) end-var)) (or (eq? tree vname) (and (pair? tree) (if (memq vname tree) (not (and (memq (car tree) '(string-ref list-ref vector-ref)) (pair? (cdr tree)) (pair? (cddr tree)) (eq? (caddr tree) vname))) (or (walker (car tree)) (walker (cdr tree)))))))))) ;; (do ((i 0 (+ i 1))) ((= i (vector-length x))) (find (vector-ref x i))) -> ;; (for-each (lambda ([x]) (find [x])) x) (lint-format "perhaps ~A" caller (lists->string form (let ((new-sym (symbol "[" (symbol->string (if (symbol? end-var) end-var (car end-var))) "]"))) `(for-each (lambda (,new-sym) ,@(cadr-subst end-var new-sym (cdddr form))) ,end-var))))))))))))) ;; check for do-loop as copy/fill! stand-in and other similar cases (when (and (pair? vars) (null? (cdr vars))) (let ((end-test (and (pair? (caddr form)) (caaddr form))) (first-var (car step-vars)) (body (cdddr form)) (setv #f)) (when (and (pair? end-test) (pair? body) (null? (cdr body)) (pair? (car body)) (memq (car end-test) '(>= =))) (let ((vname (car first-var)) (start (cadr first-var)) (step (and (pair? (cddr first-var)) (caddr first-var))) (end (caddr end-test))) (when (and (pair? step) (eq? (car step) '+) (memq vname step) (memv 1 step) (null? (cdddr step)) (or (eq? (cadr end-test) vname) (and (eq? (car end-test) '=) (eq? (caddr end-test) vname) (set! end (cadr end-test))))) ;; we have (do ((v start (+ v 1)|(+ 1 v))) ((= v end)|(= end v)|(>= v end)) one-statement) (set! body (car body)) ;; write-char is the only other common case here -> write-string in a few cases (when (and (memq (car body) '(vector-set! float-vector-set! int-vector-set! list-set! string-set! byte-vector-set!)) ;; integer type check here isn't needed because we're using this as an index below ;; the type error will be seen in report-usage if not earlier (eq? (caddr body) vname) (let ((val (cadddr body))) (set! setv val) (or (code-constant? val) (and (pair? val) (memq (car val) '(vector-ref float-vector-ref int-vector-ref list-ref string-ref byte-vector-ref)) (eq? (caddr val) vname))))) ;; (do ((i 2 (+ i 1))) ((= i len)) (string-set! s i #\a)) -> (fill! s #\a 2 len) (lint-format "perhaps ~A" caller (lists->string form (if (code-constant? setv) `(fill! ,(cadr body) ,(cadddr body) ,start ,end) `(copy ,(cadr setv) ,(cadr body) ,start ,end)))))))))))) env)) (hash-table-set! h 'do do-walker)) ;; ---------------- let ---------------- (let () (define (let-walker caller form env) (if (or (< (length form) 3) (not (or (symbol? (cadr form)) (list? (cadr form))))) ;; (let ((a 1) (set! a 2))) (lint-format "let is messed up: ~A" caller (truncated-list->string form)) (let ((named-let (and (symbol? (cadr form)) (cadr form)))) (if (keyword? named-let) ;; (let :x ((i y)) (x i)) (lint-format "bad let name: ~A" caller named-let)) (unless named-let (if (and (null? (cadr form)) ; this can be fooled by macros that define things (eq? form lint-current-form) ; i.e. we're in a body? (not (tree-set-member '(call/cc call-with-current-continuation lambda lambda* define define* define-macro define-macro* define-bacro define-bacro* define-constant define-expansion load eval eval-string require) (cddr form)))) ;; (begin (let () (display x)) y) (lint-format "pointless let: ~A" caller (truncated-list->string form)) (let ((body (cddr form))) (when (and (null? (cdr body)) (pair? (car body))) (if (memq (caar body) '(let let*)) (if (null? (cadr form)) ;; (let () (let ((a x)) (+ a 1))) (lint-format "pointless let: ~A" caller (lists->string form (car body))) (if (null? (cadar body)) ;; (let ((a x)) (let () (+ a 1))) (lint-format "pointless let: ~A" caller (lists->string form `(let ,(cadr form) ,@(cddar body)))))) (if (and (memq (caar body) '(lambda lambda*)) ; or any definer? (null? (cadr form))) ;; (let () (lambda (a b) (if (positive? a) (+ a b) b))) -> (lambda (a b) (if (positive? a) (+ a b) b)) (lint-format "pointless let: ~A" caller (lists->string form (car body))))))))) (let ((vars (if (or (not named-let) (keyword? named-let) (not (or (null? (caddr form)) (and (proper-list? (caddr form)) (every? pair? (caddr form)))))) () (list (make-fvar :name named-let :ftype 'let :decl (dummy-func caller form (list 'define (cons '_ (map car (caddr form))) #f)) :arglist (map car (caddr form)) :initial-value form :env env)))) (varlist ((if named-let caddr cadr) form)) (body ((if named-let cdddr cddr) form))) (if (not (list? varlist)) (lint-format "let is messed up: ~A" caller (truncated-list->string form)) (if (and (null? varlist) (pair? body) (null? (cdr body)) (not (side-effect? (car body) env))) ;; (let xx () z) (lint-format "perhaps ~A" caller (lists->string form (car body))))) (do ((bindings varlist (cdr bindings))) ((not (pair? bindings)) (if (not (null? bindings)) ;; (let ((a 1) . b) a) (lint-format "let variable list is not a proper list? ~S" caller varlist))) (when (binding-ok? caller 'let (car bindings) env #f) (let ((val (cadar bindings))) (if (and (pair? val) (eq? 'lambda (car val)) (tree-car-member (caar bindings) val) (not (var-member (caar bindings) env))) ;; (let ((x (lambda (a) (x 1)))) x) (lint-format "let variable ~A is called in its binding? Perhaps let should be letrec: ~A" caller (caar bindings) (truncated-list->string bindings)) (unless named-let (for-each (lambda (v) (if (and (tree-memq (var-name v) (cadar bindings)) (not (hash-table-ref built-in-functions (var-name v))) (not (tree-table-member binders (cadar bindings)))) (if (not (var-member (var-name v) env)) ;; (let ((x 1) (y x)) (+ x y)): x in (y x) (lint-format "~A in ~A does not appear to be defined in the calling environment" caller (var-name v) (car bindings)) ;; (let ((x 3)) (+ x (let ((x 1) (y x)) (+ x y)))): x in (y x) (lint-format "~A in ~A refers to the caller's ~A, not the let variable" caller (var-name v) (car bindings) (var-name v))))) vars))) (let ((e (if (symbol? val) (set-ref val caller form env) (lint-walk caller val env)))) (if (and (pair? e) (not (eq? e env)) (memq (var-name (car e)) '(:lambda :dilambda))) (let ((ldata (cdar e))) (set! (var-name (car e)) (caar bindings)) (set! (ldata 'initial-value) val) (set! vars (cons (car e) vars))) (set! vars (cons (make-var :name (caar bindings) :initial-value val :definer (if named-let 'named-let 'let)) vars))))))) (check-unordered-exprs caller form (map (if (not named-let) var-initial-value (lambda (v) (if (eq? (var-name v) named-let) (values) (var-initial-value v)))) vars) env) (let ((suggest made-suggestion)) (when (and (pair? varlist) ; (let ((x (A))) (if x (f x) B)) -> (cond ((A) => f) (else B) (pair? body) (pair? (car body)) (null? (cdr body)) (pair? (cdar body))) (when (and (pair? (car varlist)) ; ^ this happens a lot, so it's worth this tedious search (null? (cdr varlist)) ; also (let ((x (A))) (cond (x (f x))...) (pair? (cdar varlist)) (pair? (cadar varlist))) (let ((p (car body)) (vname (caar varlist)) (vvalue (cadar varlist))) (when (and (not named-let) ; (let ((x (assq a y))) (set! z (if x (cadr x) 0))) -> (set! z (cond ((assq a y) => cadr) (else 0))) (not (memq (car p) '(if cond))) ; handled separately below (= (tree-count2 vname p 0) 2)) (do ((i 0 (+ i 1)) (bp (cdr p) (cdr bp))) ((or (null? bp) (let ((b (car bp))) (and (pair? b) (eq? (car b) 'if) (= (tree-count2 vname b 0) 2) (eq? vname (cadr b)) (pair? (caddr b)) (pair? (cdaddr b)) (null? (cddr (caddr b))) (eq? vname (cadr (caddr b)))))) (if (pair? bp) (let ((else-clause (if (pair? (cdddar bp)) `((else ,@(cdddar bp))) ()))) (lint-format "perhaps ~A" caller (lists->string form `(,@(copy p (make-list (+ i 1))) (cond (,vvalue => ,(caaddr (car bp))) ,@else-clause) ,@(cdr bp))))))))) (when (and (eq? (car p) 'cond) ; (let ((x (f y))) (cond (x (g x)) ...)) -> (cond ((f y) => g) ...) (pair? (cadr p)) (eq? (caadr p) vname) (pair? (cdadr p)) (null? (cddadr p)) (or (and (pair? (cadadr p)) (pair? (cdr (cadadr p))) (null? (cddr (cadadr p))) ; one arg to func (eq? vname (cadr (cadadr p)))) (eq? vname (cadadr p))) (or (null? (cddr p)) (not (tree-unquoted-member vname (cddr p))))) (lint-format "perhaps ~A" caller (lists->string form (if (eq? vname (cadadr p)) (if (and (pair? (cddr p)) (pair? (caddr p)) (memq (caaddr p) '(else #t t))) (if (null? (cddr (caddr p))) `(or ,vvalue ,(cadr (caddr p))) `(or ,vvalue (begin ,@(cdaddr p)))) `(or ,vvalue (cond ,@(cddr p)))) `(cond (,vvalue => ,(caadr (cadr p))) ,@(cddr p)))))) (when (and (null? (cddr p)) ; (let ((x (+ y 1))) (abs x)) -> (abs (+ y 1)) (eq? vname (cadr p))) ; not tree-subst or trailing (pair) args: the let might be forcing evaluation order (let ((v (var-member (car p) env))) (if (or (and (var? v) (memq (var-definer v) '(define define* lambda lambda*))) (hash-table-ref built-in-functions (car p))) (lint-format "perhaps ~A" caller (lists->string form `(,(car p) ,vvalue))) (if (not (or (any-macro? vname env) (tree-unquoted-member vname (car p)))) (lint-format "perhaps, assuming ~A is not a macro, ~A" caller (car p) (lists->string form `(,(car p) ,vvalue))))))) (when (pair? (cddr p)) (when (and (eq? (car p) 'if) (pair? (cdddr p))) (let ((if-true (caddr p)) (if-false (cadddr p))) (when (and (eq? (cadr p) vname) ; (let ((x (g y))) (if x #t #f)) -> (g y) (boolean? if-true) (boolean? if-false) (not (eq? if-true if-false))) (lint-format "perhaps ~A" caller (lists->string form (if if-true vvalue `(not ,vvalue))))) (when (and (pair? (cadr p)) ; (let ((x (f y))) (if (not x) B (g x))) -> (cond ((f y) => g) (else B)) (eq? (caadr p) 'not) (eq? (cadadr p) vname) (pair? if-false) (pair? (cdr if-false)) (null? (cddr if-false)) (eq? vname (cadr if-false))) (let ((else-clause (if (eq? if-true vname) `((else #f)) (if (and (pair? if-true) (tree-unquoted-member vname if-true)) :oops! ; if the let var appears in the else portion, we can't do anything with => `((else ,if-true)))))) (unless (eq? else-clause :oops!) (lint-format "perhaps ~A" caller (lists->string form `(cond (,vvalue => ,(car if-false)) ,@else-clause)))))))) (let ((crf #f)) ;; all this stuff still misses (cond ((not x)...)) and (set! y (if x (cdr x)...)) i.e. need embedding in this case (when (and (or (and (memq (car p) '(if and)) ; (let ((x (f y))) (and x (g x))) -> (cond ((f y) => g) (else #f)) (eq? (cadr p) vname)) (and (eq? (car p) 'or) (equal? (cadr p) `(not ,vname))) (and (pair? vvalue) (memq (car vvalue) '(assoc assv assq member memv memq)) (pair? (cadr p)) (or (eq? (caadr p) 'pair?) (and (eq? (caadr p) 'null?) ;; (let ((x (assoc y z))) (if (null? x) (g x))) (lint-format "in ~A, ~A can't be null because ~A in ~A only returns #f or a pair" caller p vname (car vvalue) (truncated-list->string (car varlist))) #f)) (eq? (cadadr p) vname))) (or (and (pair? (caddr p)) (pair? (cdaddr p)) (null? (cddr (caddr p))) ; one func arg (or (eq? vname (cadr (caddr p))) (and (hash-table-ref combinable-cxrs (caaddr p)) ((lambda* (cr arg) ; lambda* not lambda because combine-cxrs might return just #f (and cr (< (length cr) 5) (eq? vname arg) (set! crf (symbol "c" cr "r")))) (combine-cxrs (caddr p)))))) (and (eq? (car p) 'if) (eq? (caddr p) vname) (not (tree-unquoted-member vname (cdddr p))) ;; (let ((x (g y))) (if x x (g z))) -> (or (g y) (g z)) (lint-format "perhaps ~A" caller (lists->string form (if (null? (cdddr p)) vvalue `(or ,vvalue ,(cadddr p))))) #f)) (pair? (caddr p)) (or (eq? (car p) 'if) (null? (cdddr p)))) (let ((else-clause (if (pair? (cdddr p)) (if (eq? (cadddr p) vname) `((else #f)) ; this stands in for the local var (if (and (pair? (cadddr p)) (tree-unquoted-member vname (cadddr p))) :oops! ; if the let var appears in the else portion, we can't do anything with => `((else ,(cadddr p))))) (case (car p) ((and) '((else #f))) ((or) '((else #t))) (else ()))))) (unless (eq? else-clause :oops!) ;; (let ((x (assoc y z))) (if x (cdr x))) -> (cond ((assoc y z) => cdr)) (lint-format "perhaps ~A" caller (lists->string form `(cond (,vvalue => ,(or crf (caaddr p))) ,@else-clause)))))))) )) ; one var in varlist ;; ---------------------------------------- ;; move let in: ;; (let ((a (car x))) (if b (+ a (f a)) (display c))) -> (if b (let ((a (car x))) (+ a (f a))) (display c)) ;; let* version gets only 3 hits (unless (or named-let (any? (lambda (c) (not (and (pair? c) (symbol? (car c)) (pair? (cdr c)) (not (side-effect? (cadr c) env))))) (cadr form))) (case (caar body) ((if) (let ((test (cadar body)) (true (caddar body)) (false (and (pair? (cdddar body)) (car (cdddar body)))) (vars (map car (cadr form))) (false-let #f)) (when (and (not (memq test vars)) (not (tree-set-member vars test)) (or (and (not (memq true vars)) (not (tree-set-member vars true)) (set! false-let #t)) (not false) (not (or (memq false vars) (tree-set-member vars false)))) (tree-set-member vars body)) ; otherwise we'll complain elsewhere about unused variables (lint-format "perhaps move the let to the ~A branch: ~A" caller (if false-let "false" "true") (lists->string form (let ((true-dots (if (> (tree-leaves true) 30) '... true)) (false-dots (if (and (pair? false) (> (tree-leaves false) 30)) '... false))) (if false-let `(if ,test ,true-dots (let ,(cadr form) ,@(unbegin false-dots))) (if (pair? (cdddr (caddr form))) `(if ,test (let ,(cadr form) ,@(unbegin true-dots)) ,false-dots) `(if ,test (let ,(cadr form) ,@(unbegin true-dots))))))))))) ((cond) ;; happens about a dozen times (let ((vars (map car (cadr form)))) (if (tree-set-member vars (cdar body)) (call-with-exit (lambda (quit) (let ((branch-let #f)) (for-each (lambda (c) (if (and (not branch-let) (side-effect? (car c) env)) (quit)) (when (and (pair? c) (tree-set-member vars c)) (if branch-let (quit)) (set! branch-let c))) (cdar body)) (if (and branch-let (not (memq (car branch-let) vars)) (not (tree-set-member vars (car branch-let)))) (lint-format "perhaps move the let into the '~A branch: ~A" caller (truncated-list->string branch-let) (lists->string form (if (eq? branch-let (cadar body)) `(cond (,(car branch-let) (let ,(cadr form) ,@(cdr branch-let))) ...) `(cond ... (,(car branch-let) (let ,(cadr form) ,@(cdr branch-let))) ...))))))))))) ((case) (let ((vars (map car (cadr form))) (test (cadar body))) (if (and (not (memq test vars)) (not (tree-set-member vars test)) (tree-set-member vars (cddar body))) (call-with-exit (lambda (quit) (let ((branch-let #f)) (for-each (lambda (c) (when (and (pair? c) (tree-set-member vars (cdr c))) (if branch-let (quit)) (set! branch-let c))) (cddar body)) (if branch-let (lint-format "perhaps move the let into the '~A branch: ~A" caller (truncated-list->string branch-let) (lists->string form (if (eq? branch-let (caddar body)) `(case ,test (,(car branch-let) (let ,(cadr form) ,@(cdr branch-let))) ...) `(case ,test ... (,(car branch-let) (let ,(cadr form) ,@(cdr branch-let))) ...))))))))))) ((when unless) ; no hits -- maybe someday? (let ((test (cadar body)) (vars (map car (cadr form)))) (unless (or (memq test vars) (tree-set-member vars test) (side-effect? test env)) (lint-format "perhaps move the let inside the ~A: ~A" caller (caar body) (truncated-lists->string form `(,(caar body) ,test (let ,(cadr form) ,@(cddar body)))))))))) ;; ---------------------------------------- ;; (let ((x 1) (y 2)) (+ x y)) -> (+ 1 2) ;; this happens a lot, but it often looks like a form of documentation (when (and (= suggest made-suggestion) (not named-let) (< (length varlist) 8) (not (memq (caar body) '(lambda lambda* define define* define-macro))) (not (and (eq? (caar body) 'set!) (any? (lambda (v) (eq? (car v) (cadar body))) varlist))) (not (any-macro? (caar body) env)) (not (any? (lambda (p) (and (pair? p) (not (eq? (car p) 'quote)) (or (not (hash-table-ref no-side-effect-functions (car p))) (any? pair? (cdr p))))) (cdar body))) (every? (lambda (v) (and (pair? v) (pair? (cdr v)) (< (tree-leaves (cadr v)) 8) (= (tree-count1 (car v) body 0) 1))) varlist)) (let ((new-body (copy (car body))) (bool-arg? #f)) (for-each (lambda (v) (if (not bool-arg?) (let tree-walk ((tree body)) (if (pair? tree) (if (and (memq (car tree) '(or and)) (memq (car v) (cdr tree))) (set! bool-arg? #t) (begin (tree-walk (car tree)) (tree-walk (cdr tree))))))) (set! new-body (tree-subst (cadr v) (car v) new-body))) varlist) (lint-format (if bool-arg? "perhaps, ignoring short-circuit issues, ~A" "perhaps ~A") caller (lists->string form new-body)))) ) ; null cdr body etc (when (and (pair? (cadr form)) ; (let ((x x)) (+ x 1)) -> (+ x 1), (let ((x x))...) does not copy x if x is a sequence (= suggest made-suggestion) (every? (lambda (c) (and (pair? c) ; the usual... (let binding might be messed up) (pair? (cdr c)) (eq? (car c) (cadr c)))) (cadr form)) (not (and (pair? (caddr form)) (memq (caaddr form) '(lambda lambda*))))) (let ((vs (map car (cadr form)))) (unless (any? (lambda (p) (and (pair? p) (memq (cadr p) vs) (or (eq? (car p) 'set!) (set!? p env)))) (cddr form)) (lint-format "perhaps omit this useless let: ~A" caller (truncated-lists->string form (if (null? (cdddr form)) (caddr form) `(begin ,@(cddr form)))))))) ) ; suggest let (let* ((cur-env (cons (make-var :name :let :initial-value form :definer 'let) (append vars env))) (e (lint-walk-body (or named-let caller) 'let body cur-env))) (let ((nvars (and (not (eq? e cur-env)) (env-difference caller e cur-env ())))) (if (pair? nvars) (if (memq (var-name (car nvars)) '(:lambda :dilambda)) (begin (set! env (cons (car nvars) env)) (set! nvars (cdr nvars))) (set! vars (append nvars vars))))) (if (and (pair? body) (equal? (list-ref body (- (length body) 1)) '(curlet))) ; the standard library tag (for-each (lambda (v) (set! (var-ref v) (+ (var-ref v) 1))) e)) (report-usage caller 'let vars e) ;; look for splittable lets and let-temporarily possibilities (when (and (pair? vars) (pair? (cadr form)) (pair? (caadr form))) (for-each (lambda (local-var) (let ((vname (var-name local-var))) ;; ideally we'd collect vars that fit into one let etc (when (> (length body) (* 5 (var-set local-var)) 0) (do ((i 0 (+ i 1)) (preref #f) (p body (cdr p))) ((or (not (pair? (cdr p))) (and (pair? (car p)) (eq? (caar p) 'set!) (eq? (cadar p) vname) (> i 5) (begin (if (or preref (side-effect? (var-initial-value local-var) env)) ;; (let ((x 32)) (display x) (set! y (f x)) (g (+ x 1) y) (a y) (f y) (g y) (h y) (i y) (set! x 3) (display x) (h y x)) ;; (let ... (let ((x 3)) ...)) (lint-format "perhaps add a new binding for ~A to replace ~A: ~A" caller vname (truncated-list->string (car p)) (lists->string form `(let ... (let ((,vname ,(caddar p))) ...)))) ;; (let ((x 32)) (set! y (f 1)) (a y) (f y) (g y) (h y) (i y) (set! x (+ x... -> (let () ... (let ((x (+ 32 1))) ...)) (lint-format "perhaps move the ~A binding to replace ~A: ~A" caller vname (truncated-list->string (car p)) (let ((new-value (if (tree-member vname (caddar p)) (tree-subst (var-initial-value local-var) vname (copy (caddar p))) (caddar p)))) (lists->string form `(let ,(let rewrite ((lst (cadr form))) (cond ((null? lst) ()) ((and (pair? (car lst)) (eq? (caar lst) vname)) (rewrite (cdr lst))) (else (cons (if (< (tree-leaves (cadar lst)) 30) (car lst) (list (caar lst) '...)) (rewrite (cdr lst)))))) ... (let ((,vname ,new-value)) ...)))))) #t)))) (if (tree-member vname (car p)) (set! preref i)))) (when (and (zero? (var-set local-var)) (= (var-ref local-var) 2)) ; initial value and set! (do ((saved-name (var-initial-value local-var)) (p body (cdr p)) (last-pos #f) (first-pos #f)) ((not (pair? p)) (when (and (pair? last-pos) (not (eq? first-pos last-pos)) (not (tree-equal-member saved-name (cdr last-pos)))) ;; (let ((old-x x)) (set! x 12) (display (log x)) (set! x 1) (set! x old-x)) -> ;; (let-temporarily ((x 12)) (display (log x)) (set! x 1)) (lint-format "perhaps use let-temporarily here: ~A" caller (lists->string form (let ((new-let `(let-temporarily ((,saved-name ,(if (pair? first-pos) (caddar first-pos) saved-name))) ,@(map (lambda (expr) (if (or (and (pair? first-pos) (eq? expr (car first-pos))) (eq? expr (car last-pos))) (values) expr)) body)))) (if (null? (cdr vars)) ; we know vars is a pair, want len=1 new-let `(let ,(map (lambda (v) (if (eq? (car v) vname) (values) v)) (cadr form)) ,new-let))))))) ;; someday maybe look for additional saved vars, but this happens only in snd-test ;; also the let-temp could be reduced to the set locations (so the tree-equal-member ;; check above would be unneeded). (let ((expr (car p))) (when (and (pair? expr) (eq? (car expr) 'set!) (equal? (cadr expr) saved-name) (pair? (cddr expr))) (if (not first-pos) (set! first-pos p)) (if (eq? (caddr expr) vname) (set! last-pos p)))))))) vars))) (when (and (pair? varlist) (pair? (car varlist)) (null? (cdr varlist))) (if (and (pair? body) ; (let ((x y)) x) -> y, named let is possible here (null? (cdr body)) (eq? (car body) (caar varlist)) (pair? (cdar varlist))) ; (let ((a))...) (lint-format "perhaps ~A" caller (lists->string form (cadar varlist)))) ;; also (let ((x ...)) (let ((y x)...))) happens but it looks like automatically generated code or test suite junk ;; copied from letrec below -- happens about a dozen times (when (and (not named-let) (pair? (cddr form)) (pair? (caddr form)) (null? (cdddr form))) (let ((body (caddr form)) (sym (caar varlist)) (lform (and (pair? (caadr form)) (pair? (cdaadr form)) (cadar (cadr form))))) (if (and (pair? lform) (pair? (cdr lform)) (eq? (car lform) 'lambda) (proper-list? (cadr lform))) ;; unlike in letrec, here there can't be recursion (ref to same name is ref to outer env) (if (eq? sym (car body)) (if (not (tree-memq sym (cdr body))) ;; (let ((x (lambda (y) (+ y (x (- y 1)))))) (x 2)) -> (let ((y 2)) (+ y (x (- y 1)))) (lint-format "perhaps ~A" caller (lists->string form `(let ,(map list (cadr lform) (cdr body)) ,@(cddr lform))))) (if (= (tree-count1 sym body 0) 1) (let ((call (find-call sym body))) (when (pair? call) (let ((new-call `(let ,(map list (cadr lform) (cdr call)) ,@(cddr lform)))) ;; (let ((f60 (lambda (x) (* 2 x)))) (+ 1 (f60 y))) -> (+ 1 (let ((x y)) (* 2 x))) (lint-format "perhaps ~A" caller (lists->string form (tree-subst new-call call body)))))))))))) (when (pair? body) (when (and (pair? (car body)) (pair? (cdar body)) (pair? (cddar body)) (eq? (caar body) 'set!)) (let ((settee (cadar body)) (setval (caddar body))) (if (and (not named-let) ; (let ((x 0)...) (set! x 1)...) -> (let ((x 1)...)...) (not (tree-memq 'curlet setval)) (cond ((assq settee vars) => (lambda (v) (or (and (code-constant? (var-initial-value v)) (code-constant? setval)) (not (any? (lambda (v1) (or (tree-memq (car v1) setval) (side-effect? (cadr v1) env))) varlist))))) (else #f))) (lint-format "perhaps ~A" caller ; (let ((a 1)) (set! a 2)) -> 2 (lists->string form (if (null? (cdr body)) ; this only happens in test suites... (if (null? (cdr varlist)) setval `(let ,(map (lambda (v) (if (eq? (car v) settee) (values) v)) varlist) ,setval)) `(let ,(map (lambda (v) (if (eq? (car v) settee) (list (car v) setval) v)) varlist) ,@(if (null? (cddr body)) (cdr body) `(,(cadr body) ...)))))) ;; repetition for the moment (when (and (pair? varlist) (assq settee vars) ; settee is a local var (not (eq? settee named-let)) ; (let loop () (set! loop 3))! (or (null? (cdr body)) (and (null? (cddr body)) (eq? settee (cadr body))))) ; (let... (set! local val) local) (lint-format "perhaps ~A" caller (lists->string form (if (or (tree-memq settee setval) (side-effect? (cadr (assq settee varlist)) env)) `(let ,varlist ,setval) (if (null? (cdr varlist)) setval `(let ,(remove-if (lambda (v) (eq? (car v) settee)) varlist) ,setval))))))))) (unless named-let ;; if var val is symbol, val not used (not set!) in body (even hidden via function call) ;; and var not set!, and not a function parameter (we already reported those), ;; remove it (the var) and replace with val throughout (when (and (proper-list? (cadr form)) (not (tree-set-member '(curlet lambda lambda* define define*) (cddr form)))) (do ((changes ()) (vs (cadr form) (cdr vs))) ((null? vs) (if (pair? changes) (let ((new-form (copy form))) (for-each (lambda (v) (list-set! new-form 1 (remove-if (lambda (p) (equal? p v)) (cadr new-form))) (set! new-form (tree-subst (cadr v) (car v) new-form))) changes) (lint-format "assuming we see all set!s, the binding~A ~{~A~^, ~} ~A pointless: perhaps ~A" caller (if (pair? (cdr changes)) "s" "") changes (if (pair? (cdr changes)) "are" "is") (lists->string form (if (< (tree-leaves new-form) 200) new-form `(let ,(cadr new-form) ,@(one-call-and-dots (cddr new-form))))))))) (let ((v (car vs))) (when (and (pair? v) (pair? (cdr v)) (null? (cddr v)) ; good grief (symbol? (cadr v)) (not (set-target (cadr v) body env)) (not (set-target (car v) body env)) (let ((data (var-member (cadr v) env))) (or (not (var? data)) (and (not (eq? (var-definer data) 'parameter)) (or (null? (var-setters data)) (not (tree-set-member (var-setters data) body))))))) (set! changes (cons v changes)))))) (when (pair? varlist) ;; if last is (set! local-var...) and no complications, complain (let ((last (list-ref body (- (length body) 1)))) (when (and (pair? last) (eq? (car last) 'set!) (pair? (cdr last)) (pair? (cddr last)) ; (set! a) (symbol? (cadr last)) (assq (cadr last) varlist) ; (let ((a 1) (b (display 2))) (set! a 2)) ;; this is overly restrictive: (not (tree-set-member '(call/cc call-with-current-continuation curlet lambda lambda*) form))) (lint-format "set! is pointless in ~A: use ~A" caller last (caddr last)))) (when (and (pair? (car body)) (eq? (caar body) 'do)) (when (and (null? (cdr body)) ; removing this restriction gets only 3 hits (pair? (cadar body))) (let ((inits (map cadr (cadar body)))) (when (every? (lambda (v) (and (= (tree-count1 (car v) (car body) 0) 1) (tree-memq (car v) inits))) varlist) (let ((new-cadr (copy (cadar body)))) (for-each (lambda (v) (set! new-cadr (tree-subst (cadr v) (car v) new-cadr))) varlist) ;; (let ((a 1)) (do ((i a (+ i 1))) ((= i 3)) (display i))) -> (do ((i 1 (+ i 1))) ...) (lint-format "perhaps ~A" caller (lists->string form `(do ,new-cadr ...))))))) ;; let->do -- sometimes a bad idea, set *max-cdr-len* to #f to disable this. ;; (the main objection is that the s7/clm optimizer can't handle it, and ;; instruments using it look kinda dumb -- the power of habit or something) (when (integer? *max-cdr-len*) (let ((inits (if (pair? (cadar body)) (map cadr (cadar body)) ())) (locals (if (pair? (cadar body)) (map car (cadar body)) ()))) (unless (or (and (pair? inits) (any? (lambda (v) (or (memq (car v) locals) ; shadowing (tree-memq (car v) inits) (side-effect? (cadr v) env))) ; let var opens *stdin*, do stepper reads it at init varlist)) (and (pair? (cdr body)) (pair? (cddr body))) ;; moving more than one expr here is usually ugly -- the only exception I've ;; seen is where the do body is enormous and the end stuff very short, and ;; it (the end stuff) refers to the let/do variables -- in the unedited case, ;; the result is hard to see. (> (tree-leaves (cdr body)) *max-cdr-len*)) ;; (let ((xx 0)) (do ((x 1 (+ x 1)) (y x (- y 1))) ((= x 3) xx) (display y))) -> ;; (do ((xx 0) (x 1 (+ x 1)) (y x (- y 1))) ...) (lint-format "perhaps ~A" caller (lists->string form (let ((do-form (cdar body))) (if (null? (cdr body)) ; do is only expr in let `(do ,(append varlist (car do-form)) ...) `(do ,(append varlist (car do-form)) (,(and (pair? (cadr do-form)) (caadr do-form)) ,@(if (side-effect? (cdadr do-form) env) (cdadr do-form) ()) ,@(cdr body)) ; include rest of let as do return value ...))))))))) (when (and (> (length body) 3) ; setting this to 1 did not catch anything new (every? pair? varlist) (not (tree-set-car-member '(define define* define-macro define-macro* define-bacro define-bacro* define-constant define-expansion) body))) ;; define et al are like a continuation of the let bindings, so we can't restrict them by accident ;; (let ((x 1)) (define y x) ...) (let ((last-refs (map (lambda (v) (vector (var-name v) #f 0 v)) vars)) (got-lambdas (tree-set-car-member '(lambda lambda*) body))) ;; (let ((x #f) (y #t)) (set! x (lambda () y)) (set! y 5) (x)) (do ((p body (cdr p)) (i 0 (+ i 1))) ((null? p) (let ((end 0)) (for-each (lambda (v) (set! end (max end (v 2)))) last-refs) (if (and (< end (/ i lint-let-reduction-factor)) (eq? form lint-current-form) (< (tree-leaves (car body)) 100)) (let ((old-start (let ((old-pp ((funclet lint-pretty-print) '*pretty-print-left-margin*))) (set! ((funclet lint-pretty-print) '*pretty-print-left-margin*) (+ lint-left-margin 4)) (let ((res (lint-pp `(let ,(cadr form) ,@(copy body (make-list (+ end 1))))))) (set! ((funclet lint-pretty-print) '*pretty-print-left-margin*) old-pp) res)))) (lint-format "this let could be tightened:~%~NC~A ->~%~NC~A~%~NC~A ..." caller (+ lint-left-margin 4) #\space (truncated-list->string form) (+ lint-left-margin 4) #\space old-start (+ lint-left-margin 4) #\space (lint-pp (list-ref body (+ end 1))))) (begin ;; look for bindings that can be severely localized (let ((locals (map (lambda (v) (if (and (integer? (v 1)) (< (- (v 2) (v 1)) 2) (code-constant? (var-initial-value (v 3)))) v (values))) last-refs))) ;; should this omit cases where most the let is in the one or two lines? (when (pair? locals) (set! locals (sort! locals (lambda (a b) (or (< (a 1) (b 1)) (< (a 2) (b 2)))))) (do ((lv locals (cdr lv))) ((null? lv)) (let* ((v (car lv)) (cur-line (v 1))) (let gather ((pv lv) (cur-vars ()) (max-line (v 2))) (if (or (null? (cdr pv)) (not (= cur-line ((cadr pv) 1)))) (begin (set! cur-vars (reverse (cons (car pv) cur-vars))) (set! max-line (max max-line ((car pv) 2))) (set! lv pv) (lint-format "~{~A~^, ~} ~A only used in expression~A (of ~A),~%~NC~A~A of~%~NC~A" caller (map (lambda (v) (v 0)) cur-vars) (if (null? (cdr cur-vars)) "is" "are") (format #f (if (= cur-line max-line) (values " ~D" (+ cur-line 1)) (values "s ~D and ~D" (+ cur-line 1) (+ max-line 1)))) (length body) (+ lint-left-margin 6) #\space (truncated-list->string (list-ref body cur-line)) (if (= cur-line max-line) "" (format #f "~%~NC~A" (+ lint-left-margin 6) #\space (truncated-list->string (list-ref body max-line)))) (+ lint-left-margin 4) #\space (truncated-list->string form))) (gather (cdr pv) (cons (car pv) cur-vars) (max max-line ((car pv) 2))))))))) (let ((mnv ()) (cur-end i)) (for-each (lambda (v) (when (and (or (null? mnv) (<= (v 2) cur-end)) (positive? (var-ref (v 3))) (let ((expr (var-initial-value (v 3)))) (not (any? (lambda (ov) ; watch out for shadowed vars (tree-memq (car ov) expr)) varlist)))) (set! mnv (if (= (v 2) cur-end) (cons v mnv) (list v))) (set! cur-end (v 2)))) last-refs) ;; look for vars used only at the start of the let (when (and (pair? mnv) (< cur-end (/ i lint-let-reduction-factor)) (> (- i cur-end) 3)) ;; mnv is in the right order because last-refs is reversed (lint-format "the scope of ~{~A~^, ~} could be reduced: ~A" caller (map (lambda (v) (v 0)) mnv) (lists->string form `(let ,(map (lambda (v) (if (member (car v) mnv (lambda (a b) (eq? a (b 0)))) (values) v)) varlist) (let ,(map (lambda (v) (list (v 0) (var-initial-value (v 3)))) mnv) ,@(copy body (make-list (+ cur-end 1)))) ,(list-ref body (+ cur-end 1)) ...))))))))) ;; body of do loop above (if (and (not got-lambdas) (pair? (car p)) (pair? (cdr p)) (eq? (caar p) 'set!) (var-member (cadar p) vars) (not (tree-memq (cadar p) (cdr p)))) (if (not (side-effect? (caddar p) env)) ; (set! v0 (channel->vct 1000 100)) -> (channel->vct 1000 100) (lint-format "~A in ~A could be omitted" caller (car p) (truncated-list->string form)) (lint-format "perhaps ~A" caller (lists->string (car p) (caddar p))))) ;; 1 use in cadr and none thereafter happens a few times, but looks like set-as-documentation mostly (for-each (lambda (v) (when (tree-memq (v 0) (car p)) (set! (v 2) i) (if (not (v 1)) (set! (v 1) i)))) last-refs)))))) ) ; (when (pair? body)...) ;; out of place and repetitive code... (when (and (pair? (cadr form)) (pair? (cddr form)) (null? (cdddr form)) (pair? (caddr form))) (let ((inner (caddr form)) ; the inner let (outer-vars (cadr form))) (when (pair? (cdr inner)) (let ((inner-vars (cadr inner))) (when (and (eq? (car inner) 'let) (symbol? inner-vars)) (let ((named-body (cdddr inner)) (named-args (caddr inner))) (unless (any? (lambda (v) (or (not (= (tree-count1 (car v) named-args 0) 1)) (tree-memq (car v) named-body))) varlist) (let ((new-args (copy named-args))) (for-each (lambda (v) (set! new-args (tree-subst (cadr v) (car v) new-args))) varlist) ;; (let ((x 1) (y (f g 2))) (let loop ((a (+ x 1)) (b y)) (loop a b))) -> (let loop ((a (+ 1 1)) (b (f g 2))) (loop a b)) (lint-format "perhaps ~A" caller (lists->string form `(let ,inner-vars ,new-args ,@named-body))))))) ;; maybe more code than this is worth -- combine lets (when (and (memq (car inner) '(let let*)) (pair? inner-vars)) (define (letstar . lets) (let loop ((vars (list 'curlet)) (forms lets)) (and (pair? forms) (or (and (pair? (car forms)) (or (tree-set-member vars (car forms)) (any? (lambda (a) (or (not (pair? a)) (not (pair? (cdr a))) (side-effect? (cadr a) env))) (car forms)))) (loop (append (map car (car forms)) vars) (cdr forms)))))) (cond ((and (null? (cdadr form)) ; let(1) + let* -> let* (eq? (car inner) 'let*) (not (symbol? inner-vars))) ; not named let* ;; (let ((a 1)) (let* ((b (+ a 1)) (c (* b 2))) (display (+ a b c)))) -> (let* ((a 1) (b (+ a 1)) (c (* b 2))) (display (+ a b c))) (lint-format "perhaps ~A" caller (lists->string form `(let* ,(append outer-vars inner-vars) ,@(one-call-and-dots (cddr inner)))))) ((and (pair? (cddr inner)) (pair? (caddr inner)) (null? (cdddr inner)) (eq? (caaddr inner) 'let) (pair? (cdr (caddr inner))) (pair? (cadr (caddr inner)))) (let* ((inner1 (cdaddr inner)) (inner1-vars (car inner1))) (if (and (pair? (cdr inner1)) (null? (cddr inner1)) (pair? (cadr inner1)) (eq? (caadr inner1) 'let) (pair? (cdadr inner1)) (pair? (cadadr inner1))) (let* ((inner2 (cdadr inner1)) (inner2-vars (car inner2))) (if (not (letstar outer-vars inner-vars inner1-vars inner2-vars)) ;; (let ((a 1)) (let ((b 2)) (let ((c 3)) (let ((d 4)) (+ a b c d))))) -> (let ((a 1) (b 2) (c 3) (d 4)) (+ a b c d)) (lint-format "perhaps ~A" caller (lists->string form `(let ,(append outer-vars inner-vars inner1-vars inner2-vars) ,@(one-call-and-dots (cdr inner2))))))) (if (not (letstar outer-vars inner-vars inner1-vars)) ;; (let ((b 2)) (let ((c 3)) (let ((d 4)) (+ a b c d)))) -> (let ((b 2) (c 3) (d 4)) (+ a b c d)) (lint-format "perhaps ~A" caller (lists->string form `(let ,(append outer-vars inner-vars inner1-vars) ,@(one-call-and-dots (cdr inner1))))))))) ((not (letstar outer-vars inner-vars)) ;; (let ((c 3)) (let ((d 4)) (+ a b c d))) -> (let ((c 3) (d 4)) (+ a b c d)) (lint-format "perhaps ~A" caller (lists->string form `(let ,(append outer-vars inner-vars) ,@(one-call-and-dots (cddr inner)))))) ((and (null? (cdadr form)) ; 1 outer var (pair? inner-vars) (null? (cdadr inner))) ; 1 inner var, dependent on outer ;; (let ((x 0)) (let ((y (g 0))) (+ x y))) -> (let* ((x 0) (y (g 0))) (+ x y)) (lint-format "perhaps ~A" caller (lists->string form `(let* ,(append outer-vars inner-vars) ,@(one-call-and-dots (cddr inner)))))))))))) ))) ; messed up let env) (hash-table-set! h 'let let-walker)) ;; ---------------- let* ---------------- (let () (define (let*-walker caller form env) (if (< (length form) 3) (lint-format "let* is messed up: ~A" caller (truncated-list->string form)) (let ((named-let (and (symbol? (cadr form)) (cadr form)))) (let ((vars (if named-let (list (make-var :name named-let :definer 'let*)) ())) (varlist ((if named-let caddr cadr) form)) (body ((if named-let cdddr cddr) form))) (if (not (list? varlist)) (lint-format "let* is messed up: ~A" caller (truncated-list->string form))) ;; let->do (could go further down) (when (and (integer? *max-cdr-len*) (pair? varlist) (pair? body) (pair? (car body)) (eq? (caar body) 'do) (< (tree-leaves (cdr body)) *max-cdr-len*)) (let ((inits (if (pair? (cadar body)) (map cadr (cadar body)) ())) (locals (if (pair? (cadar body)) (map car (cadar body)) ())) (lv (list-ref varlist (- (length varlist) 1)))) (unless (and (pair? inits) (or (memq (car lv) locals) ; shadowing (tree-memq (car lv) inits) (side-effect? (cadr lv) env))) ;; (let* ((x (log z))) (do ((i 0 (+ x z))) ((= i 3)) (display x))) -> (do ((x (log z)) (i 0 (+ x z))) ...) (lint-format "perhaps ~A" caller (lists->string form (let ((new-do (let ((do-form (cdar body))) (if (null? (cdr body)) `(do ,(cons lv (car do-form)) ...) `(do ,(cons lv (car do-form)) (,(and (pair? (cadr do-form)) (caadr do-form)) ,@(if (side-effect? (cdadr do-form) env) (cdadr do-form) ()) ,@(cdr body)) ; include rest of let as do return value ...))))) (case (length varlist) ((1) new-do) ((2) `(let (,(car varlist)) ,new-do)) (else `(let* ,(copy varlist (make-list (- (length varlist) 1))) ,new-do))))))))) (do ((side-effects #f) (bindings varlist (cdr bindings))) ((not (pair? bindings)) (if (not (null? bindings)) (lint-format "let* variable list is not a proper list? ~S" caller ((if named-let caddr cadr) form))) (if (not (or side-effects (any? (lambda (v) (positive? (var-ref v))) vars))) ;; (let* ((x (log y))) x) (lint-format "let* could be let: ~A" caller (truncated-list->string form)))) ;; in s7, let evaluates var values top down, so this message is correct ;; even in cases like (let ((ind (open-sound...)) (mx (maxamp))) ...) ;; in r7rs, the order is not specified (section 4.2.2 of the spec), so ;; here we would restrict this message to cases where there is only ;; one variable, or where subsequent values are known to be independent. ;; if each function could tell us what globals it depends on or affects, ;; we could make this work in all cases. (when (binding-ok? caller 'let* (car bindings) env #f) (let ((expr (cadar bindings)) (side (side-effect? (cadar bindings) env))) (if (not (or (eq? bindings varlist) ;; first var side-effect is innocuous (especially if it's the only one!) ;; does this need to protect against a side-effect that the next var accesses? ;; I think we're ok -- the accessed var must be exterior, and we go down in order side-effects)) (set! side-effects side)) (let ((e (lint-walk caller expr (append vars env)))) (if (and (pair? e) (not (eq? e env)) (memq (var-name (car e)) '(:lambda :dilambda))) (let ((ldata (cdar e))) (set! (var-name (car e)) (caar bindings)) (set! (ldata 'initial-value) expr) (set! vars (cons (car e) vars))) (set! vars (cons (make-var :name (caar bindings) :initial-value expr :definer (if named-let 'named-let* 'let*)) vars)))) ;; look for duplicate values ;; someday protect against any shadows if included in any expr (unless (or side (not (pair? expr)) (code-constant? expr) (maker? expr)) (let ((name (caar bindings))) (let dup-check ((vs (cdr vars))) (if (and (pair? vs) (pair? (car vs)) (not (eq? name (caar vs))) (not (tree-memq (caar vs) expr))) ;; perhaps also not side-effect of car vs initial-value (char-ready? + read + char-ready? again) (if (equal? expr (var-initial-value (car vs))) ;; (let* ((x (log y 2)) (y (log y 2)) (z (f x))) (+ x y z z)) (lint-format "~A's value ~A could be ~A" caller name expr (caar vs)) (dup-check (cdr vs)))))))))) ;; if var is not used except in other var bindings, it can be moved out of this let* ;; collect vars not in body, used in only one binding, gather these cases, and rewrite the let* ;; repeated names are possible here ;; also cascading dependencies: (let* ((z 1) (y (+ z 2)) (x (< y 3))) (if x (f x))) ;; (let ((x (let ((y (let ((z 1))) (+ z 2))) (< y 3)))) ...) ?? ;; new-vars: ((z y) (y x)) (when (and (pair? vars) (pair? (cdr vars))) (let ((new-vars ()) (vs-pos vars) (repeats (do ((p vars (cdr p))) ((or (null? p) (var-member (var-name (car p)) (cdr p))) (pair? p))))) (for-each (lambda (v) (let ((vname (var-name v)) (vvalue #f)) (if (not (tree-memq vname body)) (let walker ((vs vars)) (if (not (pair? vs)) (if (and vvalue (or (not (side-effect? (var-initial-value v) env)) (eq? vvalue (var-name (car vs-pos))))) (set! new-vars (cons (list vvalue vname (var-initial-value v)) new-vars))) (let ((b (car vs))) (if (or (eq? (var-name b) vname) (not (tree-memq vname (var-initial-value b)))) ; tree-memq matches the bare symbol (tree-member doesn't) (walker (cdr vs)) (if (not vvalue) (begin (set! vvalue (var-name b)) (walker (cdr vs))))))))) (set! vs-pos (cdr vs-pos)))) (cdr vars)) ; vars is reversed from code order, new-vars is in code order (when (pair? new-vars) (define (gather-dependencies var val env) (let ((deps ())) (for-each (lambda (nv) (if (and (eq? (car nv) var) (or (not repeats) (tree-memq (cadr nv) val))) (set! deps (cons (list (cadr nv) (gather-dependencies (cadr nv) (caddr nv) env)) deps)))) new-vars) (if (> (tree-leaves val) 30) (set! val '...)) (if (pair? deps) `(,(if (null? (cdr deps)) 'let 'let*) ,deps ,val) val))) (let ((new-let-binds (map (lambda (v) (if (member (var-name v) new-vars (lambda (name lst) (eq? name (cadr lst)))) (values) `(,(var-name v) ,(gather-dependencies (var-name v) (var-initial-value v) env)))) (reverse vars)))) ;; (let* ((a 1) (b 2) (c (+ a 1))) (* c 2)) -> (let* ((b 2) (c (let ((a 1)) (+ a 1)))) ...) (lint-format "perhaps restrict ~{~A~^, ~} which ~A not used in the let* body ~A" caller (map cadr new-vars) (if (null? (cdr new-vars)) "is" "are") (lists->string form `(,(if (null? (cdr new-let-binds)) 'let 'let*) ,new-let-binds ...))))) ;; this could be folded into the for-each above (unless repeats (let ((outer-vars ()) (inner-vars ())) (do ((vs (reverse vars) (cdr vs))) ((null? vs)) (let* ((v (car vs)) (vname (var-name v))) (if (not (or (side-effect? (var-initial-value v) env) (any? (lambda (trailing-var) ;; vname is possible inner let var if it is not mentioned in any trailing initial value ;; (repeated name can't happen here) (tree-memq vname (var-initial-value trailing-var))) (cdr vs)))) (set! inner-vars (cons v inner-vars)) (set! outer-vars (cons v outer-vars))))) (when (and (pair? outer-vars) (pair? inner-vars) (pair? (cdr inner-vars))) ;; (let* ((a 1) (b 2) (c (+ a 1))) (* c 2)) -> (let ((a 1)) (let ((b 2) (c (+ a 1))) ...)) (lint-format "perhaps split this let*: ~A" caller (lists->string form `(,(if (pair? (cdr outer-vars)) 'let* 'let) ,(map (lambda (v) `(,(var-name v) ,(var-initial-value v))) (reverse outer-vars)) (let ,(map (lambda (v) `(,(var-name v) ,(var-initial-value v))) (reverse inner-vars)) ...))))))) )) ; pair? vars (let* ((cur-env (cons (make-var :name :let :initial-value form :definer 'let*) (append vars env))) (e (lint-walk-body caller 'let* body cur-env))) (let ((nvars (and (not (eq? e cur-env)) (env-difference caller e cur-env ())))) (if (pair? nvars) (if (memq (var-name (car nvars)) '(:lambda :dilambda)) (begin (set! env (cons (car nvars) env)) (set! nvars (cdr nvars))) (set! vars (append nvars vars))))) (report-usage caller 'let* vars e)) (when (and (not named-let) (pair? body) (pair? varlist)) ; from here to end ;; (let*->let*) combined into one (when (and (pair? (car body)) (or (eq? (caar body) 'let*) ; let*+let* -> let* (and (eq? (caar body) 'let) ; let*+let(1) -> let* (or (null? (cadar body)) (and (pair? (cadar body)) (null? (cdadar body)))))) (null? (cdr body)) (not (symbol? (cadar body)))) ;; (let* ((a 1) (b (+ a 2))) (let* ((c (+ b 3)) (d (+ c 4))) (display a) (+ a... -> ;; (let* ((a 1) (b (+ a 2)) (c (+ b 3)) (d (+ c 4))) (display a) ...) (lint-format "perhaps ~A" caller (lists->string form `(let* ,(append varlist (cadar body)) ,@(one-call-and-dots (cddar body)))))) (when (and (proper-list? (cadr form)) (not (tree-set-member '(curlet lambda lambda* define define*) (cddr form)))) ;; see let above (do ((changes ()) (vs (cadr form) (cdr vs))) ((null? vs) (if (pair? changes) (let ((new-form (copy form))) (for-each (lambda (v) (list-set! new-form 1 (remove-if (lambda (p) (equal? p v)) (cadr new-form))) (set! new-form (tree-subst (cadr v) (car v) new-form))) changes) ;; (let* ((x y) (a (* 2 x))) (+ (f a (+ a 1)) (* 3 x))) -> (let ((a (* 2 y))) (+ (f a (+ a 1)) (* 3 y))) (lint-format "assuming we see all set!s, the binding~A ~{~A~^, ~} ~A pointless: perhaps ~A" caller (if (pair? (cdr changes)) "s" "") changes (if (pair? (cdr changes)) "are" "is") (lists->string form (let ((header (if (and (pair? (cadr new-form)) (pair? (cdadr new-form))) 'let* 'let))) (if (< (tree-leaves new-form) 200) `(,header ,@(cdr new-form)) `(,header ,(cadr new-form) ,@(one-call-and-dots (cddr new-form)))))))))) (let ((v (car vs))) (if (and (pair? v) (pair? (cdr v)) (null? (cddr v)) (symbol? (cadr v)) (not (assq (cadr v) (cadr form))) ; value is not a local var (not (set-target (car v) body env)) (not (set-target (cadr v) body env))) (let ((data (var-member (cadr v) env))) (if (and (or (not (var? data)) (and (not (eq? (var-definer data) 'parameter)) (or (null? (var-setters data)) (not (tree-set-member (var-setters data) body))))) (not (any? (lambda (p) (and (pair? p) (pair? (cdr p)) (or (set-target (cadr v) (cdr p) env) (set-target (car v) (cdr p) env) (and (var? data) (pair? (var-setters data)) (tree-set-member (var-setters data) body))))) (cdr vs)))) (set! changes (cons v changes)))))))) (let* ((varlist-len (length varlist)) (last-var (and (positive? varlist-len) (list-ref varlist (- varlist-len 1))))) ; from here to end (when (pair? last-var) ; successive vars, first used in second but nowhere else -- combine if (very!) simple-looking (do ((gone-vars ()) (v varlist (cdr v))) ((or (null? v) (null? (cdr v))) (when (pair? gone-vars) (let ((waiter #f) (new-vars ()) (save-vars ())) (set! gone-vars (reverse gone-vars)) (set! new-vars (map (lambda (v) (if (and (pair? gone-vars) (eq? v (car gone-vars))) (begin (set! waiter v) (set! gone-vars (cdr gone-vars)) (values)) (if (not waiter) v (let ((new-v (tree-subst (cadr waiter) (car waiter) v))) (set! save-vars (cons (list (car waiter) (car v)) save-vars)) (set! waiter #f) new-v)))) varlist)) ;; (let* ((y 3) (x (log y))) x) -> (let ((x (log 3))) ...) (lint-format "perhaps substitute ~{~{~A into ~A~}~^, ~}: ~A" caller (reverse save-vars) (lists->string form `(,(if (null? (cdr new-vars)) 'let 'let*) ,new-vars ...)))))) (let ((cur-var (car v)) (nxt-var (cadr v))) (when (and (pair? cur-var) (let ((v (var-member (car cur-var) vars))) (and (var? v) (zero? (var-set v)))) (pair? nxt-var) (pair? (cdr cur-var)) (pair? (cdr nxt-var)) (< (tree-leaves (cadr cur-var)) 8) (not (and (pair? (cadr nxt-var)) (eq? (caadr nxt-var) 'let) ; if named-let, forget it (pair? (cdadr nxt-var)) (symbol? (cadadr nxt-var)))) (or (not (pair? (cadr nxt-var))) (not (side-effect? (cadr cur-var) env)) (every? (lambda (a) (or (code-constant? a) (assq a varlist))) (cdadr nxt-var))) (= (tree-count1 (car cur-var) (cadr nxt-var) 0) 1) (not (tree-memq (car cur-var) (cddr v))) (not (tree-memq (car cur-var) body))) (set! gone-vars (cons cur-var gone-vars)) (set! v (cdr v))))) ;; if last var only occurs once in body, and timing can't be an issue, substitute its value ;; this largely copied from the let case above (but only one substitution) ;; in both cases, we're assuming that the possible last-var value's side-effect won't ;; affect other vars (in let* the local, in let something outside that might be used locally) ;; perhaps add (not (side-effect (cadr last-var) env))? (when (and (pair? (cdr last-var)) ; varlist-len can be 1 here (< (tree-leaves (cadr last-var)) 12) (= (tree-count1 (car last-var) body 0) 1) (pair? (car body)) (null? (cdr body)) (not (memq (caar body) '(lambda lambda* define define* define-macro))) (not (and (eq? (caar body) 'set!) (eq? (car last-var) (cadar body)))) (not (any-macro? (caar body) env)) (not (any? (lambda (p) (and (pair? p) (not (eq? (car p) 'quote)) (or (not (hash-table-ref no-side-effect-functions (car p))) (any? pair? (cdr p))))) (cdar body)))) ;; (let* ((a 1) (b 2) (c (+ a 1))) (* c 2)) -> (let* ((a 1) (b 2)) (* (+ a 1) 2)) (lint-format "perhaps ~A" caller (lists->string form `(,(if (<= varlist-len 2) 'let 'let*) ,(copy varlist (make-list (- varlist-len 1))) ,@(tree-subst (cadr last-var) (car last-var) body))))) (when (null? (cdr body)) ; (let* (...(x A)) (if x (f A) B)) -> (let(*) (...) (cond (A => f) (else B))) (when (pair? (cdr last-var)) (let ((p (car body))) (when (and (pair? p) (pair? (cdr p)) (case (car p) ((if and) (eq? (cadr p) (car last-var))) ((or) (equal? (cadr p) `(not ,(car last-var)))) (else #f)) (pair? (cddr p)) (pair? (caddr p)) (or (eq? (car p) 'if) (null? (cdddr p))) (pair? (cdaddr p)) (not (eq? (caaddr p) (car last-var))) ; ! (let* (...(x A)) (if x (x x))) (null? (cddr (caddr p))) (eq? (car last-var) (cadr (caddr p)))) (let ((else-clause (if (pair? (cdddr p)) ; only if 'if (see above) (if (eq? (cadddr p) (car last-var)) `((else #f)) ; this stands in for the local var (if (and (pair? (cadddr p)) (tree-unquoted-member (car last-var) (cadddr p))) :oops! ; if the let var appears in the else portion, we can't do anything with => `((else ,(cadddr p))))) (case (car p) ((and) '((else #f))) ((or) '((else #t))) (else ()))))) (unless (eq? else-clause :oops!) ;; (let* ((x (f y))) (and x (g x))) -> (cond ((f y) => g) (else #f) (lint-format "perhaps ~A" caller (case varlist-len ((1) (lists->string form `(cond (,(cadr last-var) => ,(caaddr p)) ,@else-clause))) ((2) (lists->string form `(let (,(car varlist)) (cond (,(cadr last-var) => ,(caaddr p)) ,@else-clause)))) (else (lists->string form `(let* ,(copy varlist (make-list (- varlist-len 1))) (cond (,(cadr last-var) => ,(caaddr p)) ,@else-clause))))))))))) (when (and (pair? (car varlist)) ; same as let: (let* ((x y)) x) -> y -- (let* (x) ...) (not (pair? (car body)))) (if (and (eq? (car body) (caar varlist)) (null? (cdr varlist)) (pair? (cdar varlist))) ; (let* ((a...)) a) ;; (let* ((x (log y))) x) -> (log y) (lint-format "perhaps ~A" caller (lists->string form (cadar varlist))) (if (and (> varlist-len 1) ; (let* (... (x y)) x) -> (let(*)(...) y) (pair? last-var) (pair? (cdr last-var)) (null? (cddr last-var)) (eq? (car body) (car last-var))) ;; (let* ((y 3) (x (log y))) x) -> (let ((y 3)) (log y)) (lint-format "perhaps ~A" caller (lists->string form `(,(if (= varlist-len 2) 'let 'let*) ,(copy varlist (make-list (- varlist-len 1))) ,(cadr last-var))))))))) (when (and (> (length body) 3) (> (length vars) 1) (every? pair? varlist) (not (tree-set-car-member '(define define* define-macro define-macro* define-bacro define-bacro* define-constant define-expansion) body))) (let ((last-ref (vector (var-name (car vars)) #f 0 (car vars)))) (do ((p body (cdr p)) (i 0 (+ i 1))) ((null? p) (let ((cur-line (last-ref 1)) (max-line (last-ref 2)) (vname (last-ref 0))) (if (and (< max-line (/ i lint-let-reduction-factor)) (> (- i max-line) 3)) (lint-format "the scope of ~A could be reduced: ~A" caller vname (lists->string form `(,(if (> (length vars) 2) 'let* 'let) ,(copy varlist (make-list (- (length vars) 1))) (let (,(list vname (var-initial-value (last-ref 3)))) ,@(copy body (make-list (+ max-line 1)))) ,(list-ref body (+ max-line 1)) ...))) (when (and (integer? cur-line) (< (- max-line cur-line) 2) (code-constant? (var-initial-value (last-ref 3)))) (lint-format "~A is only used in expression~A (of ~A),~%~NC~A~A of~%~NC~A" caller vname (format #f (if (= cur-line max-line) (values " ~D" (+ cur-line 1)) (values "s ~D and ~D" (+ cur-line 1) (+ max-line 1)))) (length body) (+ lint-left-margin 6) #\space (truncated-list->string (list-ref body cur-line)) (if (= cur-line max-line) "" (format #f "~%~NC~A" (+ lint-left-margin 6) #\space (truncated-list->string (list-ref body max-line)))) (+ lint-left-margin 4) #\space (truncated-list->string form)))))) (when (tree-memq (last-ref 0) (car p)) (set! (last-ref 2) i) (if (not (last-ref 1)) (set! (last-ref 1) i)))))) ))))) env) (hash-table-set! h 'let* let*-walker)) ;; ---------------- letrec ---------------- (let () (define (letrec-walker caller form env) (if (< (length form) 3) ; (letrec () . 1) (lint-format "~A is messed up: ~A" caller (car form) (truncated-list->string form)) (let ((vars ()) (head (car form))) (cond ((null? (cadr form)) ; (letrec () 1) (lint-format "~A could be let: ~A" caller head (truncated-list->string form))) ((not (pair? (cadr form))) ; (letrec a b) (lint-format "~A is messed up: ~A" caller head (truncated-list->string form))) ((and (null? (cdadr form)) (eq? head 'letrec*)) ; (letrec* ((a (lambda b (a 1)))) a) (lint-format "letrec* could be letrec: ~A" caller (truncated-list->string form)))) (do ((warned (or (eq? head 'letrec*) (not (pair? (cadr form))) (negative? (length (cadr form))))) ; malformed letrec (baddy #f) (bindings (cadr form) (cdr bindings))) ((not (pair? bindings)) (if (not (null? bindings)) ; (letrec* letrec)! (lint-format "~A variable list is not a proper list? ~S" caller head (cadr form)))) (when (and (not warned) ; letrec -> letrec* (pair? (car bindings)) (pair? (cdar bindings)) ;; type of current var is not important -- if used in non-function elsewhere, ;; it has to be letrec* (any? (lambda (b) (and (pair? b) (pair? (cdr b)) (or (and (not (pair? (cadr b))) (eq? (caar bindings) (cadr b))) (tree-memq (caar bindings) (cadr b))) (not (tree-set-member '(lambda lambda* define define* case-lambda) (cadr b))) (set! baddy b))) (cdr bindings))) (set! warned #t) ;; (letrec ((x 32) (f1 (let ((y 1)) (lambda (z) (+ x y z)))) (f2 (f1 x))) (+ x f2)) (lint-format "in ~A,~%~NCletrec should be letrec* because ~A is used in ~A's value (not a function): ~A" caller (truncated-list->string form) (+ lint-left-margin 4) #\space (caar bindings) (car baddy) (cadr baddy))) (when (binding-ok? caller head (car bindings) env #f) (let ((init (if (and (eq? (caar bindings) (cadar bindings)) (or (eq? head 'letrec) (not (var-member (caar bindings) vars)))) (begin ; (letrec ((x x)) x) (lint-format "~A is the same as (~A #) in ~A" caller (car bindings) (caar bindings) head) ;; in letrec* ((x 12) (x x)) is an error #) (cadar bindings)))) (set! vars (cons (make-var :name (caar bindings) :initial-value init :definer head) vars))))) (when (eq? head 'letrec) (check-unordered-exprs caller form (map var-initial-value vars) env)) (when (pair? vars) (do ((bindings (cadr form) (cdr bindings)) ; if none of the local vars occurs in any of the values, no need for the "rec" (vs (map var-name vars))) ((or (not (pair? bindings)) (not (pair? (car bindings))) (not (pair? (cdar bindings))) (memq (cadar bindings) vs) (tree-set-member vs (cadar bindings))) (when (null? bindings) (let ((letx (if (or (eq? head 'letrec) (do ((p (map cadr (cadr form)) (cdr p)) (q (map car (cadr form)) (cdr q))) ((or (null? p) (side-effect? (car p) env) (memq (car q) (cdr q))) (null? p)))) 'let 'let*))) ;; (letrec ((f1 (lambda (a) a))) 32) (lint-format "~A could be ~A: ~A" caller head letx (truncated-list->string form)))))) (when (and (null? (cdr vars)) (pair? (cddr form)) (pair? (caddr form)) (null? (cdddr form))) (let ((body (caddr form)) (sym (var-name (car vars))) (lform (cadar (cadr form)))) ; the letrec var's lambda (when (and (pair? lform) (pair? (cdr lform)) (eq? (car lform) 'lambda) (proper-list? (cadr lform))) ; includes () (if (eq? sym (car body)) ; (letrec ((x (lambda ...))) (x...)) -> (let x (...)...) (if (and (not (tree-memq sym (cdr body))) (< (tree-leaves body) 100)) ;; the limit on tree-leaves is for cases where the args are long lists of data -- ;; more like for-each than let, and easier to read if the code is first, I think. (lint-format "perhaps ~A" caller (lists->string form `(let ,sym ,(map list (cadr lform) (cdr body)) ,@(cddr lform))))) (if (and (not (eq? caller 'define)) (= (tree-count1 sym body 0) 1)) (let ((call (find-call sym body))) (when (pair? call) (let ((new-call `(let ,sym ,(map list (cadr lform) (cdr call)) ,@(cddr lform)))) (lint-format "perhaps ~A" caller (lists->string form (tree-subst new-call call body)))))))))))) ;; maybe (let () ...) here because (letrec ((x (lambda (y) (+ y 1)))) (x (define z 32))) needs to block z? ;; currently we get (let x ((y (define z 32))) (+ y 1)) ;; and even that should be (let () (define z 32) (+ z 1)) or something similar ;; lambda here is handled under define?? (let ((new-env (append vars env))) (when (pair? (cadr form)) (for-each (lambda (binding) (if (binding-ok? caller head binding env #t) (lint-walk caller (cadr binding) new-env))) (cadr form))) (let* ((cur-env (cons (make-var :name :let :initial-value form :definer head) (append vars env))) (e (lint-walk-body caller head (cddr form) cur-env))) (let ((nvars (and (not (eq? e cur-env)) (env-difference caller e cur-env ())))) (when (pair? nvars) (if (memq (var-name (car nvars)) '(:lambda :dilambda)) (begin (set! env (cons (car nvars) env)) (set! nvars (cdr nvars))) (set! vars (append nvars vars))))) (report-usage caller head vars e))))) ; constant exprs never happen here env) (hash-table-set! h 'letrec letrec-walker) (hash-table-set! h 'letrec* letrec-walker)) ;; ---------------- begin ---------------- (let () (define (begin-walker caller form env) (if (not (proper-list? form)) (begin ; (begin . 1) (lint-format "stray dot in begin? ~A" caller (truncated-list->string form)) env) (begin (when (pair? (cdr form)) (if (null? (cddr form)) ; (begin (f y)) (lint-format "begin could be omitted: ~A" caller (truncated-list->string form)) ;; these two are questionable -- simpler, but scope enlarged (when (and (pair? (cadr form)) (pair? (cddr form)) (null? (cdddr form))) (if (and (eq? (caadr form) 'do) (< (tree-leaves (caddr form)) 24) ; or maybe (< ... (min 24 (tree-leaves do-form)))? (not (tree-set-member (map car (cadadr form)) (caddr form)))) ;; (begin (do ((i 0 (+ i 1))) ((= i 3)) (display i)) 32) -> (do ((i 0 (+ i 1))) ((= i 3) 32) (display i)) ;; the do loop has to end normally to go on? That is, moving the following expr into the do end section is safe? (lint-format "perhaps ~A" caller (lists->string form (let ((do-form (cdadr form))) (let ((do-test (and (pair? (cadr do-form)) (caadr do-form))) (new-end (if (and (pair? (cadr do-form)) (pair? (cdadr do-form))) (append (cdadr do-form) (cddr form)) (cddr form)))) `(do ,(car do-form) (,do-test ,@new-end) ,@(cddr do-form)))))) (if (and (memq (caadr form) '(let let* letrec letrec*)) ; same for begin + let + expr -- not sure about this... (not (symbol? (cadadr form))) (< (tree-leaves (caddr form)) 24) ; or maybe (< ... (min 24 (tree-leaves do-form)))? (not (tree-set-member (map car (cadadr form)) (caddr form)))) (lint-format "perhaps ~A" caller (lists->string form (let ((let-form (cadr form))) `(,(car let-form) ,(cadr let-form) ,@(if (< (tree-leaves (cddr let-form)) 60) (cddr let-form) (one-call-and-dots (cddr let-form))) ,(caddr form)))))))))) (lint-walk-open-body caller 'begin (cdr form) env)))) (hash-table-set! h 'begin begin-walker)) ;; ---------------- with-baffle ---------------- (let () (define (with-baffle-walker caller form env) ;; with-baffle introduces a new frame, so we need to handle it here (lint-walk-body caller 'with-baffle (cdr form) (cons (make-var :name :let :initial-value form :definer 'with-baffle) env)) env) (hash-table-set! h 'with-baffle with-baffle-walker)) ;; -------- with-let -------- (let () (define (with-let-walker caller form env) (if (< (length form) 3) (lint-format "~A is messed up: ~A" 'with-let caller (truncated-list->string form)) (let ((e (cadr form))) (if (or (and (code-constant? e) (not (let? e))) (and (pair? e) (let ((op (return-type (car e) env))) (and op (not (return-type-ok? 'let? op)))))) ; (with-let 123 123) (lint-format "~A: first argument should be an environment: ~A" 'with-let caller (truncated-list->string form))) (if (symbol? e) (set-ref e caller form env) (if (pair? e) (begin (if (and (null? (cdr e)) (eq? (car e) 'curlet)) ; (with-let (curlet) x) (lint-format "~A is not needed here: ~A" 'with-let caller (truncated-list->string form))) (lint-walk caller e (cons (make-var :name :let :initial-value form :definer 'with-let) env))))) (let ((walked #f) (new-env (cons (make-var :name :with-let :initial-value form :definer 'with-let) env))) (if (or (and (symbol? e) (memq e '(*gtk* *motif* *gl* *libc* *libm* *libgdbm* *libgsl*))) (and (pair? e) (eq? (car e) 'sublet) (pair? (cdr e)) (memq (cadr e) '(*gtk* *motif* *gl* *libc* *libm* *libgdbm* *libgsl*)) (set! e (cadr e)))) (let ((lib (if (defined? e) (symbol->value e) (let ((file (*autoload* e))) (and (string? file) (load file)))))) (when (let? lib) (let-temporarily ((*e* lib)) (let ((e (lint-walk-open-body caller 'with-let (cddr form) new-env))) (report-usage caller 'with-let (if (eq? e env) () (env-difference caller e env ())) new-env))) (set! walked #t)))) (unless walked (lint-walk-open-body caller 'with-let (cddr form) new-env))))) env) (hash-table-set! h 'with-let with-let-walker)) ;; ---------------- defmacro ---------------- (let () (define (defmacro-walker caller form env) (if (or (< (length form) 4) (not (symbol? (cadr form)))) (begin (lint-format "~A declaration is messed up: ~A" caller (car form) (truncated-list->string form)) env) (let ((sym (cadr form)) (args (caddr form)) (body (cdddr form)) (head (car form))) (if (and (pair? args) (repeated-member? args env)) ; (defmacro hi (a b a) a) (lint-format "~A parameter is repeated: ~A" caller head (truncated-list->string args)) (lint-format "~A is deprecated; perhaps ~A" caller head ; (defmacro hi (a b) `(+ ,a ,b)) (truncated-lists->string form `(,(if (eq? head 'defmacro) 'define-macro 'define-macro*) ,(cons sym args) ,@body)))) (lint-walk-function head sym args body form env) (cons (make-var :name sym :initial-value form :definer head) env)))) (hash-table-set! h 'defmacro defmacro-walker) (hash-table-set! h 'defmacro* defmacro-walker)) ;; ---------------- load ---------------- (let () (define (load-walker caller form env) (check-call caller 'load form env) (if (and (pair? (cdr form)) (equal? (cadr form) "")) (lint-format "load needs a real file name, not the empty string: ~A" caller form)) (lint-walk caller (cdr form) env) (if (and *report-loaded-files* (string? (cadr form))) (catch #t (lambda () (lint-file (cadr form) env)) (lambda args env)) env)) (hash-table-set! h 'load load-walker)) ;; ---------------- require ---------------- (let () (define (require-walker caller form env) (if (not (pair? (cdr form))) ; (require) (lint-format "~A is pointless" caller form) (if (any? string? (cdr form)) ; (require "repl.scm") (lint-format "in s7, require's arguments should be symbols: ~A" caller (truncated-list->string form)))) (if (not *report-loaded-files*) env (let ((vars env)) (for-each (lambda (f) (let ((file (*autoload* f))) (if (string? file) (catch #t (lambda () (set! vars (lint-file file vars))) (lambda args #f))))) (cdr form)) vars))) (hash-table-set! h 'require require-walker)) ;; ---------------- call-with-input-file etc ---------------- (let () (define (call-with-io-walker caller form env) (let ((len (if (eq? (car form) 'call-with-output-string) 2 3))) ; call-with-output-string func is the first arg, not second (when (= (length form) len) (let ((func (list-ref form (- len 1)))) (if (= len 3) (lint-walk caller (cadr form) env)) (if (not (and (pair? func) (eq? (car func) 'lambda))) (let ((f (and (symbol? func) (symbol->value func *e*)))) (if (and (procedure? f) (not (aritable? f 1))) (lint-format "~A argument should be a function of one argument: ~A" caller (car form) func)) (lint-walk caller func env)) (let ((args (cadr func))) (let ((body (cddr func)) (port (and (pair? args) (car args))) (head (car form))) (if (or (not port) (pair? (cdr args))) ;; (lambda () (write args) (newline)) (lint-format "~A argument should be a function of one argument: ~A" caller head func) (if (and (null? (cdr body)) (pair? (car body)) (pair? (cdar body)) (eq? (cadar body) port) (null? (cddar body))) ;; (call-with-input-file "file" (lambda (p) (read-char p))) -> (call-with-input-file "file" read-char) (lint-format "perhaps ~A" caller (lists->string form (if (= len 2) `(,head ,(caar body)) `(,head ,(cadr form) ,(caar body))))) (let ((cc (make-var :name port :initial-value (list (case head ((call-with-input-string) 'open-input-string) ((call-with-output-string) 'open-output-string) ((call-with-input-file) 'open-input-file) ((call-with-output-file) 'open-output-file))) :definer head))) (lint-walk-body caller head body (cons cc (cons (make-var :name :let :initial-value form :definer head) env))) (report-usage caller head (list cc) env)))))))))) env) (for-each (lambda (op) (hash-table-set! h op call-with-io-walker)) '(call-with-input-string call-with-input-file call-with-output-file call-with-output-string))) ;; ---------------- catch ---------------- (let () (define (catch-walker caller form env) ;; catch tag is tricky -- it is evaluated, then eq? matches at error time, so we need ;; to catch constants that can't be eq? (if (not (= (length form) 4)) (begin (lint-format "catch takes 3 arguments (tag body error-handler): ~A" caller (truncated-list->string form)) (lint-walk caller (cdr form) env)) (let ((tag (cadr form))) (if (or (and (not (pair? tag)) (or (number? tag) (char? tag) (length tag))) (and (pair? tag) (eq? (car tag) 'quote) (or (not (pair? (cdr tag))) (length (cadr tag))))) ;; (catch #(0) (lambda () #f) (lambda a a)) (lint-format "catch tag ~S is unreliable (catch uses eq? to match tags)" caller tag)) (let ((body (caddr form)) (error-handler (cadddr form))) ;; empty catch+catch apparently never happens (lint-walk caller body (cons (make-var :name :let :initial-value form :definer 'catch) (cons (make-var :name :catch :initial-value form :definer 'catch) env))) (lint-walk caller error-handler env)))) env) (hash-table-set! h 'catch catch-walker)) ;; ---------------- call-with-exit etc ---------------- (let () (define (call-with-exit-walker caller form env) (let ((continuation (and (pair? (cdr form)) (pair? (cadr form)) (eq? (caadr form) 'lambda) (pair? (cdadr form)) (pair? (cddadr form)) (pair? (cadadr form)) (car (cadadr form))))) (if (not (symbol? continuation)) (lint-walk caller (cdr form) env) (let ((body (cddadr form)) (head (car form))) (if (not (or (eq? head 'call-with-exit) (eq? continuation (car body)) (tree-sym-set-member continuation '(lambda lambda* define define* curlet error apply) body))) ;; this checks for continuation as arg (of anything), and any of set as car ;; (call/cc (lambda (p) (+ x (p 1)))) (lint-format* caller (string-append "perhaps " (symbol->string head)) " could be call-with-exit: " (truncated-list->string form))) (if (not (tree-unquoted-member continuation body)) ;; (call-with-exit (lambda (p) (+ x 1))) (lint-format "~A ~A ~A appears to be unused: ~A" caller head (if (eq? head 'call-with-exit) "exit function" "continuation") continuation (truncated-list->string form)) (let ((last (and (proper-list? body) (list-ref body (- (length body) 1))))) (if (and (pair? last) (eq? (car last) continuation)) ;; (call-with-exit (lambda (return) (display x) (return (+ x y)))) (lint-format "~A is redundant here: ~A" caller continuation (truncated-list->string last))))) (let ((cc (make-var :name continuation :initial-value (if (eq? head 'call-with-exit) :call/exit :call/cc) :definer head))) (lint-walk-body caller head body (cons cc env)) (report-usage caller head (list cc) env))))) env) (for-each (lambda (op) (hash-table-set! h op call-with-exit-walker)) '(call/cc call-with-current-continuation call-with-exit))) ;; ---------------- import etc ---------------- (for-each (lambda (op) (hash-table-set! h op (lambda (caller form env) env))) '(define-module import export)) (hash-table-set! h 'provide (lambda (caller form env) (if (not (= (length form) 2)) ;; (provide a b c) (lint-format "provide takes one argument: ~A" caller (truncated-list->string form)) (unless (symbol? (cadr form)) (let ((op (->lint-type (cadr form)))) (if (not (memq op '(symbol? #f #t values))) ;; (provide "test") (lint-format "provide's argument should be a symbol: ~S" caller form))))) env)) (hash-table-set! h 'module ; module apparently has different syntax and expectations in various schemes (lambda (caller form env) (if (and (pair? (cdr form)) (pair? (cddr form))) (lint-walk 'module (cddr form) env)) env)) (hash-table-set! h 'define-syntax (lambda (caller form env) ;; we need to put the macro name in env with ftype=define-syntax (if (and (pair? (cdr form)) (symbol? (cadr form)) (not (keyword? (cadr form)))) ; !! this thing is a disaster from the very start (cons (make-fvar (cadr form) :ftype 'define-syntax) env) env))) (hash-table-set! h 'define-method ; guile and mit-scheme have different syntaxes here (lambda (caller form env) (if (not (and (pair? (cdr form)) (pair? (cddr form)))) env (if (symbol? (cadr form)) (if (keyword? (cadr form)) (lint-walk-body caller 'define-method (cdddr form) env) (let ((new-env (if (var-member (cadr form) env) env (cons (make-fvar (cadr form) :ftype 'define-method) env)))) (lint-walk-body caller (cadr form) (cdddr form) new-env))) (let ((new-env (if (var-member (caadr form) env) env (cons (make-fvar (caadr form) :ftype 'define-method) env)))) (lint-walk-body caller (caadr form) (cddr form) new-env)))))) (hash-table-set! h 'let-syntax (lambda (caller form env) (lint-walk-body caller 'define-method (cddr form) env) env)) (hash-table-set! h 'letrec-syntax (lambda (caller form env) (lint-walk-body caller 'define-method (cddr form) env) env)) ;; ---------------- case-lambda ---------------- (let () (define (case-lambda-walker caller form env) (when (pair? (cdr form)) (let ((lens ()) (body ((if (string? (cadr form)) cddr cdr) form)) ; might have a doc string before the clauses (doc-string (and (string? (cadr form)) (cadr form)))) (define (arg->defaults arg b1 b2 defaults) (and defaults (cond ((null? b1) (and (null? b2) defaults)) ((null? b2) (and (null? b1) defaults)) ((eq? arg b1) (cons b2 defaults)) ((eq? arg b2) (cons b1 defaults)) ((pair? b1) (and (pair? b2) (arg->defaults arg (car b1) (car b2) (arg->defaults arg (cdr b1) (cdr b2) defaults)))) (else (and (equal? b1 b2) defaults))))) (for-each (lambda (choice) (if (pair? choice) (let ((len (length (car choice)))) (if (member len lens) ;; (case-lambda (() 0) ((x y) x) ((x y) (+ x y)) ((x y z) (+ x y z)) (args (apply + args)) (lint-format "repeated parameter list? ~A in ~A" caller (car choice) form)) (set! lens (cons len lens)) (lint-walk 'case-lambda (cons 'lambda choice) env)))) body) (case (length lens) ((1) ;; (case-lambda (() (if #f #f))) -> (lambda () (if #f #f)) (lint-format "perhaps ~A" caller (lists->string form (if doc-string `(let ((documentation ,doc-string)) (lambda ,(caar body) ,@(cdar body))) `(lambda ,(caar body) ,@(cdar body)))))) ((2) (when (let arglists-equal? ((args1 (caar body)) (args2 (caadr body))) (if (null? args1) (and (pair? args2) (null? (cdr args2))) (and (pair? args1) (if (null? args2) (null? (cdr args1)) (and (pair? args2) (eq? (car args1) (car args2)) (arglists-equal? (cdr args1) (cdr args2))))))) (let* ((clause1 (car body)) (body1 (cdr clause1)) (clause2 (cadr body)) (body2 (cdr clause2)) (arglist (let ((arg1 (car clause1)) (arg2 (car clause2))) (if (> (car lens) (cadr lens)) arg2 arg1))) ; lens is reversed (arg-name (list-ref arglist (- (length arglist) 1))) (diffs (arg->defaults arg-name body1 body2 ()))) (when (and (pair? diffs) (null? (cdr diffs)) (code-constant? (car diffs))) (let ((new-body (if (> (car lens) (cadr lens)) body2 body1)) (new-arglist (if (not (car diffs)) arglist (if (null? (cdr arglist)) `((,arg-name ,(car diffs))) `(,(car arglist) (,arg-name ,(car diffs))))))) ;; (case-lambda (() (display x #f)) ((y) (display x y))) -> (lambda* (y) (display x y)) (lint-format "perhaps ~A" caller (lists->string form (if doc-string `(let ((documentation ,doc-string)) (lambda* ,new-arglist ,@new-body)) `(lambda* ,new-arglist ,@new-body)))))))))))) env) (hash-table-set! h 'case-lambda case-lambda-walker)) h)) ;; end walker-functions ;; ---------------------------------------- (define (hash-fragment reduced-form leaves env func orig-form) ;; func here is either #f or an env-style entry (cons name let) as produced by make-fvar, ;; the let entries accessed are initial-value, history, arglist (let ((old (hash-table-ref (fragments leaves) reduced-form)) (line (pair-line-number orig-form))) ;(if func (format *stderr* "hash-fragment ~A ~A~%~%" (var-name func) reduced-form)) (if (not (vector? old)) (hash-table-set! (fragments leaves) reduced-form (vector 1 (list line) (and func (list func)) orig-form #f)) ;; key = reduced-form ;; value = #(list uses line-numbers fvar original-form) (begin (vector-set! old 0 (+ (vector-ref old 0) 1)) (vector-set! old 1 (cons (pair-line-number orig-form) (vector-ref old 1))) (when func (if (not (vector-ref old 2)) (vector-set! old 2 (list func)) (let ((caller (if (keyword? (var-name func)) 'define (var-name func)))) (let search ((vs (vector-ref old 2))) (when (pair? vs) (let ((v (car vs))) (cond ((not (eqv? (length (var-arglist v)) (length (var-arglist func)))) (search (cdr vs))) ((eq? (var-history v) :built-in) (lint-format "~A is the same as the built-in ~A ~A" caller (var-name func) (if (eq? (car (var-initial-value v)) 'define-macro) 'macro 'function) (var-name v))) ((not (var-member (var-name v) env)) (lint-format "~A is the same as ~A" caller (var-name func) (if (< 0 (pair-line-number (var-initial-value v)) 100000) (format #f "~A (line ~D)" (var-name v) (pair-line-number (var-initial-value v))) (if (eq? (var-name func) (var-name v)) (format #f "previous ~A" (var-name v)) (var-name v))))) ((eq? (var-name v) (var-name func)) (lint-format "~A definition repeated: ~A" caller (var-name func) (truncated-list->string (var-initial-value func)))) (else (lint-format "~A could be (define ~A ~A)" caller (var-name func) (var-name func) (var-name v))))))) (vector-set! old 2 (cons func (vector-ref old 2)))))))))) (define (reduce-tree new-form env fvar orig-form) ;(format *stderr* "reduce-tree: ~A ~A~%" new-form (and fvar (var-name fvar))) (let ((leaves (tree-leaves new-form))) (when (< 5 leaves *fragments-size*) (call-with-exit (lambda (quit) (let ((outer-vars (if fvar (do ((e (list (list (var-name fvar) (symbol "_F_") 0 ()))) (i 1 (+ i 1)) (args (args->proper-list (var-arglist fvar)) (cdr args))) ((null? args) e) (set! e (cons (list (car args) (symbol "_" (number->string i) "_") i ()) e))) (list (list () '_1_) (list () '_2_) (list () '_3_)))) (local-ctr 0)) (let ((reduced-form (let walker ((tree new-form) (vars outer-vars)) ;(format *stderr* "walker: ~A, vars: ~A~%" tree vars) (cond ((or (not (symbol? tree)) (keyword? tree)) (if (or (not (pair? tree)) (eq? (car tree) 'quote)) tree (case (car tree) ((let let*) ;; in let we need to sort locals by order of appearance in the body (if (not (and (pair? (cdr tree)) (pair? (cddr tree)))) (quit)) (let ((locals ()) (body ()) (named-let (symbol? (cadr tree))) (lvars ())) (if named-let (begin (set! lvars (cons (list (cadr tree) (symbol "_NL" (number->string local-ctr) "_") -1) lvars)) (set! local-ctr (+ local-ctr 1)) (set! locals (caddr tree)) (set! body (cdddr tree))) (begin (set! locals (cadr tree)) (set! body (cddr tree)))) (if (not (list? locals)) (quit)) (if (eq? (car tree) 'let) (for-each (lambda (local) (if (not (and (pair? local) (pair? (cdr local)))) (quit)) (set! lvars (cons (list (car local) () 0 (walker (cadr local) vars)) lvars))) locals) (for-each (lambda (local) (if (not (and (pair? local) (pair? (cdr local)))) (quit)) (set! lvars (cons (list (car local) (symbol "_L" (number->string local-ctr) "_") local-ctr (walker (cadr local) (append lvars vars))) lvars)) (set! local-ctr (+ local-ctr 1))) locals)) ;; now walk the body, setting the reduced local name by order of encounter (in let, not let*) (let ((new-body (walker body (append lvars vars)))) (when (and (eq? (car tree) 'let) ;; fill-in unused-var dummy names etc (pair? lvars)) (for-each (lambda (v) (when (null? (cadr v)) (list-set! v 1 (symbol "_L" (number->string local-ctr) "_")) (list-set! v 2 local-ctr) (set! local-ctr (+ local-ctr 1)))) lvars)) (set! lvars (sort! lvars (lambda (a b) (< (caddr a) (caddr b))))) (if named-let `(,(car tree) ,(cadr (assq (cadr tree) lvars)) ,(map (lambda (v) (list (cadr v) (cadddr v))) (cdr lvars)) ,@new-body) `(,(car tree) ,(map (lambda (v) (list (cadr v) (cadddr v))) lvars) ,@new-body))))) ((letrec letrec*) (if (not (pair? (cdr tree))) (quit)) (let ((locals (cadr tree)) (body (cddr tree)) (lvars ())) (if (not (and (list? locals) (pair? body))) (quit)) (for-each (lambda (local) (if (not (and (pair? local) (pair? (cdr local)))) (quit)) (set! lvars (cons (list (car local) (symbol "_L" (number->string local-ctr) "_") local-ctr ()) lvars)) (set! local-ctr (+ local-ctr 1))) locals) (for-each (lambda (local lv) (list-set! lv 3 (walker (cadr local) lvars))) locals lvars) `(,(car tree) ,(map (lambda (v) (list (cadr v) (cadddr v))) lvars) ,@(walker body (append lvars vars))))) ((do) (if (not (and (pair? (cdr tree)) (list? (cadr tree)) (pair? (cddr tree)) (list? (cdddr tree)))) (quit)) (let ((locals (cadr tree)) (end+result (caddr tree)) (body (cdddr tree)) (lvars ())) (if (not (list? end+result)) (quit)) (for-each (lambda (local) (if (not (and (pair? local) (pair? (cdr local)))) (quit)) (set! lvars (cons (list (car local) () 0 (walker (cadr local) vars) (if (pair? (cddr local)) (caddr local) :unset)) lvars))) locals) (let ((new-env (append lvars vars))) (let ((new-end (walker end+result new-env)) (new-body (walker body new-env))) (when (pair? lvars) (for-each (lambda (lv) (if (not (eq? (lv 4) :unset)) (list-set! lv 4 (walker (lv 4) new-env)))) lvars) (for-each (lambda (v) (when (null? (cadr v)) (list-set! v 1 (symbol "_L" (number->string local-ctr) "_")) (list-set! v 2 local-ctr) (set! local-ctr (+ local-ctr 1)))) lvars) (set! lvars (sort! lvars (lambda (a b) (< (caddr a) (caddr b)))))) `(do ,(map (lambda (v) (if (eq? (v 4) :unset) (list (v 1) (v 3)) (list (v 1) (v 3) (v 4)))) lvars) ,new-end ,@new-body))))) ((lambda) (if (not (and (pair? (cdr tree)) (proper-list? (cddr tree)))) (quit)) (let* ((lvars (map (lambda (a) (let ((res (list a (symbol "_A" (number->string local-ctr) "_") local-ctr))) (set! local-ctr (+ local-ctr 1)) res)) (let ((args (args->proper-list (cadr tree)))) (if (pair? args) args (quit))))) (new-body (let ((new-vars (append lvars vars))) (map (lambda (p) (walker p new-vars)) (cddr tree)))) (new-args (if (symbol? (cadr tree)) (cadar lvars) (if (proper-list? (cadr tree)) (map cadr lvars) (let ((lst (map cadr lvars))) (append (copy lst (make-list (- (length lst) 1))) (list-ref lst (- (length lst) 1)))))))) `(lambda ,new-args ,@new-body))) ((lambda*) (if (not (and (pair? (cdr tree)) (or (symbol? (cadr tree)) (proper-list? (cadr tree))))) (quit)) (let* ((lvars (map (lambda (a) (if (memq a '(:rest :allow-other-keys)) (values) (let ((res (list (if (pair? a) (car a) a) (symbol "_A" (number->string local-ctr) "_") local-ctr))) (set! local-ctr (+ local-ctr 1)) res))) (args->proper-list (cadr tree)))) (new-body (let ((new-vars (append lvars vars))) (map (lambda (p) (walker p new-vars)) (cddr tree)))) (new-args (if (symbol? (cadr tree)) (cadar lvars) (map (lambda (a) (cond ((keyword? a) a) ((symbol? a) (cadr (assq a lvars))) ((and (pair? a) (pair? (cdr a))) (list (assq a lvars) (cadr a))) (else (quit)))) (cadr tree))))) `(lambda* ,new-args ,@new-body))) ((case) (if (not (and (pair? (cdr tree)) (pair? (cddr tree)) (pair? (caddr tree)))) (quit)) `(case ,(walker (cadr tree) vars) ,(map (lambda (c) (if (not (and (pair? c) (pair? (cdr c)))) (quit)) (cons (car c) (map (lambda (p) (walker p vars)) (cdr c)))) (cddr tree)))) ((if) (if (not (and (pair? (cdr tree)) (pair? (cddr tree)) (list? (cdddr tree)))) (quit)) (let ((expr (walker (cadr tree) vars)) (true (walker (caddr tree) vars))) (if (null? (cdddr tree)) (if (and (pair? expr) (eq? (car expr) 'not)) `(unless ,(cadr expr) ,@(unbegin true)) `(when ,expr ,@(unbegin true))) `(if ,expr ,true ,(walker (cadddr tree) vars))))) ((when unless) (if (not (and (pair? (cdr tree)) (pair? (cddr tree)))) (quit)) `(,(car tree) ,(walker (cadr tree) vars) ,@(map (lambda (p) (walker p vars)) (cddr tree)))) ((set!) (if (not (and (pair? (cdr tree)) (pair? (cddr tree)))) (quit)) (if (symbol? (cadr tree)) (let ((v (assq (cadr tree) vars))) (if (or (not v) ; if not a var, it's about to be an outer-var (and (not fvar) (memq (cadr v) '(_1_ _2_ _3_)))) (quit)) (when (null? (cadr v)) ; must be a previously unencountered local (list-set! v 1 (symbol "_L" (number->string local-ctr) "_")) (list-set! v 2 local-ctr) (set! local-ctr (+ local-ctr 1))) `(set! ,(cadr v) ,(walker (caddr tree) vars))) `(set! ,(walker (cadr tree) vars) ,(walker (caddr tree) vars)))) ((define define* ;; these propagate backwards and we're not returning the new env in this loop, ;; lvars can be null, so splicing a new local into vars is a mess, ;; but if the defined name is not reduced, it can occur later as itself (not via car), ;; so without lots of effort (a dummy var if null lvars, etc), we can only handle ;; functions within a function (fvar not #f). ;; but adding that possibility got no hits define-constant define-macro define-macro* define-syntax let-syntax letrec-syntax match syntax-rules case-lambda require import module cond-expand quasiquote reader-cond while unquote call-with-values let-values define-values let*-values multiple-value-bind) (quit)) (else (cons (cond ((pair? (car tree)) (walker (car tree) vars)) ((assq (car tree) vars) => (lambda (v) (if (symbol? (cadr v)) (cadr v) (car tree)))) (else (car tree))) (if (pair? (cdr tree)) (map (lambda (p) (walker p vars)) (cdr tree)) (cdr tree))))))) ((assq tree vars) => ; replace in-tree symbol with its reduction (lambda (v) ;; v is a list: local-name possible-reduced-name [counter value] (when (null? (cadr v)) (list-set! v 1 (symbol "_L" (number->string local-ctr) "_")) (list-set! v 2 local-ctr) (set! local-ctr (+ local-ctr 1))) (cadr v))) (else (if fvar (quit)) (let set-outer ((ovars outer-vars)) (if (null? ovars) (quit) (if (null? (caar ovars)) (begin (set-car! (car ovars) tree) (cadar ovars)) (set-outer (cdr ovars)))))))))) ;; if->when, for example, so tree length might change (set! leaves (tree-leaves reduced-form)) (hash-fragment reduced-form leaves env fvar orig-form) (if (and (memq (car reduced-form) '(or and)) (> (length reduced-form) 3)) (do ((i (- (length reduced-form) 1) (- i 1)) (rfsize leaves)) ((or (= i 2) (< rfsize 6))) (let ((rf (copy reduced-form (make-list i)))) (set! rfsize (tree-leaves rf)) (when (> rfsize 5) (hash-fragment rf rfsize env #f orig-form))))) (when fvar (quit)) ;; TODO: also below and clean this up! (unless (and (pair? lint-function-body) (equal? new-form (car lint-function-body))) (let ((fvars (let ((fcase (hash-table-ref (fragments leaves) (list reduced-form)))) (and (vector? fcase) (vector-ref fcase 2))))) (when (pair? fvars) (call-with-exit (lambda (ok) (for-each (lambda (fv) (when (var-member (var-name fv) env) (format outport "~NCperhaps ~A -> (~A~{ ~A~})~%" lint-left-margin #\space (truncated-list->string new-form) (var-name fv) (map (lambda (a) (if (null? (car a)) (values) (car a))) outer-vars)) (ok))) fvars) (format outport "~NCif '~A were in scope, perhaps ~A -> (~A~{ ~A~})~%" lint-left-margin #\space (var-name (car fvars)) (truncated-list->string new-form) (var-name (car fvars)) (map (lambda (a) (if (null? (car a)) (values) (car a))) outer-vars))))))) ;; now look for (f _1_) -> _1_ possibilities ;; every reference to _1_ has to be via (f _1_), and f must have no side-effects ;; so first rescan the form, gathering info about each _n_ var (let* ((rnames (map (lambda (v) (if (symbol? (car v)) (cadr v) (values))) outer-vars)) (rvars (map (lambda (v) (vector v 0 ())) rnames))) (when (and (pair? reduced-form) (not (eq? (car reduced-form) 'quote))) (let walker ((tree reduced-form)) (for-each (lambda (p) (if (pair? p) (if (not (eq? (car p) 'quote)) (walker p)) (if (and (symbol? p) (memq p rnames)) (let search ((rv rvars)) (let ((v (car rv))) (if (eq? (v 0) p) (begin (set! (v 1) (+ (v 1) 1)) (set! (v 2) (cons tree (v 2)))) (search (cdr rv)))))))) tree))) (let ((reducibles ())) (for-each (lambda (v) (if (and (pair? (v 2)) (pair? (car (v 2))) (pair? (cdar (v 2))) (null? (cddar (v 2))) (not (side-effect-with-vars? (car (v 2)) env rnames)) (or (= (v 1) 1) (let ((first (car (v 2)))) (not (member first (cdr (v 2)) (lambda (a b) (not (equal? a b)))))))) (set! reducibles (cons (car (v 2)) reducibles)))) rvars) ;; reducibles is a list of _n_ vars that can be simplified one more level (when (pair? reducibles) (for-each (lambda (r) (let ((rf (let walker ((tree reduced-form)) (if (or (not (pair? tree)) (eq? (car tree) 'quote)) tree (if (equal? tree r) (cadr tree) (cons (walker (car tree)) (walker (cdr tree)))))))) (set! leaves (tree-leaves rf)) (when (> leaves 5) (hash-fragment rf leaves env fvar orig-form)))) reducibles) ;; if more than one reducible, try all combinations (when (pair? (cdr reducibles)) (let ((combo (if (null? (cddr reducibles)) (list (list (reducibles 0) (reducibles 1))) (list (list (reducibles 0) (reducibles 1)) (list (reducibles 0) (reducibles 2)) (list (reducibles 1) (reducibles 2)) (list (reducibles 0) (reducibles 1) (reducibles 2)))))) (for-each (lambda (r) (let ((rf (let walker ((tree reduced-form)) (if (or (not (pair? tree)) (eq? (car tree) 'quote)) tree (if (member tree r) (cadr tree) (cons (walker (car tree)) (walker (cdr tree)))))))) (set! leaves (tree-leaves rf)) (when (> (tree-leaves rf) 5) (hash-fragment rf leaves env fvar orig-form)))) combo))))))))))))) (define (lint-fragment form env) (if (memq (car form) '(or and)) ;; or/and are special because leading and trailing cases are separable (like leading cases for bodies) (do ((i (length form) (- i 1)) (p (cdr form) (cdr p))) ((<= i 2)) (reduce-tree (cons (car form) p) env #f form)) (reduce-tree form env #f form))) (define (reduce-function-tree fvar env) (let ((definition (cond ((var-initial-value fvar) => cddr) (else #f)))) (when (pair? definition) (reduce-tree (if (and (string? (car definition)) (pair? (cdr definition))) (cdr definition) definition) env (and (not (keyword? (var-name fvar))) fvar) (var-initial-value fvar))))) ;; ---------------------------------------- (define lint-walk-pair (let ((unsafe-makers '(sublet inlet copy cons list append make-shared-vector vector hash-table hash-table* make-hash-table make-hook #_{list} #_{append} gentemp or and not)) (qq-form #f)) (lambda (caller form env) (let ((head (car form))) (set! line-number (pair-line-number form)) (lint-fragment form env) (cond ((hash-table-ref walker-functions head) => (lambda (f) (f caller form env))) (else (if (not (proper-list? form)) ;; these appear to be primarily macro/match arguments ;; other cases (not list) have already been dealt with far above (if (and (pair? form) (symbol? head) (procedure? (symbol->value head *e*))) ;; (+ . 1) (lint-format "unexpected dot: ~A" caller (truncated-list->string form))) (begin (cond ((symbol? head) (let ((v (var-member head env))) (if (and (var? v) (not (memq form (var-history v)))) (set! (var-history v) (cons form (var-history v)))) (check-call caller head form env) ;; look for one huge argument leaving lonely trailing arguments somewhere off the screen ;; (it needs to be one arg, not a call on values) (let ((branches (length form))) (when (and (= branches 2) (any-procedure? head env) (not (eq? head 'unquote))) (let ((arg (cadr form))) ;; begin=(car arg) happens very rarely (when (pair? arg) (when (and (memq (car arg) '(let let*)) (not (or (symbol? (cadr arg)) (and (pair? (cddr arg)) (pair? (caddr arg)) (eq? 'lambda (caaddr arg))) (assq head (cadr arg))))) ;; (string->symbol (let ((s (copy vstr))) (set! (s (+ pos 1)) #\s) s)) -> ;; (let ((s (copy vstr))) (set! (s (+ pos 1)) #\s) (string->symbol s))") (lint-format "perhaps~%~NC~A ->~%~NC~A" caller (+ lint-left-margin 4) #\space (truncated-list->string form) (+ lint-left-margin 4) #\space (let* ((body (cddr arg)) (len (- (length body) 1)) (str (object->string `(,(car arg) ,(cadr arg) ,@(copy body (make-list len)) (,head ,(list-ref body len)))))) (if (<= (length str) target-line-length) str (format #f "(~A ... (~A ~A))" (car arg) head (truncated-list->string (list-ref body len))))))) (when (eq? (car arg) 'or) (let ((else-clause (let ((last-clause (list-ref arg (- (length arg) 1)))) (if (and (pair? last-clause) (memq (car last-clause) '(error throw))) last-clause (if (or (not (code-constant? last-clause)) (side-effect? `(,head ,last-clause) env)) :checked-eval-error (let ((res (checked-eval `(,head ,last-clause)))) (if (or (and (symbol? res) (not (eq? res :checked-eval-error))) (pair? res)) (list 'quote res) res))))))) (unless (eq? else-clause :checked-eval-error) (set! last-rewritten-internal-define form) ;; (string->number (or (f x) "4")) -> (cond ((f x) => string->number) (else 4)) (lint-format "perhaps ~A" caller (lists->string form `(cond (,(if (or (null? (cddr arg)) (null? (cdddr arg))) (cadr arg) (copy arg (make-list (- (length arg) 1)))) => ,head) (else ,else-clause)))))))))) (unless (or (<= branches 2) (any-macro? head env) (memq head '(for-each map #_{list} * + - /))) (let ((leaves (tree-leaves form))) (when (> leaves (max *report-bloated-arg* (* branches 3))) (do ((p (cdr form) (cdr p)) (i 1 (+ i 1))) ((or (not (pair? p)) (null? (cdr p)) (and (pair? (car p)) (symbol? (caar p)) (not (memq (caar p) '(lambda quote call/cc list vector match-lambda values))) (> (tree-leaves (car p)) (- leaves (* branches 2))) (or (not (memq head '(or and))) (= i 1)) (not (tree-member 'values (car p))) (let ((header (copy form (make-list i))) (trailer (copy form (make-list (- branches i 1)) (+ i 1))) (disclaimer (if (or (any-procedure? head env) (hash-table-ref no-side-effect-functions head)) "" (format #f ", assuming ~A is not a macro," head)))) ;; begin=(caar p) here is almost entirely as macro arg ;; (apply env-channel (make-env ...) args) -> (let ((_1_ (make-env ...))) (apply env-channel _1_ args)) (lint-format "perhaps~A~%~NC~A ->~%~NC~A" caller disclaimer (+ lint-left-margin 4) #\space (lint-pp `(,@header ,(one-call-and-dots (car p)) ,@trailer)) (+ lint-left-margin 4) #\space (if (and (memq (caar p) '(let let*)) (list? (cadar p)) (not (assq head (cadar p)))) ; actually not intersection header+trailer (map car cadr) (let ((last (let ((body (cddar p))) (list-ref body (- (length body) 1))))) (if (< (tree-leaves last) 12) (format #f "(~A ... ~A)" (caar p) (lint-pp `(,@header ,last ,@trailer))) (lint-pp `(let ((_1_ ,(one-call-and-dots (car p)))) (,@header _1_ ,@trailer))))) (lint-pp `(let ((_1_ ,(one-call-and-dots (car p)))) (,@header _1_ ,@trailer))))) #t))))))))) (when (pair? form) ;; save any references to vars in their var-history (type checked later) ;; this can be fooled by macros, as everywhere else (for-each (lambda (arg) (if (symbol? arg) (let ((v (var-member arg env))) (if (and (var? v) (not (memq form (var-history v)))) (set! (var-history v) (cons form (var-history v))))))) form) (if (set!? form env) (set-set (cadr form) caller form env))) (if (var? v) (if (and (memq (var-ftype v) '(define lambda define* lambda*)) (not (memq caller (var-scope v)))) (let ((cv (var-member caller env))) (set! (var-scope v) (cons (if (and (var? cv) (memq (var-ftype cv) '(define lambda define* lambda*))) ; named-let does not define ftype caller (cons caller env)) (var-scope v))))) (begin (cond ((hash-table-ref special-case-functions head) => (lambda (f) (f caller head form env)))) ;; change (list ...) to '(....) if it's safe as a constant list ;; and (vector ...) -> #(...) (if (and (pair? (cdr form)) (hash-table-ref no-side-effect-functions head) (not (memq head unsafe-makers))) (for-each (lambda (p) (if (let constable? ((cp p)) (and (pair? cp) (memq (car cp) '(list vector)) (pair? (cdr cp)) (every? (lambda (inp) (or (code-constant? inp) (constable? inp))) (cdr cp)))) (lint-format "perhaps ~A -> ~A~A" caller (truncated-list->string p) (if (eq? (car p) 'list) "'" "") (object->string (eval p))))) (cdr form))) (if (and (not (= line-number last-simplify-numeric-line-number)) (hash-table-ref numeric-ops head) (proper-tree? form)) (let ((val (simplify-numerics form env))) (if (not (equal-ignoring-constants? form val)) (begin (set! last-simplify-numeric-line-number line-number) ;; (+ 1 2) -> 3, and many others (lint-format "perhaps ~A" caller (lists->string form val)))))) ;; if a var is used before it is defined, the var history and ref/set ;; info needs to be saved until the definition, so other-identifiers collects it (unless (defined? head (rootlet)) (hash-table-set! other-identifiers head (if (not (hash-table-ref other-identifiers head)) (list form) (cons form (hash-table-ref other-identifiers head))))))) ;; ---------------- ;; (f ... (if A B C) (if A D E) ...) -> (f ... (if A (values B D) (values C E)) ...) ;; these happen up to almost any number of clauses ;; need true+false in every case, and need to be contiguous ;; case/cond happen here, but very rarely in a way we can combine via values (unless (any-macro? head env) ; actually most macros are safe here... (let ((p (member 'if (cdr form) (lambda (x q) (and (pair? q) (eq? (car q) 'if) ; it's an if expression (pair? (cdr q)) (pair? (cddr q)) ; there's a true branch (pair? (cdddr q))))))) ; and a false branch (similarly below) (when (pair? p) (do ((test (cadar p)) (q (cdr p) (cdr q))) ((not (and (pair? q) (let ((x (car q))) (and (pair? x) (eq? (car x) 'if) (pair? (cdr x)) (equal? (cadr x) test) (pair? (cddr x)) (pair? (cdddr x)))))) (unless (eq? q (cdr p)) (let ((header (do ((i 1 (+ i 1)) (r (cdr form) (cdr r))) ((eq? r p) (copy form (make-list i))))) (middle (do ((r p (cdr r)) (trues ()) (falses ())) ((eq? r q) `(if ,test (values ,@(reverse trues)) (values ,@(reverse falses)))) (set! trues (cons (caddar r) trues)) (set! falses (cons (car (cdddar r)) falses))))) ;; (+ (if A B C) (if A C D) y) -> (+ (if A (values B C) (values C D)) y) (lint-format "perhaps~A ~A" caller (if (side-effect? test env) (format #f " (ignoring ~S's possible side-effects)" test) "") (lists->string form `(,@header ,middle ,@q)))))))))))) ((pair? head) (cond ((not (and (pair? (cdr head)) (memq (car head) '(lambda lambda*))))) ((and (identity? head) (pair? (cdr form))) ; identity needs an argument ;; ((lambda (x) x) 32) -> 32 (lint-format "perhaps ~A" caller (truncated-lists->string form (cadr form)))) ((and (symbol? (cadr head)) ; ((lambda x x) 1 2 3) -> (list 1 2 3) (pair? (cddr head)) (eq? (cadr head) (caddr head)) (null? (cdddr head))) (lint-format "perhaps ~A" caller (lists->string form `(list ,@(cdr form))))) ((and (null? (cadr head)) (pair? (cddr head))) ;; ((lambda () 32) 0) -> 32 (lint-format "perhaps ~A" caller (truncated-lists->string form (if (and (null? (cdddr head)) (not (and (pair? (caddr head)) (memq (caaddr head) '(define define* define-constant define-macro define-macro*))))) (caddr head) `(let () ,@(cddr head)))))) ((and (pair? (cddr head)) ; ((lambda (...) ...) ...) -> (let ...) -- lambda here is ugly and slow (proper-list? (cddr head)) (not (any? (lambda (a) (mv-range a env)) (cdr form)))) (call-with-exit (lambda (quit) ; uncountably many things can go wrong with the lambda form (let ((vars ()) (vals ())) (do ((v (cadr head) (cdr v)) (a (cdr form) (cdr a))) ((not (and (pair? a) (pair? v))) (if (symbol? v) (begin (set! vars (cons v vars)) (set! vals (cons `(list ,@a) vals))) (do ((v v (cdr v))) ((not (pair? v))) (if (not (pair? v)) (quit)) (if (pair? (car v)) (begin (if (not (pair? (cdar v))) (quit)) (set! vars (cons (caar v) vars)) (set! vals (cons (cadar v) vals))) (begin (set! vars (cons (car v) vars)) (set! vals (cons #f vals))))))) (set! vars (cons ((if (pair? (car v)) caar car) v) vars)) (set! vals (cons (car a) vals))) ;; ((lambda* (a b) (+ a b)) 1) -> (let ((a 1) (b #f)) (+ a b)) (lint-format "perhaps ~A" caller (lists->string form `(,(if (or (eq? (car head) 'lambda) (not (pair? (cadr head))) (null? (cdadr head))) 'let 'let*) ,(map list (reverse vars) (reverse vals)) ,@(cddr head)))))))))) ((and (procedure? head) (memq head '(#_{list} #_{apply_values} #_{append}))) (for-each (lambda (p) (let ((sym (and (symbol? p) p))) (when sym (let ((v (var-member sym env))) (if (var? v) (set-ref sym caller form env) (if (not (defined? sym (rootlet))) (hash-table-set! other-identifiers sym (if (not (hash-table-ref other-identifiers sym)) (list form) (cons form (hash-table-ref other-identifiers sym)))))))))) (cdr form)) (when (and (eq? head #_{list}) (not (eq? lint-current-form qq-form))) (set! qq-form lint-current-form) ; only interested in simplest cases here (case (length form) ((2) (if (and (pair? (cadr form)) (eq? (caadr form) #_{apply_values}) ; `(,@x) -> (copy x) (not (qq-tree? (cadadr form)))) (lint-format "perhaps ~A" caller (lists->string form (un_{list} (if (pair? (cadadr form)) (cadadr form) `(copy ,(cadadr form)))))) (if (symbol? (cadr form)) (lint-format "perhaps ~A" caller ; `(,x) -> (list x) (lists->string form `(list ,(cadr form))))))) ((3) (if (and (pair? (caddr form)) (eq? (caaddr form) #_{apply_values}) (not (qq-tree? (cadr (caddr form)))) (pair? (cadr form)) ; `(,@x ,@y) -> (append x y) (eq? (caadr form) #_{apply_values}) (not (qq-tree? (cadadr form)))) (lint-format "perhaps ~A" caller (lists->string form `(append ,(un_{list} (cadadr form)) ,(un_{list} (cadr (caddr form)))))))) (else (if (every? (lambda (a) ; `(,@x ,@y etc) -> (append x y ...) (and (pair? a) (eq? (car a) #_{apply_values}) (not (qq-tree? (cdr a))))) (cdr form)) (lint-format "perhaps ~A" caller (lists->string form `(append ,@(map (lambda (a) (un_{list} (cadr a))) (cdr form))))))) )))) (let ((vars env)) (for-each (lambda (f) (set! vars (lint-walk caller f vars))) form)))) env)))))) (define (lint-walk caller form env) (cond ((symbol? form) (if (memq form '(+i -i)) (format outport "~NC~A is not a number in s7~%" lint-left-margin #\space form)) (set-ref form caller #f env)) ; returns env ((pair? form) (lint-walk-pair caller form env)) ((string? form) (let ((len (length form))) (if (and (> len 16) (string=? form (make-string len (string-ref form 0)))) ;; "*****************************" -> (format #f "~NC" 29 #\*) (lint-format "perhaps ~S -> ~A" caller form `(format #f "~NC" ,len ,(string-ref form 0))))) env) ((vector? form) (let ((happy #t)) (for-each (lambda (x) (when (and (pair? x) (eq? (car x) 'unquote)) (lint-walk caller (cadr x) env) ; register refs (set! happy #f))) form) ;; (begin (define x 1) `#(,x)) (if (not happy) ; these are used exactly 4 times (in a test suite!) in 2 million lines of open source scheme code (lint-format "quasiquoted vectors are not supported: ~A" caller form))) ;; `(x #(,x)) for example will not work in s7, but `(,x ,(vector x)) will env) (else env))) ;; -------- lint-file -------- (define *report-input* #t) ;; lint-file is called via load etc above and it's a pain to thread this variable all the way down the call chain (define (lint-file-1 file env) (set! linted-files (cons file linted-files)) (let ((fp (if (input-port? file) file (begin (set! *current-file* file) (catch #t (lambda () (let ((p (open-input-file file))) (when *report-input* (format outport (if (and (output-port? outport) (not (memq outport (list *stderr* *stdout*)))) (values "~%~NC~%;~A~%" (+ lint-left-margin 16) #\-) ";~A~%") file)) p)) (lambda args (format outport "~NCcan't open ~S: ~A~%" lint-left-margin #\space file (apply format #f (cadr args))) #f)))))) (when (input-port? fp) (do ((vars env) (line 0) (last-form #f) (last-line-number -1) (form (read fp) (read fp))) ((eof-object? form) (if (not (input-port? file)) (close-input-port fp)) (when *report-repeated-code-fragments* (do ((i 6 (+ i 1))) ((= i *fragments-size*)) (when (> (hash-table-entries (fragments i)) 0) (let ((v (copy (fragments i) (make-vector (hash-table-entries (fragments i)))))) ; (key . vector) (for-each (lambda (a1) (let ((a (cdr a1))) (when (> (vector-ref a 0) 1) (vector-set! a 1 (map (lambda (b) (if (< 0 b 100000) b (values))) (reverse (vector-ref a 1))))))) v) (for-each (lambda (keyval) (let ((val (cdr keyval))) (if (and (>= (vector-ref val 0) 2) (> (* (vector-ref val 0) (vector-ref val 0) i) 100)) ; 120 seems too high (if (equal? (vector-ref val 3) (car keyval)) (format outport "~NC~A uses, size: ~A, lines: '~A):~%~NCexpression: ~A~%" lint-left-margin #\space (vector-ref val 0) i (vector-ref val 1) (+ lint-left-margin 2) #\space (truncated-list->string (car keyval))) (format outport "~NC~A uses, size: ~A, lines: '~A):~%~NCpattern: ~A~%~NCexample: ~A~%" lint-left-margin #\space (vector-ref val 0) i (vector-ref val 1) (+ lint-left-margin 2) #\space (truncated-list->string (car keyval)) (+ lint-left-margin 2) #\space (truncated-list->string (vector-ref val 3))))))) (sort! v (lambda (kv1 kv2) (let ((a (cdr kv1)) (b (cdr kv2))) (or (> (vector-ref a 0) (vector-ref b 0)) (and (= (vector-ref a 0) (vector-ref b 0)) (stringstring (vector-ref a 3)))) (or (vector-ref b 4) (vector-set! b 4 (object->string (vector-ref b 3)))))))))))))))) (if (pair? form) (set! line (max line (pair-line-number form)))) (if (not (or (= last-line-number -1) (side-effect? last-form vars))) (format outport "~NCtop-level (line ~D): this has no effect: ~A~%" lint-left-margin #\space last-line-number (truncated-list->string last-form))) (set! last-form form) (set! last-line-number line) (if (and (pair? form) (memq (car form) '(define define-macro)) (pair? (cdr form)) (pair? (cadr form))) (let ((f (caadr form))) (if (and (symbol? f) (hash-table-ref built-in-functions f)) (format outport "~NCtop-level ~Aredefinition of built-in function ~A: ~A~%" lint-left-margin #\space (if (> (pair-line-number form) 0) (format #f "(line ~D) " (pair-line-number form)) "") f (truncated-list->string form))))) (set! vars (lint-walk (if (symbol? form) form (and (pair? form) (car form))) form vars)))))) (define (lint-file file env) ;; (if (string? file) (format *stderr* "lint ~S~%" file)) (if (member file linted-files) env (let ((old-current-file *current-file*) (old-pp-left-margin pp-left-margin) (old-lint-left-margin lint-left-margin) (old-load-path *load-path*)) (dynamic-wind (lambda () (set! pp-left-margin (+ pp-left-margin 4)) (set! lint-left-margin (+ lint-left-margin 4)) (when (and (string? file) (char=? (file 0) #\/)) (let ((last-pos 0)) (do ((pos (char-position #\/ file (+ last-pos 1)) (char-position #\/ file (+ last-pos 1)))) ((not pos) (if (> last-pos 0) (set! *load-path* (cons (substring file 0 last-pos) *load-path*)))) (set! last-pos pos))))) (lambda () (lint-file-1 file env)) (lambda () (set! pp-left-margin old-pp-left-margin) (set! lint-left-margin old-lint-left-margin) (set! *current-file* old-current-file) (set! *load-path* old-load-path) (if (positive? (length *current-file*)) (newline outport))))))) ;;; --------------------------------------------------------------------------------' ;;; lint itself ;;; (let ((documentation "(lint file port) looks for infelicities in file's scheme code") (signature (list #t string? output-port? boolean?))) (lambda* (file (outp *output-port*) (report-input #t)) (set! outport outp) (set! other-identifiers (make-hash-table)) (set! linted-files ()) (fill! other-names-counts 0) (do ((i 0 (+ i 1))) ((= i *fragments-size*)) (fill! (fragments i) #f)) (set! last-simplify-boolean-line-number -1) (set! last-simplify-numeric-line-number -1) (set! last-simplify-cxr-line-number -1) (set! last-checker-line-number -1) (set! last-cons-line-number -1) (set! last-if-line-number -1) (set! last-rewritten-internal-define #f) (set! line-number -1) (set! quote-warnings 0) (set! pp-left-margin 0) (set! lint-left-margin -3) ; lint-file above adds 4 (set! big-constants (make-hash-table)) (set! *report-input* report-input) (set! *report-nested-if* (if (integer? *report-nested-if*) (max 3 *report-nested-if*) 4)) (set! *report-short-branch* (if (integer? *report-short-branch*) (max 0 *report-short-branch*) 12)) (set! *#readers* (list (cons #\e (lambda (str) (unless (string=? str "e") (let ((num (string->number (substring str 1)))) (cond ((not num)) ((rational? num) (format outport "~NCthis #e is dumb, #~A -> ~A~%" lint-left-margin #\space str (substring str 1))) ((not (real? num)) (format outport "~NC#e can't handle complex numbers, #~A -> ~A~%" lint-left-margin #\space str num)) ((= num (floor num)) (format outport "~NCperhaps #~A -> ~A~%" lint-left-margin #\space str (floor num)))))) #f)) (cons #\i (lambda (str) (unless (string=? str "i") (let ((num (string->number (substring str 1)))) (when num (format outport (if (not (rational? num)) (values "~NCthis #i is dumb, #~A -> ~A~%" lint-left-margin #\space str (substring str 1)) (values "~NCperhaps #~A -> ~A~%" lint-left-margin #\space str (* 1.0 num))))))) #f)) (cons #\d (lambda (str) (if (and (not (string=? str "d")) (string->number (substring str 1))) (format outport "~NC#d is pointless, #~A -> ~A~%" lint-left-margin #\space str (substring str 1))) #f)) (cons #\' (lambda (str) ; for Guile (and syntax-rules, I think) (list 'syntax (if (string=? str "'") (read) (string->symbol str))))) (cons #\` (lambda (str) ; for Guile (sigh) (list 'quasisyntax (if (string=? str "'") (read) (string->symbol str))))) (cons #\, (lambda (str) ; the same, the last is #,@ -> unsyntax-splicing -- right. (list 'unsyntax (if (string=? str "'") (read) (string->symbol str))))) (cons #\& (lambda (str) ; ancient Guile code (make-keyword (substring str 1)))) (cons #\\ (lambda (str) (cond ((assoc str '(("\\x0" . #\null) ("\\x7" . #\alarm) ("\\x8" . #\backspace) ("\\x9" . #\tab) ("\\xd" . #\return) ("\\xa" . #\newline) ("\\1b" . #\escape) ("\\x20" . #\space) ("\\x7f" . #\delete))) => (lambda (c) (format outport "~NC#\\~A is ~W~%" lint-left-margin #\space (substring str 1) (cdr c))))) #f)) (cons #\! (lambda (str) (if (member str '("!optional" "!default" "!rest" "!key" "!aux" "!false" "!true" "!r6rs") string-ci=?) ; for MIT-scheme (make-keyword (substring str 1)) (let ((lc (str 0))) ; s7 should handle this, but... (do ((c (read-char) (read-char))) ((or (and (eof-object? c) (or (format outport "~NCunclosed block comment~%" lint-left-margin #\space) #t)) (and (char=? lc #\!) (char=? c #\#))) #f) (set! lc c)))))))) ;; try to get past all the # and \ stuff in other Schemes ;; main remaining problem: [] used as parentheses (Gauche and Chicken for example) (set! (hook-functions *read-error-hook*) (list (lambda (h) (let ((data (h 'data)) (line (port-line-number))) (if (not (h 'type)) (begin (format outport "~NCreader[~A]: unknown \\ usage: \\~C~%" lint-left-margin #\space line data) (set! (h 'result) data)) (begin (format outport "~NCreader[~A]: unknown # object: #~A~%" lint-left-margin #\space line data) (set! (h 'result) (catch #t (lambda () (case (data 0) ((#\;) (read) (values)) ((#\T) (string=? data "T")) ((#\F) (and (string=? data "F") ''#f)) ((#\X #\B #\O #\D) (let ((num (string->number (substring data 1) (case (data 0) ((#\X) 16) ((#\O) 8) ((#\B) 2) ((#\D) 10))))) (if (number? num) (begin (format outport "~NCuse #~A~A not #~A~%" lint-left-margin #\space (char-downcase (data 0)) (substring data 1) data) num) (string->symbol data)))) ((#\l #\z) (let ((num (string->number (substring data 1)))) ; Bigloo (also has #ex #lx #z and on and on) (if (number? num) (begin (format outport "~NCjust omit this silly #~C!~%" lint-left-margin #\space (data 0)) num) (string->symbol data)))) ((#\u) ; for Bigloo (if (string=? data "unspecified") (format outport "~NCuse #, not #unspecified~%" lint-left-margin #\space)) ;; # seems to hit the no-values check? (string->symbol data)) ;; Bigloo also seems to use #" for here-doc concatenation?? ((#\v) ; r6rs byte-vectors? (if (string=? data "vu8") (format outport "~NCuse #u8 in s7, not #vu8~%" lint-left-margin #\space)) (string->symbol data)) ((#\>) ; for Chicken, apparently #>...<# encloses in-place C code (do ((last #\#) (c (read-char) (read-char))) ((and (char=? last #\<) (char=? c #\#)) (values)) (if (char=? c #\newline) (set! (port-line-number ()) (+ (port-line-number) 1))) (set! last c))) ((#\<) ; Chicken also, #< EOF (if (and (char=? (data 1) #\<) (> (length data) 2)) (do ((end (substring data 2)) (c (read-line) (read-line))) ((string-position end c) (values))) (string->symbol data))) ((#\\) (cond ((assoc data '(("\\newline" . #\newline) ("\\return" . #\return) ("\\space" . #\space) ("\\tab" . #\tab) ("\\null" . #\null) ("\\nul" . #\null) ("\\linefeed" . #\linefeed) ("\\alarm" . #\alarm) ("\\esc" . #\escape) ("\\escape" . #\escape) ("\\rubout" . #\delete) ("\\delete" . #\delete) ("\\backspace" . #\backspace) ("\\page" . #\xc) ("\\altmode" . #\escape) ("\\bel" . #\alarm) ; #\x07 ("\\sub" . #\x1a) ("\\soh" . #\x01) ;; these are for Guile ("\\vt" . #\xb) ("\\bs" . #\backspace) ("\\cr" . #\newline) ("\\sp" . #\space) ("\\lf" . #\linefeed) ("\\nl" . #\null) ("\\ht" . #\tab) ("\\ff" . #\xc) ("\\np" . #\xc)) string-ci=?) => (lambda (c) (format outport "~NCperhaps use ~W instead~%" (+ lint-left-margin 4) #\space (cdr c)) (cdr c))) (else (string->symbol (substring data 1))))) (else (string->symbol data)))) (lambda args #f))))))))) ;; preset list-tail and list-ref (hash-table-set! (fragments 10) '((if (zero? _2_) _1_ (_F_ (cdr _1_) (- _2_ 1)))) (vector 0 () (list (cons 'list-tail (inlet :initial-value '(define (list-tail x k) (if (zero? k) x (list-tail (cdr x) (- k 1)))) :arglist '(x k) :history :built-in))) '(define (list-tail x k) (if (zero? k) x (list-tail (cdr x) (- k 1)))) #f)) (hash-table-set! (fragments 12) '((if (= _2_ 0) (car _1_) (_F_ (cdr _1_) (- _2_ 1)))) (vector 0 () (list (cons 'list-ref (inlet :initial-value '(define (list-ref items n) (if (= n 0) (car items) (list-ref (cdr items) (- n 1)))) :arglist '(items n) :history :built-in))) '(define (list-ref items n) (if (= n 0) (car items) (list-ref (cdr items) (- n 1)))) #f)) ;; -------- call lint -------- (let ((vars (lint-file file ()))) (set! lint-left-margin (max lint-left-margin 1)) (when (pair? vars) (if *report-multiply-defined-top-level-functions* (for-each (lambda (var) (let ((var-file (hash-table-ref *top-level-objects* (car var)))) (if (not var-file) (hash-table-set! *top-level-objects* (car var) *current-file*) (if (and (string? *current-file*) (not (string=? var-file *current-file*))) (format outport "~NC~S is defined at the top level in ~S and ~S~%" lint-left-margin #\space (car var) var-file *current-file*))))) vars)) (if (string? file) (report-usage top-level: "" vars vars)))) (for-each (lambda (p) (if (or (> (cdr p) 5) (and (> (cdr p) 3) (> (length (car p)) 12))) (format outport "~A~A occurs ~D times~%" (if (pair? (car p)) "'" "") (truncated-list->string (car p)) (cdr p)))) big-constants) (if (and *report-undefined-identifiers* (positive? (hash-table-entries other-identifiers))) (let ((lst (sort! (map car other-identifiers) (lambda (a b) (stringstring a) (symbol->string b)))))) (format outport "~NCth~A identifier~A not defined~A: ~{~S~^ ~}~%" lint-left-margin #\space (if (= (hash-table-entries other-identifiers) 1) (values "is" " was") (values "e following" "s were")) (if (string? file) (format #f " in ~S" file) "") lst) (fill! other-identifiers #f))))))) ;;; -------------------------------------------------------------------------------- ;;; this reads an HTML file, finds likely-looking scheme code, and runs lint over it. ;;; called on all snd files in hg.scm (define (html-lint file) (define (remove-markups str) (let ((tpos (string-position "" str))) (if tpos (let ((epos (string-position "" str))) (remove-markups (string-append (substring str 0 tpos) (substring str (+ tpos 3) epos) (substring str (+ epos 4))))) (let ((apos (string-position " str (+ pos 1)) 1)) (epos (string-position (if (and apos (= pos apos)) "" "") str bpos))) (string-append (substring str 0 pos) (substring str bpos epos) (remove-markups (substring str (+ epos (if (and apos (= apos pos)) 4 5))))))))))) (define (fixup-html str) (let ((pos (char-position #\& str))) (if (not pos) str (string-append (substring str 0 pos) (let* ((epos (char-position #\; str pos)) (substr (substring str (+ pos 1) epos))) (string-append (cond ((assoc substr '(("gt" . ">") ("lt" . "<") ("mdash" . "-") ("amp" . "&")) string=?) => cdr) (else (format #t "unknown: ~A~%" substr))) (fixup-html (substring str (+ epos 1))))))))) (call-with-input-file file (lambda (f) (do ((line-num 0 (+ line-num 1)) (line (read-line f #t) (read-line f #t))) ((eof-object? line)) ;; look for
	;;   decide if it is scheme code (first char is #\()
	;;   if so, clean out html markup stuff, call lint on that
	
	(let ((pos (string-position " line) 1))))
	      (do ((cline (read-line f #t) (read-line f #t))
		   (rline 1 (+ rline 1)))
		  ((string-position "
" cline) (set! line-num (+ line-num rline))) (set! code (string-append code cline))) ;; is first non-whitespace char #\(? ignoring comments (do ((len (length code)) (i 0 (+ i 1))) ((>= i len)) (let ((c (string-ref code i))) (unless (char-whitespace? c) (if (char=? c #\;) (set! i (char-position #\newline code i)) (begin (set! i (+ len 1)) (when (char=? c #\() (catch #t (lambda () (let ((outstr (call-with-output-string (lambda (op) (call-with-input-string (object->string (with-input-from-string (fixup-html (remove-markups code)) read) #t) ; write, not display (lambda (ip) (let-temporarily ((*report-shadowed-variables* #t)) (lint ip op #f)))))))) (if (> (length outstr) 1) ; possible newline at end (format () ";~A ~D: ~A~%" file line-num outstr)))) (lambda args (format () ";~A ~D, error in read: ~A ~A~%" file line-num args (fixup-html (remove-markups code)))))))))))))))))) ;;; -------------------------------------------------------------------------------- ;;; and this reads C code looking for s7_eval_c_string. No attempt here to ;;; handle weird cases. (define (C-lint file) (call-with-input-file file (lambda (f) (do ((line-num 0 (+ line-num 1)) (line (read-line f #t) (read-line f #t))) ((eof-object? line)) ;; look for s7_eval_c_string, get string arg without backslashes, call lint (let ((pos (string-position "s7_eval_c_string(sc, \"(" line))) (when pos (let ((code (substring line (+ pos (length "s7_eval_c_string(sc, \""))))) (if (not (string-position "\");" code)) (do ((cline (read-line f #t) (read-line f #t)) (rline 1 (+ rline 1))) ((string-position "\");" cline) (set! code (string-append code cline)) (set! line-num (+ line-num rline))) (set! code (string-append code cline)))) (let ((len (string-position "\");" code))) (set! code (substring code 0 len)) ;; clean out backslashes (do ((i 0 (+ i 1))) ((>= i (- len 3))) (cond ((not (char=? (code i) #\\))) ((char=? (code (+ i 1)) #\n) (set! (code i) #\space) (set! (code (+ i 1)) #\space)) ((memv (code (+ i 1)) '(#\newline #\")) (set! (code i) #\space)) ((and (char=? (code (+ i 1)) #\\) (char=? (code (- i 1)) #\#)) (set! (code (- i 1)) #\space) (set! (code i) #\#))))) (catch #t (lambda () (let ((outstr (call-with-output-string (lambda (op) (call-with-input-string code (lambda (ip) (let-temporarily ((*report-shadowed-variables* #t)) (lint ip op #f)))))))) (if (> (length outstr) 1) ; possible newline at end (format () ";~A ~D: ~A~%" file line-num outstr)))) (lambda args (format () ";~A ~D, error in read: ~A ~A~%" file line-num args code)))))))))) ;;; -------------------------------------------------------------------------------- #| ;;; external use of lint contents (see also snd-lint.scm): (for-each (lambda (f) (if (not (hash-table-ref (*lint* 'no-side-effect-functions) (car f))) (format *stderr* "~A " (car f)))) (*lint* 'built-in-functions)) ;;; get rid of []'s! (using Snd) (define (edit file) (let* ((str (file->string file)) (len (length str))) (do ((i 0 (+ i 1))) ((= i len)) (case (str i) ((#\]) (set! (str i) #\))) ((#\[) (set! (str i) #\()))) (call-with-output-file file (lambda (p) (display str p))) #f)) |# ;;; fragments: ;;; perhaps for fragment hash-ref (list fragment) to find function? ;;; and check leading cases for all bodies? -- would need to handle this in reduce-tree walker? ;;; need any-match arg nums (a 2nd level match) ;;; if 2-arg func, reversed -> nth for list-ref -- need reversal signal ;;; this is tricky (initial code in tmp) -- if recursive call, need args reversed so check shadowing etc ;;; if several fragments share the same code, report just the biggest, and maybe give the _n_ values for at least the example? ;;; maybe divide the trigger by the _n_ top? (need to save this number) ;;; ;;; blocks: ;;; reduce-dependencies -- look for blocks with restricted outer vars, make func and add to closure, check for func-reuse ;;; but this collides with current 1-call->embedded code in lint-walk-body unless we use the closure ;;; so... perhaps use out-vars to get names -- if < 5, func? (if any out-var set, quit) ;;; perhaps start with if branches, when/unless ;;; ;;; unused var search made smarter (in any body+locals) ;;; named-let + map init ->embed as in map+map [do does not happen usefully] ;;; where assumed , or where set to or assert and report violations [expr=pattern here] ;;; 184 25029 665340