racket/collects/scheme/private/contract-guts.ss
Stevie Strickland 4ac235f534 Trunk merging, taking care to integrate Robby's changes appropriately. Only
change from what he did was that I added a name arg to the verify/contract
macro (he already has coerce-contract take a name appropriately, so this
was very simple, yay!).

svn: r11737
2008-09-13 23:17:58 +00:00

517 lines
20 KiB
Scheme

#lang scheme/base
(require "contract-helpers.ss"
scheme/pretty)
(require (for-syntax scheme/base
"contract-helpers.ss"))
(provide raise-contract-error
guilty-party
exn:fail:contract2?
exn:fail:contract2-srclocs
contract-violation->string
coerce-contract
coerce-contracts
coerce-flat-contract
coerce-flat-contracts
coerce-contract/f
flat-contract?
flat-contract
flat-contract-predicate
flat-named-contract
build-flat-contract
build-compound-type-name
and/c
any/c
none/c
make-none/c
contract?
contract-name
contract-proc
make-proj-contract
contract-stronger?
contract-first-order-passes?
proj-prop proj-pred? proj-get
name-prop name-pred? name-get
stronger-prop stronger-pred? stronger-get
flat-prop flat-pred? flat-get
flat-proj
first-order-prop
first-order-get
;; for opters
check-flat-contract
check-flat-named-contract
any)
(define-syntax (any stx)
(raise-syntax-error 'any "use of 'any' outside of an arrow contract" stx))
(define-values (proj-prop proj-pred? raw-proj-get)
(make-struct-type-property 'contract-projection))
(define-values (name-prop name-pred? name-get)
(make-struct-type-property 'contract-name))
(define-values (stronger-prop stronger-pred? stronger-get)
(make-struct-type-property 'contract-stronger-than))
(define-values (flat-prop flat-pred? flat-get)
(make-struct-type-property 'contract-flat))
(define-values (first-order-prop first-order-pred? first-order-get)
(make-struct-type-property 'contract-first-order))
(define (contract-first-order-passes? c v)
(let ([ctc (coerce-contract 'contract-first-order-passes? c)])
(cond
[(first-order-pred? ctc) (((first-order-get ctc) ctc) v)]
[(flat-pred? c) (((flat-get c) c) v)]
[else #t])))
(define (proj-get ctc)
(cond
[(proj-pred? ctc)
(raw-proj-get ctc)]
[else (error 'proj-get "unknown ~e" ctc)]))
;; contract-stronger? : contract contract -> boolean
;; indicates if one contract is stronger (ie, likes fewer values) than another
;; this is not a total order.
(define (contract-stronger? a b)
(let ([a-ctc (coerce-contract 'contract-stronger? a)]
[b-ctc (coerce-contract 'contract-stronger? b)])
((stronger-get a-ctc) a-ctc b-ctc)))
;; coerce-flat-contract : symbol any/c -> contract
(define (coerce-flat-contract name x)
(let ([ctc (coerce-contract/f x)])
(unless (flat-pred? ctc)
(error name
"expected a flat contract or a value that can be coerced into one, got ~e"
x))
ctc))
;; coerce-flat-contacts : symbol (listof any/c) -> (listof flat-contract)
;; like coerce-contracts, but insists on flat-contracts
(define (coerce-flat-contracts name xs)
(let loop ([xs xs]
[i 1])
(cond
[(null? xs) '()]
[else
(let ([fst (coerce-contract/f (car xs))])
(unless (flat-pred? fst)
(error name
"expected all of the arguments to be flat contracts, but argument ~a was not, got ~e"
i
(car xs)))
(cons fst (loop (cdr xs) (+ i 1))))])))
;; coerce-contract : symbol any/c -> contract
(define (coerce-contract name x)
(or (coerce-contract/f x)
(error name
"expected contract or a value that can be coerced into one, got ~e"
x)))
;; coerce-contracts : symbols (listof any) -> (listof contract)
;; turns all of the arguments in 'xs' into contracts
;; the error messages assume that the function named by 'name'
;; got 'xs' as it argument directly
(define (coerce-contracts name xs)
(let loop ([xs xs]
[i 1])
(cond
[(null? xs) '()]
[(coerce-contract/f (car xs)) => (λ (x) (cons x (loop (cdr xs) (+ i 1))))]
[else
(error name
"expected all of the arguments to be contracts, but argument ~a was not, got ~e"
i
(car xs))])))
;; coerce-contract/f : any -> (or/c #f contract?)
;; returns #f if the argument could not be coerced to a contract
(define (coerce-contract/f x)
(cond
[(contract? x) x]
[(and (procedure? x) (procedure-arity-includes? x 1))
(make-predicate-contract (or (object-name x) '???) x)]
[(or (symbol? x) (boolean? x) (char? x)) (make-eq-contract x)]
[(or (bytes? x) (string? x)) (make-equal-contract x)]
[(number? x) (make-=-contract x)]
[(or (regexp? x) (byte-regexp? x)) (make-regexp/c x)]
[else #f]))
(define-values (make-exn:fail:contract2
exn:fail:contract2?
exn:fail:contract2-srclocs
guilty-party)
(let-values ([(exn:fail:contract2
make-exn:fail:contract2
exn:fail:contract2?
get
set)
(parameterize ([current-inspector (make-inspector)])
(make-struct-type 'exn:fail:contract2
struct:exn:fail:contract
2
0
#f
(list (cons prop:exn:srclocs
(lambda (x)
(exn:fail:contract2-srclocs x))))))])
(values
make-exn:fail:contract2
exn:fail:contract2?
(lambda (x) (get x 0))
(lambda (x) (get x 1)))))
(define (default-contract-violation->string val src-info to-blame contract-sexp msg)
(let ([blame-src (src-info-as-string src-info)]
[formatted-contract-sexp
(let ([one-line
(let ([sp (open-output-string)])
(parameterize ([pretty-print-print-line print-contract-liner]
[pretty-print-columns 'infinity])
(pretty-print contract-sexp sp)
(get-output-string sp)))])
(if (< (string-length one-line) 30)
one-line
(let ([sp (open-output-string)])
(newline sp)
(parameterize ([pretty-print-print-line print-contract-liner]
[pretty-print-columns 50])
(pretty-print contract-sexp sp))
(get-output-string sp))))]
[specific-blame
(cond
[(syntax? src-info)
(let ([datum (syntax->datum src-info)])
(if (symbol? datum)
(format " on ~a" datum)
""))]
[(pair? src-info)
(format " on ~a" (list-ref src-info 1))]
[else ""])])
(string-append (format "~a~a broke the contract ~a~a; "
blame-src
(cond
[(not to-blame) "<<unknown>>"]
[(and (pair? to-blame)
(pair? (cdr to-blame))
(null? (cddr to-blame))
(equal? 'quote (car to-blame)))
(format "module '~s" (cadr to-blame))]
[(string? to-blame) to-blame]
[else (format "module ~s" to-blame)])
formatted-contract-sexp
specific-blame)
msg)))
(define contract-violation->string (make-parameter default-contract-violation->string))
(define (raise-contract-error val src-info blame contract-sexp fmt . args)
(raise
(make-exn:fail:contract2
(string->immutable-string
((contract-violation->string) val
src-info
blame
contract-sexp
(apply format fmt args)))
(current-continuation-marks)
(cond
[(syntax? src-info)
(list (make-srcloc
(syntax-source src-info)
(syntax-line src-info)
(syntax-column src-info)
(syntax-position src-info)
(syntax-span src-info)))]
[(srcloc? src-info) (list src-info)]
[else '()])
blame)))
(define print-contract-liner
(let ([default (pretty-print-print-line)])
(λ (line port ol cols)
(+ (default line port ol cols)
(if line
(begin (display " " port)
2)
0)))))
;; src-info-as-string : (union srcloc syntax #f) -> string
(define (src-info-as-string src-info)
(if (or (syntax? src-info)
(srcloc? src-info))
(let ([src-loc-str (build-src-loc-string src-info)])
(if src-loc-str
(string-append src-loc-str ": ")
""))
""))
;
;
;
;
;
; ; ;
; ;;; ;;; ; ;; ;;;; ; ; ;;; ;;; ;;;;
; ; ; ; ; ;; ; ; ;; ; ; ; ; ;
; ; ; ; ; ; ; ; ; ; ;
; ; ; ; ; ; ; ; ;;;; ; ;
; ; ; ; ; ; ; ; ; ; ; ;
; ; ; ; ; ; ; ; ; ; ; ; ; ;
; ;;; ;;; ; ; ;; ; ;;;;; ;;; ;;
;
;
;
;; contract = (make-contract sexp
;; (sym
;; sym
;; (union syntax #f)
;; string
;; ->
;; (alpha -> alpha)))
;; the first arg to make-contract builds the name of the contract. The
;; path records how the violation occurs
;;
;; generic contract container;
;; the first arg to proc is a symbol representing the name of the positive blame
;; the second arg to proc is the symbol representing the name of the negative blame
;; the third argument to proc is the src-info.
;; the fourth argumet is a textual representation of the original contract
;;
;; the argument to the result function is the value to test.
;; (the result function is the projection)
;;
(define (flat-proj ctc)
(let ([pred? ((flat-get ctc) ctc)])
(λ (pos neg src-info orig-str)
(λ (val)
(if (pred? val)
val
(raise-contract-error
val
src-info
pos
orig-str
"expected <~a>, given: ~e"
((name-get ctc) ctc)
val))))))
(define (double-any-curried-proj ctc) double-any-curred-proj2)
(define (double-any-curred-proj2 pos-blame neg-blame src-info orig-str) values)
(define-values (make-proj-contract)
(let ()
(define-struct proj-contract (the-name proj first-order-proc)
#:property proj-prop
(λ (ctc) (proj-contract-proj ctc))
#:property name-prop
(λ (ctc) (proj-contract-the-name ctc))
#:property first-order-prop
(λ (ctc) (or (proj-contract-first-order-proc ctc)
(λ (x) #t)))
#:property stronger-prop
(λ (this that)
(and (proj-contract? that)
(procedure-closure-contents-eq?
(proj-contract-proj this)
(proj-contract-proj that)))))
(values make-proj-contract)))
(define (flat-contract-predicate x)
(let ([ctc (coerce-flat-contract 'flat-contract-predicate x)])
((flat-get ctc) ctc)))
(define (flat-contract? x)
(let ([c (coerce-contract/f x)])
(and c
(flat-pred? c))))
(define (contract-name ctc)
(let ([ctc (coerce-contract 'contract-name ctc)])
((name-get ctc) ctc)))
(define (contract? x) (proj-pred? x))
(define (contract-proc ctc) ((proj-get ctc) ctc))
(define (check-flat-contract predicate) (coerce-flat-contract 'flat-contract predicate))
(define (flat-contract predicate) (coerce-flat-contract 'flat-contract predicate))
(define (check-flat-named-contract predicate) (coerce-flat-contract 'flat-named-contract predicate))
(define (flat-named-contract name predicate)
(coerce-flat-contract 'flat-named-contract predicate)
(make-predicate-contract name predicate))
;; build-compound-type-name : (union contract symbol) ... -> (-> sexp)
(define (build-compound-type-name . fs)
(let loop ([subs fs])
(cond
[(null? subs)
'()]
[else (let ([sub (car subs)])
(cond
[(contract? sub)
(let ([mk-sub-name (contract-name sub)])
`(,mk-sub-name ,@(loop (cdr subs))))]
[else `(,sub ,@(loop (cdr subs)))]))])))
(define (and-proj ctc)
(let ([mk-pos-projs (map (λ (x) ((proj-get x) x)) (and/c-ctcs ctc))])
(lambda (pos neg src-info orig-str)
(let ([projs (map (λ (c) (c pos neg src-info orig-str)) mk-pos-projs)])
(let loop ([projs (cdr projs)]
[proj (car projs)])
(cond
[(null? projs) proj]
[else (loop (cdr projs)
(let ([f (car projs)])
(λ (v) (proj (f v)))))]))))))
(define-struct and/c (ctcs)
#:omit-define-syntaxes
#:property proj-prop and-proj
#:property name-prop (λ (ctc) (apply build-compound-type-name 'and/c (and/c-ctcs ctc)))
#:property first-order-prop
(λ (ctc)
(let ([tests (map (λ (x) ((first-order-get x) x)) (and/c-ctcs ctc))])
(λ (x)
(andmap (λ (f) (f x)) tests))))
#:property stronger-prop
(λ (this that)
(and (and/c? that)
(let ([this-ctcs (and/c-ctcs this)]
[that-ctcs (and/c-ctcs that)])
(and (= (length this-ctcs) (length that-ctcs))
(andmap contract-stronger?
this-ctcs
that-ctcs))))))
(define (and/c . raw-fs)
(let ([contracts (coerce-contracts 'and/c raw-fs)])
(cond
[(null? contracts) any/c]
[(andmap flat-contract? contracts)
(let* ([pred
(let loop ([pred (flat-contract-predicate (car contracts))]
[preds (cdr contracts)])
(cond
[(null? preds) pred]
[else
(let* ([fst (flat-contract-predicate (car preds))])
(loop (let ([and/c-contract? (lambda (x) (and (pred x) (fst x)))])
and/c-contract?)
(cdr preds)))]))])
(flat-named-contract (apply build-compound-type-name 'and/c contracts) pred))]
[else (make-and/c contracts)])))
(define-struct any/c ()
#:omit-define-syntaxes
#:property proj-prop double-any-curried-proj
#:property stronger-prop (λ (this that) (any/c? that))
#:property name-prop (λ (ctc) 'any/c)
#:property first-order-prop (λ (ctc) (λ (val) #t))
#:property flat-prop (λ (ctc) (λ (x) #t)))
(define any/c (make-any/c))
(define (none-curried-proj ctc)
(λ (pos-blame neg-blame src-info orig-str)
(λ (val)
(raise-contract-error
val
src-info
pos-blame
orig-str
"~s accepts no values, given: ~e"
(none/c-name ctc)
val))))
(define-struct none/c (name)
#:omit-define-syntaxes
#:property proj-prop none-curried-proj
#:property stronger-prop (λ (this that) #t)
#:property name-prop (λ (ctc) (none/c-name ctc))
#:property first-order-prop (λ (ctc) (λ (val) #f))
#:property flat-prop (λ (ctc) (λ (x) #f)))
(define none/c (make-none/c 'none/c))
;
;
;
;
; ; ;;; ; ;
; ;;; ;;; ;;;
; ;;;;; ;;;;; ;;; ;;; ;; ;;; ;;; ;;; ;;; ;;; ;;; ;; ;;;;; ;;; ;;;;;;; ;;; ;;;;; ;;;;
; ;;;;;;;;;;;; ;;;;; ;;;;;;;;;;; ;;; ;;;;; ;;;;; ;;;;; ;;;;;;;;;;;; ;;;;;;;;;;;; ;;;;; ;;;;; ;;; ;;
; ;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;; ;;; ;; ;;; ;;; ;;; ;;; ;;; ;;; ;; ;;; ;;; ;; ;;; ;;;
; ;;;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;;;; ;;; ;;; ;;;;
; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;; ;;; ;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;; ;;; ;;;
; ;;; ;;; ;;;; ;;;;; ;;; ;;; ;;; ;;; ;;;;; ;;;;; ;;;;; ;;; ;;; ;;;; ;;; ;;; ;;; ;;;;; ;;;; ;; ;;;
; ;;;;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;; ;;;;;; ;;; ;;; ;;;;
;
;
;
;
(define-struct eq-contract (val)
#:property proj-prop flat-proj
#:property flat-prop (λ (ctc) (λ (x) (eq? (eq-contract-val ctc) x)))
#:property name-prop (λ (ctc)
(if (symbol? (eq-contract-val ctc))
`',(eq-contract-val ctc)
(eq-contract-val ctc)))
#:property stronger-prop (λ (this that) (and (eq-contract? that) (eq? (eq-contract-val this) (eq-contract-val that)))))
(define-struct equal-contract (val)
#:property proj-prop flat-proj
#:property flat-prop (λ (ctc) (λ (x) (equal? (equal-contract-val ctc) x)))
#:property name-prop (λ (ctc) (equal-contract-val ctc))
#:property stronger-prop (λ (this that) (and (equal-contract? that) (equal? (equal-contract-val this) (equal-contract-val that)))))
(define-struct =-contract (val)
#:property proj-prop flat-proj
#:property flat-prop (λ (ctc) (λ (x) (and (number? x) (= (=-contract-val ctc) x))))
#:property name-prop (λ (ctc) (=-contract-val ctc))
#:property stronger-prop (λ (this that) (and (=-contract? that) (= (=-contract-val this) (=-contract-val that)))))
(define-struct regexp/c (reg)
#:property proj-prop flat-proj
#:property flat-prop (λ (ctc) (λ (x) (and (or (string? x) (bytes? x))
(regexp-match (regexp/c-reg ctc) x)
#t)))
#:property name-prop (λ (ctc) (regexp/c-reg ctc))
#:property stronger-prop (λ (this that) (and (regexp/c? that) (eq? (regexp/c-reg this) (regexp/c-reg that)))))
(define-struct predicate-contract (name pred)
#:property proj-prop flat-proj
#:property stronger-prop
(λ (this that)
(and (predicate-contract? that)
(procedure-closure-contents-eq? (predicate-contract-pred this)
(predicate-contract-pred that))))
#:property name-prop (λ (ctc) (predicate-contract-name ctc))
#:property flat-prop (λ (ctc) (predicate-contract-pred ctc)))
(define (build-flat-contract name pred) (make-predicate-contract name pred))