Nested Refinement Typesfor Dynamic Languages
Ravi Chugh
2
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(‘,’)
3
Why Study Dynamic Languages?
http://stackoverflow.com/tags11-11-11
Count Tag235,334 C#175,865 Java147,557 JavaScript
95,609 C++80,941 Python44,447 C33,733 Ruby
4,652 Haskell1,265 Scheme
691 OCaml
4
Why Study Dynamic Languages?
Pervasive, thanks to the web• No static checking rapid prototyping• String libraries + code gen multi-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
5
Isn’t JavaScript a Terrible Language?
{}
typeofx := 1super
scope manipulation
weird comparasionsNaN != NaN
Infinity == Infinity ‘,,,’ == new Array(4)
implicit updates toglobal object
primitive typemanipulation
misleading syntax
implicitvar lifting
undefined value
eval()
implicit coercion
6
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
7
Research Plan
+ prototypes+ references
DJS
{}typeofx := 1super
D
System D lambdas, dictionaries, tag-tests
+ explicit references
+ prototype-based inheritance
Dependent JavaScript translates to D++
Applications in DJS
8
Research Plan
+ prototypes+ references
DJS
{}typeofx := 1super
D
System D lambdas, dictionaries, tag-tests
+ explicit references
+ prototype-based inheritance
Dependent JavaScript translates to D++
Applications in DJS
1.
2.
3.
9
Research Plan
+ prototypes+ references
DJS
{}typeofx := 1super
D
System D lambdas, dictionaries, tag-tests
+ explicit references
+ prototype-based inheritance
Dependent JavaScript translates to D++
Applications in DJS
1.
2.
3.
10
Motivating Example
if tagof f = “Str”then d.n + d[f](0) else d.n + f(0)
indexed by string literals
can appear inside dictionaries
affect control flow
or arbitrary values
tag-tests
dictionaries
first-class functions
11
Approach: Refinement Types
if tagof x = “Int” then 0 - x else not x
tag-tests
Type environment tracks control flow predicates
{|tag()=“Int”tag()=“Bool”}x ::
{|tag()=“Bool”}x ::
{|tag()=“Int”}x ::
12
Approach: Refinement Types
d.n + d[m]
d,k,k’,x. kk’
sel(upd(d,k,x),k) = x
sel(upd(d,k,x),k’) = sel(d,k’)
sel(empty,k) = bot
McCarthy axioms
{|tag()=“Dict” tag(sel(,“n”))=“Int”tag(sel(,m))=“Int”}
d ::
dictionaries
tag-tests
13
Approach: Refinement Types
1 + f (0)
first-class functions
dictionaries
tag-tests
Clear separation of base and arrow typesT ::= {|p} | x:T1T2
x:{|tag()=“Int”}{|tag()=“Int”}f ::
x:{|tag()=“Int”}{|n=x}x.x ::
14
Approach: Refinement Types
1 + f (0)
first-class functions
dictionaries
tag-tests
15
Approach: Refinement Types
1 + d[f](0)
{|tag()=“Dict”
sel(,f) ??? }d ::
first-class functions
dictionaries
tag-tests
Key Challenges
1. How to describe arrow inside formula?
2. How to keep type checking decidable?
16
• Reuse refinement type architecture
• Find a decidable refinement logic for– Tag-tests– Dictionaries– Lambdas
• Define nested refinement type architecture
Approach
✓
✗✓✓*
17
Nested Refinements
{|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!
1 + d[f](0)
18
Nested Refinements
T ::= {|p}U ::= x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x :: U
• Refinement formulas over a decidable logic– uninterpreted functions, McCarthy arrays, linear arithmetic
• Only base values refined by formulasAll values
T ::= {|p} | x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | …
traditional refinements
19
Nested Refinements
T ::= {|p}U ::= x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
• 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} | x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | …
traditional refinements
20
Nested Refinements
T ::= {|p}U ::= x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
• 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
21
let foo f d = if tagof f = “Str” then d.n + d[f](0) else d.n + f(0)
T ::= {|p}U ::= x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
f:{|Str():: IntInt }
d:{|Dict()
Int(.n)
Str(f) [f] :: IntInt }
Int
foo ::
22
let foo f d = if tagof f = “Str” then d.n + d[f](0) else d.n + f(0)
T ::= {|p}U ::= x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
f:{|Str():: IntInt }
d:{|Dict()
Int(.n)
Str(f) [f] :: IntInt }
Int
foo ::
23
let foo f d = if tagof f = “Str” then d.n + d[f](0) else d.n + f(0)
T ::= {|p}U ::= x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
f:{|Str():: IntInt }
d:{|Dict()
Int(.n)
Str(f) [f] :: IntInt }
Int
foo ::
tag() = “Str”
{|tag()=“Int” }
{|tag()=“Int” }
24
let foo f d = if tagof f = “Str” then d.n + d[f](0) else d.n + f(0)
T ::= {|p}U ::= x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
f:{|Str():: IntInt }
d:{|Dict()
Int(.n)
Str(f) [f] :: IntInt }
Int
foo ::
25
let foo f d = if tagof f = “Str” then d.n + d[f](0) else d.n + f(0)
T ::= {|p}U ::= x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
f:{|Str():: IntInt }
d:{|Dict()
Int(.n)
Str(f) [f] :: IntInt }
Int
foo ::
sel(n,“n”) sel(n,f)
26
let foo f d = if tagof f = “Str” then d.n + d[f](0) else d.n + f(0)
T ::= {|p}U ::= x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
f:{|Str():: IntInt }
d:{|Dict()
Int(.n)
Str(f) [f] :: IntInt }
Int
foo ::
27
{|::
}
{|::
} Int
d: {|tag()=“Dict”
tag(sel(,“n”))=“Int”
tag(f)=“Str”
sel(,f) :: }IntInt
f:{|tag()=“Str”:: }IntInt
let foo f d = if tagof f = “Str” then d.n + d[f](0) else d.n + f(0)
T ::= {|p}U ::= x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
foo ::
28
Nested Refinements
• Type Language
• Subtyping
• Extensions
• Recap
29
Subtyping
T ::= {|p} | x:T1T2
traditional refinements
Implication
SMT Solver
Subtyping
{|true }Top
tag()=“Int” true
{|tag()=“Int” }Int
Int <: Top
30
Subtyping
T ::= {|p} | x:T1T2
traditional refinements
Implication
SMT Solver
Subtyping
{|true }Top
{|tag()=“Int” }Int
Top Int <: Int Int
Int <: Top Int <: Int
tag()=“Int” true
tag()=“Int” tag()=“Int”
31
Subtyping
T ::= {|p} | x:T1T2
traditional refinements
Implication
SMT Solver
Subtyping
{|true }Top
{|tag()=“Int” }Int
Top Int <: Int Int
Int <: Top Int <: Int
tag()=“Int” true
tag()=“Int” tag()=“Int”
Arrow Rule
32
Subtyping
T ::= {|p} | x:T1T2
traditional refinements
Implication
SMT Solver
Subtyping Arrow Rule
Decidable if:• Only values in formulas• Underlying theories decidable
With nested refinements:• No new theories• But implication is imprecise!
33
Subtyping with Nesting
Implication
SMT Solver
Subtyping
Invalid, as these are distinctuninterpreted constants
:: Top Int :: Int Int✗
34
Subtyping with Nesting
Implication
SMT Solver
Subtyping Arrow Rule
When goal is base predicate:p q
When goal is “has-type” predicate:p x :: U
Implication
SMT Solver
Subtyping
p x :: U’
U’ <: U
p q
35
p :: Top Int
UninterpretedReasoning
Subtyping with Nesting
p :: Int Int
Normalize formulas tosubdivide obligations appropriately
Top Int <: Int Int
+ SyntacticReasoning
36
Nested Refinements
• Type Language
• Subtyping
• Extensions
• Recap
37
Extensions• Simple to add additional type constructors
• Extend the grammar of type terms
• Add additional syntactic subtyping rules
Arrow Rule
Covariant List Rule
Null List Rule
SyntacticRules
U ::= x:T1T2 | A | List[T] | Null
38
Map
let map f xs = if xs = null then null else f xs[“hd”] :: map f xs[“tl”]
∀A,B.
{|:: AB } {|:: List[A] } {|:: List[B] }
encode recursive data as dictionaries
39
Filter
let filter f xs = if xs = null then null else if f xs[“hd”] then xs[“hd”] :: filter f xs[“tl”] else filter f xs[“tl”]
∀A,B. {|:: x:A{|n = True x:: B } } {|:: List[A] } {|:: List[B] }
usual definition, but an interesting type
40
Dispatch
let dispatch d f = d[f] d
∀A,B. d:{|Dict() :: A } {|Str() d[] :: AB } {|:: B }
a form of “bounded quantification” sinced :: A but additional constraints on A
41
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
42
Research Plan
+ prototypes+ references
DJS
{}typeofx := 1super
D
System D lambdas, dictionaries, tag-tests
+ explicit references
+ prototype-based inheritance
Dependent JavaScript translates to D++
Applications in DJS
1.
2.
3.
43
Research Plan
+ prototypes+ references
DJS
{}typeofx := 1super
D
System D lambdas, dictionaries, tag-tests
+ explicit references
+ prototype-based inheritance
Dependent JavaScript translates to D++
Applications in DJS
1.
2.
3.
44
{|=upd(x,“f”,1)}x’ ::
let x = {} in
let x’ = x with “f” = 1 in
x’.f
System D
{|=empty }x ::
functional update
mutation!
JavaScriptvar x = {}
x.f = 1
x.f
45
var x = {}
x.f = 1
x.f
System !D = D + Explicit Referencesallocate cell ref xdereference !x
update cell x := y
let x = ref {} in
let _ = x := (!x with “f” = 1) in
let _ = (!x)[“f”]
System !DJavaScript
{|Dict() }x stores value of type
Update to reference cell doesn’t affect type
So this dictionary read does not type check!
need to strongly update reference type
46
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”]
47
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 ::= … | Ref L{|:: Ref L }x ::
L :: d:{|=empty }
L :: d’: {|=upd(d,“f”,1)}safe given the
updated heap type
let x = ref {} in
let _ = x := (!x with “f” = 1) in
let _ = (!x)[“f”]
48
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
49
var grandpa = { “surname” = “Quackenboss”, “saying” = “When I was your age…”, “age” = 77 }
var parent = { “age” = 44 }
var child = { “age” = 22 }
Assume proto chain is:
child
grandpa
parent
1. does child have age?
{| = 22 = 44 = 77}a ::
var a = child.age
{| = 22}a ::
{|Int()}a ::
2. what is type of child.age?
50
System !D += Prototypes L3 :: dGrandpa:
L2 :: dParent:
L1 :: dChild:
{|:: Ref L3 }grandpa ::
{|:: Ref L2 }parent ::
{|:: Ref L1 }child ::
Unknown Part of Heap
H T1
T3
T2
• Track prototype links in heap type– immutable and acyclic
• New uninterpreted predicate ObjHas(H,L,k)
– analog to has(d,k) (macro for sel(d,k) != bot )
• New uninterpreted function ObjSel(H,L,k)
– analog to sel(d,k)
51
System !D += Prototypes L3 :: dGrandpa:
L2 :: dParent:
L1 :: dChild:
{|:: Ref L3 }grandpa ::
{|:: Ref L2 }parent ::
{|:: Ref L1 }child ::
Unknown Part of Heap
H T1
T3
T2
ObjHas(H,L1,“age”)
Checking if child has “age”
ObjHas(H,L2,“age”)has(dChild,“age”)
ObjHas(H,L3,“age”)has(dParent,“age”)
has(dGrandpa,“age”) ObjHas( ,L4,“age”)UnknownHeap
unroll the known part of heap
52
System !D += Prototypes L3 :: dGrandpa:
L2 :: dParent:
L1 :: dChild:
{|:: Ref L3 }grandpa ::
{|:: Ref L2 }parent ::
{|:: Ref L1 }child ::
Unknown Part of Heap
H T1
T3
T2
a = ObjSel(H,L1,k)
var a = child[k]
!has(dChild,k) a = ObjHas(H,L2,k)
has(dChild,k) a = sel(dChild,k)
!has(dParent,k) a = ObjHas(H,L3,k)
has(dParent,k) a = sel(dParent,k)
has(dGrandpa,k) a = sel(dGranpda,k) true
unroll the known part of heap
53
Recap• System D is functional, but JS is imperative
• Mutation– Track heap flow-sensitively to allow strong updates– Precise dictionary types live on the heap
• Prototype-based inheritance– Model read semantics by traversing proto chain
• System !D = D + Refs + Protos
54
Dependent JavaScript
Lambda Calculus+ References + Prototypes
• Translation due to Guha et al.
• System !D is type system for LC + Refs + Protos
• Add !D types to source-level JS syntax
• Update translation with !D types
+ !D Types
JavaScript
+ !D Types
55
Research Plan
+ prototypes+ references
DJS
{}typeofx := 1super
D
System D lambdas, dictionaries, tag-tests
+ explicit references
+ prototype-based inheritance
Dependent JavaScript translates to D++
Applications in DJS
1.
2.
3.
56
Research Plan
+ prototypes+ references
DJS
{}typeofx := 1super
D
System D lambdas, dictionaries, tag-tests
+ explicit references
+ prototype-based inheritance
Dependent JavaScript translates to D++
Applications in DJS
1.
2.
3.
57
Applications• “JavaScript: The Good Parts”
– patterns identified as good practice• Microbenchmarks
– ~14 KLOC in SunSpider• Extensions and widgets
– secure subsets to allow 3rd party apps• Frameworks
– large development cost by few expert programmers• Type inference in Firefox JIT
– perhaps add slower, offline, more precise analysis
-------
POPL -----
ECOOP ---
ICFP -OOPSLA -
--
POPL -----
Ph.D. ---
2011
2012
2013
2. DJS = D + refs + protos
✓1. System D
3. Applications
59
Additional Directions• Better Inference
– Untyped programmers allergic to annotations – Perhaps run-time techniques
• Class-based inheritance
• Contracts– Defer obligations not discharged statically
• Study of aliasing in practice– How often is mutation “necessary”?– Pipe dream: mainstream language with explicit refs
Classes + D + Refs + Protos
60
Thanks!
Collaborators: Pat Rondon, Ranjit Jhala
Dhttp://cseweb.ucsd.edu/~rchugh/research/nested/
::
61
Extra Slides
62
Constants
tagof :: x:Top{| = tag(x)}
mem :: d:Dictk:Str {|Bool() = True has(d,k)}
get :: d:Dictk:{|Str() has(d,)}{| = sel(d,k)}
set :: d:Dictk:Strx:Top {| = upd(d,k,x)}
rem :: d:Dictk:Str{| = upd(d,k,bot)}
63
• Types
• Formulas
• Logical Values
Macros{|tag()=“Int” }
Int
x:T1T2 {|:: }x:T1T2
tag(x) = “Str”Str(x)
sel(n,“k”)x.k
sel(n,k)x[k]
sel(d,k) != bothas(d,k)
∀k’. k’ != k sel(d,k) != sel(d’,k)
EqMod(d,d’,k)
64
Onto• TODO
65
Related Work• TODO
66
Top Int<: Int Int
+ Syntactic Reasoning
:: Top Int :: Top Int
Uninterpreted Reasoning
Subtyping with Nesting
:: Top Int :: Int Int
Normalize formulas tosubdivide obligations appropriately
67
Classic to Nested Refinementsin Three Steps
68
Refinement TypesB ::= Int | Bool | …T ::= {:B|p} | x:T1T2
p ::= pq | … | x = y | x < y | …
{:Int|true}5 ::
{:Int|0 < n < 10}5 ::
{:Int| n = 10}5 ::
x:{:Int|true}{:Int|true}x.x ::
x:{:Int|true}{:Int|=x}x.x ::
69
Refinement TypesB ::= Int | Bool | …T ::= {:B|p} | x:T1T2
p ::= pq | … | x = y | x < y | …
Type checking is decidable if:• Underlying refinement logic is decidable• Only program values appear in formulas
70
Tag-testsB ::= Int | Bool | …T ::= {:B|p} | x:T1T2
p ::= pq | … | x = y | x < y | …
T ::= {|p} | x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | …
if tagof x = “Int” then 0 - x else not x
can’t pick a single base type{:???|???}x ::
{:Any|tag()=“Int”tag()=“Bool”}x ::
{:Any|tag()=“Int”tag()=“Bool”}x ::
71
Tag-tests
T ::= {|p} | x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | …
if tagof x = “Int” then 0 - x else not x
Type environment tracks control flow
predicates
{|tag()=“Int”tag()=“Bool”}x ::
{|tag()=“Bool”}x ::
{|tag()=“Int”}x ::
72
Tag-tests
T ::= {|p} | x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | …
73
Dictionaries
d.n + d[m]
T ::= {|p} | x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | …
d,k,k’,x. kk’
sel(upd(d,k,x),k) = x
sel(upd(d,k,x),k’) = sel(d,k’)
sel(empty,k) = bot
McCarthy axioms
{|tag()=“Dict” tag(sel(,“n”))=“Int”tag(sel(,m))=“Int”}
d ::
74
Dictionaries
T ::= {|p} | x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | …
T ::= {|p} | x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | …
d,k,k’,x. kk’
sel(upd(d,k,x),k) = x
sel(upd(d,k,x),k’) = sel(d,k’)
sel(empty,k) = bot
McCarthy axioms
75
Lambdas
T ::= {|p} | x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | …
1 + d[f](0)
How to describe arrow in a formula?
Key Challenge:
{|tag()=“Dict”sel(,f)) ??? }d ::
76
Refinement Types
if x = 1 then …else …
aka “subset types” and “contract types”
1,x=1
2,x1
x=1 on then-branch
x=2 on else-branch
Suppose
• Guard formulas stored in typing environment
• Logic: a natural way to track control flow!
B ::= Int | Bool | …T ::= {:B|p}
| x:T1T2
p ::= pq | … | x = y | x < y | …
x:{:Int|0<<3}
77
Type for foo, bottom-up
78
{|::
}
{|::
}
let foo f d = if tagof f = “Str” then d.n + d[f](0) else d.n + f(0)
T ::= {|p}U ::= x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
{|tag()=“Int” }Int
Int
d: {|tag()=“Dict”
tag(sel(,“n”))=“Int”
tag(f)=“Str”
sel(,f) :: }IntInt
f:{|tag()=“Str”:: }IntIntfoo ::
x:T1T2 {|:: }x:T1T2
79
let foo f d = if tagof f = “Str” then d.n + d[f](0) else d.n + f(0)
T ::= {|p}U ::= x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
{|tag()=“Int” }Int
x:T1T2 {|:: }x:T1T2
f:{|tag()=“Str”:: IntInt }
d:{|tag()=“Dict”
tag(sel(,“n”))=“Int”
tag(f)=“Str” sel(,f) :: IntInt }
Int
foo ::
tag(x)=“Str”Str(x)
80
let foo f d = if tagof f = “Str” then d.n + d[f](0) else d.n + f(0)
T ::= {|p}U ::= x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
{|tag()=“Int” }Int
x:T1T2 {|:: }x:T1T2
f:{|Str():: IntInt }
d:{|Dict()
Int(sel(,“n”))
Str(f) sel(,f) :: IntInt }
Int
foo ::
tag(x)=“Str”Str(x)
sel(n,“k”)x.k
sel(n,k)x[k]
81
let foo f d = if tagof f = “Str” then d.n + d[f](0) else d.n + f(0)
T ::= {|p}U ::= x:T1T2
p ::= pq | … | x = y | x < y | … | tag(x) = “Int” | … | sel(x,y) = z | … | x::U
{|tag()=“Int” }Int
x:T1T2 {|:: }x:T1T2
f:{|Str():: IntInt }
d:{|Dict()
Int(.n)
Str(f) [f] :: IntInt }
Int
foo ::
tag(x)=“Str”Str(x)
sel(n,“k”)x.k
sel(n,k)x[k]
82
let foo f d = if tagof f = “Str” then d.n + d[f](0) else d.n + f(0)
f:{|Str():: IntInt }
d:{|Dict() Int(.n) (Str(f) [f] :: IntInt) }
Int