Refactoring scheme/private/contract into reasonably sized pieces.

svn: r16049
This commit is contained in:
Stevie Strickland 2009-09-17 19:45:56 +00:00
parent aca0bcf82e
commit 70e8f21ba2
10 changed files with 2657 additions and 2645 deletions

View File

@ -29,10 +29,9 @@
;; except the arrow contracts
;;
(require (except-in scheme/private/contract-base
define/contract
with-contract
define-struct/contract)
(require scheme/private/contract-base
scheme/private/contract-misc
scheme/private/contract-provide
scheme/private/contract-guts
scheme/private/contract-ds
scheme/private/contract-opt
@ -43,7 +42,9 @@
(except-out (all-from-out scheme/private/contract-ds)
lazy-depth-to-look)
(except-out (all-from-out scheme/private/contract-base)
(all-from-out scheme/private/contract-base)
(all-from-out scheme/private/contract-provide)
(except-out (all-from-out scheme/private/contract-misc)
check-between/c
string-len/c
check-unary-between/c)

View File

@ -9,8 +9,12 @@ differences from v3:
|#
(require "private/contract-base.ss"
"private/contract-arrow.ss"
(require "private/contract-arrow.ss"
"private/contract-base.ss"
"private/contract-exists.ss"
"private/contract-misc.ss"
"private/contract-provide.ss"
"private/contract-regions.ss"
"private/contract-guts.ss"
"private/contract-ds.ss"
"private/contract-opt.ss"
@ -26,10 +30,13 @@ differences from v3:
procedure-accepts-and-more?
check-procedure
check-procedure/more)
(except-out (all-from-out "private/contract-base.ss")
?
(except-out (all-from-out "private/contract-exists.ss") ?)
(except-out (all-from-out "private/contract-misc.ss")
check-between/c
check-unary-between/c))
check-unary-between/c)
(all-from-out "private/contract-regions.ss")
(all-from-out "private/contract-provide.ss")
(all-from-out "private/contract-base.ss"))
;; from contract-guts.ss

View File

@ -1,6 +1,6 @@
#lang scheme
(require "../private/contract-base.ss")
(require "../private/contract-exists.ss")
;; this code builds the list of predicates (in case it changes, this may need to be re-run)
#;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
#lang scheme/base
(require "contract-guts.ss")
(provide new-/c
?)
(define (-proj ctc)
(let ([in (/c-in ctc)]
[out (/c-out ctc)]
[pred? (/c-pred? ctc)])
(λ (pos-blame neg-blame src-info orig-str positive-position?)
(if positive-position?
in
(λ (val)
(if (pred? val)
(out val)
(raise-contract-error val src-info pos-blame orig-str
"non-polymorphic value: ~e"
val)))))))
(define-struct /c (in out pred? name)
#:omit-define-syntaxes
#:property proj-prop -proj
#:property name-prop (λ (ctc) (/c-name ctc))
#:property first-order-prop
(λ (ctc) (λ (x) #t)) ;; ???
#:property stronger-prop
(λ (this that) #f))
(define-struct ())
(define (new-/c raw-name)
(define name (string->symbol (format "~a/∃" raw-name)))
(define-values (struct-type constructor predicate accessor mutator)
(make-struct-type name struct: 1 0))
(make-/c constructor (λ (x) (accessor x 0)) predicate raw-name))

View File

@ -3,14 +3,30 @@
(provide unpack-blame build-src-loc-string
mangle-id mangle-id-for-maker
build-struct-names
lookup-struct-info
nums-up-to
add-name-prop
all-but-last
known-good-contract?)
(require setup/main-collects
scheme/struct-info
(for-template scheme/base))
;; lookup-struct-info : syntax -> (union #f (list syntax syntax (listof syntax) ...))
(define (lookup-struct-info stx provide-stx)
(let ([id (syntax-case stx ()
[(a b) (syntax a)]
[_ stx])])
(let ([v (syntax-local-value id (λ () #f))])
(if (struct-info? v)
(extract-struct-info v)
(raise-syntax-error 'provide/contract
"expected a struct name"
provide-stx
id)))))
(define (add-name-prop name stx)
(cond
[(identifier? name)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,708 @@
#lang scheme/base
(provide provide/contract)
(require (for-syntax scheme/base
scheme/list
(prefix-in a: "contract-helpers.ss"))
"contract-arrow.ss"
"contract-base.ss"
"contract-exists.ss"
"contract-guts.ss")
(define-syntax (verify-contract stx)
(syntax-case stx ()
[(_ name x) (a:known-good-contract? #'x) #'x]
[(_ name x) #'(coerce-contract name x)]))
;; id->contract-src-info : identifier -> syntax
;; constructs the last argument to the -contract, given an identifier
(define-for-syntax (id->contract-src-info id)
#`(list (make-srcloc #,id
#,(syntax-line id)
#,(syntax-column id)
#,(syntax-position id)
#,(syntax-span id))
#,(format "~s" (syntax->datum id))))
(define-for-syntax (make-provide/contract-transformer contract-id id pos-module-source)
(make-set!-transformer
(let ([saved-id-table (make-hasheq)])
(λ (stx)
(if (eq? 'expression (syntax-local-context))
;; In an expression context:
(let ([key (syntax-local-lift-context)])
;; Already lifted in this lifting context?
(let ([lifted-id
(or (hash-ref saved-id-table key #f)
;; No: lift the contract creation:
(with-syntax ([contract-id contract-id]
[id id]
[pos-module-source pos-module-source]
[id-ref (syntax-case stx (set!)
[(set! whatever e)
id] ;; just avoid an error here, signal the error later
[(id . x)
#'id]
[id
(identifier? #'id)
#'id])])
(syntax-local-introduce
(syntax-local-lift-expression
#`(contract contract-id
id
pos-module-source
(#%variable-reference)
#,(id->contract-src-info #'id))))))])
(when key
(hash-set! saved-id-table key lifted-id))
;; Expand to a use of the lifted expression:
(with-syntax ([saved-id (syntax-local-introduce lifted-id)])
(syntax-case stx (set!)
[name
(identifier? (syntax name))
(syntax saved-id)]
[(set! id arg)
(raise-syntax-error 'provide/contract
"cannot set! a provide/contract variable"
stx
(syntax id))]
[(name . more)
(with-syntax ([app (datum->syntax stx '#%app)])
(syntax/loc stx (app saved-id . more)))]))))
;; In case of partial expansion for module-level and internal-defn contexts,
;; delay expansion until it's a good time to lift expressions:
(quasisyntax/loc stx (#%expression #,stx)))))))
(define-syntax (provide/contract provide-stx)
(syntax-case provide-stx (struct)
[(_ p/c-ele ...)
(let ()
;; ids : table[id -o> (listof id)]
;; code-for-each-clause adds identifiers to this map.
;; when it binds things; they are then used to signal
;; a syntax error for duplicates
(define dups-table (make-hash))
(define (add-to-dups-table id)
(hash-update!
dups-table
(syntax-e id)
(λ (ids) (cons id ids))
'()))
(define (signal-dup-syntax-error)
(hash-for-each
dups-table
(λ (k ids)
(let loop ([ids ids])
(cond
[(null? ids) (void)]
[else
(cond
[(ormap (λ (x) (bound-identifier=? (car ids) x)) (cdr ids))
(let ([dups (filter (λ (x) (bound-identifier=? (car ids) x))
ids)])
(raise-syntax-error 'provide/contract
"duplicate identifiers"
provide-stx
(car dups)
(cdr dups)))]
[else
(loop (cdr ids))])])))))
;; code-for-each-clause : (listof syntax) -> (listof syntax)
;; constructs code for each clause of a provide/contract
(define (code-for-each-clause clauses)
(let loop ([clauses clauses]
[exists-binders '()])
(cond
[(null? clauses) null]
[else
(let ([clause (car clauses)])
;; compare raw identifiers for `struct' and `rename' just like provide does
(syntax-case* clause (struct rename) (λ (x y) (eq? (syntax-e x) (syntax-e y)))
[exists
(or (eq? '#:exists (syntax-e #'exists)) (eq? '#: (syntax-e #'exists)))
(cond
[(null? (cdr clauses))
(raise-syntax-error 'provide/conract
(format "expected either a single variable or a sequence of variables to follow ~a, but found nothing"
(syntax-e #'exists))
provide-stx
clause)]
[else
(syntax-case (cadr clauses) ()
[x
(identifier? #'x)
(with-syntax ([(x-gen) (generate-temporaries #'(x))])
(cons (code-for-one-exists-id #'x #'x-gen)
(loop (cddr clauses)
(add-a-binder #'x #'x-gen exists-binders))))]
[(x ...)
(andmap identifier? (syntax->list #'(x ...)))
(with-syntax ([(x-gen ...) (generate-temporaries #'(x ...))])
(append (map code-for-one-exists-id
(syntax->list #'(x ...))
(syntax->list #'(x-gen ...)))
(loop (cddr clauses)
(let loop ([binders exists-binders]
[xs (syntax->list #'(x ...))]
[x-gens (syntax->list #'(x-gen ...))])
(cond
[(null? xs) binders]
[else (loop (add-a-binder (car xs) (car x-gens) binders)
(cdr xs)
(cdr x-gens))])))))]
[else
(raise-syntax-error 'provide/contract
(format "expected either a single variable or a sequence of variables to follow ~a"
(syntax-e #'exists))
provide-stx
(cadr clauses))])])]
[(rename this-name new-name contract)
(and (identifier? (syntax this-name))
(identifier? (syntax new-name)))
(begin
(add-to-dups-table #'new-name)
(cons (code-for-one-id provide-stx
(syntax this-name)
(add-exists-binders (syntax contract) exists-binders)
(syntax new-name))
(loop (cdr clauses) exists-binders)))]
[(rename this-name new-name contract)
(identifier? (syntax this-name))
(raise-syntax-error 'provide/contract
"malformed rename clause, expected an identifier"
provide-stx
(syntax new-name))]
[(rename this-name new-name contract)
(identifier? (syntax new-name))
(raise-syntax-error 'provide/contract
"malformed rename clause, expected an identifier"
provide-stx
(syntax this-name))]
[(rename . _)
(raise-syntax-error 'provide/contract "malformed rename clause" provide-stx clause)]
[(struct struct-name ((field-name contract) ...))
(and (well-formed-struct-name? (syntax struct-name))
(andmap identifier? (syntax->list (syntax (field-name ...)))))
(let ([sc (build-struct-code provide-stx
(syntax struct-name)
(syntax->list (syntax (field-name ...)))
(map (λ (x) (add-exists-binders x exists-binders))
(syntax->list (syntax (contract ...)))))])
(add-to-dups-table #'struct-name)
(cons sc (loop (cdr clauses) exists-binders)))]
[(struct name)
(identifier? (syntax name))
(raise-syntax-error 'provide/contract
"missing fields"
provide-stx
clause)]
[(struct name . rest)
(not (well-formed-struct-name? (syntax name)))
(raise-syntax-error 'provide/contract "name must be an identifier or two identifiers with parens around them"
provide-stx
(syntax name))]
[(struct name (fields ...))
(for-each (λ (field)
(syntax-case field ()
[(x y)
(identifier? (syntax x))
(void)]
[(x y)
(raise-syntax-error 'provide/contract
"malformed struct field, expected identifier"
provide-stx
(syntax x))]
[else
(raise-syntax-error 'provide/contract
"malformed struct field"
provide-stx
field)]))
(syntax->list (syntax (fields ...))))
;; if we didn't find a bad field something is wrong!
(raise-syntax-error 'provide/contract "internal error" provide-stx clause)]
[(struct name . fields)
(raise-syntax-error 'provide/contract
"malformed struct fields"
provide-stx
clause)]
[(name contract)
(identifier? (syntax name))
(begin
(add-to-dups-table #'name)
(cons (code-for-one-id provide-stx
(syntax name)
(add-exists-binders (syntax contract)
exists-binders)
#f)
(loop (cdr clauses) exists-binders)))]
[(name contract)
(raise-syntax-error 'provide/contract
"expected identifier"
provide-stx
(syntax name))]
[unk
(raise-syntax-error 'provide/contract
"malformed clause"
provide-stx
(syntax unk))]))])))
;; well-formed-struct-name? : syntax -> bool
(define (well-formed-struct-name? stx)
(or (identifier? stx)
(syntax-case stx ()
[(name super)
(and (identifier? (syntax name))
(identifier? (syntax super)))
#t]
[else #f])))
;; build-struct-code : syntax syntax (listof syntax) (listof syntax) -> syntax
;; constructs the code for a struct clause
;; first arg is the original syntax object, for source locations
(define (build-struct-code stx struct-name-position field-names field-contracts)
(let* ([struct-name (syntax-case struct-name-position ()
[(a b) (syntax a)]
[else struct-name-position])]
[super-id (syntax-case struct-name-position ()
[(a b) (syntax b)]
[else #t])]
[all-parent-struct-count/names (get-field-counts/struct-names struct-name provide-stx)]
[parent-struct-count (if (null? all-parent-struct-count/names)
#f
(let ([pp (cdr all-parent-struct-count/names)])
(if (null? pp)
#f
(car (car pp)))))]
[struct-info (a:lookup-struct-info struct-name-position provide-stx)]
[constructor-id (list-ref struct-info 1)]
[predicate-id (list-ref struct-info 2)]
[selector-ids (reverse (list-ref struct-info 3))]
[is-id-ok?
(λ (id i)
(if (or (not parent-struct-count)
(parent-struct-count . <= . i))
id
#t))]
[mutator-ids (reverse (list-ref struct-info 4))] ;; (listof (union #f identifier))
[field-contract-ids (map (λ (field-name field-contract)
(if (a:known-good-contract? field-contract)
field-contract
(a:mangle-id provide-stx
"provide/contract-field-contract"
field-name
struct-name)))
field-names
field-contracts)]
[struct:struct-name
(datum->syntax
struct-name
(string->symbol
(string-append
"struct:"
(symbol->string (syntax-e struct-name)))))]
[-struct:struct-name
(datum->syntax
struct-name
(string->symbol
(string-append
"-struct:"
(symbol->string (syntax-e struct-name)))))]
[is-new-id?
(λ (index)
(or (not parent-struct-count)
(parent-struct-count . <= . index)))])
(let ([unknown-info
(λ (what names)
(raise-syntax-error
'provide/contract
(format "cannot determine ~a, found ~s" what names)
provide-stx
struct-name))])
(unless (or (null? selector-ids)
(identifier? (last selector-ids)))
(unknown-info "the selectors" (map syntax->datum selector-ids)))
(unless constructor-id (unknown-info "constructor" constructor-id))
(unless predicate-id (unknown-info "predicate" predicate-id))
(unless (andmap/count is-id-ok? selector-ids)
(unknown-info "selectors"
(map (λ (x) (if (syntax? x)
(syntax->datum x)
x))
selector-ids))))
(unless (equal? (length selector-ids)
(length field-contract-ids))
(raise-syntax-error 'provide/contract
(format "found ~a field~a in struct, but ~a contract~a"
(length selector-ids)
(if (= 1 (length selector-ids)) "" "s")
(length field-contract-ids)
(if (= 1 (length field-contract-ids)) "" "s"))
provide-stx
struct-name))
;; make sure the field names are right.
(let* ([relative-counts (let loop ([c (map car all-parent-struct-count/names)])
(cond
[(null? c) null]
[(null? (cdr c)) c]
[else (cons (- (car c) (cadr c))
(loop (cdr c)))]))]
[names (map cdr all-parent-struct-count/names)]
[maker-name (format "~a" (syntax-e constructor-id))]
[struct-name (substring maker-name 5 (string-length maker-name))])
(let loop ([count (car relative-counts)]
[name (car names)]
[counts (cdr relative-counts)]
[names (cdr names)]
[selector-strs (reverse (map (λ (x) (format "~a" (syntax-e x))) selector-ids))]
[field-names (reverse field-names)])
(cond
[(or (null? selector-strs) (null? field-names))
(void)]
[(zero? count)
(loop (car counts) (car names) (cdr counts) (cdr names)
selector-strs
field-names)]
[else
(let* ([selector-str (car selector-strs)]
[field-name (car field-names)]
[field-name-should-be
(substring selector-str
(+ (string-length name) 1)
(string-length selector-str))]
[field-name-is (format "~a" (syntax-e field-name))])
(unless (equal? field-name-should-be field-name-is)
(raise-syntax-error 'provide/contract
(format "expected field name to be ~a, but found ~a"
field-name-should-be
field-name-is)
provide-stx
field-name))
(loop (- count 1)
name
counts
names
(cdr selector-strs)
(cdr field-names)))])))
(with-syntax ([((selector-codes selector-new-names) ...)
(filter
(λ (x) x)
(map/count (λ (selector-id field-contract-id index)
(if (is-new-id? index)
(code-for-one-id/new-name
stx
selector-id
(build-selector-contract struct-name
predicate-id
field-contract-id)
#f)
#f))
selector-ids
field-contract-ids))]
[(rev-selector-old-names ...)
(reverse
(filter
(λ (x) x)
(map/count (λ (selector-id index)
(if (not (is-new-id? index))
selector-id
#f))
selector-ids)))]
[(mutator-codes/mutator-new-names ...)
(map/count (λ (mutator-id field-contract-id index)
(if (and mutator-id (is-new-id? index))
(code-for-one-id/new-name stx
mutator-id
(build-mutator-contract struct-name
predicate-id
field-contract-id)
#f)
#f))
mutator-ids
field-contract-ids)]
[(predicate-code predicate-new-name)
(code-for-one-id/new-name stx predicate-id (syntax (-> any/c boolean?)) #f)]
[(constructor-code constructor-new-name)
(code-for-one-id/new-name
stx
constructor-id
(build-constructor-contract stx
field-contract-ids
predicate-id)
#f
#t)]
[(field-contract-id-definitions ...)
(filter values (map (λ (field-contract-id field-contract)
(if (a:known-good-contract? field-contract)
#f
(with-syntax ([field-contract-id field-contract-id]
[field-contract field-contract])
#'(define field-contract-id (verify-contract 'provide/contract field-contract)))))
field-contract-ids
field-contracts))]
[(field-contracts ...) field-contracts]
[(field-contract-ids ...) field-contract-ids])
(with-syntax ([((mutator-codes mutator-new-names) ...)
(filter syntax-e (syntax->list #'(mutator-codes/mutator-new-names ...)))])
(with-syntax ([(rev-selector-new-names ...) (reverse (syntax->list (syntax (selector-new-names ...))))]
[(rev-mutator-new-names ...) (reverse (syntax->list (syntax (mutator-new-names ...))))])
(with-syntax ([struct-code
(with-syntax ([id-rename (a:mangle-id provide-stx
"provide/contract-struct-expandsion-info-id"
struct-name)]
[struct-name struct-name]
[-struct:struct-name -struct:struct-name]
[super-id (if (boolean? super-id)
super-id
(with-syntax ([super-id super-id])
(syntax ((syntax-local-certifier) #'super-id))))]
[(mutator-id-info ...)
(map (λ (x)
(syntax-case x ()
[(a b) #'(slc #'b)]
[else #f]))
(syntax->list #'(mutator-codes/mutator-new-names ...)))]
[(exported-selector-ids ...) (reverse selector-ids)]
)
#`(begin
(provide (rename-out [id-rename struct-name]))
(define-syntax id-rename
(let ([slc (syntax-local-certifier)])
#;
(list (slc #'-struct:struct-name)
(slc #'#,constructor-id)
(slc #'#,predicate-id)
(list (slc #'exported-selector-ids) ...)
(list mutator-id-info ...)
super-id)
(list (slc #'-struct:struct-name)
(slc #'constructor-new-name)
(slc #'predicate-new-name)
(list (slc #'rev-selector-new-names) ...
(slc #'rev-selector-old-names) ...)
(list mutator-id-info ...)
super-id)))))]
[struct:struct-name struct:struct-name]
[-struct:struct-name -struct:struct-name]
[struct-name struct-name]
[(selector-ids ...) selector-ids])
(syntax/loc stx
(begin
struct-code
field-contract-id-definitions ...
selector-codes ...
mutator-codes ...
predicate-code
constructor-code
;; expanding out the body of the `make-pc-struct-type' function
;; directly here in the expansion makes this very expensive at compile time
;; when there are a lot of provide/contract clause using structs
(define -struct:struct-name
(make-pc-struct-type 'struct-name struct:struct-name field-contract-ids ...))
(provide (rename-out [-struct:struct-name struct:struct-name]))))))))))
(define (map/count f . ls)
(let loop ([ls ls]
[i 0])
(cond
[(andmap null? ls) '()]
[(ormap null? ls) (error 'map/count "mismatched lists")]
[else (cons (apply f (append (map car ls) (list i)))
(loop (map cdr ls)
(+ i 1)))])))
;; andmap/count : (X Y int -> Z) (listof X) (listof Y) -> (listof Z)
(define (andmap/count f l1)
(let loop ([l1 l1]
[i 0])
(cond
[(null? l1) #t]
[else (and (f (car l1) i)
(loop (cdr l1)
(+ i 1)))])))
;; get-field-counts/struct-names : syntax syntax -> (listof (cons symbol number))
;; returns a list of numbers corresponding to the numbers of fields for each of the parent structs
(define (get-field-counts/struct-names struct-name provide-stx)
(let loop ([parent-info-id struct-name])
(let ([parent-info
(and (identifier? parent-info-id)
(a:lookup-struct-info parent-info-id provide-stx))])
(cond
[(boolean? parent-info) null]
[else
(let ([fields (list-ref parent-info 3)]
[constructor (list-ref parent-info 1)])
(cond
[(and (not (null? fields))
(not (last fields)))
(raise-syntax-error
'provide/contract
"cannot determine the number of fields in super struct"
provide-stx
struct-name)]
[else
(cons (cons (length fields) (constructor->struct-name provide-stx constructor))
(loop (list-ref parent-info 5)))]))]))))
(define (constructor->struct-name orig-stx stx)
(and stx
(let ([m (regexp-match #rx"^make-(.*)$" (format "~a" (syntax-e stx)))])
(cond
[m (cadr m)]
[else (raise-syntax-error 'contract-base.ss
"unable to cope with a struct maker whose name doesn't begin with `make-'"
orig-stx)]))))
;; build-constructor-contract : syntax (listof syntax) syntax -> syntax
(define (build-constructor-contract stx field-contract-ids predicate-id)
(with-syntax ([(field-contract-ids ...) field-contract-ids]
[predicate-id predicate-id])
(syntax/loc stx
(-> field-contract-ids ...
predicate-id))))
;; build-selector-contract : syntax syntax -> syntax
;; constructs the contract for a selector
(define (build-selector-contract struct-name predicate-id field-contract-id)
(with-syntax ([field-contract-id field-contract-id]
[predicate-id predicate-id])
(syntax (-> predicate-id field-contract-id))))
;; build-mutator-contract : syntax syntax -> syntax
;; constructs the contract for a selector
(define (build-mutator-contract struct-name predicate-id field-contract-id)
(with-syntax ([field-contract-id field-contract-id]
[predicate-id predicate-id])
(syntax (-> predicate-id
field-contract-id
void?))))
;; code-for-one-exists-id : syntax -> syntax
(define (code-for-one-exists-id x x-gen)
#`(define #,x-gen (new-/c '#,x)))
(define (add-exists-binders stx exists-binders)
#`(let #,exists-binders #,stx))
(define (add-a-binder id id-gen binders)
(cons #`[#,id #,id-gen] binders))
;; code-for-one-id : syntax syntax syntax (union syntax #f) -> syntax
;; given the syntax for an identifier and a contract,
;; builds a begin expression for the entire contract and provide
;; the first syntax object is used for source locations
(define (code-for-one-id stx id ctrct user-rename-id)
(with-syntax ([(code id) (code-for-one-id/new-name stx id ctrct user-rename-id)])
(syntax code)))
;; code-for-one-id/new-name : syntax syntax syntax (union syntax #f) -> (values syntax syntax)
;; given the syntax for an identifier and a contract,
;; builds a begin expression for the entire contract and provide
;; the first syntax object is used for source locations
(define code-for-one-id/new-name
(case-lambda
[(stx id ctrct user-rename-id)
(code-for-one-id/new-name stx id ctrct user-rename-id #f)]
[(stx id ctrct user-rename-id mangle-for-maker?)
(let ([no-need-to-check-ctrct? (a:known-good-contract? ctrct)])
(with-syntax ([id-rename ((if mangle-for-maker?
a:mangle-id-for-maker
a:mangle-id)
provide-stx
"provide/contract-id"
(or user-rename-id id))]
[contract-id (if no-need-to-check-ctrct?
ctrct
(a:mangle-id provide-stx
"provide/contract-contract-id"
(or user-rename-id id)))]
[pos-module-source (a:mangle-id provide-stx
"provide/contract-pos-module-source"
(or user-rename-id id))]
[pos-stx (datum->syntax id 'here)]
[id id]
[ctrct (syntax-property ctrct 'inferred-name id)]
[external-name (or user-rename-id id)]
[where-stx stx])
(with-syntax ([extra-test
(syntax-case #'ctrct (->)
[(-> dom ... arg)
#`(and (procedure? id)
(procedure-arity-includes? id #,(length (syntax->list #'(dom ...)))))]
[_ #f])])
(with-syntax ([code
(quasisyntax/loc stx
(begin
(define pos-module-source (#%variable-reference))
#,@(if no-need-to-check-ctrct?
(list)
(list #'(define contract-id
(let ([id ctrct]) ;; let is here to give the right name.
(verify-contract 'provide/contract id)))))
(define-syntax id-rename
(make-provide/contract-transformer (quote-syntax contract-id)
(quote-syntax id)
(quote-syntax pos-module-source)))
(provide (rename-out [id-rename external-name]))))])
(syntax-local-lift-module-end-declaration
#`(begin
(unless extra-test
(contract contract-id id pos-module-source 'ignored #,(id->contract-src-info #'id)))
(void)))
(syntax (code id-rename))))))]))
(with-syntax ([(bodies ...) (code-for-each-clause (syntax->list (syntax (p/c-ele ...))))])
(signal-dup-syntax-error)
(syntax
(begin
bodies ...))))]))
(define (make-pc-struct-type struct-name struct:struct-name . ctcs)
(let-values ([(struct:struct-name _make _pred _get _set)
(make-struct-type struct-name
struct:struct-name
0 ;; init
0 ;; auto
#f ;; auto-v
'() ;; props
#f ;; inspector
#f ;; proc-spec
'() ;; immutable-k-list
(λ args
(let ([vals (let loop ([args args])
(cond
[(null? args) null]
[(null? (cdr args)) null]
[else (cons (car args) (loop (cdr args)))]))])
(apply values
(map (λ (ctc val)
(contract ctc
val
'not-enough-info-for-blame
'not-enough-info-for-blame))
ctcs
vals)))))])
struct:struct-name))

View File

@ -0,0 +1,614 @@
#lang scheme/base
(provide define-struct/contract
define/contract
with-contract)
(require (for-syntax scheme/base
scheme/list
scheme/struct-info
syntax/define
syntax/kerncase
(prefix-in a: "contract-helpers.ss"))
scheme/splicing
"contract-arrow.ss"
"contract-base.ss"
"contract-guts.ss")
;; These are useful for all below.
(define-syntax (verify-contract stx)
(syntax-case stx ()
[(_ name x) (a:known-good-contract? #'x) #'x]
[(_ name x) #'(coerce-contract name x)]))
;; id->contract-src-info : identifier -> syntax
;; constructs the last argument to the -contract, given an identifier
(define-for-syntax (id->contract-src-info id)
#`(list (make-srcloc #,id
#,(syntax-line id)
#,(syntax-column id)
#,(syntax-position id)
#,(syntax-span id))
#,(format "~s" (syntax->datum id))))
;
;
;
; ; ;;; ; ;
; ; ; ;
; ; ; ; ; ;
; ;; ; ;;; ;;;; ; ; ;; ;;; ; ;;; ;;; ; ;; ;;;; ; ; ;;; ;;; ;;;;
; ; ;; ; ; ; ; ;; ; ; ; ; ; ; ; ; ;; ; ; ;; ; ; ; ; ;
; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
; ; ; ;;;;;; ; ; ; ; ;;;;;; ; ; ; ; ; ; ; ; ;;;; ; ;
; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
; ;; ; ;;;; ; ; ; ; ;;;; ; ;;; ;;; ; ; ;; ; ;;;;; ;;; ;;
; ;
; ;
;
;; (define/contract id contract expr)
;; defines `id' with `contract'; initially binding
;; it to the result of `expr'. These variables may not be set!'d.
(define-syntax (define/contract define-stx)
(when (eq? (syntax-local-context) 'expression)
(raise-syntax-error 'define/contract
"used in expression context"
define-stx))
(syntax-case define-stx ()
[(_ name)
(raise-syntax-error 'define/contract
"no contract or body"
define-stx)]
[(_ name contract-expr)
(raise-syntax-error 'define/contract
"expected a contract expression and a definition body, but found only one expression"
define-stx)]
[(_ name+arg-list contract #:freevars args . body)
(identifier? #'args)
(raise-syntax-error 'define/contract
"expected list of identifier/contract pairs"
#'args)]
[(_ name+arg-list contract #:freevars (arg ...) #:freevar x c . body)
(syntax/loc define-stx
(define/contract name+arg-list contract #:freevars (arg ... [x c]) . body))]
[(_ name+arg-list contract #:freevar x c . body)
(syntax/loc define-stx
(define/contract name+arg-list contract #:freevars () #:freevar x c . body))]
[(_ name+arg-list contract #:freevars args body0 body ...)
(begin
(when (and (identifier? #'name+arg-list)
(not (null? (syntax->list #'(body ...)))))
(raise-syntax-error 'define/contract
"multiple expressions after identifier and contract"
#'(body ...)))
(let-values ([(name body-expr)
(if (identifier? #'name+arg-list)
(values #'name+arg-list #'body0)
(normalize-definition
(datum->syntax #'define-stx (list* 'define/contract #'name+arg-list
#'body0 #'(body ...)))
#'lambda #t #t))])
(with-syntax ([name name]
[body-expr body-expr]
[type (if (identifier? #'name+arg-list) 'definition 'function)])
(syntax/loc define-stx
(with-contract #:type type name
([name contract])
#:freevars args
(define name body-expr))))))]
[(_ name+arg-list contract body0 body ...)
(syntax/loc define-stx
(define/contract name+arg-list contract #:freevars () body0 body ...))]))
(define-syntax (define-struct/contract stx)
(define-struct field-info (stx ctc [mutable? #:mutable] auto?))
(define-struct s-info (auto-value-stx transparent? def-stxs? def-vals?))
(define syntax-error
(lambda v
(apply raise-syntax-error 'define-struct/contract v)))
(define (build-struct-names name field-infos)
(let ([name-str (symbol->string (syntax-case name ()
[id (identifier? #'id)
(syntax-e #'id)]
[(sub super)
(syntax-e #'sub)]))])
(list*
(syntax-case name ()
[id (identifier? #'id) #'id]
[(sub super) #'sub])
(datum->syntax
name
(string->symbol
(string-append "struct:" name-str)))
(datum->syntax
name
(string->symbol
(string-append "make-" name-str)))
(datum->syntax
name
(string->symbol
(string-append name-str "?")))
(apply append
(for/list ([finfo field-infos])
(let ([field-str (symbol->string (syntax-e (field-info-stx finfo)))])
(cons (datum->syntax
name
(string->symbol
(string-append name-str "-" field-str)))
(if (field-info-mutable? finfo)
(list (datum->syntax
name
(string->symbol
(string-append "set-" name-str "-" field-str "!"))))
null))))))))
(define (build-contracts stx pred field-infos)
(list* (syntax/loc stx any/c)
(syntax/loc stx any/c)
(apply append
(for/list ([finfo field-infos])
(let ([field-ctc (field-info-ctc finfo)])
(cons (quasisyntax/loc stx
(-> #,pred #,field-ctc))
(if (field-info-mutable? finfo)
(list
(quasisyntax/loc stx
(-> #,pred #,field-ctc void?)))
null)))))))
(define (check-field f ctc)
(let ([p-list (syntax->list f)])
(if p-list
(begin
(when (null? p-list)
(syntax-error "expected struct field" f))
(unless (identifier? (car p-list))
(syntax-error "expected identifier" f))
(let loop ([rest (cdr p-list)]
[mutable? #f]
[auto? #f])
(if (null? rest)
(make-field-info (car p-list) ctc mutable? auto?)
(let ([elem (syntax-e (car rest))])
(if (keyword? elem)
(cond
[(eq? elem '#:mutable)
(begin (when mutable?
(syntax-error "redundant #:mutable"
(car rest)))
(loop (cdr rest) #t auto?))]
[(eq? elem '#:auto)
(begin (when auto?
(syntax-error "redundant #:mutable"
(car rest)))
(loop (cdr rest) mutable? #t))]
[else (syntax-error "expected #:mutable or #:auto"
(car rest))])
(syntax-error "expected #:mutable or #:auto"
(car rest)))))))
(if (identifier? f)
(make-field-info f ctc #f #f)
(syntax-error "expected struct field" f)))))
(define (check-kwds kwd-list field-infos)
(let loop ([kwds kwd-list]
[auto-value-stx #f]
[mutable? #f]
[transparent? #f]
[def-stxs? #t]
[def-vals? #t])
(if (null? kwds)
(make-s-info auto-value-stx transparent? def-stxs? def-vals?)
(let ([kwd (syntax-e (car kwds))])
(when (not (keyword? kwd))
(syntax-error "expected a keyword"
(car kwds)))
(cond
[(eq? kwd '#:auto-value)
(when (null? (cdr kwds))
(syntax-error "expected a following expression"
(car kwds)))
(loop (cddr kwds) (cadr kwds)
transparent? mutable? def-stxs? def-vals?)]
;; let arbitrary properties through
[(eq? kwd '#:property)
(when (null? (cdr kwds))
(syntax-error "expected a property"
(car kwds)))
(when (null? (cddr kwds))
(syntax-error "expected a value for the property"
(car kwds)))
(loop (cdddr kwds) auto-value-stx
mutable? transparent? def-stxs? def-vals?)]
[(eq? kwd '#:mutable)
(when mutable?
(syntax-error "redundant #:mutable"
(car kwds)))
(for ([finfo field-infos])
(set-field-info-mutable?! finfo #t))
(loop (cdr kwds) auto-value-stx
#t transparent? def-stxs? def-vals?)]
[(eq? kwd '#:transparent)
(when transparent?
(syntax-error "redundant #:transparent"
(car kwds)))
(loop (cdr kwds) auto-value-stx
mutable? #t def-stxs? def-vals?)]
[(eq? kwd '#:omit-define-syntaxes)
(when (not def-stxs?)
(syntax-error "redundant #:omit-define-syntaxes"
(car kwds)))
(loop (cdr kwds) auto-value-stx
transparent? mutable? #f def-vals?)]
[(eq? kwd '#:omit-define-values)
(when (not def-vals?)
(syntax-error "redundant #:omit-define-values"
(car kwds)))
(loop (cdr kwds) auto-value-stx
transparent? mutable? def-stxs? #f)]
[else (syntax-error "unexpected keyword"
(car kwds))])))))
(syntax-case stx ()
[(_ name ([field ctc] ...) kwds ...)
(let ([fields (syntax->list #'(field ...))])
(unless (or (identifier? #'name)
(syntax-case #'name ()
[(x y) (and (identifier? #'x)
(identifier? #'y))]
[_ #f]))
(syntax-error "expected identifier for struct name or a sub-type relationship (subtype supertype)"
#'name))
(let* ([field-infos (map check-field fields (syntax->list #'(ctc ...)))]
[sinfo (check-kwds (syntax->list #'(kwds ...)) field-infos)]
[names (build-struct-names #'name field-infos)]
[pred (cadddr names)]
[ctcs (build-contracts stx pred field-infos)]
[super-fields (syntax-case #'name ()
[(child parent)
(let ([v (syntax-local-value #'parent (lambda () #f))])
(unless (struct-info? v)
(raise-syntax-error #f "identifier is not bound to a structure type" stx #'parent))
(let ([v (extract-struct-info v)])
(cadddr v)))]
[else '()])])
(let-values ([(non-auto-fields auto-fields)
(let loop ([fields field-infos]
[nautos null]
[autos null])
(if (null? fields)
(values (reverse nautos)
(reverse autos))
(if (field-info-auto? (car fields))
(loop (cdr fields)
nautos
(cons (car fields) autos))
(if (null? autos)
(loop (cdr fields)
(cons (car fields) nautos)
autos)
(syntax-error "non-auto field after auto fields"
(field-info-stx (car fields)))))))])
(with-syntax ([ctc-bindings
(let ([val-bindings (if (s-info-def-vals? sinfo)
(cons (cadr names)
(map list (cddr names)
ctcs))
null)])
(if (s-info-def-stxs? sinfo)
(cons (car names) val-bindings)
val-bindings))]
[orig stx]
[struct-name (syntax-case #'name ()
[id (identifier? #'id) #'id]
[(id1 super) #'id1])]
[(auto-check ...)
(let* ([av-stx (if (s-info-auto-value-stx sinfo)
(s-info-auto-value-stx sinfo)
#'#f)]
[av-id (datum->syntax av-stx
(string->symbol
(string-append (syntax-case #'name ()
[id (identifier? #'id)
(symbol->string (syntax-e #'id))]
[(id1 super)
(symbol->string (syntax-e #'id1))])
":auto-value"))
av-stx)])
(for/list ([finfo auto-fields])
#`(let ([#,av-id #,av-stx])
(contract #,(field-info-ctc finfo)
#,av-id
'(struct name)
'cant-happen
#,(id->contract-src-info av-id)))))]
;; a list of variables, one for each super field
[(super-fields ...) (generate-temporaries super-fields)]
;; the contract for a super field is any/c becuase the
;; super constructor will have its own contract
[(super-contracts ...) (for/list ([i (in-list super-fields)])
(datum->syntax stx 'any/c))]
[(non-auto-contracts ...)
(map field-info-ctc
(filter (lambda (f)
(not (field-info-auto? f)))
field-infos))]
;; the make-foo function. this is used to make the contract
;; print the right name in the blame
[maker (caddr names)]
[(non-auto-name ...)
(map field-info-stx non-auto-fields)])
(syntax/loc stx
(begin
(define-values () (begin auto-check ... (values)))
(define (guard super-fields ... non-auto-name ... struct-name)
(values super-fields ... non-auto-name ...))
(define blame-id
(current-contract-region))
(with-contract #:type struct struct-name
ctc-bindings
(define-struct/derived orig name (field ...)
kwds ...
#:guard (contract (-> super-contracts ... non-auto-contracts ... symbol? any)
guard
(current-contract-region) blame-id
#'maker)))))))))]
[(_ name . bad-fields)
(identifier? #'name)
(syntax-error "expected a list of field name/contract pairs"
#'bad-fields)]
[(_ . body)
(syntax-error "expected a structure name"
#'body)]))
;
;
; ; ;
; ; ; ; ;
; ; ; ; ;
; ; ; ; ; ;;;; ; ;;; ;;; ;;; ; ;;; ;;;; ; ;; ;;;; ;;; ;;;;
; ; ; ; ; ; ;; ; ; ; ; ; ;; ; ; ;; ; ; ; ; ;
; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;; ; ;
; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;; ; ; ;
; ; ; ; ; ; ; ; ; ;;;; ; ; ; ; ; ; ; ; ; ; ;
; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ;
; ; ; ; ;;; ; ; ;;; ;;; ; ; ;;; ; ;;;; ; ;;; ;;;
;
;
;
(define-for-syntax (make-contracted-id-transformer id contract-stx pos-blame-id neg-blame-id)
(make-set!-transformer
(lambda (stx)
(syntax-case stx (set!)
[(set! id arg)
(raise-syntax-error 'with-contract
"cannot set! a contracted variable"
stx
(syntax id))]
[(f arg ...)
(quasisyntax/loc stx
((contract #,contract-stx
#,id
#,pos-blame-id
#,neg-blame-id
#,(id->contract-src-info id))
arg ...))]
[ident
(identifier? (syntax ident))
(quasisyntax/loc stx
(contract #,contract-stx
#,id
#,pos-blame-id
#,neg-blame-id
#,(id->contract-src-info id)))]))))
(define-syntax (with-contract-helper stx)
(syntax-case stx ()
[(_ () ())
(begin #'(define-values () (values)))]
[(_ (p0 p ...) (u ...))
(raise-syntax-error 'with-contract
"no definition found for identifier"
#'p0)]
[(_ () (u0 u ...))
(raise-syntax-error 'with-contract
"no definition found for identifier"
#'u0)]
[(_ (p ...) (u ...) body0 body ...)
(let ([expanded-body0 (local-expand #'body0
(syntax-local-context)
(kernel-form-identifier-list))])
(define (filter-ids to-filter to-remove)
(filter (λ (i1)
(not (memf (λ (i2)
(bound-identifier=? i1 i2))
to-remove)))
to-filter))
(syntax-case expanded-body0 (begin define-values define-syntaxes)
[(begin sub ...)
(syntax/loc stx
(with-contract-helper (p ...) (u ...) sub ... body ...))]
[(define-syntaxes (id ...) expr)
(let ([ids (syntax->list #'(id ...))])
(for ([i1 (syntax->list #'(p ...))])
(when (ormap (λ (i2)
(bound-identifier=? i1 i2))
ids)
(raise-syntax-error 'with-contract
"cannot export syntax with a contract"
i1)))
(with-syntax ([def expanded-body0]
[unused-us (filter-ids (syntax->list #'(u ...)) ids)])
(with-syntax ()
(syntax/loc stx
(begin def (with-contract-helper (p ...) unused-us body ...))))))]
[(define-values (id ...) expr)
(let ([ids (syntax->list #'(id ...))])
(with-syntax ([def expanded-body0]
[unused-ps (filter-ids (syntax->list #'(p ...)) ids)]
[unused-us (filter-ids (syntax->list #'(u ...)) ids)])
(syntax/loc stx
(begin def (with-contract-helper unused-ps unused-us body ...)))))]
[else
(quasisyntax/loc stx
(begin #,expanded-body0
(with-contract-helper (p ...) (u ...) body ...)))]))]))
(define-for-syntax (check-and-split-with-contracts single-allowed? args)
(let loop ([args args]
[unprotected null]
[protected null]
[protections null])
(cond
[(null? args)
(values unprotected protected protections)]
[(identifier? (car args))
(unless single-allowed?
(raise-syntax-error 'with-contract
"expected (identifier contract)"
(car args)))
(loop (cdr args)
(cons (car args) unprotected)
protected
protections)]
[(let ([lst (syntax->list (car args))])
(and (list? lst)
(= (length lst) 2)
(identifier? (first lst))
lst))
=>
(lambda (l)
(loop (cdr args)
unprotected
(cons (first l) protected)
(cons (second l) protections)))]
[else
(raise-syntax-error 'with-contract
(format "expected ~a(identifier contract)"
(if single-allowed? "an identifier or " ""))
(car args))])))
(define-syntax (with-contract stx)
(when (eq? (syntax-local-context) 'expression)
(raise-syntax-error 'with-contract
"used in expression context"
stx))
(syntax-case stx ()
[(_ #:type type etc ...)
(not (identifier? #'type))
(raise-syntax-error 'with-contract
"expected identifier for type"
#'type)]
[(_ #:type type args etc ...)
(not (identifier? #'args))
(raise-syntax-error 'with-contract
"expected identifier for blame"
#'args)]
[(_ #:type type blame (arg ...) #:freevars (fv ...) #:freevar x c . body)
(identifier? #'x)
(syntax/loc stx
(with-contract #:type type blame (arg ...) #:freevars (fv ... [x c]) . body))]
[(_ #:type type blame (arg ...) #:freevars (fv ...) #:freevar x c . body)
(raise-syntax-error 'with-contract
"use of #:freevar with non-identifier"
#'x)]
[(_ #:type type blame (arg ...) #:freevars (fv ...) . body)
(and (identifier? #'blame)
(identifier? #'type))
(let*-values ([(marker) (make-syntax-introducer)]
[(cid-marker) (make-syntax-introducer)]
[(no-need free-vars free-ctcs)
(check-and-split-with-contracts #f (syntax->list #'(fv ...)))]
[(unprotected protected protections)
(check-and-split-with-contracts #t (syntax->list #'(arg ...)))])
(begin
(let ([dupd-id (check-duplicate-identifier (append unprotected protected))])
(when dupd-id
(raise-syntax-error 'with-contract
"identifier appears twice in exports"
dupd-id)))
(with-syntax ([blame-stx #''(type blame)]
[blame-id (car (generate-temporaries (list #t)))]
[(free-var ...) free-vars]
[(free-var-id ...) (map marker free-vars)]
[(free-ctc-id ...) (map cid-marker free-vars)]
[(free-ctc ...) (map (λ (c v)
(syntax-property c 'inferred-name v))
free-ctcs
free-vars)]
[(free-src-info ...) (map id->contract-src-info free-vars)]
[(ctc-id ...) (map cid-marker protected)]
[(ctc ...) (map (λ (c v)
(marker (syntax-property c 'inferred-name v)))
protections
protected)]
[(p ...) protected]
[(marked-p ...) (map marker protected)]
[(src-info ...) (map (compose id->contract-src-info marker) protected)]
[(u ...) unprotected]
[(marked-u ...) (map marker unprotected)])
(quasisyntax/loc stx
(begin
(define-values (free-ctc-id ...)
(values (verify-contract 'with-contract free-ctc) ...))
(define blame-id
(current-contract-region))
(define-values ()
(begin (contract free-ctc-id
free-var
blame-id
'cant-happen
free-src-info)
...
(values)))
(define-syntaxes (free-var-id ...)
(values (make-contracted-id-transformer
(quote-syntax free-var)
(quote-syntax free-ctc-id)
(quote-syntax blame-id)
(quote-syntax blame-stx)) ...))
(splicing-syntax-parameterize ([current-contract-region (λ (stx) #'blame-stx)])
(with-contract-helper (marked-p ...) (marked-u ...) . #,(marker #'body)))
(define-values (ctc-id ...)
(values (verify-contract 'with-contract ctc) ...))
(define-values ()
(begin (contract ctc-id
marked-p
blame-stx
'cant-happen
src-info)
...
(values)))
(define-syntaxes (u ... p ...)
(values (make-rename-transformer #'marked-u) ...
(make-contracted-id-transformer
(quote-syntax marked-p)
(quote-syntax ctc-id)
(quote-syntax blame-stx)
(quote-syntax blame-id)) ...)))))))]
[(_ #:type type blame (arg ...) #:freevar x c . body)
(syntax/loc stx
(with-contract #:type type blame (arg ...) #:freevars ([x c]) . body))]
[(_ #:type type blame (arg ...) . body)
(syntax/loc stx
(with-contract #:type type blame (arg ...) #:freevars () . body))]
[(_ #:type type blame bad-args etc ...)
(raise-syntax-error 'with-contract
"expected list of identifier and/or (identifier contract)"
#'bad-args)]
[(_ #:type type blame)
(raise-syntax-error 'with-contract
"only blame"
stx)]
[(_ etc ...)
(syntax/loc stx
(with-contract #:type region etc ...))]))

View File

@ -200,8 +200,10 @@
scheme/private/contract-basic-opters
scheme/private/contract-ds
scheme/private/contract-ds-helpers
scheme/private/contract-exists
scheme/private/contract-guts
scheme/private/contract-helpers
scheme/private/contract-misc
scheme/private/contract-opt
scheme/private/contract-opt-guts
scheme/private/define-struct