[racket-dev] bug: internal typechecker error

From: Paulo J. Matos (paulo at matos-sorge.com)
Date: Mon Oct 22 16:43:15 EDT 2012

Hello,

As I was converting a program into typed racket in drscheme 5.3 I got an 
internal typechecker error. Load the attached file and press run.

That should allow you to reproduce the problem.

Cheers,
-- 
PMatos
-------------- next part --------------
#lang typed/racket

;    This program is free software: you can redistribute it and/or modify
;    it under the terms of the GNU General Public License as published by
;    the Free Software Foundation, either version 3 of the License, or
;    (at your option) any later version.
;
;    This program is distributed in the hope that it will be useful,
;    but WITHOUT ANY WARRANTY; without even the implied warranty of
;    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;    GNU General Public License for more details.
;
;    You should have received a copy of the GNU General Public License
;    along with this program.  If not, see <http://www.gnu.org/licenses/>.

(require file/md5)
(require (planet synx/stat))

(define *cache-file* (build-path (find-system-path 'home-dir) ".dupfi.cache"))
(define *cache* (make-parameter (make-hash)))
(define *cache-diff-max* 1000)

(struct: file
  ((name : String)
   (path : Path)
   (chksum : Bytes))
  #:transparent)

(struct: dir
  ((name : String)
   (path : Path)
   (contents : (Listof file)))
  #:transparent)

(define old-cache?
  (let ([cache-counter 0])
    (lambda ()
      (if (= cache-counter *cache-diff-max*)
          (begin
            (set! cache-counter 0)
            #t)
          (begin
            (set! cache-counter (+ cache-counter 1))
            #f)))))

(define (file< f1 f2)
  ; sort two files alphabetically
  (string<? (file-name f1) (file-name f2)))

(define (file= f1 f2)
  (and (file? f1) (file? f2)
       (string=? (file-name f1) (file-name f2))
       (bytes=? (file-chksum f1) (file-chksum f2))))

;;
;; Returns hash table
;; (<path> (<modtime> . <hash>))
;;
(define (read-cache)
  (*cache* (make-hash))
  (when (file-exists? *cache-file*)
    (call-with-input-file *cache-file*
      (lambda (in)
        (let: loop : Void ([line : String (read-line in)])
          (when (not (eof-object? line))
            (let ([split-str (string-split line ",")])
              (cond 
                [(not (= (length split-str) 3))
                 (printf "[1] read-cache fails, unexpected line in cache file: ~a, ignoring.~n" line)
                 (loop (read-line in))]
                [else 
                 (let ([path (first split-str)]
                       [modtime (string->number (string-trim (second split-str)))]
                       [md5 (string->bytes/utf-8 (string-trim (third split-str)))])
                   (cond 
                     [(or (not path) (not modtime) (not md5))
                      (printf "[2] read-cache fails, unexpected line in cache file: ~a, ignoring.~n" line)
                      (loop (read-line in))]
                     [(not modtime)
                      (error "fail")] ;; unreachable?
                     [else 
                      (hash-set! (*cache*) path (cons modtime md5))
                      (loop (read-line in))]))])))))
      #:mode 'text))
  (printf "cache has ~a entries~n" (hash-count (*cache*))))

(define (write-cache)
  (call-with-output-file *cache-file*
    (lambda (out)
      (hash-for-each (*cache*)
                     (lambda (key val)
                       (when (not (string? key))
                         (error "writing cache failed, key is not string: " key))
                       (when (not (number? (car val)))
                         (error "writing cache failed, modtime is not number: " (car val)))
                       (when (not (bytes? (cdr val)))
                         (error "writing cache failed, md5 is not bytes: " (cdr val)))
                       (fprintf out "~a, ~a, ~a~n" key (car val) (bytes->string/utf-8 (cdr val))))))
    #:mode 'text
    #:exists 'replace))

(define (dump-cache)
  (printf "CACHE DUMP:~n")
  (hash-for-each (*cache*)
   (lambda (key val)
     (let ([modtime (car val)]
           [md5 (cdr val)])
       (printf "~a: ~a,~a~n"
               key modtime md5))))
  (printf "CACHE DUMP DONE~n"))

(define (make-file name path)
  ; Checks if file is in cache, if it is and modification seconds are the same, returns cached file.
  ; Otherwise, it calculates its md5sum and adds file to cache.
  (let* ([fullpath (build-path path name)]
         [modtime (file-or-directory-modify-seconds fullpath)]
         [val (hash-ref (*cache*) (path->string fullpath) (lambda () #f))])
    (if (and val (= (car val) modtime))
        (begin
          (printf "found cache for file ~a~n" (path->string fullpath))
          (file name path (cdr val)))
        (let ([md5 (begin
                     (printf "computing md5 for ~a~n" fullpath)
                     (if (normal-file? (type-bits fullpath))
                       (call-with-input-file fullpath md5)
                       (string->bytes/utf-8 "0")))])
          (if (not val)
            (printf "file not in cache: ~a~n" (path->string fullpath))
            (printf "file is in cache but modify times are different: old ~a, new ~a~n" (car val) modtime))
              
          (when val
            (hash-remove! (*cache*) fullpath))
          (hash-set! (*cache*) (path->string fullpath) (cons modtime md5))
          (when (old-cache?)
            (printf "found old cache, WRITING CACHE~n")
            (write-cache))
          (file name path md5)))))
  
(define (chkfile p)
  ; path -> file
  ; given a path to a file that exists, it returns its structure
  (when (not (file-exists? p))
    (error 'chkfile "path ~a doesn't point to valid file" (path->string p)))
  
  (let-values ([(base name must-be-dir?) (split-path p)])
    (make-file (path->string name) base)))
    
(define (file-sig f)
  (bytes-append (string->bytes/utf-8 (string-append (file-name f) ":")) (file-chksum f)))

(define (dir< d1 d2)
  ; sort two directories alphabetically with the directories coming first
  (string<? (dir-name d1) (dir-name d2)))

(define (dir-contents< d1c d2c)
  (cond [(and (dir? d1c) (dir? d2c))
         (dir< d1c d2c)]
        [(and (file? d1c) (file? d2c))
         (file< d1c d2c)]
        [(and (file? d1c) (dir? d2c))
         #f]
        [(and (dir? d1c) (file? d2c))
         #t]))

(define (dir= d1 d2)
  ; two directories are equal if their names are equal and their contents are equal
  (and (dir? d1) (dir? d2)
       (string=? (dir-name d1) (dir-name d2))
       (= (length (dir-contents d1)) (length (dir-contents d2)))
       (andmap (lambda (d1i d2i)
                 (cond [(and (file? d1i) (file? d2i))
                        (file= d1i d2i)]
                       [(and (dir? d1i) (dir? d2i))
                        (dir= d1i d2i)]
                       [else #f]))
               (dir-contents d1) (dir-contents d2))))
                         
(define (chkdir p)
  ; path -> dir
  ; given a path to a dir that exists, it returns its structure
  (when (not (directory-exists? p))
    (error 'chkdir "path ~a doesn't point to a valid directory" (path->string p)))
  
  (let-values ([(base name must-be-dir?) (split-path p)])
    (dir (path->string name) base
              (sort (map (lambda (path)
                           (let ([path (build-path p path)])
                             (cond [(file-exists? path)
                                    (printf "checking file ~a~n" path)
                                    (chkfile path)]
                                   [(directory-exists? path)
                                    (printf "checking dir ~a~n" path)
                                    (chkdir path)])))
                         (filter (lambda (path) (not (link-exists? (build-path p path)))) (directory-list p)))
                    dir-contents<))))
    
(define (bytestring-xor* . bss)
  (define (bytestring-xor bs1 bs2)
    (let* ([bs1-lst (bytes->list bs1)]
           [bs2-lst (bytes->list bs2)]
           [bs1-len (length bs1-lst)]
           [bs2-len (length bs2-lst)]
           [diff (abs (- bs2-len bs1-len))])
      (list->bytes (map bitwise-xor
                        (if (> bs2-len bs1-len)
                            (append bs1-lst (build-list diff (lambda (x) 0)))
                            bs1-lst)
                        (if (> bs1-len bs2-len)
                            (append bs2-lst (build-list diff (lambda (x) 0)))
                            bs2-lst)))))
  (cond [(null? bss)
         (error 'bytestring-xor* "needs at least 1 argument")]
        [(null? (cdr bss))
         (car bss)]
        [else
         (bytestring-xor (car bss) (apply bytestring-xor* (cdr bss)))]))
      
         
(define (dir-sig d)
  ; the signature of a directory is the xor of all its contents, together with its name preppended
  (let ([sigs (map (lambda (i)
                     (cond [(file? i)
                            (file-sig i)]
                           [(dir? i)
                            (dir-sig i)]))
                   (dir-contents d))])
    (if (null? sigs)
        (string->bytes/utf-8 (dir-name d))
        (bytes-append (string->bytes/utf-8 (string-append (dir-name d) "@"))
                      (apply bytestring-xor* sigs)))))

(define (item-sig i)
  (if (file? i)
      (file-sig i)
      (dir-sig i)))

(define (partition-items i)
  (define (flatten-item i) 
    ; return a list of dir and of all its content objects
    (printf "f")
    (if (file? i)
        (list i)
        (cons i (append-map flatten-item (dir-contents i)))))
  
  (define (group-equal groups rem)
    (printf "g")
    (if (null? rem)
        groups
        (let* ([item (caar rem)]
               [sig (cdar rem)]
               [rst (cdr rem)])
          (let-values ([(item-dup others) (partition (lambda (i/sig) (bytes=? sig (cdr i/sig)))
                                                     rst)])
            (let ([dupitems (map car item-dup)])
              (if (null? dupitems)
                  (group-equal groups others)
                  (group-equal (cons (cons item dupitems) groups)
                               others)))))))
  (printf "Partitioning items")
  (let* ([flat (begin (printf "Flattening~n") (flatten-item i))]
         [sigs (begin (printf "Sigs~n") (map item-sig flat))]
         [flat (map cons flat sigs)])
    (group-equal '() flat)))

(define (simplify-groups groups)
  (printf "Simplifying groups~n")
  ; Given a list of groups, it ensures the groups are as simple as possible.
  ; It follows the following rules:
  (sort groups (lambda (a b)
                 (cond [(and (dir? (car a)) (dir? (car b)))
                        (< (apply min (map (compose length explode-path dir-path) a))
                           (apply min (map (compose length explode-path dir-path) b)))]
                       [else 
                        (and (dir? (car a)) (file? (car b)))]))))

(define (display-results groups)
  (printf "Displaying groups~n")
  (if (null? groups)
      (printf "no duplicate files~n")
      (for-each (lambda (group)
                  (printf "GROUP:~n")
                  (for-each (lambda (i)
                              (if (file? i)
                                  (printf "F ~a~a: ~a~n"
                                          (file-path i)
                                          (file-name i)
                                          (file-chksum i))
                                  (printf "D ~a~a~n"
                                          (dir-path i)
                                          (dir-name i))))
                            group)
                  (printf "--~n~n"))
                groups)))

(read-cache)

(current-command-line-arguments (vector "/home/pmatos/Desktop/pmatos"))
(display-results (simplify-groups (partition-items (chkdir (vector-ref (current-command-line-arguments) 0)))))

Posted on the dev mailing list.