820 likes | 947 Views
Nested Refinement Types for Dynamic Languages. Ravi Chugh. What are “Dynamic” Languages?. Untyped, and characterized by common features. Reflective type-tests typeof x == ‘number’ Dictionary-style objects obj [ key ] = val “Duck typing” if ( duck.quack ) { duck.quack () }
E N D
Nested Refinement Typesfor Dynamic Languages Ravi Chugh
What are “Dynamic” Languages? Untyped, and characterized by common features • Reflective type-tests typeof x == ‘number’ • Dictionary-style objects obj[key] = val • “Duck typing” if(duck.quack){ duck.quack()} • Dynamic code generation eval(‘var x = 1’) • Rich string primitives arr.join(‘,’)
Why Study Dynamic Languages? http://stackoverflow.com/tags 11-11-11
Why Study Dynamic Languages? Pervasive, thanks to the web • No static checking rapid prototyping • String libraries + code genmulti-language systems Important, thanks to the web • Large applications written in these languages • Reliability matters: security, availability, extensibility • Performance matters: the new Browser Wars • No static checking reliability and performance is hard JavaScript is at the heart of this
Isn’t JavaScript a Terrible Language? undefined value scope manipulation primitive typemanipulation implicit updates toglobal object {} typeofx := 1super implicit coercion misleading syntax eval() weird comparasions NaN != NaN Infinity == Infinity ‘,,,’ == new Array(4) implicitvar lifting
Isn’t JavaScript Awesome?! Core consists of bread-and-butter features {} typeofx := 1super These features not going away, nor should they* Found in many languages, typed and untyped Lambdas in a mainstream language! • Core features are difficult good for research • Core features are expressive good for practice JavaScript is evolving
Research Plan System D lambdas, dictionaries, tag-tests + explicit references + prototype-based inheritance Dependent JavaScript translates to D++ Applications in DJS DJS {} typeofx := 1super + prototypes + references D
Research Plan System D lambdas, dictionaries, tag-tests + explicit references + prototype-based inheritance Dependent JavaScript translates to D++ Applications in DJS DJS {} typeofx := 1super + prototypes + references D
Research Plan System D lambdas, dictionaries, tag-tests + explicit references + prototype-based inheritance Dependent JavaScript translates to D++ Applications in DJS DJS {} typeofx := 1super + prototypes + references D
Motivating Example tag-tests affect control flow iftagoff=“Str”thend.n + d[f](0) elsed.n + f(0) dictionaries indexed by string literals or arbitrary values first-class functions can appear inside dictionaries
Approach: Refinement Types tag-tests Type environment tracks control flow predicates x :: {|tag()=“Int”tag()=“Bool”} x :: {|tag()=“Bool”} x :: {|tag()=“Int”} iftagofx=“Int”then 0 - xelse notx
Approach: Refinement Types tag-tests d.n + d[m] dictionaries {|tag()=“Dict” tag(sel(,“n”))=“Int” tag(sel(,m))=“Int”} d :: McCarthy axioms
Approach: Refinement Types tag-tests 1 +f(0) dictionaries first-class functions x.x :: f :: x:{|tag()=“Int”}{|tag()=“Int”} x:{|tag()=“Int”}{|n=x} Clear separation of base and arrow types T ::= {|p} | x:T1T2
Approach: Refinement Types tag-tests 1 +f(0) dictionaries first-class functions
Approach: Refinement Types tag-tests 1 + d[f](0) dictionaries d :: {|tag()=“Dict” sel(,f) ??? } first-class functions Key Challenges How to describe arrow inside formula? How to keep type checking decidable?
Approach • Reuse refinement type architecture • Find a decidable refinement logic for • Tag-tests • Dictionaries • Lambdas • Define nested refinement type architecture ✓ ✓ ✗ ✓*
Nested Refinements 1 + d[f](0) • {|tag()=“Dict” • sel(,f):: • } d :: {|tag()=“Int”} {|tag()=“Int”} uninterpreted predicate“x::U” says“x has-type U” uninterpreted constantin the logic… … but syntactic arrowin the type system!
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
let foofd = iftagoff=“Str”thend.n + d[f](0) elsed.n + f(0) f:{|Str()::IntInt} d:{|Dict() Int(.n) Str(f)[f]::IntInt} Int foo :: T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
let foofd = iftagoff=“Str”thend.n + d[f](0) elsed.n + f(0) f:{|Str()::IntInt} d:{|Dict() Int(.n) Str(f)[f]::IntInt} Int foo :: T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
let foofd = iftagoff=“Str”thend.n + d[f](0) elsed.n + f(0) {|tag()=“Int”} {|tag()=“Int”} f:{|Str()::IntInt} d:{|Dict() Int(.n) Str(f)[f]::IntInt} Int foo :: tag()=“Str” T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
let foofd = iftagoff=“Str”thend.n + d[f](0) elsed.n + f(0) f:{|Str()::IntInt} d:{|Dict() Int(.n) Str(f)[f]::IntInt} Int foo :: T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
let foofd = iftagoff=“Str”thend.n + d[f](0) elsed.n + f(0) sel(n,“n”) sel(n,f) f:{|Str()::IntInt} d:{|Dict() Int(.n) Str(f)[f]::IntInt} Int foo :: T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
let foofd = iftagoff=“Str”thend.n + d[f](0) elsed.n + f(0) f:{|Str()::IntInt} d:{|Dict() Int(.n) Str(f)[f]::IntInt} Int foo :: T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
let foofd = iftagoff=“Str”thend.n + d[f](0) elsed.n + f(0) foo :: f: {|tag()=“Str”::} IntInt d: {|tag()=“Dict” tag(sel(,“n”))=“Int” tag(f)=“Str” sel(,f):: } {|:: {|:: IntInt T ::= {|p}U ::= x:T1T2p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U Int } }
Nested Refinements • Type Language • Subtyping • Extensions • Recap
Subtyping T ::= {|p} | x:T1T2 Subtyping Top Int {|true} {|tag()=“Int”} traditional refinements tag()=“Int”true Implication Int <: Top SMT Solver
Subtyping T ::= {|p} | x:T1T2 Subtyping Top Int {|true} {|tag()=“Int”} traditional refinements tag()=“Int” true tag()=“Int” tag()=“Int” Implication SMT Solver Int <: Top Int <: Int Top Int <: Int Int
Subtyping T ::= {|p} | x:T1T2 Subtyping Arrow Rule Top Int {|true} {|tag()=“Int”} traditional refinements tag()=“Int” true tag()=“Int” tag()=“Int” Implication SMT Solver Int <: Top Int <: Int Top Int <: Int Int
Subtyping T ::= {|p} | x:T1T2 Subtyping Arrow Rule traditional refinements • Decidable if: • Only values in formulas • Underlying theories decidable Implication SMT Solver • With nested refinements: • No new theories • But implication is imprecise!
Subtyping with Nesting Subtyping Implication ✗ ::TopInt::IntInt SMT Solver Invalid, as these are distinct uninterpreted constants
Subtyping with Nesting When goal is base predicate:pq When goal is “has-type” predicate:px::U Subtyping Subtyping Arrow Rule Implication Implication U’<:U px::U’ pq SMT Solver SMT Solver
Subtyping with Nesting UninterpretedReasoning Normalize formulas to subdivide obligations appropriately SyntacticReasoning + p ::TopInt TopInt<:IntInt p::Int Int
Nested Refinements • Type Language • Subtyping • Extensions • Recap
Extensions • Simple to add additional type constructors • Extend the grammar of type terms • Add additional syntactic subtyping rules U ::= x:T1T2 | A | List[T] | Null SyntacticRules Arrow Rule Covariant List Rule Null List Rule
Map ∀A,B. {|::AB}{|::List[A]}{|::List[B]} let mapfxs = ifxs=nullthen null else f xs[“hd”] :: map f xs[“tl”] encode recursive data as dictionaries
Filter let filterfxs = ifxs=nullthen null else if f xs[“hd”]then xs[“hd”] :: filter f xs[“tl”] else filter f xs[“tl”] • ∀A,B.{|::x:A{|n=Truex::B}} • {|::List[A]} • {|::List[B]} usual definition, but an interesting type
Dispatch let dispatch d f=d[f] d • ∀A,B.d:{|Dict() ::A} • {|Str() d[]::AB} • {|::B} a form of “bounded quantification” since d::A but additional constraints on A
Recap • Refinement types are a compelling approach • Dynamic dictionaries require dependency • Tag-tests require path sensitivity • But, not enough for lambdas in dictionaries • Nested refinement types are a clean solution • Natural way to describe dynamic idioms • Novel subtyping remains decidable and automatic • Interesting soundness proof
Research Plan System D lambdas, dictionaries, tag-tests + explicit references + prototype-based inheritance Dependent JavaScript translates to D++ Applications in DJS DJS {} typeofx := 1super + prototypes + references D
Research Plan System D lambdas, dictionaries, tag-tests + explicit references + prototype-based inheritance Dependent JavaScript translates to D++ Applications in DJS DJS {} typeofx := 1super + prototypes + references D
System D functionalupdate let x = {} in let x’ = x with “f” = 1 in x’.f JavaScript var x = {} x.f= 1 x.f x’ :: x :: {|=upd(x,“f”,1)} {|=empty} mutation!
System !D = D + Explicit References Update to reference cell doesn’t affect type So this dictionary read does not type check! JavaScript System !D var x = {} x.f= 1 x.f let x = ref {} in let _ = x := (!x with “f” = 1)in let _ = (!x)[“f”] x stores value of type {|Dict()} need to strongly update reference type
System !D = D + Explicit References • In JS, every dictionary is stored in a reference • No strong update cannot extend dictionary! • So, we must support flow-sensitive invariants var x = {} x.f= 1 x.f let x = ref {} in let _ = x := (!x with “f” = 1)in let _ = (!x)[“f”]
Strong Update à la Alias Types • Types are flow-insensitive (as usual) • But heap types are flow-sensitive • Mappings from locations to types can change U ::= … | RefL let x = ref {} in let _ = x := (!x with “f” = 1)in let _ = (!x)[“f”] x :: {|::RefL} safe given the updated heap type L::d: {|=empty} L::d’: {|=upd(d,“f”,1)}
One Last Feature: Prototypes • Each object is backed by a prototype object • Can be any ordinary object • Immutable link set at construction time* • Object read x[k] • If x has key k, return the binding • Else, recurse on x.__proto__[k] • Object write x[k]=y • does not affect prototype chain
var grandpa = { “surname” = “Quackenboss”, “saying” = “When I was your age…”, “age” = 77 } var parent = { “age” = 44 } var child = { “age” = 22 } var a = child.age Assume proto chain is: 1. does childhave age? 2. what is type of child.age? grandpa a :: a :: a :: {| = 22 = 44 = 77} {| = 22} {|Int()} parent child
System !D += Prototypes L3:: dGrandpa: L2:: dParent: L1:: dChild: Unknown Part of Heap • Track prototype links in heap type • immutable and acyclic • New uninterpreted predicate ObjHas(H,L,k) • analog to has(d,k) (macro forsel(d,k) != bot ) • New uninterpreted function ObjSel(H,L,k) • analog to sel(d,k) H T3 T2 T1 grandpa :: parent :: child :: {|::RefL3} {|::RefL2} {|::RefL1}