|
1 | 1 | #lang curly-fn racket/base |
2 | 2 |
|
3 | | -; This module contains the core implementation of the Hackett typechecker, as well as the core type |
4 | | -; representations. The Hackett typechecker operates using a mutable typechecker context implemented |
5 | | -; as a Racket parameter, which contains information about solutions for existing solver variables. |
| 3 | +; This module contains the core implementation of the Hackett typechecker. The Hackett typechecker |
| 4 | +; operates using a mutable typechecker context implemented as a Racket parameter, which contains |
| 5 | +; information about solutions for existing solver variables. |
6 | 6 | ; |
7 | | -; The core typechecker implements typing subsumption rules and the constraint solver for resolving |
8 | | -; typeclass instances. Other functionality is deferred to the implementation site of Hackett forms. |
9 | | -; The functions that perform the actual process of type inference (via recursive expansion) are |
10 | | -; defined in hackett/private/base, and they provide the primary interface to the various typechecker |
11 | | -; functions in this module. |
| 7 | +; The core typechecker implements typing subsumption rules and variable instantiation. Other |
| 8 | +; functionality is deferred to the implementation site of Hackett forms. The functions that perform |
| 9 | +; the actual process of type inference (via recursive expansion) are defined in hackett/private/base, |
| 10 | +; and they provide the primary interface to the various typechecker functions in this module. |
12 | 11 |
|
13 | 12 | (require (for-template racket/base |
14 | | - syntax/id-table |
15 | 13 | hackett/private/type-language) |
16 | 14 | (for-syntax racket/base |
17 | 15 | syntax/transformer) |
18 | | - data/gvector |
| 16 | + |
19 | 17 | racket/contract |
20 | 18 | racket/format |
21 | 19 | racket/function |
22 | 20 | racket/list |
23 | 21 | racket/match |
24 | 22 | racket/string |
25 | 23 | racket/syntax |
26 | | - racket/stxparam-exptime |
27 | | - syntax/id-table |
28 | 24 | syntax/parse |
29 | 25 | syntax/parse/class/local-value |
30 | 26 | syntax/parse/define |
|
35 | 31 | hackett/private/util/list |
36 | 32 | hackett/private/util/stx) |
37 | 33 |
|
38 | | -(provide (contract-out [struct ctx:solution ([x^ identifier?] [t type?])] |
39 | | - [struct class:info ([vars (listof identifier?)] |
40 | | - [method-table immutable-free-id-table?] |
41 | | - [default-methods immutable-free-id-table?] |
42 | | - [superclasses (listof constr?)] |
43 | | - [deriving-transformer (or/c (-> syntax? syntax?) #f)])] |
44 | | - [struct class:instance ([class class:info?] |
45 | | - [vars (listof identifier?)] |
46 | | - [subgoals (listof constr?)] |
47 | | - [ts (listof (and/c type? type-mono?))] |
48 | | - [dict-expr syntax?])]) |
49 | | - type? type=? constr? type-mono? type-vars^ type->string |
| 34 | +(provide type? type=? constr? type-mono? type-vars^ type->string |
50 | 35 | generalize inst insts type<:/full! type<:/elaborate! type<:! type-inst-l! type-inst-r! |
51 | | - ctx-elem? ctx? ctx-elem=? ctx-member? ctx-remove |
52 | | - ctx-find-solution current-ctx-solution apply-subst apply-current-subst |
| 36 | + apply-subst apply-current-subst |
53 | 37 | current-type-context modify-type-context |
54 | | - register-global-class-instance! constr->instances lookup-instance! |
55 | | - reduce-context type-reduce-context |
56 | 38 | attach-type attach-expected get-type get-expected apply-current-subst-in-tooltips |
57 | 39 | make-typed-var-transformer |
58 | | - |
59 | | - (for-template (all-from-out hackett/private/type-language) |
60 | | - local-class-instances @%superclasses-key)) |
| 40 | + (for-template (all-from-out hackett/private/type-language))) |
61 | 41 |
|
62 | 42 | ;; --------------------------------------------------------------------------------------------------- |
63 | 43 | ;; type operations |
|
173 | 153 |
|
174 | 154 | (define/contract (ctx-find-solution ctx x^) |
175 | 155 | (-> ctx? identifier? (or/c type? #f)) |
176 | | - (and~> (findf #{and (ctx:solution? %) (free-identifier=? x^ (ctx:solution-x^ %))} ctx) |
| 156 | + (and~> (findf #{and (ctx:solution? %) |
| 157 | + (free-identifier=? x^ (ctx:solution-x^ %))} ctx) |
177 | 158 | ctx:solution-t)) |
178 | | -(define/contract (current-ctx-solution x^) |
179 | | - (-> identifier? (or/c type? #f)) |
180 | | - (ctx-find-solution (current-type-context) x^)) |
181 | 159 |
|
182 | 160 | (define/contract (apply-subst ctx t) |
183 | 161 | (-> ctx? type? type?) |
|
226 | 204 | (-> (-> ctx? ctx?) void?) |
227 | 205 | (current-type-context (f (current-type-context)))) |
228 | 206 |
|
229 | | -;; --------------------------------------------------------------------------------------------------- |
230 | | -;; instance contexts |
231 | | - |
232 | | -(struct class:info (vars method-table default-methods superclasses deriving-transformer) #:transparent |
233 | | - #:property prop:procedure |
234 | | - (λ (info stx) |
235 | | - ((make-variable-like-transformer |
236 | | - #`(#%type:con #,(syntax-parse stx |
237 | | - [id:id #'id] |
238 | | - [(id:id . _) #'id]))) |
239 | | - stx))) |
240 | | -(struct class:instance (class vars subgoals ts dict-expr) #:transparent) |
241 | | - |
242 | | -(define-syntax-class (class-id #:require-deriving-transformer? [require-deriving-transformer? #f]) |
243 | | - #:description "class id" |
244 | | - #:attributes [local-value] |
245 | | - [pattern |
246 | | - {~var || (local-value class:info? #:failure-message "identifier was not bound to a class")} |
247 | | - #:fail-unless (or (not require-deriving-transformer?) |
248 | | - (class:info-deriving-transformer (attribute local-value))) |
249 | | - "class is not derivable"]) |
250 | | - |
251 | | -(define global-class-instances (make-gvector)) |
252 | | -(define/contract (register-global-class-instance! instance) |
253 | | - (-> class:instance? void?) |
254 | | - (gvector-add! global-class-instances instance)) |
255 | | - |
256 | | -(module local-instances-stxparam racket/base |
257 | | - (require (for-syntax racket/base) racket/stxparam) |
258 | | - (provide local-class-instances) |
259 | | - (define-syntax-parameter local-class-instances '())) |
260 | | -(require (for-template 'local-instances-stxparam)) |
261 | | - |
262 | | -(define/contract (current-class-instances) |
263 | | - (-> (listof class:instance?)) |
264 | | - (append (syntax-parameter-value #'local-class-instances) |
265 | | - (gvector->list global-class-instances))) |
266 | | - |
267 | | -(define (current-instances-of-class class) |
268 | | - (-> class:info? (listof class:instance?)) |
269 | | - (filter #{eq? class (class:instance-class %)} (current-class-instances))) |
270 | | - |
271 | 207 | ;; --------------------------------------------------------------------------------------------------- |
272 | 208 | ;; subsumption, instantiation, and elaboration |
273 | 209 |
|
|
394 | 330 | [_ (error 'type-inst-r! (format "internal error: failed to instantiate ~a to a supertype of ~a" |
395 | 331 | (type->string #`(#%type:wobbly-var #,x^)) (type->string t)))])) |
396 | 332 |
|
397 | | -; Performs one-way unification to see if a type matches another one. Unlike general unification, |
398 | | -; one-way matching is asymmetric: it only solves wobbly variables in the first type argument, never in |
399 | | -; the second. If unifying the two types would require unification in the second type, matching fails. |
400 | | -; Also, matching is more restricted than unification: it never instantiates quantifiers in other type, |
401 | | -; nor does it permit qualified types. If a quantifier or qualified type is encountered, matching |
402 | | -; fails. |
403 | | -(define/contract (types-match?! a b) |
404 | | - (-> type? type? boolean?) |
405 | | - (syntax-parse (list (apply-current-subst a) (apply-current-subst b)) |
406 | | - #:context 'match-types! |
407 | | - #:literal-sets [type-literals] |
408 | | - [[(#%type:rigid-var x^) (#%type:rigid-var y^)] |
409 | | - #:when (free-identifier=? #'x^ #'y^) |
410 | | - #t] |
411 | | - [[(#%type:wobbly-var x^) t] |
412 | | - #:when (type-mono? #'t) |
413 | | - (modify-type-context #{snoc % (ctx:solution #'x^ #'t)}) |
414 | | - #t] |
415 | | - [[(#%type:con a) (#%type:con b)] |
416 | | - #:when (free-identifier=? #'a #'b) |
417 | | - #t] |
418 | | - [[(#%type:app a b) (#%type:app c d)] |
419 | | - (and (types-match?! #'a #'c) (types-match?! #'b #'d))] |
420 | | - [[_ _] |
421 | | - #f])) |
422 | | - |
423 | | -;; --------------------------------------------------------------------------------------------------- |
424 | | - |
425 | | -(module superclasses-key racket/base |
426 | | - (require (for-syntax racket/base)) |
427 | | - (provide @%superclasses-key) |
428 | | - (define-syntax (@%superclasses-key stx) |
429 | | - (raise-syntax-error #f "cannot be used as an expression" stx))) |
430 | | -(require (for-template 'superclasses-key)) |
431 | | - |
432 | | -(define/contract constr->class:info+ts |
433 | | - (-> constr? (values class:info? (listof type?))) |
434 | | - (syntax-parser |
435 | | - #:context 'constr->class:info |
436 | | - #:literal-sets [type-literals] |
437 | | - [(~#%type:app* (#%type:con class-id:class-id) ts ...) |
438 | | - (values (attribute class-id.local-value) (attribute ts))])) |
439 | | - |
440 | | -; Given a constraint, calculate the instances it brings in scope, including instances that can be |
441 | | -; derived via superclasses. For example, the constraint (Monad m) brings in three instances, one for |
442 | | -; Monad and two for Functor and Applicative. |
443 | | -(define/contract (constr->instances constr dict-expr) |
444 | | - (-> constr? syntax? (listof class:instance?)) |
445 | | - (let-values ([(class-info ts) (constr->class:info+ts constr)]) |
446 | | - (let* ([ts* (map apply-current-subst ts)] |
447 | | - [instance (class:instance class-info '() '() ts* dict-expr)] |
448 | | - ; instantiate the superclass constraints, so for (Monad Unit), we get (Applicative Unit) |
449 | | - ; instead of (Applicative m) |
450 | | - [insts-dict (map cons (class:info-vars class-info) ts*)] |
451 | | - [super-constrs (map #{insts % insts-dict} (class:info-superclasses class-info))] |
452 | | - [superclass-dict-expr #`(free-id-table-ref #,dict-expr #'@%superclasses-key)] |
453 | | - [super-instances (for/list ([(super-constr i) (in-indexed (in-list super-constrs))]) |
454 | | - (constr->instances |
455 | | - super-constr |
456 | | - #`(vector-ref #,superclass-dict-expr '#,i)))]) |
457 | | - (cons instance (append* super-instances))))) |
458 | | - |
459 | | -; Attempts to unify a type with an instance head with a type for the purposes of picking a typeclass. |
460 | | -; If the match succeeds, it returns a list of instantiated subgoals for the instance, otherwise it |
461 | | -; returns #f. |
462 | | -(define/contract (unify-instance-head ts vars subgoals head) |
463 | | - (-> (listof type?) (listof identifier?) (listof constr?) (listof (and/c type? type-mono?)) |
464 | | - (or/c (listof constr?) #f)) |
465 | | - (let* ([vars^ (generate-temporaries vars)] |
466 | | - [var-subst (map #{cons %1 #`(#%type:wobbly-var #,%2)} vars vars^)] |
467 | | - [head-inst (map #{insts % var-subst} head)] |
468 | | - [subgoals-inst (map #{insts % var-subst} subgoals)]) |
469 | | - (and (andmap types-match?! head-inst ts) |
470 | | - subgoals-inst))) |
471 | | - |
472 | | -(define/contract (lookup-instance! |
473 | | - constr |
474 | | - #:src src |
475 | | - #:failure-thunk [failure-thunk |
476 | | - (λ () |
477 | | - (raise-syntax-error |
478 | | - 'typechecker |
479 | | - (~a "could not deduce " |
480 | | - (type->string (apply-current-subst constr))) |
481 | | - src))]) |
482 | | - (->* [constr? #:src syntax?] |
483 | | - [#:failure-thunk (-> any)] |
484 | | - any) ; (values class:instance? (listof constr?)) |
485 | | - (define-values [class ts] (constr->class:info+ts constr)) |
486 | | - (define ts* (map apply-current-subst ts)) |
487 | | - (define instance+subgoals |
488 | | - (for/or ([instance (in-list (current-instances-of-class class))]) |
489 | | - (let ([old-type-context (current-type-context)]) |
490 | | - (let ([constrs (unify-instance-head ts* |
491 | | - (class:instance-vars instance) |
492 | | - (class:instance-subgoals instance) |
493 | | - (class:instance-ts instance))]) |
494 | | - (if constrs (list instance constrs) |
495 | | - (begin (current-type-context old-type-context) #f)))))) |
496 | | - (if instance+subgoals |
497 | | - (apply values instance+subgoals) |
498 | | - (failure-thunk))) |
499 | | - |
500 | | -;; --------------------------------------------------------------------------------------------------- |
501 | | -;; context reduction |
502 | | - |
503 | | -; Context reduction is the process of simplifying contexts by pruning unnecessary constraints. |
504 | | -; Removing these constraints reduces the number of dictionaries that need to be passed, which is |
505 | | -; especially important in macro-enabled Hackett, since users might write macros that expand to |
506 | | -; constraints with redundant or otherwise unnecessary information. To avoid placing an unreasonable |
507 | | -; burden on macro authors to intelligently prune these contexts themselves, Hackett guarantees it will |
508 | | -; perform a certain amount of context reduction automatically. |
509 | | -; |
510 | | -; Implementing context reduction is a problem with a large design space, but fortunately, the various |
511 | | -; choices and their tradeoffs have been covered extensively in the paper “Type classes: an exploration |
512 | | -; of the design space”, available here: |
513 | | -; |
514 | | -; https://www.microsoft.com/en-us/research/wp-content/uploads/1997/01/multi.pdf |
515 | | -; |
516 | | -; Hackett implements context reduction based on the following rules: |
517 | | -; |
518 | | -; 1. Duplication constraint elimination. For example, the type: |
519 | | -; |
520 | | -; (forall [a] (Eq a) (Eq a) => ....) |
521 | | -; |
522 | | -; ...can be simplified to this type: |
523 | | -; |
524 | | -; (forall [a] (Eq a) => ....) |
525 | | -; |
526 | | -; 2. Superclass subsumption. Since subclass dictionaries include dictionaries for their |
527 | | -; superclasses, superclass constraints can be eliminated in favor of equivalent subclass ones. |
528 | | -; For example, the type: |
529 | | -; |
530 | | -; (forall [f] (Functor f) (Applicative f) => ....) |
531 | | -; |
532 | | -; ...can be simplified to this type: |
533 | | -; |
534 | | -; (forall [f] (Applicative f) => ....) |
535 | | -; |
536 | | -; 3. Discharging tautological constraints. A tautological constraint is a constraint that can be |
537 | | -; immediately resolved via an in-scope instance declaration. For example, the constraint |
538 | | -; (Eq Integer) always holds, so there is never any reason to include it in a context. |
539 | | -; |
540 | | -; This is complicated slightly by the fact that instances can also have contexts, so for a |
541 | | -; constraint to be tautological, all constraints in the chosen instance context must also be |
542 | | -; tautological. For example, given an instance: |
543 | | -; |
544 | | -; (instance (forall [a] (Eq a) => (Foo (Tuple a b))) ....) |
545 | | -; |
546 | | -; ...the constraint (Foo (Tuple Integer t)) is tautological regardless of t, since (Eq Integer) |
547 | | -; is tautological. |
548 | | - |
549 | | -(define/contract (constr-tautological? constr #:extra-constrs [extra-constrs '()]) |
550 | | - (->* [constr?] [#:extra-constrs (listof constr?)] boolean?) |
551 | | - (or (ormap #{type=? constr %} extra-constrs) |
552 | | - (match/values (lookup-instance! constr #:src #'here #:failure-thunk (λ () (values #f #f))) |
553 | | - [[#f _] #f] |
554 | | - [[_ subgoals] (andmap constr-tautological? subgoals)]))) |
555 | | - |
556 | | -(define/contract (superclasses-entail? constr-a constr-b) |
557 | | - (-> constr? constr? boolean?) |
558 | | - (match-let-values ([(class ts) (constr->class:info+ts constr-a)]) |
559 | | - (let* ([inst-dict (map cons (class:info-vars class) ts)] |
560 | | - [supers (map #{insts % inst-dict} (class:info-superclasses class))]) |
561 | | - (or (ormap #{types-match?! % constr-b} supers) |
562 | | - (ormap #{superclasses-entail? % constr-b} supers))))) |
563 | | - |
564 | | -(define/contract (constrs-entail? constrs constr) |
565 | | - (-> (listof constr?) constr? boolean?) |
566 | | - (and (or (member constr constrs type=?) |
567 | | - (ormap #{superclasses-entail? % constr} constrs)) |
568 | | - #t)) |
569 | | - |
570 | | -(define/contract (reduce-context constrs |
571 | | - #:extra-tautological-constrs [extra-tautological-constrs '()]) |
572 | | - (->* [(listof constr?)] [#:extra-tautological-constrs (listof constr?)] (listof constr?)) |
573 | | - (let loop ([constrs constrs] |
574 | | - [constrs* '()]) |
575 | | - (match constrs |
576 | | - [(cons constr remaining-constrs) |
577 | | - (loop remaining-constrs |
578 | | - (let ([reduced-context (append remaining-constrs constrs*)]) |
579 | | - (if (or (constr-tautological? |
580 | | - constr |
581 | | - #:extra-constrs (append reduced-context extra-tautological-constrs)) |
582 | | - (constrs-entail? reduced-context constr)) |
583 | | - constrs* |
584 | | - (cons constr constrs*))))] |
585 | | - [_ |
586 | | - (reverse constrs*)]))) |
587 | | - |
588 | | -(define/contract type-reduce-context |
589 | | - (-> type? type?) |
590 | | - (syntax-parser |
591 | | - #:context 'type-reduce-context |
592 | | - [(~#%type:forall* [x ...] {~and t_qual (~#%type:qual* [constr ...] t)}) |
593 | | - #:with [constr* ...] (reduce-context (attribute constr)) |
594 | | - (quasisyntax/loc/props this-syntax |
595 | | - (?#%type:forall* [x ...] #,(syntax/loc/props #'t_qual |
596 | | - (?#%type:qual* [constr* ...] t))))])) |
597 | | - |
598 | 333 | ;; ------------------------------------------------------------------------------------------------- |
599 | 334 |
|
600 | 335 | ; To support type tooltips, we attach 'mouse-over-tooltips properties to syntax objects whenever we |
|
0 commit comments