Document 17889404

advertisement
>> Nikhil Swamy: All right. Well, thank you all for coming to Aseem Rastogi’s End of Internship
Talk. Aseem’s been here for the last three months and has had a super productive summer
working on Safe TypeScript which he’s worked on pretty much for the whole year actually since
last summer. And we've also spent some time this summer looking at a secure multi-body
computation. So Aseem’s had this sort of doubleheader sort of internship. So he's going to tell
you today about Safe TypeScript, our type safe the version of the TypeScript Compiler.
>> Aseem Rastogi: Okay. Thanks Nick. Good morning everyone. Thanks for coming. My name
is Aseem Rastogi, and I'm a PhD student at University of Maryland, College Park where I'm
advised by Mike Hicks. So I'm going to talk about, as Nick said, about Safe TypeScript which is
joint work with Nikhil Swamy.
Okay. So TypeScript. It's a language from Microsoft, and the goal is to improve the state of the
art in JavaScript programming. It's a gradually typed superset of JavaScript and just putting
some codes from the TypeScript website it gives strong tools for large applications such as
static checking, symbol-based navigation, auto completion, re-factoring and so on. It provides
classes, modules, and interfaces to help you build robust components, and it complies to simple
JavaScript. And compared to current JavaScript programming there's a great leap forward. So
if you're still writing JavaScript code I would strongly recommend you using TypeScript.
Okay, but typing JavaScript is in a way hard because it has so many dynamic programming
idioms that assigning, having a sound type system is hard. So TypeScript, like its counterparts
Dart and Closure, intentionally gives up on type soundness. What that means is when the code
is compiled in JavaScript all the types from the program are uniformly erased and that leads to
unsoundness because, for example, [inaudible] at runtime.
This unsound type eraser has some benefits. You get very lightweight code generation. So the
produced JavaScript is readable; it's in one to one correspondence with the source TypeScript
that you write. Since there are no runtime checks the performance is identical to what it would
have been had you written plain JavaScript, and finally, types do not get in the way of good
programmers. So if you're a good programmer you get it on the type system and if you know
what you're doing. But this unsound type erasure comes at some cost, and the cost is that
TypeScript components are not robust.
Okay. So let's make it a bit concrete on what I mean by TypeScript components are not robust.
Let's say I am a service provider, I have some variable X which encapsulates a state which is,
let's say an array of numbers, there is an index which is the current index into the array, an
index of the number, and it has a method next through which I expect my clients to navigate
through this array. So next just returns whatever is the current index and increments the index.
And as a service provider my expectation is that clients will only use that this variable X at the
interface of this iterator. So I defined an interface, I iterate a generic interface which has a
method next, and I call the client with this X variable of mine only if clients have this expected
type, and then my expectation from this robust interface is that clients will respect my
abstraction. But is it possible in TypeScript? Well, unfortunately no.
So here's a client code which has the correct type. It takes an iterator to number but in the
body it just writes a dynamic field index and in a sense it's [inaudible] thereby breaking the
abstraction. Now this is a well typed TypeScript program. Types are [inaudible] uniformly, it
compiles to [inaudible] Script and there is a TypeScript [inaudible] because I expected the
clients to use only iterated variables but they can write dynamic properties. But not only this,
in fact I want a stronger guarantee. I want that client should not be even able to see my index
field. So even if it writes a number to index I want even that to be prohibited because I
explicitly said that you should only use my next method and not muck around with the index at
all. So as expected from a robust interface I would like clients to not even access my index field.
And this is an abstraction violation and in TypeScript it’s not possible to do so currently.
Okay. So in this talk I present Safe TypeScript which is a sound and efficient gradual type
system for TypeScript. We demonstrate that contrary to the popular belief soundness and
efficiency is achievable for idiomatic TypeScript. The sound typing in Safe TypeScript comes
with advantages. For example it helps you find type errors early. So we compiled the
TypeScript compiler with Safe TypeScript and found 478 type errors. We found one functional
correctness bug in NavierStokes which is a heavily used and heavily tested Octane benchmark,
but still this bug was written[phonetic] and Safe TypeScript found it. And another benefit of
sound typing is that you get provably robust components as opposed to what we just saw.
But sound typing comes with its cost, and the cost for one is the runtime performance penalty.
Since we have to check all the downcast at runtime we have to instrument the dynamically
typed code there is a runtime performance penalty for soundness. And the penalty is, so we
compiled 118,000 lines of code using Safe TypeScript which was spread across eight
applications and the performance over that ranged from six percent to 3x. Now more details
on this will follow later in the talk. Another cost of sound typing is that the programmer needs
to understand some subtle corners of JavaScript semantics and he needs to know how our type
system works. So it's a trade-off.
Okay. So how is Safe TypeScript implemented? So this is the usual TypeScript workflow. The
programmer writes an application app.ts, invokes the TypeScript compiler using tsc app.ts , the
compiler does syntactic checking, does some type inference and some [inaudible] checking,
provides some basic diagnostics and finally emits the JavaScript code. Safe TypeScript is fully
integrated into the TypeScript compiler currently in version 0.9.5. Programmer can invoke Safe
TypeScript using this one compiler flag minus, minus safe to the TypeScript compiler. So
programmer can enable or disable using this compiler flag. And our time checker runs both
TypeScript type imprints and provides stricter static checks, and moreover, [inaudible] checks in
the emitted JavaScript code. So the kind of static diagnostics that are type checker does are
inconsistent subtyping, implicit downcast, variable scoping and unsafe uses of this and so on.
Okay. So in the next 15-20 minutes I'm going to walk you through some highlights of our type
system and show how Safe TypeScript enforces soundness. So Safe TypeScript type system is
object-oriented as with the TypeScript type system, but in contrast to TypeScript which is
purely structural, Safe TypeScript distinguishes between nominal classes and structural
interfaces to match the JavaScript semantics of classes.
Okay. So let's take an example. So let's define a point interface which has two fields X and Y
both are type number. We have point class, which again has two public numbers X and Y, again
both are type number. We write a function F that expects an argument of type point class and
inside the body it's going to assert the argument that it receives is indeed of type point class by
doing P instances of PointC. So I didn't say it earlier, but I'm using this function to assert whose
semantics is that on false are just I’s and if it’s true then just [inaudible].
Okay. So that's it. We call this function F with a structural [inaudible] with fields X0 and Y0.
Now, as you may notice, with this call the assertion inside the function is going to fail because
this is not an instance of point class. But for TypeScript this is a well typed program because
classes can be converted to structures, structures can be converted to classes and therefore
TypeScript will say it’s a well-typed program, it will go on but my assertion will fail at runtime.
In Safe TypeScript, however, classes are treated nominally. So what that means is you cannot
convert structures to classes and therefore in Safe TypeScript at this point in the code you’ll get
a static type error saying that X, the structural type X number, Y number is not a subtype of
point class thereby preventing the assertion violation as compile time. On the other hand, if
you called F with a new, by creating a new class instance this will be okay with Safe TypeScript.
Okay. So now let's modify the example a bit. Let's have function F instead of taking point class
takes point interface but we still call F with new point class. Now this is also okay with Safe
TypeScript because it allows point classes to be converted to structures. So you cannot go the
other way, you cannot convert class structures to classes but you can always create classes
structurally using this subtyping provided by Safe TypeScript. So point class is a subtype of
point interface.
Okay. So next we are going to see how Safe TypeScript instruments the dynamically typed code
in order to ensure type safety in that part of the code. So with that point interface and point
class as before let's define a function F that takes an argument B of type any and it just writes a
property X with string boom. Now there's a call of function G that expects a point interface. It
calls F and then asserts that type of field X must be a number. As you can probably see this
code is going to fail the assertion but for TypeScript this is well-typed code. And again, this
code is, the assertion is going to violate it at runtime.
So instead what Safe TypeScript does as a first step towards enforcing types F [inaudible] in the
dynamic code it tags the values when they pass from statically typed code to the dynamically
typed code. So the compilation of function G, before calling F is going to Shallowtag P with
point interface. We'll see how these tags are used later on, but for now the key thing to notice
is that before calling F it instruments a call to function Shallowtag which is implemented by
runtime labeling.
Okay. Now this function Shallowtag, it's a very simple function. It just takes [inaudible]
whatever you give it and just combines it with the current RTTI. So the current RTTI of P maybe
nothing in which case it’s just going to add point interface to the RTTI of the object.
Okay. So now let's see how these tags are used. So now we’ll look at the compilation of the
function F. All the writes in the dynamically typed code are instrumented with another runtime
function called write which is also provided by Safe TypeScript runtime library, and this write
function is going to enforce that when you write a field you’re always going to respect the
invariants of the object. So in this case what that means is when you write we are going to look
at that P’s RTTI. We’ll see in that RTTI what does P expect for field X and then we'll see if the
value boom respects that type. Since P’s RTTI because of the Shallowtag is going to be point
interface field X of point interface is a type number. So this will take you from boom string is of
type number which it's not so it's going to fail. And we catch the type error early.
Okay. So classes are treated a bit special in Safe TypeScript in that the tagging operations and
classes are optimized. So let's modify our previous example slightly. Instead of point interface
let's say the G function is point class and similarly call the function F remains the same and we
call F and then assert again. In this case when this code is compiled there's no tag Safe
TypeScript in search no tagging operation for P. But earlier as you saw there was a Shallowtag
for point interface. And why is there no tagging? Because classes have primitive tags provided
by the JavaScript semantics. When classes are compiled to JavaScript they have a prototype
field where the prototype field points to the class that they are instance of and that gives us the
RTTI so we need not add our own RTTI. So classes just as a primitive RTTI, no tagging, so you
get efficiency if you are using more classes.
Okay. So this was what happens when values flow from statically typed code to dynamically
typed code. Now we are going to see what happens when values flow the other way around,
when dynamically typed code passes values to the statically typed code and they're used
[inaudible] downcasts. So we have a function F, which takes P which is of type point interface
and it asserts that whatever it got has actually a field X which is of type number. But here's a
dynamic caller G which has P of type any and then it calls function F but performs a downcast
on P before passing it to F. And then this is a caller of G which has [inaudible] a string. So in
TypeScript it’s going to, it is all the types. It's going to erase all the costs, and therefore as you
can see, this assertion is going to fail at runtime because X is value boom which is a string and
it’s not type number it’s type string.
In Safe TypeScript, however, the downcasts are not, it is completely, but downcasts are checked
at runtime. So when this function G is compiled the call to F is actually it inserts a call check
that P is of type PointI. And this check function is, again, a Safe TypeScript runtime labeling
function. It's going to say that okay, you're checking for P’s conformance to PointI, so P should
have a field X of type number, it should have a field Y of type number. If it does I’ll add PointI to
the tag and return otherwise [inaudible] type error. So they’ll maybe catch the assertion
violation early in the code. So all the downcasts I checked in Safe TypeScript. Okay. As with
the tagging operations>>: I have a question [inaudible]?
>> Aseem Rastogi: Oh, yeah. Definitely.
>>: Can you go back a step?
>> Aseem Rastogi: Yeah. Please feel free to ask questions.
>>: So you have, you’re passing in like object literal that matches the shape of PointI but before
you said when I was nominally subtyped, sort of structurally subtyped and then your assertion
over here checks structurally instead of nominally. Is>> Aseem Rastogi: The point class was>>: PointC.
>> Aseem Rastogi: So PointC was the class point; I was interface. [inaudible].
>>: So if you did not have the cast would you still call a check at the PointI cast?
>> Aseem Rastogi: You mean this cast?
>>: Sorry. I can see. No, the cast in TypeScript. In function G, rather.
>>: The PointI cast.
>>: If you did not have the-
>> Aseem Rastogi: In that case TypeScript would give you another because it won’t. But we
will check [inaudible] even if it doesn't.
>>: So you'll still check.
>> Aseem Rastogi: We'll still check. Yes.
>>: So [inaudible] write function in some previous write? [inaudible] function write into this is
the same version of the [inaudible]? Does this mean all the assignments are [inaudible] and
you have to explicitly use [inaudible] write function?
>> Aseem Rastogi: So if you have an assignment in the dynamic part of the code then it's going
to be instrumented with this write. It does not necessarily mean that it's unsafe. So, for
example, you could have written a number over here in which case write would have
succeeded.
>>: So the X function is [inaudible] write automatically?
>> Aseem Rastogi: The compiler, the safe type compiler inserts instruments that call to insert
this write every time you write a field in the dynamically typed code.
>>: The right-hand side of the slides is generated code by the compiler.
>>: In the first example where you have the index and the client was actually updating index
directly what was the check that prevented that from happening?
>> Aseem Rastogi: I’ll tell you about that.
>>: Okay.
>> Aseem Rastogi: [inaudible] those examples towards the end of the talk.
>>: Okay. So your [inaudible] function instead of casting to [inaudible] then your relation for
the [inaudible] function just compares structurally, right?
>> Aseem Rastogi: No.
>>: [inaudible] different checks?
>> Aseem Rastogi: We have check depending on what's the target type. So let's see. That’s
the next thing. So yeah; it’s a good thing. So let’s say instead PointI I was downcasting it to
PointC. But as happens with the tagging even downcasts are optimized for classes. So now
instead of taking it structurally I can just do JavaScript native instance of check so I don't need
to do structural checks. I can just call JavaScript native instance off. And this is very fast, much
optimized and so on.
>>: So TypeScript doesn't do that runtime check [inaudible]? Do you know why they chose not
to do that?
>>: So TypeScript’s really, the philosophy there is [inaudible] and leaving the JavaScript as
[inaudible] as possible. Like the moniker is the kind of JavaScript you would've written by and I
think any kind of runtime checks, any kind of conserving that and you would have to be, been
doing all the work to back to that point. Instead we could give you [inaudible] experience
where you can get IntelliSense, you can get it a lot of good error checking but maybe not all the
error checking that’s [inaudible].
>>: Then again, you would do this without the cast as well.
>> Aseem Rastogi: Yes. Any other questions? Okay. Great. So now comes the part, so if
you've seen that when going from statically typed code to dynamically typed code we inserted
tags on the objects. But Safe TypeScript is even smarter. It's going to add minimum amount of
tags that are needed to ensure type safety, and in addition it's going to provide programmers a
way to get around the tagging behaviors. So if the programmer says that I'm willing to use this
type system at a much stricter mode I don't need any tagging behavior. Programmer has a way
to say that. So that's what I'm going to talk about next.
Okay. So let's extend our example a bit. So instead of, so we had a PointI before which was an
interface with X and Y fields. Let's write another interface which is 3dPointI, which is a 3d point
and it adds a field Z of type number to the interface. We have these small functions F, G and H
which, so the function takes this argument at 3dPointI it calls H. H takes an argument of
3dPointI, calls G, G takes an argument of PointI calls F and F takes any. So as you can see going
from bottom to top the precision in the type is decreasing. So how is this compiled in Safe
TypeScript? There will be some tagging involved. But how does the tagging work?
So when you compile F there is no tagging.
>>: [inaudible].
>> Aseem Rastogi: I'm sorry, [inaudible]. Thanks. So when you compile mean there is no
tagging. And the reason is that there's no loss in position in the argument type. I have a
3dPointI, the [inaudible] expects a 3dPointI so I'm just going to call the function. Then you
compile H. There is a loss in precision because I have a 3dPointI but the other guy expects a
2dPointI so there's a loss in precision of field Z and Safe TypeScript is then just going to add only
the field Z on the tag. So we call this differential tagging. So you only add parts of the tag that
are being lost in precision rather than the complete type. So you just add the field Z and that's
it.
Okay. Now when you compile function G there’s a complete loss in precision. You're going
from point interface to any and so you need to add to the tag point interface. So these tags
evolve at runtime. So at this point the tag will be maybe nothing. At this point the tag will be Z,
call a number. At this point it’s again going to become a 3dPointI. And again, the Shallowtag
just takes the existing tag, combines it with the tag that you give it, and resets that tag.
>>: So that records equal the number and then the one above it, PointI, what exactly are those
that you're passing?
>> Aseem Rastogi: So those are runtime representation of types.
>>: Okay.
>> Aseem Rastogi: So Safe TypeScript compiles types into the imager JavaScript program and
types have some runtime representation like objects. So we have a type class here of different
instances of the cast and so on.
>>: So for each type you actually emit>> Aseem Rastogi: [inaudible] representation.
>>: [inaudible] type representation as well.
>>: You'll see it in the demo.
>> Aseem Rastogi: Yes. We’ll see it in the demo.
>>: Okay.
>> Aseem Rastogi: Okay. Right. And function F will be compiled like we don't care. Okay. So,
but this is a bit unfortunate. I mean if you're going to say that the compiler is always going to
emit tags no matter what server than programmer has no flexibility to say that I don't want this
tagging operation. There’s a performance overhead and I want to avoid that. So then Safe
TypeScript provides programmers a way to adhere to a stricter type discipline but avoid
tagging. So we provide a new operator called erased point types. The semantics of erased
types is that a value of type erase D is known to be a D statically but it may not have a runtime
type there at runtime. It may not have a type there at runtime. And if programmer writes
these erased types in the program he has to conform to a more strict discipline which we'll see
in the next slide. But all the rest types it is from the output. So we are compiling these nominal
types into the JavaScript output, but the erased types will be erased just like what TypeScript
does with of all the types.
Okay. So how does programmer say that some types should be erased? So this is how
programmer says that. So let's say programmer wants the point interface to be erasable. So he
says okay, extend erased. It is an empty interface provided by the Safe TypeScript compiler and
it's just doing the [inaudible] that this interface should be marked at instance one. And the
other 3dpoint as before. And now let's see how the compilation of this looks like. So when this
code is compiled there is no tagging involved in this code. Specifically in function H we were
losing precision going from 3dPointI to PointI but it just compiles as is. Previously it was a
Shallowtag but now no tagging despite the loss in precision and the reason is that your target
type is an erased type so the compiler knows that it’s not going to be used dynamically because
it will be erased, so I do not tag it.
On the other hand, in function G we are passing erased type to dynamic context and that's a
static type error. So Safe TypeScript does not allow you to program dynamically with the
erased types because recall that they may not have an RTTI at runtime. We cannot enforce the
invariants; therefore you have to use them only statically. So this is a static type error and you
cannot pass erased types to dynamic context.
Okay. So now we will visit the robust component example. So this was our first example in
which>>: So, I mean couldn’t you have allowed that one but then it’s passing the 3dPointI to the
PointI and then just to make sure that there was no other downcasts from on the other side
when you're asking the two.
>> Aseem Rastogi: So that’s what we did. So we allowed 3dpointI to go to PointI>>: The static type error is where?
>> Aseem Rastogi: This is in G part on the call [inaudible], give a PointI to any.
>>: What's any?
>> Aseem Rastogi: That's a static type error. But we did allow 3dPointI to go to PointI and it
compiled as is. There was no tagging involved. But if you try to provide PointI to the dynamic
context that's a static type error.
>>: [inaudible]? And you also wouldn’t be able to downcast from PointI to 3dPointI, right?
>> Aseem Rastogi: No.
>>: And you also wouldn't be able to index like inside G to index into it with a Z.
>> Aseem Rastogi: Exactly. No you cannot.
>>: Right.
>> Aseem Rastogi: Good point. Yeah because PointI is an erased type all we know that it has X
and Y. We don't know anything about it. At runtime it will not have an RTTI so that’s it. You
need to adhere to a stricter type discipline. And only then we can guarantee there is property
erased types.
Right. So using our robust components example. This was our first example in which the client
was trying to write index to 1 Boolean value true which was bad. But now Safe TypeScript
provides robustness by a [inaudible] type soundness to them. So at runtime this is going to be
our type error and Safe TypeScript gives you formal guarantees for that. So there are several
useful results that come out of type soundness [inaudible] one of which is the runtime type tags
that you add always remain consistent with the object. So if I say that the tag has a field F type
number then the object has a field F and it contains number in it. Safe TypeScript invariants
always ensure that, and the tags evolve in the subtyping hierarchy. So the tag’s just not, are
some ad hoc tags. They always become more and more precise in the subtyping hierarchy.
And our full formalization and proofs are in the [inaudible] technical report.
Okay. So now this is the more interesting example. In this case we won't even distribute this
[inaudible]. This is basically types F in the sense that the client is writing a number to A but we
don't want to even this to happen because index is my private field. I don't want to expose
index to you. So how can programmer achieve this? So programmers can make iterator to be
an erased type. So he says hey, the interface iterator A extends the erased interface and this is
going to ensure that this is not a well-typed code anymore. So this will be a static error because
you're trying to take it to any and this way you can get stronger abstraction properties from
various types.
>>: So opaque types, is the erased the same as opaque or is there some sort of, why didn’t you
just, I guess the idea of hiding abstractions, right, by saying you can't do anything inside the box
has been around.
>> Aseem Rastogi: Right.
>>: So is it the same, I guess?
>>: I mean there are ways, erased types are like you’d expect types to behave in most libraries
Their types are often erased in a language like [inaudible] see? And you have to respect to the
invariants of, the only way you can use the values by published interface and that's what this is
allowing you to do.
>>: So it’s just what other languages have anyway.
>>: The interesting thing here is that it’s a partial iteration [inaudible]. You don't systematically
erase everything. You get to decide exactly what is erased and what is not erased. And it’s not
quite the same as opaque types because here the iterator, it’s saying that it has a next field. It's
not completely abstract.
>>: Right.
>>: So you're revealing that this is the interface I published and that’s all you can use.
>> Aseem Rastogi: Right. So we have an abstraction theorem for various types as well. It's an
our technical report again. Okay. So this finishes the overview of Safe TypeScript system.
There are many more features in Safe TypeScript. We have support for generics, we have
support for erased, and we support covariant subtyping of erased in a way by having mutability
controls. We've support sound over-riding, inheritance and so on. And all these features help
us scale our Safe TypeScript to large TypeScript applications.
Okay. So some experience with TypeScript.
>>: You first.
>>: I was just looking at the list and one of the [inaudible] with a lot of JavaScript programmers
is this pointer>> Aseem Rastogi: Yes.
>>: At how flexible a dynamic that thing is.
>> Aseem Rastogi: Yeah. We did not add at this point [inaudible] but Safe TypeScript enforces
that you use this pointer in a sound manner and a short introduction is that it reads methods
and functions differently. So, for example, you do not allow it to project a method. So if you
write a method and you use this parameter in that we won't allow you to project a method out
of the object because this will be [inaudible] dynamically to something else. So Safe TypeScript
enforces that you use this in its own manner. Does that answer your question?
>>: Yeah. I had to think about that for second.
>>: So how big is the language manual for Safe TypeScript? How much does it extend, the
TypeScript manual?
>> Aseem Rastogi: We don't, it's only implemented in the TypeScript.
>>: Well, I mean it’s [inaudible] and it has features, and the question is how long does it take to
tell people all the features that you provide? [inaudible] erased is one part of it. You're talking
about mutability controls. What exactly is that?
>> Aseem Rastogi: Yeah. So I mean there is some learning involved. So mutability is basically
in TypeScript code. It allows arrays covariant subtyping which is unsound, but it's a useful
idiom. So we provide you covariant subtyping of erase provided that the target is that you’d
only array. So you can go from 3dpoint array to a point array but that point array, the target
point array should be immutable because otherwise it's not sound. So we have these many
things like that which are to support the [inaudible] like this. But [inaudible].
>>: [inaudible] checking the report is [inaudible], it formalizes a large part of this TypeScript,
and it's larger than any known formalization of TypeScript. So formalizing the whole thing is a
huge effort.
>>: Yeah. I'm not talking about formalization. I'm more interested in, I’m a programmer that
knows TypeScript and I want to know you how to use this. How many pages of text do I have to
read until like I understand all the things that are necessary to use it?
>>: That's a good question. 12 pages [inaudible] at this point.
>>: That's pretty good.
>>: I don’t know. Maybe just look on topical.
>> Aseem Rastogi: You can just read the technical session and know a lot. You don't have to
read the formalization to understand Safe TypeScript.
>>: Sorry. One other question. Do you treat properties and indexers invariantly or covariantly?
>> Aseem Rastogi: Invariantly because they’re immutable so it’s [inaudible]. You have a
question?
Okay. So experience with Safe TypeScript. So we used, we Bootstrapped the Safe TypeScript
compiler. It's about 90,000 lines of code with 80,000 lines of TypeScript compiler code and
10,000 lines of code written by us. The compiler is heavily, so it's implemented in version 0.9.5,
and that compiler code base is heavily class-based which is good for us because then the
performance will be minimal, as we saw. Most of the code is carefully type generated. It's
written quite well I would say. We found 478 static type errors which are classified as follows,
so 98 uses of bi-variant and covariant subtyping, 130 uses of covariant method argument
subtyping, 128 cases of variable scoping, 52 cases of projecting a method and leading to a
potential unsound use of this and so on. We fixed all of those errors and then we found 26
failed downcasts as well when we ran the compiler out of which five were in the code that we
wrote. So, I mean without tool supporting it's very hard to write the sound code.
>>: So, like I know [inaudible]?
>> Aseem Rastogi: They were type safety violations, but we did not have an example where
that would reveal you to some bug. But these are type safety violations where you make some
downcast, the downcast was not sound but it so happened that you didn't end up using the
variable later on in the code.
>>: You might've been doing some kind of [inaudible] check if you have an invariant that once I
have this invariant I can [inaudible] the downcast?
>>: Or you did the downcast, it succeeded silently, and you projected a field that didn't exist but
that undefined, vaporized into a Boolean at some point that it>>: So when you say bi-variant array subtyping you just said that arrays were allowed to keep
covariant Safe TypeScript. So how do you have>> Aseem Rastogi: So they're allowed to be covariant if the tag is immutable. But in TypeScript
there's no such notion. So we had to add mutibility support for the>>: [inaudible].
>> Aseem Rastogi: Yes, yes, yes. Okay. So the runtime [inaudible] type safety in this
experiment was only 15 percent which is quite good I would say. And it's mainly attributed to
the fact that most of the code was class-based. So the downcast checks were very cheap
because we could do instance of and tagging was just [inaudible].
Okay. So we also compiled the latest TypeScript code version 1.1 with that compiler. That's a
much smaller code base. It's 18,000 lines of code as opposed to 80,000 lines of code
previously. But there is a design paradigm shift. Instead of heavily class-based the code is now
heavily interface-based, so this gave us like the other end of the spectrum. And so we found 81
total static type errors out of which, so mainly they were variable scoping and error subtyping.
But the runtime we wanted now increased to three times and it's because interface checking
and tagging is costly.
>>: [inaudible] new version of TypeScript? [inaudible] smaller?
>> Aseem Rastogi: I think it's a previous version but, yeah.
>>: [inaudible] runtime is faster.
>>: I mean part of it is that the compiler leading up to 1.0 kept changing because the language,
we kept mucking with the language and changing it. So it kept getting bloated and bloated
because of the code that was left behind. And then we decided okay, let's actually start from
scratch and first principles, what we already know and that’s a language that’s not changing so
much and then builds something. And we built a much lighter weight [inaudible].
>> Aseem Rastogi: Okay. So as I said, the high overhead is because more structural types, but
having said that I would also say that we did this experiment in the last two weeks, so we did
not optimize a runtime library for interface as much. So there may be even more useful
optimization there.
Okay. The third experiment we did was we used Octane, the standard Octane benchmarks.
That's a total of 10,000 lines of code. We found one variable scoping bug in NavierStokes which
is a heavily tested and heavily used benchmark, but we found this bug which yields to
functional incorrectness. So for these benchmarks there was high runtime overhead if you
don't have any type annotations. And the overhead ranged from 2.4 times in Splay to 72 times
in Crypto and average of 22 times. But once you start adding type annotations to these
benchmarks you recover the performance, and for the type benchmarks the average overhead
was just 6.5 percent.
>>:. How many annotations did you have to write?
>> Aseem Rastogi: It's in the paper. I don't have that number here.
>>: So it starts as just pure JavaScript?
>> Aseem Rastogi: Yeah. It starts at pure JavaScript and then we add annotations to it to make
it type and this is what the performance [inaudible].
>>: What's interesting in some sense when you have these benchmarks kind of came to Octane
from language like Smalltalk>>: Right.
>>: Where, you know, they started out life as typed programs, they became untyped, and now
they're sort of became typed again.
>> Aseem Rastogi: Okay. So now let's see a small demo. So this is that Safe TypeScript
Playground. So I have it at this PointI, I have it at this point class, there’s a function H which is
writing X star X2 boom. There’s a function F, which is pointed to this and then G which has
three numbers just like 3dPoint interface.
Okay. So this is what the first thing happens. We'll adjust our types. So this comes to your
question for the type representation is. So it's registering a PointI interface and saying that it
has an X field which is of type number by field type number and some other things. This is the
class compilation and this is the registration of the class with the runtime system and so on. So
if you see below we see that incremental tagging here. So we have X, Y, and Z. PointI is just X
and Y so the Shallowtag in this case is just for the Z caller number. Then you go from PointI to
any you tag it with the interface type point and finally the write in the dynamically typed code is
instrumented by using write field.
Okay. So now let's see. Let's try to modify this to user point class and I’ll just recompile it. I
don't know how good dynamic thing works. Anyway, so once you annotate it to be a class you
know that the tagging disappears. So there's no tagging anymore because tags for classes are
free. So when you go from F to H there’s no tagging. Moreover, this guy now has to be
checked and tagged to the class type. So recall that structures are not [inaudible] to classes so
we have to insert a runtime check and depending on how this guy originated, started its life this
check is either going to fail or succeed.
Okay. So now let's make this a point in the first part. Let's make this guy erase. So we call it
virtual [inaudible] implementation. So this is how you say that an interface should be erased,
and if you have to recompile it, so if you recompile it then you should see that right. So now at
this point the tagging disappears. Earlier we were tagging at field Z, but now since PointI is
erased this just goes through as it is. But at this point we get a static type error which is that
you cannot pass any argument to, you cannot pass erased arguments to context any, so this is a
type error and Safe TypeScript emits with this unsafe annotation and you can give some
semantics to unsafe, whatever you like.
Okay. So limitations and work in progress. So we currently do not have eval and other unsafe
constructs of the language in Safe TypeScript. But we know how to add them, and this is some
work that we did last summer on giving this eval and other unsafe costructs and adversarial
type. So we explored on how you can have unsafe JavaScript and still interact in a sound
manner. So we think that same ideas can be used in this case.
Some implementation limitations. So as I said it’s implemented in version 0.9.5, but we
currently do not support external modules. We only support internal modules and the
TypeScript compiler has now moved on to be 1.1. And we are having ongoing discussions on
how to integrate our Safe TypeScript with the TypeScript compiler version 1.1.
Okay. So that's the end of my talk. So I talked about Safe TypeScript. Feel free to download it.
It's available on this link for download. We've submitted a POPL paper on this with our terms
and [inaudible] and whatnot and [inaudible] paper is here. We have a full technical report
which contains hard formalization of a large call of Safe TypeScript and complete proofs, and
here's the online Playground which I just showed you in the demo. So thank you.
>>: Excuse me. I had a quick question on sort of the overhead you're talking about, right? It
seems like there are two parts. One is you just have to do the tag checks, but the other is the
particular, I mean you're encoding all the type information in the JavaScript itself, right? So
that seems like just pushing all of that type stuff in JavaScript could be pretty expensive. I was
wondering if you knew sort of a rough breakdown of what was the cost of just having the
conditional check versus the cost of managing all the type information. And then sort of the
following one is would it be useful to push that type information in the actual JavaScript
runtime? It might be a lot more efficient to represent there and it might be actually useful to
the runtime as far as its code gen and execution.
>> Aseem Rastogi: Definitely. So I'll answer the second part first.
>>: Sure.
>> Aseem Rastogi: So it's much more, it's going to be more efficient. And in fact if you have the
option of more defined runtime then you can even make use of soundness of Safe TypeScript
to, for example, do a [inaudible] that checks the JavaScript virtual machine already does. So if
you have X plus Y in the source types we know the X is a number, Y is a number, so we can add
them without doing all the case analysis in the JavaScript virtual machine. So if you had the
option you can definitely optimize much more.
For your first question, I think the cost of translating types is one-time cost. It just happens
during>>: But I guess when you're doing a comparison you type X and you have type Y. Like if you're
representing them as strings or something>> Aseem Rastogi: Yeah.
>>: You're doing a lot of stuff there. I was sort of wondering like>>: We're trying to be careful about using it a lot of sharing. So the same type is going to be
[inaudible] program and we’ll try to use reference [inaudible] as much as possible. We've tried
to use nominal types as much as possible. If a type is nominal we can do a nominal check as
opposed to a structural check.
>> Aseem Rastogi: We are caching off, for example, if you are in interface and you want to
check if some object complies to that interface we go to the structural interface and we cache
all the structural representations also. So we are trying to optimize it, but then at some point
for structural types you have to go through each field and do these expensive checks.
>>: All right.
>> Aseem Rastogi: But I don't think we have a breakdown on just that because checks in a way
involve every single type. So I don't know we can separate cost on just the checks versus
traversing their type.
>>: Well, I guess I was thinking that the transversal part versus just the if range, right? I mean
you always have the same if and do a little call through there, and then there's the other cost
which is actually traversing the type information or how you could get actual resolution of that.
And one of those you’re always have to pay, right? You always have to do the if check. The
other one can be optimized more or less than the [inaudible]. Whatever representations you
have.
>>: I guess the other thing is that it's nice to generate a code that's just in Script five and apply
it. Changing the engine would be great but would also scope down its usability.
>>: So I also have two questions. So with your instance of checks, not for the classes, how does
that deal with inheritance and base classes? If I say I have a drive and I want to check instance
of base, so how does that work with>> Aseem Rastogi: So in this inheritance system compiled by TypeScript it's going to maintain
the prototype. So, the base is going to eventually appear in the prototype chain of the derived
instance. So the instance of check will work and we'll use the instance of check itself.
>>: Okay. We’ll just piggyback on the>> Aseem Rastogi: Yes. You can just piggyback on the JavaScript instance of check.
>>: And my second question is you showed kind of properties. So how does the RTTI work with
signatures and overloads? It might be a very big question.
>> Aseem Rastogi: Right. So if you have methods in the type then those methods are also put
in the RTTI, and if you call those methods dynamically we will do dynamic type checking for
arguments and for return type and so on.
>>: Including what? Loose signatures and interface?
>> Aseem Rastogi: You mean like overloads?
>>: Yeah. But without, like not methods but loose signatures. So I have an interface I that is
[inaudible].
>>: We support that too. So the way, for those of you not TypeScript experts, correct me if I'm
wrong on this one, so the way TypeScript supports overloading is that you get to write, you
have an implementation of a function and then you get to supply multiple signatures of that
function that need not be related by subtyping to the implementation signatures. I can write a
function that is a number function and then claim it as type any [inaudible] also. That would be
fine.
>>: They do have to be related by subtype, but it's>>: But it's your weak notion of subtyping, right? So for soundness what we do is we say okay,
what goes into the RTTI is the implementation signature only, and if during type checking we
will piggyback on your type inference results to resolve to the overload that TypeScript
chooses; but at runtime we will check against the implementation signature which the only
sound signature assumption. Does that answer your question?
>>: Yeah. Part of it. There’s also the case where you have no implementation.
>>: Yes. So when you have no implementation then what you can do is to go, so we do this, so
the TypeScript programs are all type with respect to this library that gives types to the
JavaScript environment. It's like a 15,000 lines speck. So we implicitly treat every type
preparation in the .ts as an erased type meaning that you can some external library is claiming
that it provides to you a function that behaves both like an any to any function as well as
number to number function. And you're willing to trust that array.splice behaves in this kind of
ad hoc homomorphic way, and then since it's erased there’s no runtime representation for it,
and as long as you call array.splice on a value where the receiver has static type array you will
just resolve it the way the captured compiler [inaudible] and there’s no runtime check.
>>: So if I declare interface of my own code>>: And then you can mock that interface that’s erased and you can just use it. But you would
have to then respect the static typing discipline for that particular, for values of that integer.
>>: I see.
>> Aseem Rastogi: So that's how we support [inaudible].ts. So we mark all the specification in
the [inaudible].ts to be erased so that we don't add tags to things that are not ours.
>>: Other questions? Let's thank Aseem.
Download