520 likes | 608 Views
Nested Refinements: A Logic for Duck Typing. ::. Ravi Chugh , Pat Rondon, Ranjit Jhala (UCSD). What are “Dynamic Languages”?. tag tests. affect control flow. dictionary objects. indexed by arbitrary string keys. first-class functions. can appear inside objects.
E N D
Nested Refinements: A Logic for Duck Typing :: Ravi Chugh, Pat Rondon, Ranjit Jhala (UCSD)
What are “Dynamic Languages”? tag tests affect control flow dictionary objects indexed by arbitrary string keys first-class functions can appear inside objects let onto callbacks f obj= if f = null then new List(obj, callbacks) else let cb =if tagof f =“Str” then obj[f]else f in new List(fun()-> cb obj, callbacks)
tag tests affect control flow dictionary objects indexed by arbitrary string keys first-class functions can appear inside objects • Problem: Lack of static types • … makes rapid prototyping / multi-language applications easy • … makes reliability / performance / maintenance hard • This Talk: System D • … a type system for these features
tag tests affect control flow dictionary objects indexed by arbitrary string keys first-class functions can appear inside objects Usability Our Approach: Quantifier-free formulas F≤ 1. increase expressiveness 2. retain level of automation ∨, ∧ refinement types nested refinements occurrence types … Coq syntactic approaches dependent approaches Expressiveness
tag tests affect control flow dictionary objects indexed by arbitrary string keys x :: {|tag()=“Int”tag()=“Bool”} {|tag()=“Dict” tag(sel(,“n”))=“Int” tag(sel(,m))=“Int”} d :: first-class functions can appear inside objects Challenge: Functions inside dictionaries
Key Idea: Nested Refinements 1 + d[f](0) • {|tag()=“Dict” • sel(,f):: • } d :: {|tag()=“Int”} {|tag()=“Int”} uninterpreted predicate“x::U” says“x has-type U” syntactic arrow type…
Key Idea: Nested Refinements 1 + d[f](0) • {|tag()=“Dict” • sel(,f):: • } d :: {|tag()=“Int”} {|tag()=“Int”} uninterpreted predicate“x::U” says“x has-type U” syntactic arrow type… … but uninterpreted constant in the logic
Key Idea: Nested Refinements • All values described by refinement formulas T ::= {|p} • “Has-type” predicate for arrows in formulas p ::= … | x::y:T1T2 • Can express idioms of dynamic languages • Automatic type checking • Decidable refinement logic • Subtyping = SMT Validity + Syntactic Subtyping
Outline Intro Examples Subtyping Type Soundness Conclusion
x:{|tag()=“Int”tag()=“Bool”} {|tag()=tag(x)} x:IntOrBool{|tag()=tag(x)} let negate x = if tagof x =“Int” then 0 – x else not x tagof :: y:Top{|=tag(y)} y:{|true}
✓ x:IntOrBool{|tag()=tag(x)} let negate x = if tagof x =“Int” then 0 – x else not x type environment Γ tag(x)=“Int” x :: 0 - x:: x :: IntOrBool {|Int()} {|Int()} ✓ SMT Solver ✓
✓ x:IntOrBool{|tag()=tag(x)} let negate x = if tagof x =“Int” then 0 – x else not x type environment Γ not(tag(x)=“Int”) x :: not x:: x :: IntOrBool {|Bool()} {|Bool()} ✓ SMT Solver ✓
Nesting structure hidden with syntactic sugar x:IntOrBool{|tag()=tag(x)} x: IntOrBool {|tag()=tag(x)} {|:: }
Dictionary Operations Types in terms of McCarthy operators mem :: get :: set :: d:Dictk:Str{| =true has(d,k)} d:Dictk:{|has(d,)}{| =sel(d,k)} d:Dictk:Strx:Top{| =upd(d,k,x)}
d:Dictc:StrInt get d c let getCount d c = if mem d c then toInt (d[c])else 0 safe dictionary key lookup {| =true has(d,c)}
d:Dictc:StrInt let getCount d c = if mem d c then toInt (d[c])else 0 tag(sel(,c))=“Int” d:Dictc:Str{|EqMod(,d,c) Int([c])} let incCount d c = let i = getCountd c in{d with c = i + 1} set d c (i+1)
Adding Type Constructors T ::= {|p} p ::= … | x::U U ::= y:T1T2 | A | ListT | Null “type terms”
let apply f x = fx ∀A,B.{|::{|::A}{|::B}} {|::A} {|::B} ∀A,B.(AB)AB
let dispatch d f = d[f](d) ∀A,B.d:{|::A}f:{|d[]::AB}{|::B} a form of“bounded quantification” d::A but additional constraints on A ≈ ∀A<:{f:AB}.d::A
∀A,B. {|::AB}{|::List[A]}{|::List[B]} ∀A,B.(AB)List[A]List[B] letmapfxs = if xs=null then null else new List(fxs[“hd”], map f xs[“tl”]) encode recursive data as dictionaries
let filterfxs = if xs=null then null else if not (f xs[“hd”]) then filterf xs[“tl”] else new List(xs[“hd”], filterf xs[“tl”]) ∀A,B.(x:A{|n=truex::B} List[A]List[B] usual definition,but an interesting type
Outline Intro Examples Subtyping Type Soundness Conclusion
type environment Γ _ | SMT Γ=42tag()=“Int” applyInt (42,negate) applyInt :: negate :: x:IntOrBool{|tag()=tag(x)} (Int,IntInt)Int ✓ Γ {|=42}<Int
type environment Γ …negate::x:IorB{|tag()=tag(x)} …=negate ::IntInt SMT _ | applyInt (42,negate) applyInt :: negate :: x:IntOrBool{|tag()=tag(x)} (Int,IntInt)Int Γ {|=negate}<{|::IntInt}
type environment Γ …negate::x:IorB{|tag()=tag(x)} …=negate ::IntInt SMT _ | applyInt (42,negate) applyInt :: negate :: x:IntOrBool{|tag()=tag(x)} (Int,IntInt)Int distinct uninterpreted constants! ✗ Γ {|=negate}<{|::IntInt}
Invalid, since these are uninterpreted constants ::x:IorB{|tag()=tag(x)} ::IntInt tag()=“Int” tag()=“Int” tag()=“Bool” tag()=“Int”tag()=tag(x) tag()=“Int” ✗ Want conventional syntactic subtyping Int <: IorB {|tag()=tag(x)} <: Int ✓ ✓ IorB {|tag()=tag(x)} <: Int Int
Subtyping with Nesting To prove pq: 1) Convert qto CNF clauses (q11…)…(qn1…) 2) For each clause, discharge some literal qij as follows: base predicate:pqij anything except x::U e.g. tag()=tag(x) e.g. tag(sel(d,k))=“Int”
Subtyping with Nesting To prove pq: 1) Convert qto CNF clauses (q11…)…(qn1…) 2) For each clause, discharge some literal qij as follows: base predicate:pqij “has-type” predicate:px::U Subtyping Subtyping Arrow Rule Implication Implication U’<:U px::U’ pqij SMT Solver SMT Solver
applyInt (42,negate) …negate::x:IorB{|tag()=tag(x)} …=negate ::x:IorB{|tag()=tag(x)} UninterpretedReasoning _ | + SyntacticReasoning Γ x:IorB{|tag()=tag(x)}<:IntInt _ Γ {|=negate}<{|::IntInt} |
Outline Intro Examples Subtyping Type Soundness Conclusion
x:Tx,Γ e[::T v :: Tx and If SubstitutionLemma Γ[v/x] e[v/x]::T[v/x] then _ _ _ | | | independent of 0, and just echoes the binding from the environment x.x+1 ::{|::IntInt} _ _ _ f:{|::IntInt} 0::{|f::IntInt} | | | 0::{|x.x+1::IntInt}
x:Tx,Γ e[::T v :: Tx and If SubstitutionLemma Γ[v/x] e[v/x]::T[v/x] then _ _ _ | | | SMT =0x.x+1::IntInt 1st attempt ✗ _ 0::{|=0} {|=0}<{|x.x+1::IntInt} | 0::{|x.x+1::IntInt}
x:Tx,Γ e[::T v :: Tx and If SubstitutionLemma ✗ Γ[v/x] e[v/x]::T[v/x] then _ _ _ | | | ✗ SMT =0::U’ + 2nd attempt Arrow U’ <:IntInt _ 0::{|=0} {|=0}<{|x.x+1::IntInt} | 0::{|x.x+1::IntInt}
[S-Valid-Uninterpreted] • Rule not closed under substitution • Interpret formulas by “hooking back” into type system • Stratification to create ordering for induction I I iff = = | | [S-Valid-Interpreted] SMT Γpq x.x+1::{|::IntInt} Γpq n _ | n Γ {|=p}<{|=q} Γ {|=p}<{|=q} _ _ x.x+1::IntInt | | n n-1
Type Soundness StratifiedSubstitutionLemma x:Tx,Γ e[::T If n _ _ _ _ v :: Tx and | | | | n Γ[v/x] e[v/x]::T[v/x] then n+1 • “Level 0” for type checking source programs, • using only [S-Valid-Uninterpreted] Stratified Preservation e v e[::T and If 0 _ v[::T for some m then | m • artifact of the metatheory
Recap • Dynamic languages make heavy use of: • run-time tag tests, dictionary objects, lambdas • Nested refinements • generalizes refinement type architecture • enables combination of dictionaries and lambdas • Decidable refinement logic • all proof obligations discharged algorithmically • novel subtyping decomposition to retain precision • Syntactic type soundness
Future Work • Imperative Updates • Inheritance (prototypes in JS, classes in Python) • Applications • More local type inference / syntactic sugar • Dictionaries in statically-typed languages
Thanks! D :: ravichugh.com/nested
Macros {|::} x:T1T2 x:T1T2 • Types • Formulas • Logical Values EqMod(d,d’,k) Int has(d,k) Str(x) x.k x[k] {|tag()=“Int”} tag(x) = “Str” ∀k’. k’!=k sel(d,k)!=sel(d’,k) sel(d,k)!=bot sel(n,“k”) sel(n,k)
Onto functional version of Dojo function let onto callbacks f obj= if f = null then new List(obj,callbacks) else let cb =if tagof f =“Str” then obj[f]else f in new List(fun()-> cb obj, callbacks) ∀A. callbacks:List[TopTop] f:{|=null Str()::ATop} obj:{|::A (f=null::AInt) (Str(f)[f]::AInt)} List[TopTop] onto ::
Onto (2) functional version of Dojo function let onto (callbacks,f,obj)= if f = null then new List(obj,callbacks) else let cb =if tagof f =“Str” then obj[f]else f in new List(fun()-> cb obj, callbacks) callbacks:List[TopTop] *f:{g|g=null Str(g)g::{x|x=obj}Top} *obj:{o|(f=nullo::{x|x=o}Int) (Str(f)o[f]::{x|x=o}Int)} List[TopTop] onto ::
Approach: Refinement Types • Reuse refinement type architecture • Find a decidable refinement logic for • Tag-tests • Dictionaries • Lambdas • Define nested refinement type architecture ✓ ✓ ✗ ✓*
Nested Refinements • Refinement formulas over a decidable logic • uninterpreted functions, McCarthy arrays, linear arithmetic • Only base values refined by formulas All values T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | …| x :: U T ::= {|p} | x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … traditional refinements
Nested Refinements • Refinement formulas over a decidable logic • uninterpreted functions, McCarthy arrays, linear arithmetic • Only base values refined by formulas • “has-type” allows “type terms” in formulas All values T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U T ::= {|p} | x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … traditional refinements
Nested Refinements • Refinement formulas over a decidable logic • uninterpreted functions, McCarthy arrays, linear arithmetic • Only base values refined by formulas • “has-type” allows “type terms” in formulas All values T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
Subtyping (Traditional Refinements) T ::= {|p} | x:T1T2 Subtyping traditional refinements tag()=“Int”true Implication Int <: Top SMT Solver
Subtyping (Traditional Refinements) T ::= {|p} | x:T1T2 Subtyping traditional refinements tag()=“Int” true tag()=“Int” tag()=“Int” Implication SMT Solver Int <: Top Int <: Int Top Int <: Int Int