#lang racket/base

;; Check that any-wrap/c handles all base types
;; We do this by:
;; - enumerating the base types from `base-env/base-types.rkt`
;; - for each type:
;;   - define a value
;;   - typecheck the value
;;   - wrap the value as 'Any', check failure / use in untyped code

(require
  (for-syntax racket/base)
  racket/require)

;; When #t, print a warning if some base-types do not have tests
(define-for-syntax WARN-MISSING #t)

;; `known-base-types` is a list of triples:
;;  [TYPE VALUE USE]
;; TYPE is a base type exported by Typed Racket
;; VALUE is an expression with type TYPE
;; USE is an untyped function of 1 argument, or #f.
;;  If a function, a test will apply (USE (contract VALUE any-wrap/c)).
;;  If #f, a test will assert that (contract VALUE any-wrap/c) fails.
;; These USE functions should exercise specific functionality
;;  and not be dummy functions like (lambda (x) x).
(define-for-syntax known-base-types '(
 ;; --- TODO missing value for these types
 ;[Read-Table (or (current-readtable) (error 'noo)) #f]
 ;[Internal-Definition-Context (syntax-local-make-definition-context) #f]
 ;[Place (place hi (void)) place-kill]

 ;; --- Types that should NOT be passes as 'Any' are marked with #f
 [Async-ChannelTop (make-async-channel) #f]
 [ClassTop object% #f]
 [Compiled-Expression (compile-syntax #'#t) #f]
 [Continuation-Mark-KeyTop (make-continuation-mark-key) #f]
 [Continuation-Mark-Set (current-continuation-marks) #f]
 [Custodian (current-custodian) #f]
 [Identifier (syntax exit) #f]
 [MPairTop (mcons 1 1) #f]
 [Namespace (make-empty-namespace) #f]
 [Parameterization (current-parameterization) #f]
 [Prompt-TagTop (make-continuation-prompt-tag) #f]
 [Security-Guard (current-security-guard) #f]
 [Special-Comment (make-special-comment 'hi) #f]
 [Struct-Type-Property (let-values ([(n g s) (make-struct-type-property 'foo)]) n) #f]
 [Syntax (syntax 'B) #f]
 [Syntax-E (syntax-e (syntax 'A)) #f]
 [Thread-CellTop (make-thread-cell 'X) #f]
 [UnitTop (unit (import) (export)) #f]
 [Variable-Reference (let ([x 4]) (#%variable-reference x)) #f]
 [Weak-BoxTop (make-weak-box 3) #f]

 ;; -- Normal base types
 [Subprocess
  (let*-values ([(sp _out _in _err) (subprocess #f #f #f ".")])
    (close-output-port _in)
    (close-input-port _out)
    (close-input-port _err)
    sp)
  choice-evt]
 [Single-Flonum-Complex 1f0+1f0i add1]
 [ExtFlonum-Zero 0.0t0 extflround]
 [ExtFlonum-Negative-Zero -0.0t0 extflround]
 [ExtFlonum-Positive-Zero +0.0t0 extflround]
 [Complex 0 add1]
 [Number 0 add1]
 [Inexact-Complex (let ([n (exact->inexact 1/3+1/3i)]) (if (not (real? n)) n (error 'pr241 "Failed to make Inexact-Complex"))) zero?]
 [Float-Complex 1.0+1i add1]
 [Exact-Number 0 add1]
 [Real 0 add1]
 [Nonpositive-Real 0 add1]
 [Negative-Real -1 add1]
 [Nonnegative-Real 0 add1]
 [Positive-Real 1 add1]
 [Real-Zero 0 add1]
 [Inexact-Real (exact->inexact 1/3) add1]
 [Single-Flonum 1.0f0 add1]
 [Nonpositive-Inexact-Real (- (exact->inexact 1/3)) add1]
 [Nonpositive-Single-Flonum -1.0f0 add1]
 [Negative-Inexact-Real -1.0f0 add1]
 [Negative-Single-Flonum -1.0f0 add1]
 [Positive-Single-Flonum +1.0f0 add1]
 [Nonnegative-Inexact-Real (exact->inexact 1/3) add1]
 [Nonnegative-Single-Flonum 1.0f0 add1]
 [Positive-Inexact-Real 1.0f0 add1]
 [Inexact-Real-Nan +nan.0 zero?]
 [Inexact-Real-Zero 0.0 add1]
 [Inexact-Real-Negative-Zero -0.0 add1]
 [Inexact-Real-Positive-Zero 0.0 add1]
 [Single-Flonum-Nan +nan.f add1]
 [Single-Flonum-Zero 0f0 add1]
 [Single-Flonum-Negative-Zero -0f0 add1]
 [Single-Flonum-Positive-Zero 0f0 add1]
 [Float 1.0 add1]
 [Flonum 1.0 add1]
 [Nonpositive-Float -1.0 add1]
 [Nonpositive-Flonum -1.0 add1]
 [Negative-Float -1.0 add1]
 [Negative-Flonum -1.0 add1]
 [Nonnegative-Float 1.0 add1]
 [Nonnegative-Flonum 1.0 add1]
 [Positive-Float 1.0 add1]
 [Positive-Flonum 1.0 add1]
 [Float-Nan +nan.0 add1]
 [Flonum-Nan +nan.0 add1]
 [Float-Zero 0.0 add1]
 [Flonum-Zero 0.0 add1]
 [Float-Negative-Zero -0.0 add1]
 [Flonum-Negative-Zero -0.0 add1]
 [Float-Positive-Zero 0.0 add1]
 [Flonum-Positive-Zero 0.0 add1]
 [Exact-Rational 1/3 add1]
 [Nonpositive-Exact-Rational 0/1 add1]
 [Negative-Exact-Rational -3/2 add1]
 [Nonnegative-Exact-Rational 1 add1]
 [Positive-Exact-Rational 4/3 add1]
 [Integer 0 add1]
 [Nonpositive-Integer 0 add1]
 [Negative-Integer -2 add1]
 [Exact-Nonnegative-Integer 0 add1]
 [Nonnegative-Integer 0 add1]
 [Natural 62 add1]
 [Exact-Positive-Integer 6 add1]
 [Positive-Integer 6 add1]
 [Fixnum 9 add1]
 [Negative-Fixnum -3 add1]
 [Nonpositive-Fixnum -4 add1]
 [Nonnegative-Fixnum 4 add1]
 [Positive-Fixnum 2 add1]
 [Index 3 add1]
 [Positive-Index 3 add1]
 [Byte 0 add1]
 [Positive-Byte 1 add1]
 [Zero 0 add1]
 [One  1 add1]
 [ExtFlonum pi.t extflround]
 [Nonpositive-ExtFlonum (->extfl -1) extflround]
 [Negative-ExtFlonum (->extfl -1) extflround]
 [Nonnegative-ExtFlonum (->extfl 1) extflround]
 [Positive-ExtFlonum (->extfl 1) extflround]
 [ExtFlonum-Nan +nan.t (lambda (n) (extfl= n n))]

 [Any 'a boolean?]
 [Boolean #f not]
 [BoxTop (box 5) boolean?]
 [Byte-PRegexp (byte-pregexp #"\\d\\d") (lambda (p) (regexp-match? p "013a"))]
 [Byte-Regexp (byte-regexp #"hi$") (lambda (p) (regexp-match? p "hi"))]
 [Bytes #"hello" bytes-length]
 [Bytes-Converter (or (bytes-open-converter "UTF-8" "UTF-8") (error 'pr241 "Failed to make bytes converter")) bytes-close-converter]
 [ChannelTop (make-channel) channel-try-get]
 [Char #\space char->integer]
 [Datum 'A (lambda (x) (datum->syntax #f x))]
 [EOF eof eof-object?]
 [ExtFlVector (extflvector pi.t) extflvector-length]
 [FSemaphore (make-fsemaphore 0) fsemaphore-post]
 [False #f not]
 [FlVector (flvector 1.14 2.14 3.14) flvector-length]
 [FxVector (fxvector 1) fxvector-length]
 [HashTableTop (hash) (lambda (h) (hash-ref h 'a #f))]
 [Impersonator-Property (let-values ([(i i? i-val) (make-impersonator-property 'i)]) i) (lambda (i) (impersonate-procedure (lambda () (void)) #f i 2))]
 [Input-Port (current-input-port) port?]
 [Inspector (current-inspector) (lambda (i) (parameterize ([current-inspector i]) (void)))]
 [Keyword (string->keyword "hi") keyword->string]
 [Log-Level 'info symbol->string]
 [Log-Receiver (make-log-receiver (current-logger) 'info) choice-evt]
 [Logger (current-logger) (lambda (l) (log-level? l 'info))]
 [Module-Path "hello.rkt" module-path?]
 [Null '() length]
 [Output-Port (current-output-port) port?]
 [PRegexp #px"\\d\\d" (lambda (p) (regexp-match? p "013a"))]
 [Path (current-directory) path->string]
 [Path-For-Some-System (current-directory) path->string]
 [Path-String "foo/bar" relative-path?]
 [Place-Channel (let-values ([(p1 p2) (place-channel)]) p1) choice-evt]
 [Port (current-input-port) port?]
 [Pretty-Print-Style-Table (pretty-print-current-style-table) (lambda (x) (pretty-print-extend-style-table x '() '()))]
 [Procedure (lambda (x) x) (lambda (f) (procedure-arity-includes? f 1))]
 [Pseudo-Random-Generator (current-pseudo-random-generator) pseudo-random-generator->vector]
 [Regexp #rx"hi$" (lambda (p) (regexp-match? p "hi"))]
 [Resolved-Module-Path (make-resolved-module-path (current-directory)) resolved-module-path-name]
 [Semaphore (make-semaphore) semaphore-post]
 [Sexp (syntax->datum (syntax 'foo)) (lambda (x) x)]
 [String "yolo" string->symbol]
 [Symbol 'a symbol->string]
 [TCP-Listener (tcp-listen 0) choice-evt]
 [Thread (thread (lambda () (void))) choice-evt]
 [Thread-Group (current-thread-group) make-thread-group]
 [True #t not]
 [UDP-Socket (udp-open-socket) udp-close]
 [VectorTop (vector 1 2 3) (lambda (x) (vector-ref x 0))]
 [Void (void) void?]
 [Will-Executor (make-will-executor) choice-evt]
))

(define-values-for-syntax (base-untyped* base-typed*)
  (for/fold ([u* '()] [t* '()])
            ([tvf (in-list known-base-types)])
    (unless (and (list? tvf) (= 3 (length tvf)))
      (error 'pr241 "Expected (TYPE VAL FUN)"))
    (with-syntax ([type (car tvf)]
                  [val (cadr tvf)]
                  [use (caddr tvf)]
                  [pos-blame 'pr241-test]
                  [neg-blame 'known-base-types]
                  [id (gensym (car tvf))])
      ;; Wrap value in contract, pass it to `use` or assert a wrap failure
      ;; Ignore the result
      (define u-stx
        (if (eq? #f (caddr tvf))
          #'(with-handlers ([exn:fail:contract? (lambda (e) (void))])
              (contract any-wrap/c val 'pos-blame 'neg-blame)
              (error 'pr241 (format "Higher-Order value '~a' incorrectly allowed as Any" val)))
          #'((lambda any* (void))
             (use (contract any-wrap/c val 'pos-blame 'neg-blame)))))
      ;; Bind the value to a typed identifier
      (define t-stx
        #'(begin (: id type) (define id val)))
      (values
        (cons u-stx u*)
        (cons t-stx t*)))))

;; `known-polymorphic-types` is a list of 4-lists:
;;   [TYPE-TAG TYPE VALUE USE]
;; TYPE-TAG is a polymorphic type exported by Typed Racket
;; TYPE is an instantiation of TYPE-TAG
;; VALUE is an expression with type TYPE
;; USE is the same as for `known-base-types`
(define-for-syntax known-poly-types '(
  ;; --- Higher-Order polymorphic types
  [Async-Channelof (Async-Channelof Any) (make-async-channel) #f]
  [Continuation-Mark-Keyof (Continuation-Mark-Keyof Any) (make-continuation-mark-key 'X) #f]
  [Custodian-Boxof (Custodian-Boxof Integer) (make-custodian-box (current-custodian) 1) #f]
  [Ephemeronof (Ephemeronof Integer) (make-ephemeron 'key 4) #f]
  [Evtof (Evtof String) never-evt choice-evt]
  [Futureof (Futureof Integer) (future (lambda () 4)) #f]
  [MPairof (MPairof Integer String) (mcons 4 "ad") #f]
  [MListof (MListof Integer) (mcons 4 '()) #f]
  [Prompt-Tagof (Prompt-Tagof Any Any) (make-continuation-prompt-tag) #f]
  [Syntaxof (Syntaxof Integer) #'1 #f]
  [Thread-Cellof (Thread-Cellof Integer) (make-thread-cell 1) #f]
  [Weak-Boxof (Weak-Boxof Integer) (make-weak-box 1) #f]

  ;; -- Wrappable polymorphic types
  [Boxof (Boxof Integer) (box 3) (lambda (v) (add1 (unbox v)))]
  [Channelof (Channelof Integer) (make-channel) channel-try-get]
  [HashTable (HashTable Symbol String) (hash) (lambda (h) (hash-ref h 'a #f))]
  [Listof (Listof Integer) (list 1) (lambda (xs) (add1 (car xs)))]
  [Option (Option Integer) 1 add1]
  [Pair (Pair Integer Boolean) (cons 1 #t) (lambda (v) (add1 (car v)))]
  [Pairof (Pairof Integer Boolean) (cons 1 #f) (lambda (v) (add1 (car v)))]
  [Promise (Promise Integer) (delay 3) force]
  [Sequenceof (Sequenceof Natural) '(1 2 3) sequence->list]
  [Setof (Setof Integer) (set) set-empty?]
  [Sexpof (Sexpof Integer) (syntax->datum #'(1 2 3)) (lambda (xs) (add1 (car xs)))]
  [Vectorof (Vectorof Integer) (vector 1) (lambda (v) (add1 (vector-ref v 0)))]
))

;; We don't have values to test these types with, but trust they're wrapped correctly
(define-for-syntax whitelist '(
 (Undefined . #f)
 (Nothing . #f)
 (Struct-TypeTop . #f)
 (Module-Path-Index . #f)
 (Compiled-Module-Expression . #f)
 (Namespace-Anchor . #f)
))

(define-values-for-syntax (poly-untyped* poly-typed*)
  (for/fold ([u* '()] [t* '()])
            ([gtvf (in-list known-poly-types)])
    (unless (and (list? gtvf) (= 4 (length gtvf)))
      (error 'pr241 "Expected (TYPE-TAG TYPE VAL FUN)"))
    (with-syntax ([_tag (car gtvf)] ;; Unused here
                  [type (cadr gtvf)]
                  [val (caddr gtvf)]
                  [use (cadddr gtvf)]
                  [pos-blame 'pr241-test]
                  [neg-blame 'known-poly-types]
                  [id (gensym (car gtvf))])
      ;; Wrap value in contract, pass it to `use` or assert a wrap failure
      ;; Ignore the result
      (define u-stx
        (if (eq? #f (cadddr gtvf))
          #'(with-handlers ([exn:fail:contract? (lambda (e) (void))])
              (contract any-wrap/c val 'pos-blame 'neg-blame)
              (error 'pr241 (format "Higher-Order value '~a' incorrectly allowed as Any" val)))
          #'((lambda any* (void))
             (use (contract any-wrap/c val 'pos-blame 'neg-blame)))))
      ;; Bind the value to a typed identifier
      (define t-stx
        #'(begin (: id type) (define id val)))
      (values
        (cons u-stx u*)
        (cons t-stx t*)))))

(define-syntax (known-types->tests stx)
  ;; Put the `use-` list in an untyped module,
  ;;  put the `declare-` list in a typed module.
  #`(begin
      (module check-uses racket
       (require
         racket/async-channel
         racket/flonum
         racket/extflonum
         racket/fixnum
         typed-racket/utils/any-wrap)
       #,@base-untyped*
       #,@poly-untyped*
       (printf "Successfully used ~a wrapped values\n"
         #,(+ (length base-untyped*) (length poly-untyped*))))
      (module check-types typed/racket
        (require
          racket/flonum
          racket/fixnum
          racket/extflonum)
        (require/typed racket/base
          [variable-reference->module-path-index (-> Any Module-Path-Index)])
        (require/typed racket/async-channel
          [make-async-channel (-> (Async-Channelof Any))])
        #,@base-typed*
        #,@poly-typed*
        (printf "Successfully typechecked ~a identifiers\n"
          #,(+ (length base-typed*) (length poly-typed*))))))

(define-for-syntax (known-string? str)
  (define s (string->symbol str))
  (member* s known-base-types known-poly-types whitelist))

(define-for-syntax (member* s . x**)
  (for*/or ([x* (in-list x**)]
            [x (in-list x*)])
    (eq? s (car x))))

(require
  (filtered-in
    (if WARN-MISSING
      (lambda (str)
        (when (not (known-string? str)) (printf "WARNING: Missing test for base type '~a'\n" str))
        #f)
      (lambda (str) #f))
    typed-racket/base-env/base-types))

(known-types->tests)

(require
 'check-uses
 'check-types)
