diff --git a/typed-racket-lib/typed-racket/base-env/base-types-extra.rkt b/typed-racket-lib/typed-racket/base-env/base-types-extra.rkt index 9fe186bc..ed415397 100644 --- a/typed-racket-lib/typed-racket/base-env/base-types-extra.rkt +++ b/typed-racket-lib/typed-racket/base-env/base-types-extra.rkt @@ -17,7 +17,7 @@ (define-other-types -> ->* case-> U Rec All Opaque Vector Parameterof List List* Class Object Values Instance Refinement - pred Struct Struct-Type Top Bot) + pred Struct Struct-Type Prefab Top Bot) (provide (rename-out [All ∀] [U Un] diff --git a/typed-racket-lib/typed-racket/base-env/prims.rkt b/typed-racket-lib/typed-racket/base-env/prims.rkt index 5b72699b..eea2a71b 100644 --- a/typed-racket-lib/typed-racket/base-env/prims.rkt +++ b/typed-racket-lib/typed-racket/base-env/prims.rkt @@ -605,9 +605,10 @@ This file defines two sorts of primitives. All of them are provided into any mod (define-splicing-syntax-class struct-options #:description "typed structure type options" - #:attributes (guard mutable? transparent? [prop 1] [prop-val 1]) + #:attributes (guard mutable? transparent? prefab? [prop 1] [prop-val 1]) (pattern (~seq (~or (~optional (~seq (~and #:mutable mutable?))) (~optional (~seq (~and #:transparent transparent?))) + (~optional (~seq (~and #:prefab prefab?))) ;; FIXME: unsound, but relied on in core libraries ;; #:guard ought to be supportable with some work ;; #:property is harder @@ -651,14 +652,16 @@ This file defines two sorts of primitives. All of them are provided into any mod (syntax-parse stx [(_ vars:maybe-type-vars nm:struct-name/new (fs:fld-spec ...) opts:struct-options) - (let ([mutable? (if (attribute opts.mutable?) #'(#:mutable) #'())]) + (let ([mutable? (if (attribute opts.mutable?) #'(#:mutable) #'())] + [prefab? (if (attribute opts.prefab?) #'(#:prefab) #'())]) (with-syntax ([d-s (ignore (quasisyntax/loc stx (struct #,@(attribute nm.new-spec) (fs.fld ...) . opts)))] [dtsi (quasisyntax/loc stx (dtsi* (vars.vars ...) nm.old-spec (fs.form ...) - #,@mutable?))]) + #,@mutable? + #,@prefab?))]) ;; see comment above (if (eq? (syntax-local-context) 'top-level) #'(begin (eval (quote-syntax d-s)) diff --git a/typed-racket-lib/typed-racket/infer/infer-unit.rkt b/typed-racket-lib/typed-racket/infer/infer-unit.rkt index c24dc3db..48d6cf11 100644 --- a/typed-racket-lib/typed-racket/infer/infer-unit.rkt +++ b/typed-racket-lib/typed-racket/infer/infer-unit.rkt @@ -13,7 +13,7 @@ (utils tc-utils) (rep free-variance type-rep filter-rep object-rep rep-utils) (types utils abbrev numeric-tower union subtype resolve - substitute generalize) + substitute generalize prefab) (env index-env tvar-env)) make-env -> ->* one-of/c) "constraint-structs.rkt" @@ -547,6 +547,18 @@ [else empty])]) (% cset-meet proc-c (cgen/flds context flds flds*)))] + ;; two prefab structs with the same key + [((Prefab: k ss) (Prefab: k* ts)) + #:when (and (prefab-key-subtype? k k*) + (>= (length ss) (length ts))) + (% cset-meet* + (for/list/fail ([s (in-list ss)] + [t (in-list ts)] + [mut? (in-list (prefab-key->field-mutability k*))]) + (if mut? + (cgen/inv context s t) + (cgen context s t))))] + ;; two struct names, need to resolve b/c one could be a parent [((Name: n _ #t) (Name: n* _ #t)) (if (free-identifier=? n n*) diff --git a/typed-racket-lib/typed-racket/private/parse-type.rkt b/typed-racket-lib/typed-racket/private/parse-type.rkt index a78dc8d4..01eb098b 100644 --- a/typed-racket-lib/typed-racket/private/parse-type.rkt +++ b/typed-racket-lib/typed-racket/private/parse-type.rkt @@ -5,7 +5,7 @@ (require "../utils/utils.rkt" (except-in (rep type-rep object-rep) make-arr) (rename-in (types abbrev union utils filter-ops resolve - classes) + classes prefab) [make-arr* make-arr]) (utils tc-utils stxclass-util literal-syntax-class) syntax/stx (prefix-in c: (contract-req)) @@ -95,6 +95,7 @@ (define-literal-syntax-class #:for-label Vector) (define-literal-syntax-class #:for-label Struct) (define-literal-syntax-class #:for-label Struct-Type) +(define-literal-syntax-class #:for-label Prefab) (define-literal-syntax-class #:for-label Values) (define-literal-syntax-class #:for-label values) (define-literal-syntax-class #:for-label Top) @@ -341,11 +342,20 @@ [(:Struct-Type^ t) (define v (parse-type #'t)) (match (resolve v) - [(? Struct? s) (make-StructType s)] + [(or (? Struct? s) (? Prefab? s)) (make-StructType s)] [_ (parse-error #:delayed? #t "expected a structure type for argument to Struct-Type" "given" v) (Un)])] + [(:Prefab^ key ts ...) + #:fail-unless (prefab-key? (syntax->datum #'key)) "expected a prefab key" + (define num-fields (length (syntax->list #'(ts ...)))) + (define new-key (normalize-prefab-key (syntax->datum #'key) num-fields)) + (unless (= (prefab-key->field-count new-key) num-fields) + (parse-error "the number of fields in the prefab key and type disagree" + "key" (prefab-key->field-count new-key) + "fields" num-fields)) + (make-Prefab new-key (parse-types #'(ts ...)))] [(:Instance^ t) (let ([v (parse-type #'t)]) (if (not (or (F? v) (Mu? v) (Name? v) (Class? v) (Error? v))) diff --git a/typed-racket-lib/typed-racket/rep/type-rep.rkt b/typed-racket-lib/typed-racket/rep/type-rep.rkt index 30fd3adc..12a9e510 100644 --- a/typed-racket-lib/typed-racket/rep/type-rep.rkt +++ b/typed-racket-lib/typed-racket/rep/type-rep.rkt @@ -402,9 +402,18 @@ ;; This should eventually be based on understanding of struct properties. [#:key '(struct procedure)]) +;; Represents prefab structs +;; key : prefab key encoding mutability, auto-fields, etc. +;; flds : the types of all of the prefab fields +(def-type Prefab ([key prefab-key?] + [flds (listof Type/c)]) + [#:frees (λ (f) (combine-frees (map f flds)))] + [#:fold-rhs (*Prefab key (map type-rec-id flds))] + [#:key 'prefab]) + ;; A structure type descriptor (def-type StructTypeTop () [#:fold-rhs #:base] [#:key 'struct-type]) -(def-type StructType ([s (or/c F? B? Struct?)]) [#:key 'struct-type]) +(def-type StructType ([s (or/c F? B? Struct? Prefab?)]) [#:key 'struct-type]) ;; the supertype of all of these values (def-type BoxTop () [#:fold-rhs #:base] [#:key 'box]) diff --git a/typed-racket-lib/typed-racket/typecheck/internal-forms.rkt b/typed-racket-lib/typed-racket/typecheck/internal-forms.rkt index f27e665d..d18556d6 100644 --- a/typed-racket-lib/typed-racket/typecheck/internal-forms.rkt +++ b/typed-racket-lib/typed-racket/typecheck/internal-forms.rkt @@ -46,10 +46,11 @@ ;;; Helpers (define-splicing-syntax-class dtsi-fields - #:attributes (mutable type-only maker) + #:attributes (mutable prefab type-only maker) (pattern (~seq (~or (~optional (~and #:mutable (~bind (mutable #t)))) + (~optional (~and #:prefab (~bind (prefab #t)))) (~optional (~and #:type-only (~bind (type-only #t)))) (~optional (~seq #:maker maker))) ...))) @@ -59,11 +60,12 @@ (define-syntax-class define-typed-struct-body - #:attributes (name mutable type-only maker nm (tvars 1) (fields 1) (types 1)) + #:attributes (name mutable prefab type-only maker nm (tvars 1) (fields 1) (types 1)) (pattern ((~optional (tvars:id ...) #:defaults (((tvars 1) null))) nm:struct-name ([fields:id : types:expr] ...) options:dtsi-fields) #:attr name #'nm.nm #:attr mutable (attribute options.mutable) + #:attr prefab (attribute options.prefab) #:attr type-only (attribute options.type-only) #:attr maker (or (attribute options.maker) #'nm.nm))) diff --git a/typed-racket-lib/typed-racket/typecheck/tc-literal.rkt b/typed-racket-lib/typed-racket/typecheck/tc-literal.rkt index 8d37cada..f35f6440 100644 --- a/typed-racket-lib/typed-racket/typecheck/tc-literal.rkt +++ b/typed-racket-lib/typed-racket/typecheck/tc-literal.rkt @@ -3,7 +3,8 @@ (require "../utils/utils.rkt" racket/match (typecheck signatures check-below) - (types abbrev numeric-tower resolve subtype union generalize) + (types abbrev numeric-tower resolve subtype union generalize + prefab) (rep type-rep) (only-in (infer infer) restrict) (utils stxclass-util) @@ -124,8 +125,21 @@ [ks (hash-map h (lambda (x y) (tc-literal x)))] [vs (hash-map h (lambda (x y) (tc-literal y)))]) (make-Hashtable (generalize (apply Un ks)) (generalize (apply Un vs))))])] + [(~var i (3d prefab-struct-key)) + (tc-prefab (syntax-e #'i) expected)] [_ Univ])) - - - +;; Typecheck a prefab struct literal +(define (tc-prefab struct-inst expected) + (define expected-ts + (match (and expected (resolve expected)) + [(Prefab: _ ts) (in-sequence-forever (in-list ts) #f)] + [_ (in-cycle (in-value #f))])) + (define key (prefab-struct-key struct-inst)) + (define struct-vec (struct->vector struct-inst)) + (define fields + (for/list ([elem (in-vector struct-vec 1)] + [expected-t expected-ts]) + (tc-literal elem expected-t))) + (make-Prefab (normalize-prefab-key key (length fields)) + fields)) diff --git a/typed-racket-lib/typed-racket/typecheck/tc-structs.rkt b/typed-racket-lib/typed-racket/typecheck/tc-structs.rkt index 12e92a2c..c33256e2 100644 --- a/typed-racket-lib/typed-racket/typecheck/tc-structs.rkt +++ b/typed-racket-lib/typed-racket/typecheck/tc-structs.rkt @@ -6,7 +6,7 @@ (prefix-in c: (contract-req)) (rep type-rep object-rep free-variance) (private parse-type syntax-properties) - (types abbrev utils resolve substitute struct-table) + (types abbrev utils resolve substitute struct-table prefab) (env global-env type-name-env type-alias-env tvar-env) (utils tc-utils) (typecheck def-binding internal-forms) @@ -14,7 +14,8 @@ (require-for-cond-contract racket/struct-info) -(provide tc/struct name-of-struct d-s +(provide tc/struct + name-of-struct d-s refine-struct-variance! register-parsed-struct-sty! register-parsed-struct-bindings!) @@ -24,7 +25,7 @@ (pattern (name:id par:id)) (pattern name:id #:attr par #f)) -;; sty : Struct? +;; sty : (U Struct? Prefab?) ;; names : Listof[Identifier] ;; desc : struct-desc ;; struct-info : struct-info? @@ -55,9 +56,9 @@ ;; parse name field of struct, determining whether a parent struct was specified -;; syntax -> (values identifier Option[Name] Option[Struct]) -(define/cond-contract (parse-parent nm/par) - (c:-> syntax? (values identifier? (c:or/c Name? #f) (c:or/c Mu? Poly? Struct? #f))) +;; syntax any -> (values identifier Option[Name] Option[Struct]) +(define/cond-contract (parse-parent nm/par prefab?) + (c:-> syntax? (values identifier? (c:or/c Name? #f) (c:or/c Mu? Poly? Struct? Prefab? #f))) (syntax-parse nm/par [v:parent (if (attribute v.par) @@ -66,7 +67,8 @@ [parent (let loop ((parent parent0)) (cond ((Name? parent) (loop (resolve-name parent))) - ((or (Poly? parent) (Mu? parent) (Struct? parent)) + ((or (Poly? parent) (Mu? parent) + (if prefab? (Prefab? parent) (Struct? parent))) parent) (else (tc-error/stx #'v.par "parent type not a valid structure name: ~a" @@ -119,14 +121,14 @@ ;; identifier listof[identifier] type listof[fld] listof[Type] boolean -> ;; (values Type listof[Type] listof[Type]) (define/cond-contract (register-sty! sty names desc) - (c:-> Struct? struct-names? struct-desc? void?) + (c:-> (c:or/c Struct? Prefab?) struct-names? struct-desc? void?) ;; a type alias needs to be registered here too, to ensure ;; that parse-type will map the identifier to this Name type (define type-name (struct-names-type-name names)) (register-resolved-type-alias type-name - (make-Name type-name #f #t)) + (make-Name type-name (struct-desc-tvars desc) (Struct? sty))) (register-type-name type-name (make-Poly (struct-desc-tvars desc) sty))) @@ -135,7 +137,7 @@ ;; Register the approriate types to the struct bindings. (define/cond-contract (register-struct-bindings! sty names desc si) - (c:-> Struct? struct-names? struct-desc? (c:or/c #f struct-info?) (c:listof binding?)) + (c:-> (c:or/c Struct? Prefab?) struct-names? struct-desc? (c:or/c #f struct-info?) (c:listof binding?)) (define tvars (struct-desc-tvars desc)) @@ -169,6 +171,7 @@ (make-def-binding (struct-names-struct-type names) (make-StructType sty)) (make-def-binding (struct-names-predicate names) (make-pred-ty (if (not covariant?) + ;; FIXME: does this make sense with prefabs? (make-StructTop sty) (subst-all (make-simple-substitution tvars (map (const Univ) tvars)) poly-base)))) @@ -225,7 +228,9 @@ (define (refine-struct-variance! parsed-structs) (define stys (map parsed-struct-sty parsed-structs)) (define tvarss (map (compose struct-desc-tvars parsed-struct-desc) parsed-structs)) - (define names (map Struct-name stys)) + (define names + (for/list ([parsed-struct (in-list parsed-structs)]) + (struct-names-type-name (parsed-struct-names parsed-struct)))) (refine-variance! names stys tvarss)) ;; check and register types for a define struct @@ -236,9 +241,10 @@ #:proc-ty [proc-ty #f] #:maker [maker #f] #:mutable [mutable #f] - #:type-only [type-only #f]) + #:type-only [type-only #f] + #:prefab? [prefab? #f]) ;; parent field types can't actually be determined here - (define-values (nm parent-name parent) (parse-parent nm/par)) + (define-values (nm parent-name parent) (parse-parent nm/par prefab?)) ;; create type variables for the new type parameters (define tvars (map syntax-e vars)) (define new-tvars (map make-F tvars)) @@ -262,16 +268,35 @@ ;; that the outside world will see ;; then register it (define names (get-struct-names nm fld-names maker)) - (define desc (struct-desc - (map fld-t (get-flds concrete-parent)) - types - tvars - mutable - (and proc-ty (parse-type proc-ty)))) - (define sty (mk/inner-struct-type names desc concrete-parent)) - (parsed-struct sty names desc (struct-info-property nm/par) type-only)) + (cond [prefab? + (define-values (parent-key parent-fields) + (match concrete-parent + [#f (values null null)] + [(Prefab: parent-key parent-fields) + (values parent-key parent-fields)])) + (define key-prefix + (if mutable + (list (syntax-e nm) + (length fld-names) + (build-vector (length fld-names) values)) + (list (syntax-e nm)))) + (define key + (normalize-prefab-key (append key-prefix parent-key) + (+ (length fld-names) (length parent-fields)))) + (define desc (struct-desc parent-fields types tvars mutable #f)) + (parsed-struct (make-Prefab key (append parent-fields types)) + names desc (struct-info-property nm/par) #f)] + [else + (define desc (struct-desc + (map fld-t (get-flds concrete-parent)) + types + tvars + mutable + (and proc-ty (parse-type proc-ty)))) + (define sty (mk/inner-struct-type names desc concrete-parent)) + (parsed-struct sty names desc (struct-info-property nm/par) type-only)])) ;; register a struct type ;; convenience function for built-in structs diff --git a/typed-racket-lib/typed-racket/typecheck/tc-toplevel.rkt b/typed-racket-lib/typed-racket/typecheck/tc-toplevel.rkt index 7bd25aff..e607c1d6 100644 --- a/typed-racket-lib/typed-racket/typecheck/tc-toplevel.rkt +++ b/typed-racket-lib/typed-racket/typecheck/tc-toplevel.rkt @@ -36,7 +36,8 @@ (tc/struct (attribute t.tvars) #'t.nm (syntax->list #'(t.fields ...)) (syntax->list #'(t.types ...)) #:mutable (attribute t.mutable) #:maker (attribute t.maker) - #:type-only (attribute t.type-only))] + #:type-only (attribute t.type-only) + #:prefab? (attribute t.prefab))] [t:typed-struct/exec (tc/struct null #'t.nm (syntax->list #'(t.fields ...)) (syntax->list #'(t.types ...)) #:proc-ty #'t.proc-type)]))) diff --git a/typed-racket-lib/typed-racket/types/abbrev.rkt b/typed-racket-lib/typed-racket/types/abbrev.rkt index f50eb07c..a394f461 100644 --- a/typed-racket-lib/typed-racket/types/abbrev.rkt +++ b/typed-racket-lib/typed-racket/types/abbrev.rkt @@ -14,7 +14,7 @@ (prefix-in c: (contract-req)) (rename-in (rep type-rep filter-rep object-rep) [make-Base make-Base*]) - (types union numeric-tower) + (types union numeric-tower prefab) ;; Using this form so all-from-out works "base-abbrev.rkt" "match-expanders.rkt" @@ -66,6 +66,8 @@ (define -evt make-Evt) (define -weak-box make-Weak-Box) (define -inst make-Instance) +(define (-prefab key . types) + (make-Prefab (normalize-prefab-key key (length types)) types)) (define (-seq . args) (make-Sequence args)) diff --git a/typed-racket-lib/typed-racket/types/prefab.rkt b/typed-racket-lib/typed-racket/types/prefab.rkt new file mode 100644 index 00000000..35adade9 --- /dev/null +++ b/typed-racket-lib/typed-racket/types/prefab.rkt @@ -0,0 +1,125 @@ +#lang racket/base + +;; Utilities for dealing with prefab struct types + +(require "../utils/utils.rkt" + (contract-req) + racket/list + racket/match) + +(provide/cond-contract [normalize-prefab-key + (-> prefab-key? integer? prefab-key?)] + [prefab-key->field-count + (-> prefab-key? integer?)] + [abbreviate-prefab-key + (-> prefab-key? prefab-key?)] + [prefab-key-subtype? + (-> prefab-key? prefab-key? any)] + [prefab-key->field-mutability + (-> prefab-key? (listof boolean?))]) + +;; Convert a prefab key to its expanded version +(define (normalize-prefab-key key field-length) + (cond [(symbol? key) `(,key ,field-length (0 #f) #())] + [(list? key) + (define base-sym (car key)) + (define-values (base-clauses rst) + (splitf-at (cdr key) (λ (x) (not (symbol? x))))) + (define parent-fragments + (let loop ([key rst] [fragments null]) + (cond [(null? key) fragments] + [else + (define-values (clauses rst) + (splitf-at (cdr key) (λ (x) (not (symbol? x))))) + (loop rst (cons (cons (car key) clauses) + fragments))]))) + (define-values (processed-parents remaining-length) + (for/fold ([processed null] + [field-length field-length]) + ([parent (in-list parent-fragments)]) + (match parent + [(list _ n (and auto (list auto-n _)) _) + (values (cons parent processed) + (- field-length n auto-n))] + [(list sym (? number? n) (and auto (list auto-n _))) + (values (cons `(,sym ,n ,auto #()) processed) + (- field-length n auto-n))] + [(list sym (? number? n) (? vector? mut)) + (values (cons `(,sym ,n (0 #f) ,mut) processed) + (- field-length n))] + [(list sym n) + (values (cons `(,sym ,n (0 #f) #()) processed) + (- field-length n))]))) + (define processed-base + (match base-clauses + [(list n _ _) (cons base-sym base-clauses)] + [(list (? number? n) (and auto (list auto-n _))) + `(,base-sym ,n ,auto #())] + [(list (? number? n) (? vector? mut)) + `(,base-sym ,n (0 #f) ,mut)] + [(list (and auto (list auto-n _)) (? vector? mut)) + `(,base-sym ,(- remaining-length auto-n) ,auto ,mut)] + [(list (? number? n)) + `(,base-sym ,n (0 #f) #())] + [(list (and auto (list auto-n _))) + `(,base-sym ,(- remaining-length auto-n) ,auto #())] + [(list (? vector? mut)) + `(,base-sym ,remaining-length (0 #f) ,mut)] + [(list) + `(,base-sym ,remaining-length (0 #f) #())])) + (append processed-base (apply append processed-parents))])) + +;; Accepts a normalized prefab key and returns the number of fields +;; a struct with this key should have +(define (prefab-key->field-count key) + (let loop ([key key] [count 0]) + (cond [(null? key) count] + [else + (match-define (list _ len (list auto-len _) _ rst ...) key) + (loop rst (+ len auto-len count))]))) + +;; Convert a prefab key to a shortened version +(define (abbreviate-prefab-key key) + (let loop ([key key] [first? #t]) + (cond [(null? key) null] + [(symbol? key) key] + [(list? key) + (define sym (car key)) + (define-values (other-clauses rst) + (splitf-at (cdr key) (λ (x) (not (symbol? x))))) + (define simplified-clauses + (for/list ([elem (in-list other-clauses)] + #:unless (and first? (number? elem)) + #:unless (and (list? elem) + (= (car elem) 0)) + #:unless (and (vector? elem) + (= (vector-length elem) 0))) + elem)) + (if (and (null? simplified-clauses) + (null? rst)) + sym + (cons sym (append simplified-clauses + (loop rst #f))))]))) + +;; Determine if the first prefab key can be a subtype of the second +;; Invariant: the keys are fully expanded (normalized) +(define (prefab-key-subtype? key1 key2) + (or (equal? key1 key2) + (suffix? key2 key1))) + +(define (suffix? l1 l2) + (for/or ([n (in-range (add1 (length l2)))]) + (equal? (drop l2 n) l1))) + +;; Returns a list of flags indicating the mutability of prefab struct types +;; in order from parent to the children (#t is mutable, #f is not) +;; Precondition: the key is fully expanded +(define (prefab-key->field-mutability key) + (let loop ([key key]) + (cond [(null? key) null] + [else + (match-define (list sym len auto mut parents ...) key) + (define mut-list (vector->list mut)) + (append (loop parents) + (for/list ([idx (in-range len)]) + (and (member idx mut-list) #t)))]))) diff --git a/typed-racket-lib/typed-racket/types/printer.rkt b/typed-racket-lib/typed-racket/types/printer.rkt index 4bb0270d..cf33d91b 100644 --- a/typed-racket-lib/typed-racket/types/printer.rkt +++ b/typed-racket-lib/typed-racket/types/printer.rkt @@ -13,6 +13,7 @@ "types/kw-types.rkt" "types/utils.rkt" "types/resolve.rkt" + "types/prefab.rkt" "utils/utils.rkt" "utils/tc-utils.rkt") (for-syntax racket/base syntax/parse)) @@ -406,6 +407,7 @@ [(StructType: ty) `(Struct-Type ,(t->s ty))] [(StructTypeTop:) 'Struct-TypeTop] [(StructTop: (Struct: nm _ _ _ _ _)) `(Struct ,(syntax-e nm))] + [(Prefab: key fields) `(Prefab ,(abbreviate-prefab-key key) ,@fields)] [(BoxTop:) 'BoxTop] [(Weak-BoxTop:) 'Weak-BoxTop] [(ChannelTop:) 'ChannelTop] diff --git a/typed-racket-lib/typed-racket/types/subtype.rkt b/typed-racket-lib/typed-racket/types/subtype.rkt index e3f1fe33..8edf88d9 100644 --- a/typed-racket-lib/typed-racket/types/subtype.rkt +++ b/typed-racket-lib/typed-racket/types/subtype.rkt @@ -5,7 +5,7 @@ (rep type-rep filter-rep object-rep rep-utils) (utils tc-utils early-return) (types utils resolve base-abbrev match-expanders - numeric-tower substitute current-seen) + numeric-tower substitute current-seen prefab) (for-syntax racket/base syntax/parse unstable/sequence)) (lazy-require @@ -554,6 +554,20 @@ ;; subtyping on structs follows the declared hierarchy [((Struct: nm (? Type/c? parent) _ _ _ _) other) (subtype* A0 parent other)] + [((Prefab: k1 ss) (Prefab: k2 ts)) + (and (prefab-key-subtype? k1 k2) + (and (>= (length ss) (length ts)) + (for/fold ([A A0]) + ([s (in-list ss)] + [t (in-list ts)] + [mut? (in-list (prefab-key->field-mutability k2))] + #:break (not A)) + (and A + (if mut? + (subtype-seq A + (subtype* t s) + (subtype* s t)) + (subtype* A s t))))))] ;; subtyping on values is pointwise, except special case for Bottom [((Values: (list (Result: (== -Bottom) _ _))) _) A0] diff --git a/typed-racket-test/succeed/prefab.rkt b/typed-racket-test/succeed/prefab.rkt new file mode 100644 index 00000000..90ab3130 --- /dev/null +++ b/typed-racket-test/succeed/prefab.rkt @@ -0,0 +1,36 @@ +#lang typed/racket + +;; Test prefab struct declarations + +(struct foo ([x : Symbol]) #:prefab) +(struct bar foo ([y : String] [z : String]) #:prefab) + +(: a-bar (Prefab (bar foo 1) Symbol String String)) +(define a-bar (bar 'foo "bar1" "bar2")) + +(foo-x (foo 'foo)) +(bar-y (bar 'foo "bar1" "bar2")) + +;; prefab keys may be normalized or not +(: a-bar-2 (Prefab (bar 2 foo 1 (0 #f) #()) Symbol String String)) +(define a-bar-2 (bar 'foo "bar1" "bar2")) + +;; prefab subtyping is computed via the key and field length +(: a-bar-3 (Prefab foo Symbol)) +(define a-bar-3 (bar 'foo "bar1" "bar2")) + +;; Mutable prefab structs + +(struct baz ([x : String]) #:mutable #:prefab) + +(define a-baz (baz "baz")) +(set-baz-x! a-baz "baz2") +(baz-x a-baz) + +;; Polymorphic prefab structs + +(struct (X) poly ([x : X]) #:prefab) + +(poly-x (poly "foo")) +(poly-x (poly 3)) +(poly-x #s(poly "foo")) diff --git a/typed-racket-test/unit-tests/all-tests.rkt b/typed-racket-test/unit-tests/all-tests.rkt index c6abd632..ccf64642 100644 --- a/typed-racket-test/unit-tests/all-tests.rkt +++ b/typed-racket-test/unit-tests/all-tests.rkt @@ -43,4 +43,5 @@ "generalize-tests.rkt" "rep-tests.rkt" "prims-tests.rkt" - "tooltip-tests.rkt") + "tooltip-tests.rkt" + "prefab-tests.rkt") diff --git a/typed-racket-test/unit-tests/parse-type-tests.rkt b/typed-racket-test/unit-tests/parse-type-tests.rkt index e16ca15e..46c61ed0 100644 --- a/typed-racket-test/unit-tests/parse-type-tests.rkt +++ b/typed-racket-test/unit-tests/parse-type-tests.rkt @@ -262,6 +262,10 @@ [(String [#:a String] String * -> String) (->optkey -String [] #:rest -String #:a -String #f -String)] + ;;; Prefab structs + [(Prefab foo String) (-prefab 'foo -String)] + [FAIL (Prefab (foo 0) String)] + ;;; Classes [(Class) (-class)] [(Class (init [x Number] [y Number])) diff --git a/typed-racket-test/unit-tests/prefab-tests.rkt b/typed-racket-test/unit-tests/prefab-tests.rkt new file mode 100644 index 00000000..0666d71e --- /dev/null +++ b/typed-racket-test/unit-tests/prefab-tests.rkt @@ -0,0 +1,90 @@ +#lang racket/base + +;; Unit tests for prefab type helpres + +(require "test-utils.rkt" + racket/list + rackunit + typed-racket/types/prefab) + +(provide tests) +(gen-test-main) + +(define-check (check-normalize key n norm) + (check-equal? (normalize-prefab-key key n) norm)) + +;; check that the abbreviate function is consistent with how +;; Racket abbreviates in the run-time +(define-check (check-abbreviate key n) + (check-equal? (abbreviate-prefab-key key) + (prefab-struct-key + (apply make-prefab-struct key (make-list n 0))))) + +(define tests + (test-suite + "Tests for prefab type helpers" + (check-normalize '(foo) 1 '(foo 1 (0 #f) #())) + (check-normalize '(foo) 3 '(foo 3 (0 #f) #())) + (check-normalize '(foo 1) 1 '(foo 1 (0 #f) #())) + (check-normalize '(foo 3) 3 '(foo 3 (0 #f) #())) + (check-normalize '(foo bar 3) 4 '(foo 1 (0 #f) #() bar 3 (0 #f) #())) + (check-normalize '(foo 3 bar 3) 6 '(foo 3 (0 #f) #() bar 3 (0 #f) #())) + (check-normalize '(foo 1 #()) 1 '(foo 1 (0 #f) #())) + (check-normalize '(foo 1 #(0)) 1 '(foo 1 (0 #f) #(0))) + (check-normalize '(foo 5 (0 #f)) 5 '(foo 5 (0 #f) #())) + (check-normalize '(foo 5 (1 #f)) 6 '(foo 5 (1 #f) #())) + (check-normalize '(foo 5 (0 #f) #()) 5 '(foo 5 (0 #f) #())) + (check-normalize '(foo bar 4) 7 '(foo 3 (0 #f) #() bar 4 (0 #f) #())) + (check-normalize '(foo (1 #f) bar 4) 8 '(foo 3 (1 #f) #() bar 4 (0 #f) #())) + (check-normalize '(foo #(1) bar 4) 7 '(foo 3 (0 #f) #(1) bar 4 (0 #f) #())) + (check-normalize '(foo bar 4 (1 #f)) 8 '(foo 3 (0 #f) #() bar 4 (1 #f) #())) + (check-normalize '(foo bar 1 baz 4 (1 #f)) + 8 + '(foo 2 (0 #f) #() bar 1 (0 #f) #() baz 4 (1 #f) #())) + + (check-equal? (prefab-key->field-count '(foo 1 (0 #f) #())) + 1) + (check-equal? (prefab-key->field-count '(foo 1 (1 #f) #())) + 2) + (check-equal? (prefab-key->field-count + '(foo 1 (1 #f) #() bar 1 (0 #f) #())) + 3) + (check-equal? (prefab-key->field-count + '(foo 1 (1 #f) #() bar 1 (0 #f) #() baz 2 (0 #f) #())) + 5) + + (check-abbreviate '(foo) 1) + (check-abbreviate '(foo 1) 1) + (check-abbreviate '(foo 3) 3) + (check-abbreviate '(foo bar 3) 4) + (check-abbreviate '(foo 3 bar 3) 6) + (check-abbreviate '(foo 1 #()) 1) + (check-abbreviate '(foo 1 #(0)) 1) + (check-abbreviate '(foo 5 (0 #f)) 5) + (check-abbreviate '(foo 5 (1 #f)) 6) + (check-abbreviate '(foo 5 (0 #f) #()) 5) + (check-abbreviate '(foo 5 (1 #f) #()) 6) + (check-abbreviate '(foo 5 (0 #f) #(1)) 5) + (check-abbreviate '(foo 5 (1 #f) #(1)) 6) + (check-abbreviate '(foo 5 (0 #f) #() bar 1) 6) + (check-abbreviate '(foo 5 (0 #f) #(1) bar 1) 6) + (check-abbreviate '(foo 5 (1 #f) #(1) bar 1) 7) + (check-abbreviate '(foo 5 (1 #f) #(1) bar 1 #()) 7) + (check-abbreviate '(foo 5 (0 #f) #(1) bar 1 #()) 6) + (check-abbreviate '(foo 5 (0 #f) #() bar 1 #()) 6) + (check-abbreviate '(foo 5 (1 #f) #(1) bar 1 #(0)) 7) + + (check-true (prefab-key-subtype? '(foo 1 (0 #f) #() bar 1 (0 #f) #()) + '(bar 1 (0 #f) #()))) + (check-false (prefab-key-subtype? '(foo 1 (0 #f) #()) + '(bar 1 (0 #f) #()))) + + (check-equal? (prefab-key->field-mutability + '(foo 1 (0 #f) #() bar 1 (0 #f) #())) + (list #f #f)) + (check-equal? (prefab-key->field-mutability + '(foo 2 (0 #f) #(0) bar 3 (0 #f) #(1))) + (list #f #t #f #t #f)) + (check-equal? (prefab-key->field-mutability + '(foo 2 (0 #f) #(0) bar 3 (0 #f) #(1) baz 1 (0 #f) #(0))) + (list #t #f #t #f #t #f)))) diff --git a/typed-racket-test/unit-tests/subtype-tests.rkt b/typed-racket-test/unit-tests/subtype-tests.rkt index 7eda6560..016dc861 100644 --- a/typed-racket-test/unit-tests/subtype-tests.rkt +++ b/typed-racket-test/unit-tests/subtype-tests.rkt @@ -347,4 +347,18 @@ [FAIL (-class #:method ((m (-> -Nat))) #:augment ((m (-> -Nat)))) (-class #:method ((m (-> -Nat))))] + + ;; prefab structs + [(-prefab 'foo -String) (-prefab 'foo -String)] + [(-prefab 'foo -String) (-prefab 'foo (-opt -String))] + [(-prefab '(bar foo 1) -String -Symbol) (-prefab 'foo -String)] + [(-prefab '(bar foo 1) -String -Symbol) (-prefab 'foo (-opt -String))] + [FAIL + (-prefab '(foo #(0)) -String) (-prefab '(foo #(0)) (-opt -String))] + [(-prefab '(foo 1 #(0)) -String -Symbol) + (-prefab '(foo #(0)) -String)] + [(-prefab '(bar foo 1 #(0)) -String -Symbol) + (-prefab '(foo #(0)) -String)] + [FAIL + (-prefab '(foo #()) -String) (-prefab '(foo #(0)) (-opt -String))] )) diff --git a/typed-racket-test/unit-tests/typecheck-tests.rkt b/typed-racket-test/unit-tests/typecheck-tests.rkt index 8f26329c..7440556a 100644 --- a/typed-racket-test/unit-tests/typecheck-tests.rkt +++ b/typed-racket-test/unit-tests/typecheck-tests.rkt @@ -96,6 +96,10 @@ ;; test: syntax? tc-results? [(option/c tc-results?)] ;; [(listof (list id type))] -> void? ;; Checks that the expression typechecks using the expected type to the golden result. + ;; + ;; The new-mapping argument (here and in subsequent functions) is used to extend the + ;; lexical type environment in the test case with additional bindings. Its use is to + ;; simulate forms that are difficult to put in unit tests, like `struct`. (define (test expr golden (expected #f) (new-mapping '())) (test/proc expr (lambda (_) golden) expected new-mapping)) @@ -3525,6 +3529,24 @@ ;; PR 14889 [tc-err (ann (vector-ref (ann (vector "hi") (Vectorof String)) 0) Symbol) #:msg #rx"Polymorphic function.*could not be applied"] + + [tc-e (let () + (foo-x (foo "foo")) + (foo-x #s(foo "foo")) + (foo-x #s((foo 1 (0 #f) #()) "foo")) + (foo-x #s((bar foo 1 (0 #f) #()) "foo" 'bar)) + (foo-x #s((baz bar 1 foo 1 (0 #f) #()) "foo" 'bar 'baz))) + -String + #:extend-env ([foo (t:-> -String (-prefab 'foo -String))] + [foo-x (t:-> (-prefab 'foo -String) -String)])] + [tc-err (begin (foo-x "foo") + (error "foo")) + #:extend-env ([foo-x (t:-> (-prefab 'foo -String) -String)]) + #:msg #rx"expected: \\(Prefab.*given: String"] + [tc-err (begin (foo-x #s(bar "bar")) + (error "foo")) + #:extend-env ([foo-x (t:-> (-prefab 'foo -String) -String)]) + #:msg #rx"expected: \\(Prefab foo.*given: \\(Prefab bar"] ) (test-suite @@ -3580,5 +3602,16 @@ #:expected (-mu X (-pair (-vec (t:Un (-val ':a) X)) (t:Un (-val ':b) X)))] [tc-l/err #(1 2) #:expected (make-HeterogeneousVector (list -Number -Symbol)) #:msg #rx"expected: Symbol"] + [tc-l #s(foo "a" a) + (-prefab 'foo -String (-val 'a))] + [tc-l #s((foo 2 (0 #f) #()) "a" a) + (-prefab 'foo -String (-val 'a))] + [tc-l #s((foo bar 1) "a" a) + (-prefab '(foo bar 1) -String (-val 'a))] + [tc-l #s((foo bar 1 baz 1) "a" a) + (-prefab '(foo bar 1 baz 1) -String (-val 'a))] + [tc-l #s(foo "a") + (-prefab 'foo (-opt -String)) + #:expected (-prefab 'foo (-opt -String))] ) ))