Safe & Efficient Gradual Typing for Typescript
Total Page:16
File Type:pdf, Size:1020Kb
rtifact Comple * A t * te n * te A is W s E * e n l l C o L D C o P * * c u e m s O E u e e P n R t v e o d t * y * s E a a l d u e a Safe & Efficient Gradual Typing for TypeScript t Aseem Rastogi ∗ Nikhil Swamy Cedric´ Fournet Gavin Bierman ∗ Panagiotis Vekris ∗ University of Maryland Microsoft Research Oracle Labs University of California College Park San Diego [email protected] fnswamy, [email protected] [email protected] [email protected] Abstract language abstractions such as static types, classes, and interfaces can hamper programmer productivity and undermine tool support. Current proposals for adding gradual typing to JavaScript, such as Unfortunately, retrofitting abstraction into JavaScript is difficult, Closure, TypeScript and Dart, forgo soundness to deal with issues as one must support awkward language features and programming of scale, code reuse, and popular programming patterns. patterns in existing code and third-party libraries, without either re- We show how to address these issues in practice while retaining jecting most programs or requiring extensive annotations (perhaps soundness. We design and implement a new gradual type system, using a PhD-level type system). prototyped for expediency as a ‘Safe’ compilation mode for Type- Gradual type systems set out to fix this problem in a principled Script. Our compiler achieves soundness by enforcing stricter static manner, and have led to popular proposals for JavaScript, notably checks and embedding residual runtime checks in compiled code. Closure, TypeScript and Dart (although the latter is strictly speak- It emits plain JavaScript that runs on stock virtual machines. Our ing not JavaScript but a variant with some features of JavaScript re- main theorem is a simulation that ensures that the checks intro- moved). These proposals bring substantial benefits to the working duced by Safe TypeScript (1) catch any dynamic type error, and (2) programmer, usually taken for granted in typed languages, such as do not alter the semantics of type-safe TypeScript code. a convenient notation for documenting code; API exploration; code Safe TypeScript is carefully designed to minimize the perfor- completion; refactoring; and diagnostics of basic type errors. Inter- mance overhead of runtime checks. At its core, we rely on two new estingly, to be usable at scale, all these proposals are intentionally differential subtyping ideas: , a new form of coercive subtyping that unsound: typeful programs may be easier to write and maintain, but computes the minimum amount of runtime type information that their type annotations do not prevent runtime type errors. erasure modality must be added to each object; and an , which we Instead of giving up on soundness at the outset, we contend that use to safely and selectively erase type information. This allows a sound gradual type system for JavaScript is practically feasible. us to scale our design to full-fledged TypeScript, including arrays, There are, undoubtedly, some significant challenges to overcome. maps, classes, inheritance, overloading, and generic types. For starters, the language includes inherently type-unsafe features We validate the usability and performance of Safe TypeScript such as eval and stack walks, some of JavaScript’s infamous “bad empirically by type-checking and compiling around 120,000 lines parts”. However, recent work is encouraging: Swamy et al.(2014) of existing TypeScript source code. Although runtime checks can proposed TS? a sound type system for JavaScript to tame untyped be expensive, the end-to-end overhead is small for code bases that adversarial code, isolating it from a gradually typed core language. already have type annotations. For instance, we bootstrap the Safe Although the typed fragment of TS? is too limited for large-scale TypeScript compiler (90,000 lines including the base TypeScript JavaScript developments, its recipe of coping with the bad parts compiler): we measure a 15% runtime overhead for type safety, using type-based memory isolation is promising. and also uncover programming errors as type safety violations. We In this work, we tackle the problem of developing a sound, yet conclude that, at least during development and testing, subjecting practical, gradual type system for a large fragment of JavaScript, JavaScript/TypeScript programs to safe gradual typing adds signif- confining its most awkward features to untrusted code by relying icant value to source type annotations at a modest cost. implicitly on memory isolation. Concretely, we take TypeScript as our starting point. In brief, TypeScript is JavaScript with optional 1. Introduction type annotations: every valid JavaScript program is a valid Type- Originally intended for casual scripting, JavaScript is now widely Script program. TypeScript adds an object-oriented gradual type used to develop large applications. Using JavaScript in complex system, while its compiler erases all traces of types and emits Java- codebases is, however, not without difficulties: the lack of robust Script that can run on stock virtual machines. The emitted code is syntactically close to the source (except for type erasure), hence ∗ This work was done at Microsoft Research. TypeScript and JavaScript interoperate with the same performance. TypeScript’s type system is intentionally unsound; Bierman et al.(2014) catalog some of its unsound features, including bi- Permission to make digital or hard copies of all or part of this work for personal or variant subtyping for functions and arrays, as well as in class and classroom use is granted without fee provided that copies are not made or distributed interface extension. The lack of soundness limits the benefits of for profit or commercial advantage and that copies bear this notice and the full citation on the first page. Copyrights for components of this work owned by others than the writing type annotations in TypeScript, making abstractions hard author(s) must be honored. Abstracting with credit is permitted. To copy otherwise, or to enforce and leading to unconventional programming patterns, republish, to post on servers or to redistribute to lists, requires prior specific permission even for programmers who steer clear of the bad parts. Consider and/or a fee. Request permissions from [email protected]. for instance the following snippet from TouchDevelop (Tillmann POPL ’15, January 15–17, 2015, Mumbai, India. et al. 2012), a mobile programming platform written in TypeScript: Copyright is held by the owner/author(s). Publication rights licensed to ACM. ACM 978-1-4503-3300-9/15/01. $15.00. private parseColorCode (c:string) f http://dx.doi.org/10.1145/2676726.2676971 if (typeofc !== "string") return −1; ::: g ues need not be tagged with RTTI. Erased types are not subtypes of any, nor can they be coerced to it, yielding four important capa- bilities. First, information hiding: we show how to use erased types to encode private fields in an object and prove a confidentiality the- orem (Theorem2). Second, user-controlled performance: through careful erasure, the user can minimize the overhead of RTTI op- erations. Third, modularity: erased-types allow us to ensure that objects owned by external modules do not have RTTI. And, fourth, long-term evolution, allowing us to scale Safe TypeScript up to a language with a wide range of typing features. (2) Differential subtyping. In addition, we rely on a form of coer- cive subtyping (Luo 1999) that allows us to attach partial RTTI on Figure 1: Architecture of Safe TypeScript any-typed objects, and is vital for good runtime performance. Despite annotating the formal parameter c as a string, the prudent 1.2 Main contributions TypeScript programmer must still check that the argument received We present the first sound gradual type system with a formal treat- is indeed a string using JavaScript reflection, and deal with errors. ment of objects with mutable fields and immutable methods, ad- dition and deletion of computed properties from objects, nomi- 1.1 Safe TypeScript nal class-based object types, interfaces, structural object types with We present a new type-checker and code generator for a subset of width-subtyping, and partial type erasure. TypeScript that guarantees type safety through a combination of static and dynamic checks. Its implementation is fully integrated as Formal core (x3). We develop SafeTS: a core calculus for Safe a branch of the TypeScript-0.9.5 compiler. Programmers can opt in TypeScript. Our formalization includes the type system, compiler to Safe TypeScript simply by providing a flag to the compiler (sim- and runtime for a subset of Safe TypeScript, and also provides a ilar in spirit to JavaScript’s strict mode, which lets the programmer dynamic semantics suitable for a core of both TypeScript and Safe abjure some unsafe features). As with TypeScript, the code gener- TypeScript. Its metatheory establishes that well-typed SafeTS pro- ated by Safe TypeScript is standard JavaScript and runs on stock grams (with embedded runtime checks) simulate programs running virtual machines. under TypeScript’s semantics (without runtime checks), except for Figure1 illustrates Safe TypeScript at work. A programmer the possibility of a failed runtime check that stops execution early authors a TypeScript program, app.ts, and feeds it to the Type- (Theorem1). Pragmatically, this enables programmers to switch Script compiler, tsc, setting the --safe flag to enable our system. between ‘safe’ and ‘unsafe’ mode while testing and debugging. The compiler initially processes app.ts using standard TypeScript Full-fledged implementation for TypeScript (x4). Relying on dif- passes: the file is parsed and a type inference algorithm computes ferential subtyping and erasure, we extend SafeTS to the full Safe (potentially unsound) types for all subterms. For the top-level func- TypeScript language, adding support for several forms of inheri- tion f in the figure, TypeScript infers the type (x:any))number, us- tance for classes and interfaces; structural interfaces with recursion; ing by default the dynamic type any for its formal parameter.