Slides - University of Maryland

advertisement
Safe & Efficient Gradual Typing
for TypeScript
Aseem Rastogi
University of Maryland, College Park
Nikhil Swamy Cédric Fournet
(Microsoft Research)
Gavin Bierman
(Oracle)
Panagiotis Vekris
(UCSD)
Gradual Typing
Combines benefits of static and dynamic typing
Static type errors
Performance
Typed interface
documentation
Dynamic type any
Statically-typed
fragment
Dynamically-typed
fragment
Rapid prototyping
Flexibility
Runtime checks mediate interaction
2
Increasing Adoption in Industry
Mainly for JavaScript
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)
3
TypeScript is Intentionally Unsound
For 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)
4
Programmers Cannot Rely On Types
private parseColorCode (c:string) {
if (typeof c !== "string") return -1;
…
}
Snippet from TouchDevelop, a large TypeScript Development
Tools can’t rely on types
Refactoring, compiler optimizations, etc. are not safe
5
Can we design a gradual type system that is:
Safe
Efficient
Supports idiomatic TypeScript/JavaScript
We present Safe TypeScript
6
Safe TypeScript is Sound
TypeScript is Intentionally Unsound
Standard variance for arrays and function arguments
Sound treatment of JavaScript this
Careful separation of nominal and structural types
Easy local rewriting of unsound idioms in 120,000 line corpus
TypeScript has unsound typing rules to support common idioms
7
Safe TypeScript is Sound
TypeScript is Intentionally Unsound
Safe TypeScript guarantees type safety
Combination of static checking and runtime checks
Runtime-type-information (RTTI) based gradual typing
Efficiency using two new notions of partial erasure
Runtime overhead of only 15% for bootstrapping Safe TypeScript
6.5% for typed Octane benchmarks
TypeScript uniformly erases all types making uses of any unsafe
8
Our Contributions
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.5
Can be invoked with --safe command line flag
Evaluated on 120,000 lines of code
Bootstrapped Safe TypeScript compiler, New TypeScript compiler, Octane
9
Safe TypeScript Tour
Overview of RTTI-based Gradual Typing
Values carry Run Time Type Tags
consistent with their contents
f = 2
g = "s"
h = true
tag = {f:number; g:string}
Object with 3 fields and type tag
Dynamically-typed code instrumented to respect RTTI tags
Invariant: v : [| v.tag |]
10
RTTI Tagging in Safe TypeScript
On-demand and Differential
interface Point {
x:number
function g(p:Point) {
p.x = p.x + 1;
}
function f() {
var p:Point = { x = 0 };
g(p);
}
Safe TypeScript
}
Compiles unchanged
No runtime checks
No tagging !
Previous systems tag eagerly
JavaScript
11
RTTI Tagging in Safe TypeScript
On-demand
function g(p:any) {
p.x = p.x + 1;
}
function f() {
var p:Point = { x = 0 };
g(p);
}
Add RTTI when types lose precision
function f() {
var p = { x = 0 };
g(shallowTag(p, Point));
}
shallowTag(x,t) =
x.tag := combine(x.tag, t);
x
Safe TypeScript
JavaScript
12
RTTI Tagging in Safe TypeScript
Differential
interface 2dPoint extends Point {
y:number
}
Add Minimum Required RTTI
function h(p:any) { .. }
shallowTag(x,t) =
x.tag := combine(x.tag, t);
x
function g(p:Point) {
h(p);
}
function g(p) {
h(shallowTag(p, Point));
}
function f() {
var p:2dPoint = { x = 0; y = 0 };
g(p);
}
function f() {
var p = { x = 0; y = 0 };
g(shallowTag(p, { y:number }));
}
Safe TypeScript
JavaScript
13
RTTI Tagging in Safe TypeScript
Add Minimum Required RTTI
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 }));
}
x = 0
y = 0
JavaScript
14
RTTI Tagging in Safe TypeScript
Add Minimum Required RTTI
shallowTag(x,t) =
x.tag := combine(x.tag, t);
x
x = 0
y = 0
tag = {y:number}
function g(p) {
h(shallowTag(p, Point));
}
function f() {
var p = { x = 0; y = 0 };
g(shallowTag(p, { y:number }));
}
x = 0
y = 0
JavaScript
15
RTTI Tagging in Safe TypeScript
x = 0
y = 0
tag = {x:number; y:number}
x = 0
y = 0
tag = {y:number}
Add Minimum Required RTTI
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 }));
}
x = 0
y = 0
JavaScript
16
Differential Subtyping
Technical Device for On-Demand and Differential Tagging
Differential Subtyping
t1 <: t2
~> d
d = 0 | t
d is loss in precision that must be captured in the RTTI
17
Differential Subtyping
t1 <: t2 ~> d
d is loss in precision
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
18
Differential Subtyping
t1 <: t2 ~> d
d is loss in precision
Γ |- e : t2 ~> e’
t2 <: t1 ~> d
T-Sub
Γ |- e : t1 ~> shallowTag(e’, d)
19
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 g(p) {
write(p, "x", "boom"); // Fails
}
function f() { .. }
write(o,f,v) =
let t = o.tag;
o[f] = check(v, t[f]);
(Recall that f tags p with type Point)
Safe TypeScript
JavaScript
20
Differential Tagging Useful, But Not Ideal
function g(p:Point) {
return p.x;
}
function g(p) { return p.x; }
Unnecessary shallowTag
function f() {
var p:2dPoint = { x = 0; y = 0 };
g(p);
}
Safe TypeScript
function f(p) {
var p = { x = 0; y = 0 };
g(shallowTag(p, { y:number }));
}
JavaScript
21
Reason: Need Conservative Tagging
Previous gradual type systems: t
any
for all static types t
Incurs tagging even for statically typed code
Relaxing it opens opportunities for sound type erasure
22
Erased Types
Programmer-controlled Tagging Behavior
A new type modality: Erased t
Erased t cannot be cast to any
Statically a t
but may not have an RTTI at runtime
23
Subtyping for Erased Types
t1 <: t2 ~> d
t1 <: Erased t2 ~> 0
Erased t <\: any
// Zero delta
// Ensure full erasure is safe
24
Erased Types Example: No Tagging
function g(p:Point) {
return p.x;
}
function g(p) { return p.x; }
Unnecessary shallowTag
function f() {
var p:2dPoint = { x = 0; y = 0 };
g(p);
}
Safe TypeScript
function f(p) {
var p = { x = 0; y = 0 };
g(shallowTag(p, { y:number }));
}
JavaScript
25
Erased Types Example: No Tagging
function g(p:Erased Point) {
return p.x;
}
function g(p) { return p.x; }
function f() {
var p:2dPoint = { x = 0; y = 0 };
g(p);
}
function f(p) {
var p = { x = 0; y = 0 };
g(p); // No tagging
}
Safe TypeScript
Recall shallowTag for non-erased types
JavaScript
26
Erased Types Example: No Tagging
function h(p:any) { .. }
function g(p:Erased Point) {
return h(p);
}
function f() {
var p:2dPoint = { x = 0; y = 0 };
g(p);
}
Safe TypeScript
Static Type Error
function f(p) {
var p = { x = 0; y = 0 };
g(p); // No tagging
}
JavaScript
27
Erased Types
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 types
Working with external libraries that may not have RTTI
Adding new features to the type system (e.g. Polymorphism)
See our paper for more details
28
Soundness Theorem: Forward Simulation
Checks introduced by Safe TypeScript:
Catch any dynamic type error
Do not alter the semantics of type safe code
Tag heap evolution
invariant
29
Safe TypeScript Implementation
Open Source on Github
Implemented in a branch of TypeScript v0.9.5
Invoke 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, …)
30
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
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
Static Type Errors found in Safe TypeScript Code
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 annotations
No overhead for statically-typed code
Found a variable scoping bug in navier-stokes
It has since been fixed
Efficiency conditioned on precise type inference
TypeScript quite conservative
Also in the paper …
Nominal handling of prototype-based classes
Sound handling of this
Abstraction theorem for Erased { }
Zero-subtyping to avoid object identity issues
More experiments
(TypeScript v1.1, Octane benchmarks, Different tagging schemes)
35
Safe TypeScript Summary
Safe & Efficient Gradual Typing for TypeScript
Significant value for type annotations at a modest cost
(runtime checks at least during development and testing)
When to use Safe TypeScript ?
For mature TypeScript projects, improved code quality
Good in development setting for finding early bugs
Baseline for JS analyses focusing on deeper properties
Give it a go !
36
Erased Types Example: Type Abstraction
interface Point {..}
interface 2dPoint {..}
function g(p:Point) {
(<any> p).y = 3;
}
function g(p:Point) {
write(p, "y", 3);
}
function f() {
var p:2dPoint = {x = 0;y = 0};
g(p);
}
function f() {..}
RTTI of p at write is {y:number}
write succeeds: abstraction violation
Safe TypeScript
JavaScript
37
Erased Types Example: Type Abstraction
interface Point extends Erased {..}
interface 2dPoint extends Point {..}
function g(p:Point) {
(<any> p).y = 3;
}
Static Type Error
function f() {
var p:2dPoint = {x = 0;y = 0};
g(p);
}
Safe TypeScript
JavaScript
38
Download