Improve letrec-values type-checking
Split into two passes in order to be able to find more cases where the RHS doesn't refer to the bindings in the letrec. Closes PR 13124 original commit: bdb8833fa501a068f8fcebcf89cbf7c071a6c9c6
This commit is contained in:
parent
f90c6f92b0
commit
fbf9614f02
|
@ -127,31 +127,39 @@
|
|||
[(var) (add-scoped-tvars b (lookup-scoped-tvars #'var))]
|
||||
[_ (void)]))
|
||||
|
||||
(let loop ([names names] [exprs exprs] [flat-names orig-flat-names] [clauses clauses])
|
||||
;; First look at the clauses that do not bind the letrec names
|
||||
(define all-clauses
|
||||
(for/list ([name-lst names] [expr exprs] [clause clauses])
|
||||
(lr-clause name-lst expr clause)))
|
||||
|
||||
(define-values (ordered-clauses remaining)
|
||||
(get-non-recursive-clauses all-clauses orig-flat-names))
|
||||
|
||||
(define-values (remaining-names remaining-exprs remaining-clauses)
|
||||
(for/lists (_1 _2 _3) ([remaining-clause remaining])
|
||||
(match-define (lr-clause name expr clause) remaining-clause)
|
||||
(values name expr clause)))
|
||||
|
||||
;; Check those and gather an environment for use below
|
||||
(define-values (env-names env-types)
|
||||
(check-non-recursive-clauses ordered-clauses))
|
||||
|
||||
(with-lexical-env/extend env-names env-types
|
||||
(cond
|
||||
;; after everything, check the body expressions
|
||||
[(null? names)
|
||||
[(null? remaining-names)
|
||||
(do-check void null null null form null body null expected #:abstract orig-flat-names)]
|
||||
;; if none of the names bound in the letrec are free vars of this rhs
|
||||
[(not (ormap (lambda (n) (member n flat-names bound-identifier=?))
|
||||
(free-vars (car exprs))))
|
||||
;; then check this expression separately
|
||||
(with-lexical-env/extend
|
||||
(list (car names))
|
||||
(list (match (get-type/infer (car names) (car exprs) (lambda (e) (tc-expr/maybe-expected/t e (car names)))
|
||||
tc-expr/check)
|
||||
[(tc-results: ts) ts]))
|
||||
(loop (cdr names) (cdr exprs) (apply append (cdr names)) (cdr clauses)))]
|
||||
[else
|
||||
(define flat-names (apply append remaining-names))
|
||||
(do-check (lambda (stx e t) (tc-expr/check e t))
|
||||
names
|
||||
remaining-names
|
||||
;; compute set of variables that can't be undefined. see below.
|
||||
(let-values
|
||||
([(safe-bindings _)
|
||||
(for/fold ([safe-bindings '()] ; includes transitively-safe
|
||||
[transitively-safe-bindings '()])
|
||||
([names (in-list names)]
|
||||
[clause (in-list clauses)])
|
||||
([names (in-list remaining-names)]
|
||||
[clause (in-list remaining-clauses)])
|
||||
(case (safe-letrec-values-clause? clause transitively-safe-bindings flat-names)
|
||||
;; transitively safe -> safe to mention in a subsequent rhs
|
||||
[(transitively-safe) (values (append names safe-bindings)
|
||||
|
@ -167,10 +175,59 @@
|
|||
l)
|
||||
types-from-user
|
||||
(map (λ (x) (Un x -Undefined)) types-from-user)))))
|
||||
names))
|
||||
remaining-names))
|
||||
;; types the user gave. check against that to error if we could get undefined
|
||||
(map (λ (l) (ret (map get-type l))) names)
|
||||
form exprs body clauses expected)]))))
|
||||
(map (λ (l) (ret (map get-type l))) remaining-names)
|
||||
form remaining-exprs body remaining-clauses expected)]))))
|
||||
|
||||
;; An lr-clause is a
|
||||
;; (lr-clause (Listof Identifier) Syntax Syntax)
|
||||
;;
|
||||
;; interp. represents a letrec binding
|
||||
(struct lr-clause (names expr clause) #:transparent)
|
||||
|
||||
;; get-non-recursive-clauses : (Listof lr-clause) (Listof Identifier) ->
|
||||
;; (Listof lr-clause) (Listof )
|
||||
;; Find an approximation of letrec clauses that do not create variable cycles
|
||||
;;
|
||||
;; Note: this is currently approximate, but using Tarjan's algorithm would yield
|
||||
;; an optimal ordering for checking these clauses.
|
||||
(define (get-non-recursive-clauses clauses flat-names)
|
||||
(define-values (non-recursive remaining _ignore)
|
||||
(for/fold ([non-recursive '()]
|
||||
[remaining '()]
|
||||
[flat-names flat-names])
|
||||
([clause clauses])
|
||||
(match-define (lr-clause names expr _) clause)
|
||||
(cond [(not (ormap (lambda (n) (member n flat-names bound-identifier=?))
|
||||
(free-vars expr)))
|
||||
(values (cons clause non-recursive)
|
||||
remaining
|
||||
(remove* names flat-names bound-identifier=?))]
|
||||
[else
|
||||
(values non-recursive
|
||||
(cons clause remaining)
|
||||
flat-names)])))
|
||||
(values (reverse non-recursive) remaining))
|
||||
|
||||
;; check-non-recursive-clauses : (Listof lr-clause) ->
|
||||
;; (Listof Identifier) (Listof Type)
|
||||
;; Given a list of non-recursive clauses, check the clauses in order and
|
||||
;; build up a type environment for use in the second pass.
|
||||
(define (check-non-recursive-clauses clauses)
|
||||
(let loop ([clauses clauses] [env-ids '()] [env-types '()])
|
||||
(cond [(null? clauses) (values env-ids env-types)]
|
||||
[else
|
||||
(match-define (lr-clause names expr _) (car clauses))
|
||||
(define results
|
||||
(get-type/infer names expr
|
||||
(lambda (e) (tc-expr/maybe-expected/t e names))
|
||||
tc-expr/check))
|
||||
(match-define (tc-results: types) results)
|
||||
(with-lexical-env/extend names types
|
||||
(loop (cdr clauses)
|
||||
(cons names env-ids)
|
||||
(cons types env-types)))])))
|
||||
|
||||
;; determines whether any of the variables bound in the given clause can have an undefined value
|
||||
;; in this case, we cannot trust the type the user gave us and must union it with undefined
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
#lang typed/racket/base
|
||||
|
||||
;; Test for PR 13124
|
||||
|
||||
(: foo (-> Flonum))
|
||||
(define (foo)
|
||||
(: bn (Flonum -> Flonum))
|
||||
(define (bn n)
|
||||
(cond [(= n 0.0)
|
||||
1.0]
|
||||
[else (let loop ([s 0.0] [i 0.0])
|
||||
(cond [(i . < . n)
|
||||
(loop (+ s (bn i))
|
||||
(+ i 1.0))]
|
||||
[else s]))]))
|
||||
;; we want this `v` to type-check without extra annotation
|
||||
(define v 0.0)
|
||||
v)
|
||||
|
||||
;; simpler version
|
||||
(: foo2 (-> Integer))
|
||||
(define (foo2)
|
||||
(: bn (Integer -> Integer))
|
||||
(define (bn n)
|
||||
(if (= n 0)
|
||||
1 (bn (- n 1))))
|
||||
(define v 1)
|
||||
0)
|
Loading…
Reference in New Issue
Block a user