Safe & Efficient Gradual Typingfor TypeScript
Aseem RastogiUniversity of Maryland, College Park
Nikhil Swamy Cédric Fournet Gavin Bierman Panagiotis Vekris
(Microsoft Research) (Oracle) (UCSD)
2
Gradual Typing
Combines benefits of static and dynamic typing
Statically-typed
fragment
Dynamically-typed
fragment
Dynamic type any
Runtime checks mediate interaction
Static type errors Performance Typed interface documentation
Rapid prototyping
Flexibility
3
TypeScript, Dart, Closure, Flow (we focus on TypeScript)
Increase programmer productivity(static type errors, intellisense, code refactoring, modularity …)
Types don’t get in the way of good programmers(retain flavor of programming in JavaScript)
Increasing Adoption in IndustryMainly for JavaScript
4
TypeScript is Intentionally UnsoundFor All Its Dynamic Idioms, Typing JavaScript is Hard !
Unsound typing rules to support supposedly common idioms(covariant array subtyping, covariant function arguments, …)
Types are uniformly erased during compilation(lightweight compilation, no runtime performance cost, …)(unchecked runtime casts, types don’t guarantee anything)
5
Programmers Cannot Rely On Typesprivate parseColorCode (c:string) { if (typeof c !== "string") return -1; …}
Snippet from TouchDevelop, a large TypeScript Development
Tools can’t rely on typesRefactoring, compiler optimizations, etc. are not safe
6
Can we design a gradual type system that is:
Safe
Efficient
Supports idiomatic TypeScript/JavaScriptWe present Safe TypeScript
7
Safe TypeScript is SoundTypeScript is Intentionally Unsound
Standard variance for arrays and function argumentsSound treatment of JavaScript thisCareful separation of nominal and structural typesEasy local rewriting of unsound idioms in 120,000 line corpus
TypeScript has unsound typing rules to support common idioms
8
Safe TypeScript is SoundTypeScript is Intentionally Unsound
Safe TypeScript guarantees type safetyCombination of static checking and runtime checksRuntime-type-information (RTTI) based gradual typingEfficiency using two new notions of partial erasureRuntime overhead of only 15% for bootstrapping Safe TypeScript 6.5% for typed Octane benchmarks
TypeScript uniformly erases all types making uses of any unsafe
9
Formalized core of Safe TypeScript, proven sound class C, interface I, {M;F}, number, any, Erased t
Implemented in a branch of TypeScript v0.9.5Can be invoked with --safe command line flag
Evaluated on 120,000 lines of codeBootstrapped Safe TypeScript compiler, New TypeScript compiler, Octane
Our Contributions
10
Safe TypeScript TourOverview of RTTI-based Gradual Typing
Values carry Run Time Type Tagsconsistent with their contents
Dynamically-typed code instrumented to respect RTTI tags
Invariant: v : [| v.tag |]
Object with 3 fields and type tag
f = 2g = "s"h = true
tag = {f:number; g:string}
11
interface Point { x:number }
function g(p:Point) { p.x = p.x + 1;}
function f() { var p:Point = { x = 0 }; g(p);}
Compiles unchanged
No runtime checks
No tagging !
Previous systems tag eagerly
RTTI Tagging in Safe TypeScriptOn-demand and Differential
Safe TypeScript JavaScript
12
RTTI Tagging in Safe TypeScriptOn-demand
function g(p:any) { p.x = p.x + 1;}
function f() { var p:Point = { x = 0 }; g(p);}
function f() { var p = { x = 0 }; g(shallowTag(p, Point));}
shallowTag(x,t) = x.tag := combine(x.tag, t); x
Safe TypeScript JavaScript
Add RTTI when types lose precision
13
RTTI Tagging in Safe TypeScriptDifferential
shallowTag(x,t) = x.tag := combine(x.tag, t); x
interface 2dPoint extends Point { y:number}
function h(p:any) { .. }
function g(p:Point) { h(p);}
function f() { var p:2dPoint = { x = 0; y = 0 }; g(p);}
function g(p) { h(shallowTag(p, Point));}
function f() { var p = { x = 0; y = 0 }; g(shallowTag(p, { y:number }));}
Safe TypeScript JavaScript
Add Minimum Required RTTI
14
RTTI Tagging in Safe TypeScript
shallowTag(x,t) = x.tag := combine(x.tag, t); x
function g(p) { h(shallowTag(p, Point));}
function f() { var p = { x = 0; y = 0 }; g(shallowTag(p, { y:number }));}
JavaScript
Add Minimum Required RTTI
x = 0y = 0
15
RTTI Tagging in Safe TypeScript
shallowTag(x,t) = x.tag := combine(x.tag, t); x
function g(p) { h(shallowTag(p, Point));}
function f() { var p = { x = 0; y = 0 }; g(shallowTag(p, { y:number }));}
JavaScript
Add Minimum Required RTTI
x = 0y = 0
tag = {y:number}
x = 0y = 0
16
RTTI Tagging in Safe TypeScript
shallowTag(x,t) = x.tag := combine(x.tag, t); x
function g(p) { h(shallowTag(p, Point));}
function f() { var p = { x = 0; y = 0 }; g(shallowTag(p, { y:number }));}
JavaScript
Add Minimum Required RTTIx = 0y = 0
tag = {x:number; y:number}
x = 0y = 0
tag = {y:number}
x = 0y = 0
17
Differential Subtyping
t1 <: t2 ~> d d = 0 | t
d is loss in precision that must be captured in the RTTI
Differential SubtypingTechnical Device for On-Demand and Differential Tagging
18
t <: t ~> 0
{x:number;y:number} <: {x:number} ~> {y:number}
{x:number;y:number} <: any ~> {x:number;y:number}
Primitive RTTI Aware: number <: any ~> 0
Differential Subtypingt1 <: t2 ~> d d is loss in precision
19
Γ |- e : t2 ~> e’t2 <: t1 ~> d
Γ |- e : t1 ~> shallowTag(e’, d) T-Sub
Differential Subtypingt1 <: t2 ~> d d is loss in precision
20
Instrumentation of Dynamically Typed Code
function g(p:any) { p.x = "boom";}
function f() { var p:Point = { x = 0 }; g(p); assert(typeof p.x === "number");}
function f() { .. }
function g(p) { write(p, "x", "boom");}
write(o,f,v) = let t = o.tag; o[f] = check(v, t[f]);
(Recall that f tags p with type Point)
// Fails
Safe TypeScript JavaScript
21
Differential Tagging Useful, But Not Ideal
function g(p) { return p.x; }
function f(p) { var p = { x = 0; y = 0 }; g(shallowTag(p, { y:number }));}
Safe TypeScript JavaScript
function g(p:Point) { return p.x;}
Unnecessary shallowTag
function f() { var p:2dPoint = { x = 0; y = 0 }; g(p);}
22
Reason: Need Conservative Tagging
Previous gradual type systems: t anyfor all static types t
Incurs tagging even for statically typed code
Relaxing it opens opportunities for sound type erasure
23
A new type modality: Erased tErased t cannot be cast to any
Statically a tbut may not have an RTTI at runtime
Erased TypesProgrammer-controlled Tagging Behavior
24
t1 <: t2 ~> dt1 <: Erased t2 ~> 0
Erased t <\: any
Subtyping for Erased Types
// Zero delta
// Ensure full erasure is safe
25
Erased Types Example: No Tagging
Safe TypeScript JavaScript
function g(p) { return p.x; }
function f(p) { var p = { x = 0; y = 0 }; g(shallowTag(p, { y:number }));}
function g(p:Point) { return p.x;}
Unnecessary shallowTag
function f() { var p:2dPoint = { x = 0; y = 0 }; g(p);}
26
Erased Types Example: No Tagging
function g(p) { return p.x; }
function f(p) { var p = { x = 0; y = 0 }; g(p);}
Safe TypeScript JavaScript
function g(p:Erased Point) { return p.x;}
function f() { var p:2dPoint = { x = 0; y = 0 }; g(p);}
// No tagging
Recall shallowTag for non-erased types
27
Erased Types Example: No Tagging
function f(p) { var p = { x = 0; y = 0 }; g(p);}
Safe TypeScript JavaScript
function g(p:Erased Point) { return h(p);}
function f() { var p:2dPoint = { x = 0; y = 0 }; g(p);}
// No tagging
function h(p:any) { .. }
Static Type Error
28
Obey a fully static type discipline(Sound type erasure, type abstraction, …)(Loss in expressiveness: not all code can be turned dynamic)
Other advantages of Erased typesWorking with external libraries that may not have RTTIAdding new features to the type system (e.g. Polymorphism)See our paper for more details
Erased Types
29
Soundness Theorem: Forward Simulation
Checks introduced by Safe TypeScript:
Catch any dynamic type errorDo not alter the semantics of type safe code
Tag heap evolution invariant
30
Implemented in a branch of TypeScript v0.9.5Invoke our type checker with --safe flag
10,000 Lines of Code
Compiles to plain JavaScript (with instrumented checks)
(recursive interfaces, inheritance, overloading, generics, arrays, external libs, …)
Safe TypeScript ImplementationOpen Source on Github
Bootstrapping Safe TypeScript
90,000 Lines of Code, heavily class based, carefully annotated Number of Errors
Covariant method arguments
130
Variable scoping 128
Potential unsound use of this
52
Bivariant array subtyping
98
... …
Total 478 Static Type Errors found in Safe TypeScript Code
Bootstrapping Safe TypeScript
90,000 Lines of Code, heavily class based, carefully annotated
Static Type Errors found in Safe TypeScript Code
All cases in one file using visitor pattern, easily rewritten
Sloppy use of var declarations, easily fixed
Projection of methods, easily fixed by making them functions
Fixed by introducing array mutability qualifiers
Number of Errors
Covariant method arguments
130
Variable scoping 128
Potential unsound use of this
52
Bivariant array subtyping
98
... …
Total 478
Bootstrapping Safe TypeScript
90,000 Lines of Code, heavily class based, carefully annotated
478 Static type errors
26 failed runtime downcasts; 5 in our own code !
15% runtime overhead of type safety
Octane Benchmarks (6 / 17)22x (ave.) and 72x (max.) overhead with no type annotations
Down to 6.5% after adding type annotationsNo overhead for statically-typed code
Found a variable scoping bug in navier-stokesIt has since been fixed
Efficiency conditioned on precise type inferenceTypeScript quite conservative
35
Nominal handling of prototype-based classesSound handling of thisAbstraction theorem for Erased { }Zero-subtyping to avoid object identity issuesMore experiments(TypeScript v1.1, Octane benchmarks, Different tagging schemes)
Also in the paper …
36
When to use Safe TypeScript ?For mature TypeScript projects, improved code qualityGood in development setting for finding early bugsBaseline for JS analyses focusing on deeper properties
Safe TypeScript SummarySafe & Efficient Gradual Typing for TypeScript
Significant value for type annotations at a modest cost
(runtime checks at least during development and testing)
Give it a go !
37
interface Point {..}
interface 2dPoint {..}
Erased Types Example: Type Abstraction
function g(p:Point) { write(p, "y", 3);}
function f() {..}
RTTI of p at write is {y:number}
write succeeds: abstraction violation
function g(p:Point) { (<any> p).y = 3;}
function f() { var p:2dPoint = {x = 0;y = 0}; g(p);}
Safe TypeScript JavaScript
38
function g(p:Point) { (<any> p).y = 3;}
function f() { var p:2dPoint = {x = 0;y = 0}; g(p);}
interface Point extends Erased {..}
interface 2dPoint extends Point {..}
Static Type Error
Erased Types Example: Type Abstraction
Safe TypeScript JavaScript