Nested Refinements:A Logic for Duck Typing
Ravi Chugh, Pat Rondon, Ranjit Jhala (UCSD)
2
What are “Dynamic Languages”?d._onto = function(arr,obj,fn) { if (!fn) { arr.push(obj); } else if (fn) { var func = (typeof fn == “string”) ? obj[fn] : fn; arr.push(function() { func.call(obj); }); }}
mutation
tag-tests affect control flow
dictionary objects indexed by arbitrary string keys
first-class functions can appear inside objects
inheritance affects object lookup
3
What are “Dynamic Languages”?
Lack of static types … makes rapid prototyping / multi-language applications easy
… makes reliability / performance / maintenance hard
Goal: Design a type system for these features
tag-tests
mutation
inheritance
first-class functions
dictionary objects
affect control flow
indexed by arbitrary string keys
can appear inside objects
affects object lookup
These alone are hard!
4
tag-tests
first-class functions
dictionary objects
affect control flow
indexed by arbitrary string keys
can appear inside objects
if tagof f = “Str”then d.n + d[f](0) else d.n + f(0)
These alone are hard!
5
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 ::
6
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
7
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”}{|=x}x.x ::
8
Approach: Refinement Types
1 + f (0)first-class functions
dictionaries
tag-tests
9
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?
10
• Reuse refinement type architecture
• Find a decidable refinement logic for– Tag-tests– Dictionaries– Lambdas
• Define nested refinement type architecture
Approach: Refinement Types
✓✗✓✓*
11
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)
12
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
13
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
14
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
15
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 ::
16
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 ::
17
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”}
18
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 ::
19
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”) sel(,f)
20
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 ::
21
{|::
}
{|::
} 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 ::
22
Nested Refinements
• Type Language
• Subtyping
• Extensions
• Recap
23
Subtyping
T ::= {|p} | x:T1T2
traditional refinements
Implication
SMT Solver
Subtyping
{|true}Top
tag()=“Int” true
{|tag()=“Int”}Int
Int <: Top
24
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”
25
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
26
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!
27
Subtyping with Nesting
Implication
SMT Solver
Subtyping
Invalid, as these are distinctuninterpreted constants
::TopInt ::IntInt✗
28
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
29
p ::TopInt
UninterpretedReasoning
Subtyping with Nesting
p ::Int Int
Normalize formulas tosubdivide obligations appropriately
TopInt <: IntInt
+ SyntacticReasoning
30
Nested Refinements
• Type Language
• Subtyping
• Extensions
• Recap
31
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
32
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
33
Filterlet 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{|=Truex::B}} {|::List[A]} {|::List[B]}
usual definition, but an interesting type
34
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
35
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
36
Future Work• Extend to imperative JavaScript setting
– Employ strong update techniques– Track prototype chain for inheritance
• Better inference– Untyped programmers allergic to annotations – Perhaps utilize run-time information
• Applications– JavaScript benchmarks (e.g. SunSpider)– JavaScript frameworks (e.g. Dojo)
37
Thanks!
Dhttp://cseweb.ucsd.edu/~rchugh/research/nested/
::
38
Extra Slides
39
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)}
40
• Types
• Formulas
• Logical Values
Macros{|tag()=“Int”}Int
x:T1T2 {|:: }x:T1T2
tag(x) = “Str”Str(x)
sel(,“k”)x.k sel(,k)x[k]
sel(d,k)!=bothas(d,k) ∀k’. k’!=k sel(d,k)!=sel(d’,k)
EqMod(d,d’,k)
41
Functional Ontolet 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:{|Str()::ATop}obj:{|::A (f=null::AInt) (Str(f)[f]::AInt)}List[TopTop]
onto ::
42
Functional Onto (2)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|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 ::
43
Normalization• TODO
44
Related Work• TODO