skip to main content
research-article
Open Access

Capturing Types

Published:20 November 2023Publication History

Skip Abstract Section

Abstract

Type systems usually characterize the shape of values but not their free variables. However, many desirable safety properties could be guaranteed if one knew the free variables captured by values. We describe CC< :◻, a calculus where such captured variables are succinctly represented in types, and show it can be used to safely implement effects and effect polymorphism via scoped capabilities. We discuss how the decision to track captured variables guides key aspects of the calculus, and show that CC< :◻  admits simple and intuitive types for common data structures and their typical usage patterns. We demonstrate how these ideas can be used to guide the implementation of capture checking in a practical programming language.

Skip 1INTRODUCTION Section

1 INTRODUCTION

Effects are aspects of computation that go beyond describing shapes of values and that we still want to track in types. What exactly is modeled as an effect is a question of language or library design. Some possibilities are reading or writing to mutable state outside a function; throwing an exception to signal abnormal termination of a function; I/O including file operations, network access, or user interaction; non-terminating computations; suspending a computation, such as waiting for an event; or using a continuation for control operations.

Despite hundreds of published papers, there is comparatively little adoption of static effect checking in programming languages. The few designs that are widely implemented (e.g., Java’s checked exceptions or monadic effects in some functional languages) are often critiqued for being both too verbose and too rigid. The problem is not lack of expressiveness—systems have been proposed and implemented for many kinds of effects. Rather, the problem is the lack of usability and flexibility, with particular difficulties in describing polymorphism. This leads either to overly complex definitions or to the requirement to duplicate large bodies of code.

Classical type-systematic approaches fail since effects are inherently transitive along the edges of the dynamic call graph: a function’s effects include the effects of all the functions it calls, transitively. Traditional type and effect systems have no lightweight mechanism to describe this behavior. The standard approach is either manual specialization along specific effect classes, which means large-scale code duplication, or quantifiers on all definitions along possible call graph edges to account for the possibility that some call target has an effect, which means large amounts of boilerplate code. Arguably, it is this problem more than any other that has so far hindered wide-scale application of effect systems.

A promising alternative that circumvents this problem is to model effects via capabilities tracked in the type system [Craig et al. 2018; Miller 2006; Marino and Millstein 2009; Gordon 2020; Liu 2016; Brachthäuser et al. 2020a; Osvald et al. 2016]. Capabilities exist in many forms, but we will restrict the meaning here to simple object capabilities represented as regular program variables. For instance, consider the following two morally equivalent formulations of a method in Scala:1

The first version looks like it describes an effect: function f returns a T, or it might throw exception E. The effect is mentioned in the return type throws[T, E] where the throws is written infix.

The second version expresses analogous information as a capability: function f returns a value of type T, provided it can be passed a capability ct of type CanThrow[T]. The capability is modeled as a parameter. To avoid boilerplate, that parameter is synthesized automatically by the compiler at the call site assuming a matching capability is defined there. This is expressed by a using keyword, which indicates that a parameter is implicit in Scala 3 (Scala 2 would have used the implicit keyword instead). The fact that capabilities are implicit rather than explicit parameters helps with conciseness and readability of programs but is not essential for understanding the concepts discussed in this work.

Aside. The link between the “effect” and the “capability” version of f can be made more precise by means of context function types [Odersky et al. 2018]. It is embodied in the following definition of the throws type:

The context function type CanThrow[E] ?=> T represents functions from CanThrow[E] to T that are applied implicitly to arguments synthesized by the compiler. This gives a direct connection between the effect view based on the throws type and the capability view based on its expansion.

An important benefit of this switch from effects to capabilities is that it gives us polymorphism for free. For instance, consider the map function in class List[A]. If we wanted to take effects into account, it would look like this:

Here, A -> B eff E is hypothetical syntax for the type of functions from A to B that can have effect E. While looking reasonable in the small, this scheme quickly becomes unmanageable, if we consider that every higher-order function has to be expanded that way and, furthermore, that in an object-oriented language almost every method is a higher-order function [Cook 2009]. Indeed, many designers of programming languages with support for effect systems agree that programmers should ideally not be confronted with explicit effect quantifiers [Brachthäuser et al. 2020a; Leijen 2017; Lindley et al. 2017].

However, here is the type of map if we represent effects with capabilities.

Interestingly, this is exactly the same as the type of map in current Scala, which does not track effects! In fact, compared to effect systems, we now decompose the space of possible effects differently: map is classified as pure since it does not produce any effects in its own code, but when analyzing an application of map to some argument, the capabilities required by the argument are also capabilities required by the whole expression. In that sense, we get effect polymorphism for free.

The reason this works is that in an effects-as-capabilities discipline, the type A => B represents the type of impure function values that can close over arbitrary effect capabilities. Alongside, we also define a type of pure functions A -> B that are not allowed to close over capabilities.

This seems almost too good to be true, and indeed there is a catch: it now becomes necessary to reason about capabilities captured in closures.

Fig. 1.

Fig. 1. Exception handling: source (left) and compiler-generated code (right).

To see why, consider that effect capabilities are often scoped and therefore have a limited lifetime. For instance a CanThrow[E] capability would be generated by a try expression that catches E. It is valid only as long as the try is executing. Figure 1 shows an example of capabilities for checked exceptions, both as source syntax on the left and with compiler-generated implicit capability arguments on the right. The following slight variation of this program would throw an unhandled exception since the function f is now evaluated only when the iterator’s next method is called, which is after the try handling the exception has exited.

A question answered in this article is how to rule out Iterator’s lazy map statically while still allowing List’s strict map. A large body of research exists that could address this problem by restricting reference access patterns. Relevant techniques include linear types [Wadler 1990], rank 2 quantification [Launchbury and Sabry 1997], regions [Tofte and Talpin 1997; Grossman et al. 2002], uniqueness types [Barendsen and Smetsers 1996], ownership types [Clarke et al. 1998; Noble et al. 1998], and second-class values [Osvald et al. 2016]. A possible issue with many of these approaches is their relatively high notational overhead, particularly when dealing with polymorphism.

The approach we pursue here is different. Instead of restricting certain access patterns a priori, we focus on describing what capabilities are possibly captured by values of a type. At its core there are the following two interlinked concepts:

  • A capturing type is of the form T^\(\lbrace c_1, \ldots , c_n\rbrace\) where T is a type and \(\lbrace c_1, \ldots , c_n\rbrace\) is a capture set of capabilities.

  • A capability is a parameter or local variable that has as type a capturing type with non-empty capture set. We call such capabilities tracked variables.

Every capability gets its authority from some other, more sweeping capabilities which it captures. The most sweeping capability, from which ultimately all others are derived, is “cap”, the universal capability.

As an example how capabilities are defined and used, consider a typical try-with-resources pattern:

The usingFile method runs a given operation op on a freshly created file, closes the file, and returns the operation’s result. The method enables an effect (writing to a file) and limits its validity (to until the file is closed). Function good invokes usingFile with an operation that writes each element of a given list xs to the file. By contrast, function fail represents an illegal usage: it invokes usingFile with an operation that returns a function that, when invoked, will write list elements to the file. The problem is that the writing happens in the application later(1) when the file has already been closed.

We can accept the first usage and reject the second by marking the output stream passed to op as a capability. This is done by adding the capture set annotation ^{cap} after the type proper. Our implementation of capture checking rejects the second usage with an error message that the result of usingFile leaks f.

This example used a capability parameter that was directly derived from cap. But capabilities can also be derived from other non-universal capabilities. For instance:

The usingLogFile method takes an output stream (which is a capability) and an operation, which gets passed a Logger. The Logger capability is derived from the output stream capability, as can be seen from its type Logger^{f}.

This article develops a capture calculus, \(\mathsf {CC}_{\lt :\Box }\), as a foundational type system that allows reasoning about scoped capabilities. By sketching a prototype language design based on this calculus, we argue that it is expressive enough to support a wide range of usage patterns with very low notational overhead. The article makes the following specific contributions:

  • We define a simple yet expressive type system for tracking captured capabilities in types. The calculus extends \(\textsf {System F}_{\lt :}~\)with capture sets of capabilities.

  • We prove progress and preservation of types relative to a small-step evaluation semantics. We also prove a capture prediction lemma that states that capture sets in types over-approximate captured variables in runtime values.

  • We illustrate the practical applicability of the calculus with a number of examples that have been checked by a prototype capture checker implemented within the Scala 3 compiler.

The presented design is at the same time simple in theory and concise and flexible in its practical application. We demonstrate that the following elements are essential for achieving good usability:

  • Use reference-dependent typing, where a formal function parameter stands for the potential references captured by its argument [Odersky et al. 2021; Brachthäuser et al. 2022]. This avoids the need to introduce separate binders for capabilities or effects. Technically, this means that references (but not general terms) can form part of types as members of capture sets. A similar approach is taken in the path-dependent typing discipline of DOT [Amin et al. 2016; Rompf and Amin 2016] and by reachability types for alias checking [Bao et al. 2021].

  • Employ a subtyping discipline that mirrors subsetting of capabilities and that allows capabilities to be refined or abstracted. Subtyping of capturing types relies on a new notion of subcapturing that encompasses both subsetting (smaller capability sets are more specific than larger ones) and derivation (a capability singleton set is more specific than the capture set of the capability’s type). Both dimensions are essential for a flexible modeling of capability domains.

  • Limit propagation of capabilities in instances of generic types where they cannot be accessed directly. This is achieved by boxing types when they enter a generic context and unboxing on every use site [Brachthäuser et al. 2022].

Whereas many of our motivating examples describe applications in effect checking, the formal treatment presented here does not mention effects. In fact, the effect domains are intentionally kept open since they are orthogonal to the aim of the article. Effects could be exceptions, file operations, or region allocations, but also algebraic effects, IO, or any sort of monadic effects. To express more advanced control effects, one usually needs to add continuations to the operational semantics, or use an implicit translation to the continuation monad. In short, capabilities can delimit what effects can be performed at any point in the program, but they by themselves do not perform an effect [Marino and Millstein 2009; Gordon 2020; Liu 2016; Brachthäuser et al. 2020a; Osvald et al. 2016]. For that, one needs a library or a runtime system that would be added as an extension of \(\mathsf {CC}_{\lt :\Box }\). Since \(\mathsf {CC}_{\lt :\Box }\) is intended to work with all such effect extensions, we refrain from adding a specific application to its core operational semantics.

We introduce later an extension of \(\mathsf {CC}_{\lt :\Box }\)to demonstrate that scoping is properly enforced. The extension adds just enough primitives to \(\mathsf {CC}_{\lt :\Box }\) so that ill-scoped programs could “go wrong” at runtime, then proceeds to show that all such programs are ruled out by the type system.

The version of \(\mathsf {CC}_{\lt :\Box }\) presented here evolved from a system that was originally proposed to make exception checking safe [Odersky et al. 2021]. The earlier paper described a way to encode information about potentially raised exceptions as object capabilities passed in parameters. It noted that the proposed system is not completely safe since capabilities can escape in closures, and it hypothesized a possible way to fix the problem by presenting a draft of what became \(\mathsf {CC}_{\lt :\Box }\). At the time, the metatheory of the proposed system was not worked out yet and the progress and preservation properties were left as conjectures. TThis work presents a fully worked-out metatheory with proofs of type soundness as well as a semantic characterization of capabilities. There are some minor differences in the operational semantics, which were necessary to make the progress theorem go through. We also present a range of use cases outside of exception handling, demonstrating the broad applicability of the calculus.

The rest of this article is organized as follows. Section 2 explains and motivates the core elements of our calculus. Section 3 presents \(\mathsf {CC}_{\lt :\Box }\). Section 4 lays out its metatheory. Section 5 illustrates the expressiveness of typing disciplines based on the calculus in examples. Section 6 illustrates the role of the \(\mathsf {CC}_{\lt :\Box }\)’s boxing feature in making polymorphism sound. Section 7 presents an extension of \(\mathsf {CC}_{\lt :\Box }\) for demonstrating that scoping is enforced. Section 8 discusses related work, and Section 9 concludes.

Skip 2INFORMAL DISCUSSION Section

2 INFORMAL DISCUSSION

This section motivates and discusses some of the key aspects of capture checking. All examples are written in an experimental language extension of Scala 3 [Scala 2022b] and were compiled with our prototype implementation of a capture checker [Scala 2022c].

2.1 Capability Hierarchy

We saw in Section 1 that every capability except cap is created from some other capabilities which it retains in the capture set of its type. Here is an example that demonstrates this principle:

Here, the test method takes a FileSystem as a parameter. fs is a capability since its type has a non-empty capture set. The capability is passed to the Logger constructor and retained as a field in class Logger. Hence, the local variable lgr has type Logger^{fs}: it is a Logger that retains the fs capability.

The second variable defined in test is xs, a lazy list that is obtained from LazyList.from(1) by logging and mapping consecutive numbers. Since the list is lazy, it needs to retain the reference to the logger lgr for its computations. Hence, the type of the list is LazyList[Int]^{lgr}. However, since xs only logs but does not do other file operations, it retains the fs capability only indirectly. That is why fs does not show up in the capture set of xs.

Capturing types come with a subtype relation where types with “smaller” capture sets are subtypes of types with larger sets (the subcapturing relation is defined in more detail in the following). If a type T does not have a capture set, it is called pure and is a subtype of any capturing type that adds a capture set to T.

2.2 Function Types

The function type A => B stands for a function that can capture arbitrary capabilities. We call such functions impure. By contrast, the new single arrow function type A -> B stands for a function that cannot capture any capabilities, or otherwise said, is pure. One can add a capture set after the arrow of an otherwise pure function. For instance, A ->{c, d} B would be a function that can capture capabilities c and d, but no others. It can be seen as a shorthand for the type (A -> B)^{c, d}.

The impure function type A => B is treated as an alias for A ->{cap} B. In other words, impure functions are functions that can capture anything.

Note. Like other object-functional languages, Scala distinguishes between functions and methods (which are defined using def). Functions are values, whereas methods represent pieces of code that logically form part of the enclosing object. The type system treats method signatures and function types separately: a function type is treated as an object type with a single apply method. Methods are converted to functions by eta expansion—that is, the unapplied method reference m is transparently converted to the function value x => m(x).

Since methods are not values in Scala, they never capture anything directly. Therefore, the distinctions between pure vs impure function types do not apply to methods. The capabilities captured by a method would show up in the object closure of which the method forms part.

2.3 Capture Checking of Closures

If a closure refers to capabilities in its body, it captures these capabilities in its type. For instance, consider the following:

Here, the body of test is a lambda that refers to the capability fs, which means that fs is retained in the lambda. Consequently, the type of the lambda is String ->{fs} Unit.

Note. On the term level, function values are always written with => (or ?=> for context functions). There is no syntactic distinction between pure and impure function values. The distinction is only made in their types.

A closure also captures all capabilities that are captured by the functions it calls. For instance, in

the result of test has type String ->{fs} Unit even though function g itself does not refer to fs.

2.4 Subtyping and Subcapturing

Capturing influences subtyping. As usual, we write \(T_1 \lt : T_2\) to express that the type \(T_1\) is a subtype of the type \(T_2\), or, equivalently, that \(T_1\) conforms to \(T_2\). An analogous subcapturing relation applies to capture sets. If \(C_1\) and \(C_2\) are capture sets, we write \(C_1 \lt : C_2\) to express that \(C_1\) is covered by \(C_2\), or, swapping the operands, that \(C_2\) covers \(C_1\).

Subtyping extends as follows to capturing types:

  • Pure types are subtypes of capturing types. In other words, \(T \lt : T \mathrel {\wedge }C\), for any type T and capturing set C.

  • For capturing types, smaller capture sets produce subtypes: \(T_1 \mathrel {\wedge }C_1 \lt : T_2 \mathrel {\wedge }C_2\) if \(C_1 \lt : C_2\) and \(T_1 \lt : T_2\).

A subcapturing relation \(C_1 \lt : C_2\) holds if \(C_2\) accounts for every element c in \(C_1\). This means one of the following two conditions must be true:

  • \(c \in C_2\),

  • c’s type has capturing set C and \(C_2\) accounts for every element of C (i.e., \(C \lt : C_2\)).

Example. Given

we have

The set consisting of the root capability {cap} covers every other capture set. This is a consequence of the fact that, ultimately, every capability is created from cap.

2.5 Capture Tunneling

Next, we discuss how type polymorphism interacts with reasoning about capture. To this end, consider the following simple definition of a Pair class:2

What happens if we pass arguments to the constructor of Pair that capture capabilities?

Here the arguments x and y close over different capabilities ct and fs, which are assumed to be in scope. So what should be the type of p? Maybe surprisingly, it will be typed as follows:

In other words, the outer capture set is empty and it neither mentions ct nor fs, even though the value Pair(x, y) does capture them. So why do they not show up in its type at the outside?

Although assigning p the capture set {ct, fs} would be sound, types would quickly grow inaccurate and unbearably verbose. To remedy this, \(\mathsf {CC}_{\lt :\Box }\) performs capture tunneling. Once a type variable is instantiated to a capturing type, the capture is not propagated beyond this point. However, if the type variable is instantiated again on access, the capture information “pops out” again.

Even though p is technically untracked because its capture set is empty, writing p.fst would record a reference to the captured capability ct. So if this access was put in a closure, the capability would again form part of the outer capture set. For example,

In other words, references to capabilities “tunnel through” generic instantiations—from creation to access; they do not affect the capture set of the enclosing generic data constructor applications.

As mentioned previously, this principle plays an important part in making capture checking concise and practical. To illustrate, let us take a look at the following example:

Relying on capture tunneling, neither the types of the parameters to mapFirst nor its result type need to be annotated with capture sets. Intuitively, the capture sets do not matter for mapFirst, since parametricity forbids it from inspecting the actual values inside the pairs. If not for capture tunneling, we would need to annotate p as Pair[A,B]^{cap}, since both A and B and through them, p can capture arbitrary capabilities. In turn, this means that for the same reason, without tunneling we would also have Pair[C,B]^{cap} as the result type. This is of course unacceptably inaccurate.

Section 3 describes the foundational theory on which capture checking is based. It makes tunneling explicit through so-called box and unbox operations. Boxing hides a capture set, and unboxing recovers it. Boxed values need an explicit unbox operation before they can be accessed, and that unbox operation charges the capture set of the environment. If the unbox operation is part of a closure, the unboxed type’s capture set will contribute to the captured variables of that closure. The need for such a mechanism is explained in more detail in Section 6.

The capture checker inserts virtual box and unbox operations based on actual and expected types similar to the way the type checker inserts implicit conversions. Boxing and unboxing has no runtime effect, so the insertion of these operations is only simulated, but not kept in the generated code.

2.6 Escape Checking

Following the principle of object capabilities, the universal capability cap should conceptually only be available as a parameter to the main program. Indeed, if it was available everywhere, capability checking would be undermined since one could mint new capabilities at will. In line with this reasoning, some capture sets are restricted and must not contain the universal capability.

Specifically, if a capturing type is an instance of a type variable, that capturing type is not allowed to carry the universal capability {cap}.3 There is a connection to tunneling here. The capture set of a type has to be present in the environment when a type is instantiated from a type variable. But cap is not itself available as a global entity in the environment. Hence, this should result in an error.

Using this principle, we can show why the introductory example in Section 1 reported an error. To recall, function usingFile was declared like this:

The capture checker rejects the illegal definition of later

with the following error message

This error message was produced by the following reasoning steps:

  • Parameter f has type FileOutputStream^{cap}, which makes it a capability.

  • Therefore, the type of the expression

    is Int ->{f} Unit.

  • Consequently, we assign the whole closure passed to usingFile the dependent function type (f: FileOutputStream)^{cap} Int ->{f} Unit.

  • The expected type of the closure is a simple, parametric, impure function type FileOutputStream^{cap} => T, for some instantiation of the type variable T.

  • We cannot instantiate T with Int ->{f} Unit since the expected function type is non-dependent. The smallest supertype that matches the expected type is thus FileOutputStream^{cap} => Int ->{cap} Unit.

  • Hence, the type variable T is instantiated to Int ->{cap} Unit, which is not allowed and causes the error.

2.7 Escape Checking of Mutable Variables

Another way one could try to undermine capture checking would be to assign a closure with a local capability to a global variable. For instance, like this:4

We prevent such scope extrusions by imposing the restriction that mutable variables cannot have types with universal capture sets.

One also needs to prevent returning or assigning a closure with a local capability in an argument of a parametric type. For instance, here is a slightly more refined attack:

At the point where the Pair is created, the capture set of the first argument is {f}, which is OK. But at the point of use, it is {cap}: since f is no longer in scope, we need to widen the type to a supertype that does not mention it (c.f. the explanation of avoidance in Section 3.3). This causes an error, again, as the universal capability is not permitted to be in the unboxed form of the return type (c.f. the precondition of (Unbox) in Figure 3, presented later).

Fig. 2.

Fig. 2. Syntax of system \(\mathsf {CC}_{\lt :\Box }\) .

Skip 3The \(\mathsf {CC}_{\lt :\Box }\) Calculus Section

3 The \(\mathsf {CC}_{\lt :\Box }\) Calculus

The syntax of \(\mathsf {CC}_{\lt :\Box }\) is given in Figure 2. In short, it describes a dependently typed variant of System \(\textsf {F}_{\lt :}~\)in Monadic Normal Form (MNF) with capturing types and boxes.

Dependently Typed. Types may refer to term variables in their capture sets, which introduces a simple form of (variable-)dependent typing. As a consequence, a function’s result type may now refer to the parameter in its capture set. To be able to express this, the general form of a function type \(\forall (x : U) T\) explicitly names the parameter x. We retain the non-dependent syntax \(U \rightarrow T\) for function types as an abbreviation if the parameter is not mentioned in the result type T.

Dependent typing is attractive since it means that we can refer to object capabilities directly in types instead of having to go through auxiliary region or effect variables. We thus avoid clutter related to quantification of such auxiliary variables.

Monadic Normal Form. The term structure of \(\mathsf {CC}_{\lt :\Box }\) requires operands of applications to be variables. This does not constitute a loss of expressiveness, since a general application \(t_1\,t_2\) can be expressed as \({\operatorname{{\bf \textsf {l}et}} x_1 = t_1 \operatorname{{\bf \textsf {i}n}} \operatorname{{\bf \textsf {l}et}} x_2 = t_2 \operatorname{{\bf \textsf {i}n}} x_1\,x_2}\). This syntactic convention has advantages for variable-dependent typing. In particular, typing function application in such a calculus requires substituting actual arguments for formal parameters. If arguments are restricted to be variables, these substitutions are just variable/variable renamings, which keep the general structure of a type. If arguments were arbitrary terms, such a substitution would in general map a type to something that was not syntactically a type. MNF [Hatcliff and Danvy 1994] is a slight generalization of the better-known A-normal form (ANF) [Sabry and Felleisen 1993] to allow arbitrary nesting of let expressions. We use a here a variant of MNF where applications are over variables instead of values.

A similar restriction to MNF was employed in DOT [Amin et al. 2016], the foundation of Scala’s object model, for the same reasons. The restriction is invisible to source programs, which can still be in direct style. For instance, the Scala compiler selectively translates a source expression in direct style to MNF if a non-variable argument is passed to a dependent function. Type checking then takes place on the translated version.

Capturing Types. The types in \(\mathsf {CC}_{\lt :\Box }\) are stratified as shape types S and regular types T. Regular types can be shape types or capturing types \(S \mathrel {\wedge }\lbrace x_1, \ldots , x_n\rbrace\). “\(\,\mathrel {\wedge }\,\)” has a higher precedence than \(\Box\) or \(\forall\) prefixes—for instance, \(\forall (x: S)T \mathrel {\wedge }C\) is read as \(\forall (x: S)(T \mathrel {\wedge }C)\). Shape types are made up from the usual type constructors in \(\textsf {F}_{\lt :}~\)plus boxes. We freely use shape types in place of types, assuming the equivalence \(S \mathrel {\wedge }\lbrace \rbrace \equiv S\).

Boxes. Type variables X can be bounded or instantiated only with shape types, not with regular types. To make up for this restriction, a regular type T can be encapsulated in a shape type by prefixing it with a box operator \(\Box T\). On the term level, \(\Box ~x\) injects a variable into a boxed type. A variable of boxed type is unboxed using the syntax \(C ⟜ x\) where C is the capture set of the underlying type of x. We saw in Section 2 that boxing and unboxing allow a kind of capability tunneling by omitting capabilities when values of parametric types are constructed and charging these capabilities instead at use sites.

System \(\textsf {F}_{\lt :}~\). We base \(\mathsf {CC}_{\lt :\Box }\) on a standard type system that supports the two principal forms of polymorphism: subtyping and universal.

Subtyping comes naturally with capabilities in capture sets. First, a type capturing fewer capabilities is naturally a subtype of a type capturing more capabilities, and pure types are naturally subtypes of capturing types. Second, if capability x is derived from capability y, then a type capturing x can be seen as a subtype of the same type but capturing y.

Universal polymorphism poses specific challenges when capture sets are introduced which are addressed in \(\mathsf {CC}_{\lt :\Box }\)by the stratification into shape types and regular types and the box/unbox operations that map between them.

Note that the only form of term dependencies in \(\mathsf {CC}_{\lt :\Box }\) relate to capture sets in types. If we omit capture sets and boxes, the calculus is equivalent to standard \(\textsf {F}_{\lt :}~\), despite the different syntax. We highlight in the figures the essential additions with respect to \(\textsf {F}_{\lt :}~\)with a gray background.

\(\mathsf {CC}_{\lt :\Box }\) is intentionally meant to be a small and canonical core calculus that does not cover higher-level features such as records, modules, objects, or classes. Although these features are certainly important, their specific details are also somewhat more varied and arbitrary than the core that is covered. Many different systems can be built on \(\mathsf {CC}_{\lt :\Box }\), extending it with various constructs to organize code and data on higher levels.

Capture Sets. Capture sets C are finite sets of variables of the form \(\lbrace x_1, \dots , x_n\rbrace\). We understand \({\bf cap}\) to be a special variable that can appear in capture sets but cannot be bound in \(\Gamma\). We write \(C \setminus x\) as a shorthand for subtraction of capture sets \(C \setminus \lbrace x\rbrace\).

Capture sets of closures are determined using a function \(\operatorname{cv}\) over terms.

Definition (Captured Variables). The captured variables \(\operatorname{cv}(t)\) of a term t are given as follows: \(\begin{equation*} \begin{array}{lcl} \operatorname{cv}(\lambda (x : T)t) &=& \operatorname{cv}(t) \backslash x \\ \operatorname{cv}(\lambda [X \lt :S]t) &=& \operatorname{cv}(t) \\ \operatorname{cv}(x) &=& \lbrace x\rbrace \\ \operatorname{cv}(\operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} t) &=& \operatorname{cv}(t) \quad \quad \quad \quad \quad \quad \operatorname{{\bf \textsf {if}}} x \notin \operatorname{cv}(t) \\ \operatorname{cv}(\operatorname{{\bf \textsf {l}et}} x = s \operatorname{{\bf \textsf {i}n}} t) &=& \operatorname{cv}(s) \cup \operatorname{cv}(t) \backslash x\\ \operatorname{cv}(x\,y) &=& \lbrace x, y\rbrace \\ \operatorname{cv}(x[S]) &=& \lbrace x\rbrace \\ \operatorname{cv}(\Box x) &=& \lbrace \rbrace \\ \operatorname{cv}(C ⟜ x) &=& C \cup \lbrace x\rbrace \end{array} \end{equation*}\)

The definitions of captured and free variables of a term are very similar, with the following three differences:

(1)

Boxing a term \(\Box x\) obscures x as a captured variable.

(2)

Dually, unboxing a term \(C ⟜ x\) counts the variables in C as captured.

(3)

In an evaluated let binding \(\operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} t\), the captured variables of v are counted only if x is a captured variable of t.

The first two rules encapsulate the essence of box-unbox pairs: boxing a term obscures its captured variable and makes it necessary to unbox the term before its value can be accessed; unboxing a term presents variables that were obscured when boxing. The third rule is motivated by the case where a variable x is bound to a value v; then we do not want to count the captured variables of v if x is either boxed or not mentioned at all in the let body. The intuition behind this rule is that such variables would naturally be disregarded if \(\mathsf {CC}_{\lt :\Box }\) was not in MNF.

Fig. 3.

Fig. 3. Typing and evaluation rules of system \(\mathsf {CC}_{\lt :\Box }\) .

Figure 3 presents typing and evaluation rules for \(\mathsf {CC}_{\lt :\Box }\). There are four main sections on subcapturing, subtyping, typing, and evaluation. These are explained in the following.

3.1 Subcapturing

Subcapturing establishes a preorder relation on capture sets that gets propagated to types. Smaller capture sets with respect to subcapturing lead to smaller types with respect to subtyping.

The relation is defined by three rules. The first two rules (sc-set) and (sc-elem) establish that subsets imply subcaptures. In other words, smaller capture sets subcapture larger ones. The last rule (sc-var) is the most interesting since it reflects an essential property of object capabilities. It states that a variable x of capturing type \(S \mathrel {\wedge }C\) generates a capture set \(\lbrace x\rbrace\) that subcaptures the capabilities C with which the variable was declared. In a sense, (sc-var) states a monotonicity property: a capability refines the capabilities from which it is created. In particular, capabilities cannot be created from nothing. Every capability needs to be derived from some more sweeping capabilities which it captures in its type.

The rule also validates our definition of capabilities as variables with non-empty capture sets in their types. Indeed, if a variable is defined as \(x: S \mathrel {\wedge }\lbrace \rbrace\), then by (sc-var) we have \(\lbrace x\rbrace \lt : \lbrace \rbrace\). This means that the variable can be disregarded in the formation of \(\operatorname{cv}\), for instance. Even if x occurs in a term, a capture set with x in it is equivalent (with respect to mutual subcapturing) to a capture set without. Hence, x can safely be dropped without affecting subtyping or typing.

Rules (sc-set) and (sc-elem) mean that if set C is a subset of \(C^{\prime }\), we also have \(C \lt :C^{\prime }\). But the reverse is not true. For instance, with (sc-var) we can derive the following relationship assuming lambda-bound variables x and y: \(\begin{equation*} x: \top \!\mathrel {\wedge }\lbrace {\bf cap}\rbrace , y: \top \!\mathrel {\wedge }\lbrace x\rbrace \,\vdash \,\lbrace y\rbrace \lt :\lbrace x\rbrace . \end{equation*}\) Intuitively this makes sense, as y can capture no more than x. However, we cannot derive \(\lbrace x\rbrace \lt :\lbrace y\rbrace\), since arguments passed for y may in fact capture less than x—for example, they could be pure.

Although there are no subcapturing rules for top or bottom capture sets, we can still establish the following proposition.

Proposition 3.1.

If C is well-formed in \(\Gamma\), then \(\Gamma \vdash \lbrace \rbrace \lt : C \lt : \lbrace {\bf cap}\rbrace\).

A proof is enclosed in the appendix.

Proposition 3.2.

The subcapturing relation \(\Gamma \,\vdash \,\_ \lt : \_\) is a preorder.

Proof.

We can show that transitivity and reflexivity are admissible.□

3.2 Subtyping

The subtyping rules of \(\mathsf {CC}_{\lt :\Box }\) are very similar to those of \(\textsf {System F}_{\lt :}~\), with the only significant addition being the rules for capturing and boxed types. Note that as \(S \equiv S \mathrel {\wedge }\lbrace \rbrace\), both transitivity and reflexivity apply to shape types as well. Rule (capt) allows comparing types that have capture sets, where smaller capture sets lead to smaller types. Rule (boxed) propagates subtyping relations between types to their boxed versions.

3.3 Typing

Typing rules are again close to \(\textsf {System F}_{\lt :}~\), with differences to account for capture sets.

Rule (var) is the basis for capability refinements. If x is declared with type \(S \mathrel {\wedge }C\), then the type of x has \(\lbrace x\rbrace\) as its capture set instead of C. The capture set \(\lbrace x\rbrace\) is more specific than C, in the subcapturing sense. Therefore, we can recover the capture set C through subsumption.

Rules (abs) and (tabs) augment the abstraction’s type with a capture set that contains the captured variables of the term. Through subsumption and rule (sc-var), untracked variables can immediately be removed from this set.

The (app) rule substitutes references to the function parameter with the argument to the function. This is possible since arguments are guaranteed to be variables. The function’s capture set C is disregarded, reflecting the principle that the function closure is consumed by the application. Rule (tapp) is analogous.

Aside. A more conventional version of (tapp) would be \(\begin{equation*} \frac{\Gamma \,\vdash \,x :(\forall [X \lt :S^{\prime }]T) \mathrel {\wedge }C \quad \quad \Gamma \,\vdash \,S \lt :S^{\prime }}{\Gamma \,\vdash \,x[S] :[X := S]T}. { \small {(TAPP}^{\prime } {)}} \end{equation*}\)

That formulation is equivalent to (tapp) in the sense that either rule is derivable from the other, using subsumption and contravariance of type bounds.

Rules (box) and (unbox) map between boxed and unboxed types. They require all members of the capture set under the box to be bound in the environment \(\Gamma\). Consequently, although one can create a boxed type with \(\lbrace {\bf cap}\rbrace\) as its capture set through subsumption, one cannot unbox values of this type. This property is fundamental for ensuring scoping of capabilities.

Avoidance. As is usual in dependent type systems, Rule (let) has as a side condition that the bound variable x does not appear free in the result type U. This so called avoidance property is usually attained through subsumption. For instance, consider an enclosing capability \(c: T_1\) and the following term: \(\begin{equation*} \operatorname{{\bf \textsf {l}et}} x = \lambda (y: T_2)c \operatorname{{\bf \textsf {i}n}} \lambda (z: T_3\mathrel {\wedge }\lbrace x\rbrace)z. \end{equation*}\) The most specific type of x is \((\forall (y:T_2)T_1)\mathrel {\wedge }\lbrace c\rbrace ,\) and the most specific type of the body of the let is \(\forall (z: T_3\mathrel {\wedge }\lbrace x\rbrace)T_3\mathrel {\wedge }\lbrace z\rbrace\). We need to find a supertype of the latter type that does not mention x. It turns out the most specific such type is \((\forall (y:T_3) T_3)\mathrel {\wedge }\lbrace c\rbrace\), so that is a possible type of the let, and it should be the inferred type.

In general, there is always a most specific avoiding type for a (let), as we prove in Appendix A.7.

Proposition 3.3.

Consider a term \(\operatorname{{\bf \textsf {l}et}} x = s \operatorname{{\bf \textsf {i}n}} t\) in an environment \(\Gamma\) such that \(\Gamma \,\vdash \,s : T_1\) and \(\Gamma , x: T_1 \,\vdash \,t: T_2\). Then there exists a minimal (wrt \(\lt :\)) type \(T_3\) such that \(T_2 \lt : T_3\) and \(x \notin \operatorname{fv}(T_3)\).

3.4 Well-Formedness

Well-formedness \(\Gamma \,\vdash \,T \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\) is equivalent to well-formedness in \(\textsf {System F}_{\lt :}~\)in that free variables in types and terms must be defined in the environment, except that capturing types may mention the universal capability \({\bf cap}\) in their capture sets. We present the well-formedness rules in Figure 4.

Fig. 4.

Fig. 4. Type well-formedness rules of system \(\mathsf {CC}_{\lt :\Box }\) .

3.5 Evaluation

Evaluation is defined by a small-step reduction relation. This relation is quite different from usual reduction via term substitution. Substituting values for variables would break the MNF of a program. Instead, we reduce the right-hand sides of let-bound variables in place and lookup the bindings in the environment of a redex.

Every redex is embedded in an outer store context and an inner evaluation context. These represent orthogonal decompositions of let bindings. An evaluation context e always puts the focus \([]\) on the right-hand side \(t_1\) of a let binding \(\operatorname{{\bf \textsf {l}et}} x = t_1 \operatorname{{\bf \textsf {i}n}} t_2\). By contrast, a store context \(\sigma\) puts the focus on the following term \(t_2\) and requires that \(t_1\) is evaluated.

The first three rules—(apply), (tapply), (open)—rewrite simple redexes: applications, type applications, and unboxings. Each of these rules looks up a variable in the enclosing store and proceeds based on the value that was found.

The last two rules are administrative in nature. They both deal with evaluated lets in redex position. If the right-hand side of the let is a variable, the let gets expanded out by renaming the bound variable using (rename). If it is a value, the let gets lifted out into the store context using (lift).

Proposition 3.4 ().

Evaluation is deterministic. If \(t \longrightarrow u_1\) and \(t \longrightarrow u_2\), then \(u_1 = u_2\).

Proof.

By a straightforward inspection of the reduction rules and definitions of contexts.□

Skip 4METATHEORY Section

4 METATHEORY

We prove that \(\mathsf {CC}_{\lt :\Box }\) is sound through the standard progress and preservation theorems. The proofs for all the lemmas and theorems stated in this section are provided in the appendix. Progress and preservation and the capture prediction lemma for the calculus have also been mechanized by Fourment and Xu [2023].

We follow the Barendregt convention and only consider typing contexts where all variables are unique: for all contexts of the form \(\Gamma ,x:T,\) we have \(x \not\in \operatorname{dom} (\Gamma)\).

To prove both progress and preservation, we need technical lemmas that allow manipulation of typing judgments for terms under store and evaluation contexts. To state these lemmas, we first need to define what it means for typing and store contexts to match, which we do in Figure 5.

Fig. 5.

Fig. 5. Matching environment \(\begin{array}{|c|}\hline\hline{\Gamma} \,{\vdash} \,{\sigma} {\sim} {\Delta}\end{array}\) .

Having \(\Gamma \,\vdash \,\sigma \sim \Delta\) lets us know that \(\sigma\) is well-typed in \(\Gamma\) if we use \(\Delta\) as the types of the bindings. Using this definition, we can state the following four lemmas, which also illustrate how the store and evaluation contexts interact with typing.

Definition 4.1

(Evaluation Context Typing (\(\Gamma \vdash e : U \Rightarrow T\)))

We say that e can be typed as \(U \Rightarrow T\) in \(\Gamma\) iff for all t such that \(\Gamma \vdash t : U\), we have \(\Gamma \vdash e[\ t\ ] : T\).

Lemma 4.2 (Evaluation Context Typing Inversion).

\(\Gamma \,\vdash \,e[\ s\ ] : T\) implies that for some \(U,\) we have \(\Gamma \vdash e : U \Rightarrow T\) and \(\Gamma \vdash s : U\).

Lemma 4.3 (Evaluation Context Reification).

If both \(\Gamma \,\vdash \,e : U \Rightarrow T\) and \(\Gamma \,\vdash \,s : U\), then \(\Gamma \,\vdash \,e[\ s\ ] : T\).

Lemma 4.4 (Store Context Typing Inversion).

\(\Gamma \,\vdash \,\sigma [\ t\ ] :T\) implies that for some \(\Delta ,\) we have \(\Gamma \,\vdash \,\sigma \sim \Delta\) and \(\Gamma ,\Delta \,\vdash \,t :T\).

Lemma 4.5 (Store Context Reification).

If both \(\Gamma ,\Delta \,\vdash \,t :T\) and \(\Gamma \,\vdash \,\sigma \sim \Delta\), then also \(\Gamma \,\vdash \,\sigma [\ t\ ] :T\).

We can now proceed to our main theorems; their statements differ slightly from \(\textsf {System F}_{\lt :}~\), as we need to account for MNF. Our preservation theorem captures that the important type to preserve is the one assigned to the term under the store. It is stated as follows.

Theorem 4.6 (Preservation).

If we have \(\Gamma \,\vdash \,\sigma \sim \Delta\) and \(\Gamma ,\Delta \,\vdash \,t : T\), then \(\sigma [\ t\ ] \longrightarrow \sigma [\ t^{\prime }\ ]\) implies that \(\Gamma ,\Delta \,\vdash \,t^{\prime } : T\).

Before stating the progress theorem, we need an auxiliary definition.

Definition 4.7

(Proper Configuration).

A term form \(\sigma [\ t\ ]\) is a proper configuration if t is not of the form \(\operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} t^{\prime }\).

Theorem 4.8 (Progress).

If \(\,\vdash \,\sigma [\ t\ ] :T\) and \(\sigma [\ t\ ]\) is a proper configuration, then either t is an answer a or \(\sigma [\ t\ ] \longrightarrow \sigma [\ t^{\prime }\ ]\) for some \(t^{\prime }\).

The lemmas needed to prove progress and preservation are for the most part standard. As our calculus is term-dependent, we also need to account for term substitution affecting both environments and types, not only terms. For instance, the lemma stating that term substitution preserves typing is expressed as follows.

Lemma 4.9 (Term Substitution Preserves Typing).

If \(\Gamma ,x:U,\Delta \,\vdash \,t :T\) and \(\Gamma \,\vdash \,y :U\), then \(\Gamma ,[x := y]\Delta \,\vdash \,[x := y]t :[x := y]T\).

In this statement, we can also see that we only consider substituting one term variable for another, due to MNF. Using MNF affects other parts of the proof as well—in addition to typical canonical forms lemmas, we also need to show that looking up the value bound to a variable in a store preserves the types we can assign to the variable.

Lemma 4.10 (Variable Lookup Inversion).

If we have both \(\Gamma \,\vdash \,\sigma \sim \Delta\) and \(x : S \mathrel {\wedge }C \in \Gamma ,\Delta\), then \(\sigma (x) = v\) implies that \(\Gamma ,\Delta \,\vdash \,v :S \mathrel {\wedge }C\).

Capture Sets and Captured Variables.

Our typing rules use \(\operatorname{cv}\) to calculate the capture set that should be assigned to terms. With that in mind, we can ask this question: what is the exact relationship between captured variables and capture sets we use to type the terms?

Because of subcapturing, this relationship is not as obvious as it might seem. For fully evaluated terms (of the form \(\sigma [\ a\ ]\)), their captured variables are the most precise capture set they can be assigned. The following lemma states this formally.

Lemma 4.11 (Capture Prediction for Answers).

If \(\Gamma \,\vdash \,\sigma [a] :S \mathrel {\wedge }C\), then \(\Gamma \,\vdash \,\operatorname{cv}(\sigma [a]) \lt :C\).

If we start with an unreduced term \(\sigma [\ t\ ]\), then the situation becomes more complex. It can mention and use capabilities that will not be reflected in the capture set at all—for instance, if \(t = x\,y\), the capture set of x is irrelevant to the type assigned to t by (app). However, if \(\sigma [\ t\ ]\) reduces fully to a term of the form \(\sigma [\ \sigma ^{\prime }[\ a\ ]\ ]\), the captured variables of \(\sigma ^{\prime }[\ a\ ]\) will correspond to capture sets we could assign to t.

In other words, the capture sets we assign to unevaluated terms under a store context predict variables that will be captured by the answer those terms reduce to. Formally, we can express this as follows.

Lemma 4.12 (Capture Prediction for Terms).

Let \(\,\vdash \,\sigma \sim \Delta\) and \(\Delta \,\vdash \,t :S \mathrel {\wedge }C\). Then \(\sigma [\ t\ ] \longrightarrow ^{*} \sigma [\ \sigma ^{\prime }[\ a\ ]\ ]\) implies that \(\Delta \,\vdash \,\operatorname{cv}(\sigma ^{\prime }[\ a\ ]) \lt :C\).

4.1 Predicting Used Capabilities

In this section, we develop an additional correctness criterion: a theorem that uses capture sets to predict what capabilities may be used while a term is evaluated. Since the ability to perform effects is mediated by capabilities in capability-safe systems, predicting what capabilities may be used by terms is the same as reasoning about the authority of terms to perform side-effectful operations [Miller 2006; Drossopoulou et al. 2016]. This theorem is also an important correctness criterion for boxing, as we will discuss later.

If we want to reason about what capabilities are used, we need to have a concept of primitive capabilities which must be tracked, not unlike how STLC needs base types [Pierce 2002] to make its correctness theorem non-vacuous. Although object capabilities come in many forms, for our current purposes it suffices to consider capabilities that exist for the entire duration of the program, such as a capability to access the filesystem or the standard output. Within our base system, we can simply designate an outer fragment of the store as the platform context \(\Psi\), which introduces well-behaved primitive capabilities: \(\begin{equation*} \begin{array}{lcl} \Psi &::=& [\,]\mathop {\ \ \ |\ \ \ }\operatorname{{\bf \textsf {let}}} x = v \operatorname{{\bf \textsf {in}}} \Psi \quad \quad \operatorname{{\bf \textsf {if}}}\ \operatorname{fv}(v) = \lbrace \rbrace . \\ \end{array} \end{equation*}\) The operational semantics of the capabilities in \(\Psi\) are defined by the values v. The values need to be closed, as otherwise the capabilities would depend on other capabilities and would not be primitive. Since \(\Psi\) binds capabilities, their capture set should be \(\lbrace {\bf cap}\rbrace\).

Definition (Well-Typed Program). A term \(\Psi [\ t\ ]\) is a well-typed program if we have \(\Delta \vdash t : T\) for some \(\Delta\) such that \(\vdash \Psi \sim \Delta\) and for all \(x \in \Delta\) there exists a shape type S such that \(x : S \mathrel {\wedge }\lbrace {\bf cap}\rbrace \in \Delta\).

We can now state an intermediate lemma necessary to prove our correctness criterion.

Lemma 4.13 (Program Authority Preservation).

Let \(\Psi [\ t\ ] \longrightarrow \Psi [\ t^{\prime }\ ]\), where \(\Psi [\ t\ ]\) is a well-typed program. Then \(\operatorname{cv}(t^{\prime })\) is a subset of \(\operatorname{cv}(t)\).

We now formally state what capabilities are used during evaluation. Since \(\Psi\) only binds abstractions, it makes sense to say a capability x is used if during evaluation we reduced an application form.

Definition (Used Capabilities).

\(\begin{equation*} \begin{array}{lll} \mathrm{used}(t_1 \longrightarrow t_2 \longrightarrow \cdots \longrightarrow t_n) &=& \mathrm{used}(t_1 \longrightarrow t_2) \cup \mathrm{used}(t_2 \longrightarrow \cdots \longrightarrow t_n) \\ \mathrm{used}(\sigma [\ e[\ x\,y\ ]\ ] \longrightarrow \sigma [\ t\ ]) &=& \lbrace x\rbrace \\ \mathrm{used}(\sigma [\ e[\ x\,[S]\ ]\ ] \longrightarrow \sigma [\ t\ ]) &=& \lbrace x\rbrace \\ \mathrm{used}(t_1 \longrightarrow t_2) &=& \lbrace \rbrace \quad \quad \quad \quad {({\bf otherwise})} \end{array} \end{equation*}\) The last case applies to rules (OPEN), (RENAME), (LIFT).

We are ready to state the theorem.

Theorem 4.14 (Used Capability Prediction).

Let \(\Psi [\ t\ ] \longrightarrow ^\ast \Psi [\ t^{\prime }\ ]\), where \(\Psi [\ t\ ]\) is a well-typed program. Then the primitive capabilities used during the reduction are a subset of the authority of t: \(\begin{equation*} \lbrace \ x \mid x \in \mathrm{used}(\Psi [\ t\ ] \longrightarrow ^\ast \Psi [\ t^{\prime }\ ]), x \in \operatorname{dom} (\Psi)\ \rbrace \subseteq \operatorname{cv}(t). \end{equation*}\)

4.2 Correctness of Boxing

Both Lemma 4.13 and Theorem 4.14 would be trivially true if \(\operatorname{cv}(t)\) was just the free variables of t, since evaluation typically does not add new free variables to a term. However, boxes allow preventing some captured free variables from appearing in capture sets. For instance, if we first box x and then pass it as an argument to f, the overall \(\operatorname{cv}\) will not mention x: \(\begin{equation*} \operatorname{cv}(\operatorname{{\bf \textsf {let}}} y = \Box x \operatorname{{\bf \textsf {in}}} f\,y) = \lbrace f\rbrace . \end{equation*}\)

Given this behavior, what is the correctness criterion for how we type box and unbox forms? Intuitively, we should be unable to “smuggle in” a capability via boxes: a term’s capabilities should all be accounted for. By the progress theorem and a straightforward induction, we can prove that the cv of a term that boxes and immediately unboxes a capability accounts for the unboxed capability.

Proposition 4.15.

Let \(\vdash \sigma \sim \Delta\) and \(t = (\operatorname{{\bf \textsf {let}}} y = \Box x \operatorname{{\bf \textsf {in}}} C ⟜ y)\) such that we have \(\Delta \vdash e[\ t\ ] : T\) for some e and T. Then \(\operatorname{cv}(t) = C\) and we also have \(\begin{equation*} \Delta \vdash \lbrace x\rbrace \lt : C. \end{equation*}\)

Speaking more generally, the fundamental function of boxes is that they allow temporarily preventing a captured free variable from affecting the \(\operatorname{cv}\) of a term. The capability inside the box can still be used via the unbox form \(C ⟜ x\), but only at the cost of adding C, the “key” used to open the box, to the \(\operatorname{cv}\) of the term. The correctness criterion for box and unbox forms is that the keys used to open boxes should account for the capabilities inside the box: a term should only be able to use capabilities that are accounted for by its \(\operatorname{cv}\), just as Lemma 4.13 and Theorem 4.14 show.5

There is another aspect of boxing explained by these theorems: boxes can later be opened with unbox forms, shifting where capture sets appear. As an example, consider the following two lambdas, both of which may use \(\operatorname{\mathsf {fs}}\) (we define \(\operatorname{\mathsf {Proc}} \triangleq \forall (x:\operatorname{\mathsf {Unit}})\,\operatorname{\mathsf {Unit}}\)): the first lambda’s argument is a capability—a closure capturing \(\operatorname{\mathsf {fs}}\). The lambda can invoke this closure without affecting its capture set. Meanwhile, the argument of the second lambda is pure: a box containing a closure capturing \(\operatorname{\mathsf {fs}}\). The second lambda can still invoke its argument, but only after unboxing it, which charges its capture set with the \(\operatorname{\mathsf {fs}}\) capability.

Fig. 6.

Fig. 6. An example of boxes shifting what capture sets are charged with capabilities.

Understanding that capture sets describe the authority of terms explains why it is sound for boxes to shift a capability from one capture set to another. To illustrate, let \(\Gamma\) bind the first closure from Figure 6 as \(f_1 : \forall (g: \operatorname{\mathsf {Proc}} \mathrel {\wedge }\lbrace \operatorname{\mathsf {fs}}\rbrace)\,\operatorname{\mathsf {Unit}}\) and the second closure as \(f_2 : (\forall (g: \Box \operatorname{\mathsf {Proc}} \mathrel {\wedge }\lbrace \operatorname{\mathsf {fs}}\rbrace)\,\operatorname{\mathsf {Unit}}) \mathrel {\wedge }\lbrace \operatorname{\mathsf {fs}}\rbrace\) and also bind an \(\operatorname{\mathsf {fs}}\)-capturing procedure as \(p : \operatorname{\mathsf {Proc}} \mathrel {\wedge }\lbrace \operatorname{\mathsf {fs}}\rbrace\). Calling either \(f_1\) or \(f_2\) can use \(\operatorname{\mathsf {fs}}\), which is reflected by \(\operatorname{cv}\) even if the capture sets of \(f_1\) and \(f_2\) are different. In the first case, we have \(\Gamma \vdash \operatorname{cv}(f_1\,p) \lt :\lbrace p\rbrace \lt :\lbrace \operatorname{\mathsf {fs}}\rbrace\): we can elide \(f_1\) from the capture set, but afterward the smallest set we can widen to is \(\lbrace \operatorname{\mathsf {fs}}\rbrace\). In the second case, we have \(\Gamma \vdash \operatorname{cv}(\operatorname{{\bf \textsf {let}}} p^{\prime } = \Box p \operatorname{{\bf \textsf {in }}}f_2\,p^{\prime }) = \lbrace f_2\rbrace \lt :\lbrace \operatorname{\mathsf {fs}}\rbrace\): p is absent from the \(\operatorname{cv}\), but the smallest capture set to which we can widen \(\lbrace f_2\rbrace\) is still \(\lbrace \operatorname{\mathsf {fs}}\rbrace\). We correctly predict the authority of both terms.

When we refer to untracked closures, such as \(f : (\forall (x:\operatorname{\mathsf {Unit}})\,\operatorname{\mathsf {Unit}}) \mathrel {\wedge }\lbrace \rbrace\), as pure, we are also indirectly using the notion that a term’s \(\operatorname{cv}\) reflects its authority. What we mean is that such closures cannot be used to cause any effects on their own. Formally, when we reduce \(f\,()\) to \([x := ()]t\), based on (abs) we must have \(\operatorname{cv}([x := ()]t) = \lbrace \rbrace\): a term that cannot use any capabilities.

Skip 5EXAMPLES Section

5 EXAMPLES

We have implemented a type checker for \(\mathsf {CC}_{\lt :\Box }\) as an extension of the Scala 3 compiler to enable experimentation with larger code examples. Notably, our extension infers which types must be boxed, and automatically generates boxing and unboxing operations when values are passed to and returned from instantiated generic datatypes, so none of these technical details appear in the actual user-written Scala code. We now present examples that demonstrate the usability of the language.

5.1 Church-Encoded Lists

In this section, we remain close to the core calculus by encoding lists using only functions; here, we still show the boxed types and boxing and unboxing operations that the compiler infers in gray, although they are not in the source code.

Using the Scala prototype implementation of \(\mathsf {CC}_{\lt :\Box }\), the encoding by Böhm and Berarducci [1985] of a linked list data structure can be implemented and typed as follows. Here, a list is represented by its right fold function:

A list inherently captures any capabilities that may be captured by its elements. Therefore, naively, one may expect the capture set of the list to include the capture set of the type T of its elements. However, boxing and unboxing enables us to elide the capture set of the elements from the capture set of the containing list. When constructing a list using cons, the elements must be boxed:

A map function over the list can be implemented and typed as follows:

The mapped function f may capture any capabilities, as documented by the capture set cap in its type. However, this does not affect the type of map or its result type List[B], since the mapping is strict, so the resulting list does not capture any capabilities captured by f. If a value returned by the function f were to capture capabilities, this would be reflected in its type, the concrete type substituted for the type variable B, and would therefore be reflected in the concrete instantiation of the result type List[B] of map.

5.2 Stack Allocation

In this and the following section, we use additional Scala features in larger examples to implement stack allocation and polymorphic data structures. For these examples, we present the source code without cluttering it with the boxing operations inferred by the compiler. Furthermore, we use the abbreviation that a single trailing ^ that is not followed by an opening brace stands for ^{cap}.

Automatic memory management using a garbage collector is convenient and prevents many errors, but it can impose significant performance overheads in programs that need to allocate large numbers of short-lived objects. If we can bound the lifetimes of some objects to coincide with a static scope, it is much cheaper to allocate those objects on a stack as follows:6

The withFreshPooled method calls the provided function op with a freshly stack-allocated instance of class Pooled. It works as follows. The stack maintains a pool of already allocated instances of Pooled. The nextFree variable records the offset of the first element of stack that is available to reuse; elements before it are in use. The withFreshPooled method first checks whether the stack has any available instances; if not, it adds one to the stack. Then it increments nextFree to mark the first available instance as used, calls op with the instance, and decrements nextFree to mark the instance as freed. In the fast path, allocating and freeing an instance of Pooled is reduced to just incrementing and decrementing the integer nextFree.

However, this mechanism fails if the instance of Pooled outlives the execution of op, if op captures it in its result. Then the captured instance may still be accessed while at the same time also being reused by later executions of op. For example, the following invocation of withFreshPooled returns a closure that accesses the Pooled instance when it is invoked on the second line, after the Pooled instance has been freed:

Using capture sets, we can prevent such captures and ensure the safety of stack allocation just by marking the Pooled instance as tracked:

Now the pooled instance can be captured only in values whose capture set accounts for {pooled}. The type variable T cannot be instantiated with such a capture set because pooled is not in scope outside of withFreshPooled, so only {cap} would account for {pooled}, but we disallowed instantiating a type variable with {cap}. With this declaration of withFreshPooled, the preceding pooledClosure example is correctly rejected, whereas the following safe example is allowed:

5.3 Collections

In the following examples, we show that a typing discipline based on \(\mathsf {CC}_{\lt :\Box }\) can be lightweight enough to make capture checking of operations on standard collection types practical. This is important, since such operations are the backbone of many programs. All examples compile with our current capture checking prototype [Scala 2022b].

We contrast the APIs of common operations on Scala’s standard collection types List and Iterator when capture sets are taken into account. Both APIs are expressed as Scala 3 extension methods [Odersky and Martres 2020] over their first parameter. Here is the List API:

Notably, these methods have almost exactly the same signatures as their versions in the standard Scala collections library. The only differences concern the arguments to flatMap and ++ which now admit an IterableOnce argument with an arbitrary capture set. The type IterableOnce[B]^ makes a subtle distinction: this collection may perform computation to produce elements of type B, and it may have captured capabilities to perform this computation as denoted by the “^”. All these capabilities will have been used (and therefore discarded) by the time the resulting List[B] is produced. Of course, we could have left out the trailing “^”s, but this would have needlessly restricted the argument to non-capturing collections.

Contrast this with some of the same methods for iterators:

Here, methods apply, foldLeft, foldRight, and foreach again have the same signatures as in the current Scala standard library. But the remaining four operations need additional capture annotations. Method drop on iterators returns the given iterator it after skipping n elements. Consequently, its result has {it} as capture set. Methods map and flatMap lazily map elements of the current iterator as the result is traversed. Consequently, they retain both it and f in their result capture set. Method ++ concatenates two iterators and therefore retains both of them in its result capture set.

The examples attest to the practicality of capture checking. Method signatures are generally concise. Higher-order methods over strict collections by and large keep the same types as before. Capture annotations are only needed for capabilities that are retained in closures and are executed on demand later, which matches the developer’s intuitive understanding of reference patterns and signal information that is relevant in this context.

Skip 6WHY BOXES? Section

6 WHY BOXES?

Boxed types and box/unbox operations are a key part of the calculus to make type abstraction work. This might seem surprising. After all, as long as the capture set is not the root capture set {cap}, one can always go from a capturing type to its boxed type and back by boxing and unboxing operations. So in what sense is this more than administrative ceremony? The key observation here is that an unbox operation \(C ⟜ x\) charges the capture set of the environment with the capture set C. If the unbox operation is part of a closure with body \(t,\) then C will contribute to the captured variables \(\operatorname{cv}(t)\) of the body and therefore to the capture set of the closure as a whole. In short, unbox operations propagate captures to enclosing closures (whereas, dually, box operations suppress propagation).

To see why this matters, assume for the moment a system with type polymorphism but without boxes, where type variables can range over capturing types but type variables are not themselves tracked in capture sets. Then the following is possible:

The framework function combines the two sides of an interaction, with an argument x and an argument plugin acting on x. The interaction is generic over type variable X. Now instantiate framework like this:

This looks suspicious since inst now has a pure type with empty capture set, yet invoking it can access the c capability. Here is an example of such an access:

This invocation clearly executes an effect on the formal parameter x, which gets instantiated with c. Yet both inst and writer have pure types with no retained capabilities. Note that writer gets the necessary capability {c} from its argument, so the function itself does not retain capabilities in its environment, which makes it pure. It is difficult to see how a system along these lines could be sound. At the very least, it would violate the capture prediction Lemma 4.12.

Boxing the bound of X and adding the required box/unbox operations rejects the unsound instantiation. The definitions of framework and inst now become:

Now any attempt to invoke inst as before would lead to an error:

Indeed, writer, the argument to inst, now has the type

because the unbox operation in the lambda’s body charges the closure with the capture set {c}. Therefore, the argument is now incompatible with plugin’s formal parameter type

which is a pure function type.

This example shows why one cannot simply drop boxes and keep everything else unchanged. But one could also think of several other possibilities.

One alternative is to drop boxes but keep the stratification of shape types and full types. Type variables would still be full types but not shape types. Such a system would certainly be simpler, but it would also be too restrictive to be useful in practice. For instance, it would not be possible to invoke a polymorphic constructor that creates a list of functions that capture some capability.

Another alternative is to drop both boxes and the stratification of shape types and full types. In this case, to regain soundness, one would have to admit capture sets that range over both term variables and type variables. We have explored this alternative system elsewhere [Boruch-Gruszecki et al. 2021]. This alternative turns out to lead to much larger capture sets in practice, since most polymorphic type variables would end up in capture sets. For instance, the classical list cons function has a pure type in the boxed system:

But in the system that tracks type variables in capture sets, it would have the following more verbose type:

In summary, a system with boxes turned out to lead to the best ergonomics of expression among the alternatives we considered. The core property of boxes is that unboxing charges the environment with the capture set of the unboxed type and thus allows to correctly recover captured references in a box without having to propagate these captures into the types of polymorphic type constructors. So in a sense, the conclusion is that one can always unbox (as long as the capture set is not the universal one), but it does not come for free.

We show in a separate work [Xu and Odersky 2023] that boxed types and boxing and unboxing operations can be inferred. That work presents an algorithmic type system that inserts boxed type constructors around capturing type arguments and inserts box and unbox operations as needed in the terms accessing values of these type arguments. As is typical, the algorithmic type system is significantly more complicated than the declarative system in this article.

One can also turn that around. If we have a sound system with type variables (i.e., by inserting implicit boxed types and box/unbox operation in the way our implementation works), then it is possible to define box and unbox as library operations in the language, along the following lines:

This construction demonstrates that in essence, boxes can be seen as a mechanism to obtain sound polymorphism for capturing types. Once we have a such a system, the functionality of source boxes can also be obtained by defining a parametric class (or an equivalent Church encoding) with a constructor/destructor pair. That is why our implemented language does not need to expose boxed types and primitive box and unbox operations in the source code—a construction like the preceding one is enough to simulate this functionality.

Fig. 7.

Fig. 7. Scoped capability extensions to the static rules of System \(\mathsf {CC}_{\lt :\Box }\) .

Skip 7SCOPED CAPABILITIES Section

7 SCOPED CAPABILITIES

In this section, we discuss how boxes can be used to ensure that capabilities are well-scoped, based on an extension to \(\mathsf {CC}_{\lt :\Box }\). Figure 7 shows the extensions to the static semantics. The extension is minimal: we add a boundary form \(\operatorname{{\bf \textsf {boundary}}}[S]\,x \Rightarrow t\), mirroring a Scala 3 feature [Scala 2022a]. The boundary form delimits a scope that can be broken out of by using the break capability \(x : \operatorname{\mathsf {Break}}[S]\); the form is parameterized by a type argument S that can be inferred in the implementation. A boundary is a more expressive version of a labeled block that can be returned from: it also allows returning across closure and function boundaries since the break capability is a first-class value that can be closed over and passed as an argument. The type system should disallow invoking the capability once the boundary is left, since intuitively at that point there is no scope to be broken out of. As we explained in Section 4.2, a variable x of boxed type can only be opened via an unbox form \(C ⟜ x\) such that C accounts for the capability in the box. Our plan is simple: we (1) ensure that all capabilities leaving the \(\operatorname{{\bf \textsf {boundary}}}\) scope are boxed and (2) ensure that the scoped capability cannot be accounted for by any variable other than itself. In this extension, the only way for a scoped capability to leak is by being directly returned from its scope, so it suffices to require in rule (boundary) that the result of a \(\operatorname{{\bf \textsf {boundary}}}\) form is pure. To illustrate, consider the following attempt to leak a scoped capability by returning a closure (where \(\operatorname{\mathsf {Proc}} \triangleq \forall (y:\operatorname{\mathsf {Unit}})\,\operatorname{\mathsf {Unit}}\)): \(\begin{equation*} \vdash\quad \operatorname{{\bf \textsf {boundary}}}[\ldots ]\,x \Rightarrow \operatorname{{\bf \textsf {l}et}} f = \lambda (y:\operatorname{\mathsf {Unit}})\, x\,() \operatorname{{\bf \textsf {i}n}} \Box f \quad :\quad \Box \operatorname{\mathsf {Proc}} \mathrel {\wedge }\lbrace {\bf cap}\rbrace \end{equation*}\) Since a boundary’s result must be pure, we have no choice but to box the closure. Since x is not in scope outside of the boundary, the capture set under the box must be \(\lbrace {\bf cap}\rbrace\). Since no typing context accounts for \(\lbrace {\bf cap}\rbrace\), the box cannot be opened anymore and we are safe.

In a fully featured programming language, there are other channels for scoped capabilities to leak (e.g., via mutable state). With boxing, to make such channels sound it suffices to only allow pure values to pass through them. For instance, if we want to store a capability in mutable state, we need to box it; afterward, we can only use it in a typing context that accounts for the capabilities under the box. In more complicated scenarios, a capability may return to its scope after leaving it; such cases could occur, for instance, when we allow sending values between threads and when we allow effect-polymorphic effect handlers [Leijen 2014; Biernacki et al. 2020]. Boxing has been shown to be sound and behave as expected in the latter scenario: the boxed capability can be unboxed once it is back in its scope, but not earlier [Brachthäuser et al. 2022]. Thus, although the extension we show is minimal, it presents all the formal foundations we need for ensuring scoping of capabilities.

Fig. 8.

Fig. 8. Scoped capability extensions to dynamic rules of System \(\mathsf {CC}_{\lt :\Box }\) .

7.1 Dynamic Semantics of Scoped Capabilities

Figure 8 shows the extensions to the dynamic semantics of \(\mathsf {CC}_{\lt :\Box }\). We add new evaluation-time term forms; we explain them by inspecting the relevant evaluation rules. Rule (enter) reduces a term of the form \(\sigma [\ e[\ \operatorname{{\bf \textsf {boundary}}}[S]\,x \Rightarrow t\ ]\ ]\) to \(\sigma [\ \operatorname{{\bf \textsf {l}et}} x = {l}_{S} \operatorname{{\bf \textsf {i}n}} e[\ \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\,t\ ]\ ]\): entering a boundary binds the break capability \({l}_{S}\) in the store and adds a scope form to the evaluation context. The break capability is a label l annotated with the boundary’s return type, where a label represents a boundary’s unique runtime identifier. The scope form \(\operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\,t\) is a marker on the stack (formally, the evaluation context), denoting where a boundary ends; all scopes are annotated with their corresponding labels. When the break capability is invoked, the term has the form \(\sigma [\ e_1[\ \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\, e_2[\ x\,y\ ] \ ]\ ]\) and the evaluation context is split by a scope form into the part outside and inside the scope. Rule (breakout) reduces such terms to \(\sigma [\ e_1[\ y\ ]\ ]\), dropping the scope form together with the inner evaluation context. Once only an answer remains under the scope, rule (leave) reduces \(\sigma [\ e[\ \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\,a\ ]\ ]\) to \(\sigma [\ e[\ a\ ]\ ]\). Typing ensures that after a boundary is left, its capability is never invoked; otherwise, we could get stuck terms since the scope form needed by (break) would be absent from the evaluation context.

7.2 Metatheory

If we start evaluation from a term well-typed according to the static typing rules (one that does not mention any labels or scope forms), the evaluation rules maintain an invariant: all break capabilities are well-scoped, and all scope labels are unique. Since terms could get stuck without this invariant, we state it formally and incorporate it into the main correctness theorems. For the purposes of our metatheory (including this invariant), we understand labels as primitive capabilities provided by the “runtime” to the program—in particular, the \(\operatorname{cv}\) of a closed term may now mention labels, which we understand as the primitive capabilities a program can access.

Definition (Captured Variables of Contexts). We extend \(\operatorname{cv}\) to contexts by \(\operatorname{cv}([\,]) = \lbrace \rbrace\).

Definition (Proper Program). A term is a proper program if it has the form \(\sigma [\ e[\ t\ ]\ ]\) s.t.:

  • for all l such that \(l \in \operatorname{cv}(\sigma [\ e[\ t\ ]\ ])\):

    there exists a unique x such that \(\sigma (x) = {l}_{S}\) for some S

    there exist unique \(e_1\) and \(e_2\) such that \(e= e_1[\ \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\,e_2\ ]\) for the same S

    for the same \(e_1\) we have \(l \not\in \operatorname{cv}(e_1)\)

  • scope forms in \(\sigma [\ e[\ t\ ]\ ]\) only occur in \(e.\)

Theorem 7.1 (Preservation).

Let \(\Gamma \,\vdash \,\sigma \sim \Delta\) and \(\Gamma ,\Delta \,\vdash \,t : T\), where \(\sigma [\ t\ ]\) is a proper program. Then \(\sigma [\ t\ ] \longrightarrow \sigma [\ t^{\prime }\ ]\) implies that \(\Gamma ,\Delta \,\vdash \,t^{\prime } : T\) and that \(\sigma [\ t^{\prime }\ ]\) is a proper program.

Theorem 7.2 (Progress).

If \(\,\vdash \,\sigma [\ t\ ] :T\) and \(\sigma [\ t\ ]\) is a proper program and a proper configuration, then either t is an answer a or \(\sigma [\ t\ ] \longrightarrow \sigma [\ t^{\prime }\ ]\) for some \(t^{\prime }\).

In the base system, we needed Theorem 4.14 to demonstrate that boxes are typed correctly, since unboxing a capability could never lead to a stuck term. In this extension, unboxing an out-of-scope capability can lead to a stuck term, so we can demonstrate soundness of the boxing rules in a more direct way, by showing the classical progress and preservation theorems. In fact, Lemma 4.13 and Theorem 7.1 both employ an identical argument in the case for rule (open).

7.2.1 Predicting Used Capabilities.

We can understand labels as the primitive capabilities a program may access. This makes the situation more complicated than before: primitive capabilities can now be created and dropped. From the object capability perspective, this is as expected. For example, in Wyvern [Melicher et al. 2017], creating capabilities is a commonplace occurrence, since an object with mutable state counts as a capability. In systems where file handles are capabilities, a capability is created or dropped every time we open or close a file handle.

This means that when reasoning about what capabilities are used, we need to consider what capabilities were created or dropped. To account for this, we reason about traces: sets of events that occurred during evaluation.

\(\begin{equation*} \begin{array}{llll} \mathrm{trace}(t_1 \longrightarrow t_2 \longrightarrow \cdots \longrightarrow t_n) &=& { \mathrm{trace}(t_1 \longrightarrow t_2) \cup \mathrm{trace}(t_2 \longrightarrow \cdots \longrightarrow t_n) } \\ \mathrm{trace}(\sigma [\ e[\ x\,y \ ]\ ] \longrightarrow s) &=& \lbrace \mathbf {use}(l)\rbrace &\operatorname{{\bf \textsf {if}}}\ \sigma (x) = {l}_{S} \\ \mathrm{trace}(\sigma [\ e[\ \operatorname{{\bf \textsf {boundary}}}[S]\,x \Rightarrow t \ ]\ ] \longrightarrow s) &=& \lbrace \mathbf {create}(l)\rbrace &\operatorname{{\bf \textsf {if}}}\ s = \sigma ^{\prime }[\ e[\ \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\,t\ ]\ ]\\ \mathrm{trace}(\sigma [\ e[\ \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\,a \ ]\ ] \longrightarrow s) &=& \lbrace \mathbf {drop}(l)\rbrace \\ \mathrm{trace}(t_1 \longrightarrow t_2) &=& \lbrace \rbrace &{{\bf otherwise}} \end{array} \end{equation*}\)

We define the following auxiliary functions.

\(\begin{align*} \mathrm{used}(t \longrightarrow ^\ast s) &= \lbrace x \mid \mathbf {use}(x) \in \mathrm{trace}(t \longrightarrow ^\ast s) \rbrace \\ \mathrm{created}(t \longrightarrow ^\ast s) &= \lbrace x \mid \mathbf {create}(x) \in \mathrm{trace}(t \longrightarrow ^\ast s) \rbrace \\ \mathrm{gained}(t \longrightarrow ^\ast s) &= \lbrace x \mid \mathbf {create}(x) \in \mathrm{trace}(t \longrightarrow ^\ast s), \mathbf {drop}(x) \notin \mathrm{trace}(t \longrightarrow ^\ast s) \rbrace \end{align*}\)

The program authority preservation lemma is now stated slightly differently. First, we only consider break capabilities to be primitive. Second, programs can gain authority over new capabilities, but only by creating them and only until the capabilities are dropped. Typing already ensures that all break capabilities are tracked and labels are always “bound,” so it is now unnecessary to separately define platform contexts and well-typed programs.

Lemma 7.3 (Program Authority Preservation).

Let \(t \longrightarrow t^{\prime }\), where \(\vdash t : T\). Then, \(\begin{equation*} \operatorname{cv}(t^{\prime }) \subseteq \operatorname{cv}(t) \cup \mathrm{gained}(t \longrightarrow t^{\prime }). \end{equation*}\)

Finally, we reformulate the used capability prediction theorem.

Theorem 7.4 (Used Capability Prediction).

Let \(t \longrightarrow ^\ast t^{\prime }\), where \(\vdash t : T\). Then the primitive capabilities used during the evaluation are within the authority of t: \(\begin{equation*} \mathrm{used}(t \longrightarrow ^\ast t^{\prime }) \subseteq \operatorname{cv}(t) \cup \mathrm{created}(t \longrightarrow ^\ast t^{\prime }). \end{equation*}\)

Skip 8RELATED WORK Section

8 RELATED WORK

The results presented in this article did not emerge in a vacuum, and many of the underlying ideas appeared individually elsewhere in similar or different form. We follow the structure of the informal presentation in Section 2 and organize the discussion of related work according to the key ideas behind \(\mathsf {CC}_{\lt :\Box }\).

Effects as Capabilities. Establishing effect safety by moderating access to effects via term-level capabilities is not a new idea [Marino and Millstein 2009]. It has been proposed as a strategy to retrofit existing languages with means to reason about effect safety [Choudhury and Krishnaswami 2020; Liu 2016; Osvald et al. 2016]. Recently, it also has been applied as the core principle behind a new programming language featuring effect handlers [Brachthäuser et al. 2020a]. Similar to the preceding prior work, we propose to use term-level capabilities to restrict access to effect operations and other scoped resources with a limited lifetime. Representing effects as capabilities results in a good economy of concepts: existing language features, like term-level binders, can be reused; programmers are not confronted with a completely new concept of effects or regions.

Making Capture Explicit. Having a term-level representation of scoped capabilities introduces the challenge to restrict use of such capabilities to the scope in which they are still live. To address this issue, effect systems have been introduced [Zhang and Myers 2019; Biernacki et al. 2020; Brachthäuser et al. 2020b], but those can result in overly verbose and difficult to understand types [Brachthäuser et al. 2020a]. A third approach, which we follow in this work, is to make capture explicit in the type of functions.

Hannan [1998] proposes a type-based escape analysis with the goal to facilitate stack allocation. The analysis tracks variable reference using a type-and-effect system and annotates every function type with the set of free variables it captures. The authors leave the treatment of effect polymorphism to future work. In a similar spirit, Scherer and Hoffmann [2013] present open closure types to facilitate reasoning about dataflow properties such as non-interference. They present an extension of the simply typed lambda calculus that enhances function types \([ \Gamma _0 ](\tau) \rightarrow \tau\) with the lexical environment \(\Gamma _0\) that was originally used to type the closure.

Brachthäuser et al. [2022] show System C, which mediates between first- and second-class values with boxes. In their system, scoped capabilities are second-class values. Normally, second-class values cannot be returned from any scope, but in System C they can be boxed and returned from some scopes. The type of a boxed second-class value tracks which scoped capabilities it has captured and, accordingly, from which scopes it cannot be returned. System C tracks second-class values with a coeffect-like environment and uses an effect-like discipline for tracking captured capabilities, which can in specific cases be more precise than \(\operatorname{cv}\). In comparison, \(\mathsf {CC}_{\lt :\Box }\) does not depend on a notion of second-class values and deeply integrates capture sets with subtyping.

Recently, Bao et al. [2021] proposed to qualify types with reachability sets. Their reachability types allow reasoning about non-interference, scoping, and uniqueness by tracking for each reference what other references it may alias or (indirectly) point to. Their system formalizes subtyping but not universal polymorphism. However, it relates reachability sets along a different dimension than \(\mathsf {CC}_{\lt :\Box }\). Whereas in \(\mathsf {CC}_{\lt :\Box }\) a subtyping relationship is established between a capability c and the capabilities in the type of c, reachability types assume a subtyping relationship between a variable x and the variable owning the scope where x is defined. Reachability types track detailed points-to and aliasing information in a setting with mutable variables, whereas \(\mathsf {CC}_{\lt :\Box }\) is a more foundational calculus for tracking references and capabilities that can be and was used as a guide for an implementation in a complete programming language. It would be interesting to explore how reachability and separation can be tracked in \(\mathsf {CC}_{\lt :\Box }\).

Capture Polymorphism. Combining effect tracking with higher-order functions immediately gives rise to effect polymorphism, which has been a long-studied problem.

Similar to the usual (parametric) type polymorphism, the seminal work by Lucassen and Gifford [1988] on type and effect systems featured (parametric) effect polymorphism by adding language constructs for explicit region abstraction and application. Similarly, work on region-based memory management [Tofte and Talpin 1997] supports region polymorphism by explicit region abstraction and application. Recently, languages with support for algebraic effects and handlers, such as Koka [Leijen 2017] and Frank [Lindley et al. 2017], feature explicit, parametric effect polymorphism.

It has been observed multiple times, for instance, by Osvald et al. [2016] and Brachthäuser et al. [2020a] that parametric effect polymorphism can become verbose and results in complicated types and confusing error messages. Languages sometimes attempt to hide the complexity—they “simplify the types more and leave out ‘obvious’ polymorphism” [Leijen 2017]. However, this solution is not satisfying since the full types resurface in error messages. In contrast, we support polymorphism by reusing existing term-level binders and support simplifying types by means of subtyping and subcapturing.

Rytz et al. [2012] present a type-and-effect system in which higher-order functions like map can be assigned simple signatures that do not mention effect variables. As in \(\mathsf {CC}_{\lt :\Box }\), it is not necessary to modify the signatures of higher-order functions which only call their argument. However, in the “argument-relative” system of Rytz et al., it is impossible to reference an effect of a particular argument. This limits the overall expressivity in their system, compared to \(\mathsf {CC}_{\lt :\Box }\)—for instance, it is not possible to type function composition, or in general a function that returns a value whose effect is relative to its argument. Their system also does not allow user-defined effects, whereas \(\mathsf {CC}_{\lt :\Box }\) allows tracking any variable by annotating it with an appropriate capture set.

The problem of how to prevent capabilities from escaping in closures is also addressed by second-class values that can only be passed as arguments but not be returned in results or stored in mutable fields. Siek et al. [2012] enforce second-class function arguments using a classical polymorphic effect discipline, whereas Osvald et al. [2016] and Brachthäuser et al. [2020a] present a specialized type discipline for this task. Second-class values cannot be returned or closed-over by first-class functions. However, second-class functions can freely close over capabilities, since they are second class themselves. This gives rise to a convenient and lightweight form of contextual effect polymorphism [Brachthäuser et al. 2020a]. Although this approach allows for effect polymorphism with a simple type system, it is also restrictive because it also forbids local returns and retentions of capabilities—a problem solved by adding boxing and unboxing [Brachthäuser et al. 2022].

Foundations of Boxing. Contextual Modal Type Theory (CMTT) [Nanevski et al. 2008] builds on intuitionistic modal logic. In intuitionistic modal logic, the graded propositional constructor \([ \Psi ]\;A\) (pronounced box) witnesses that A can be proven only using true propositions in \(\Psi\). Judgments in CMTT have two contexts: \(\Gamma\) roughly corresponding to \(\mathsf {CC}_{\lt :\Box }\)bindings with \(\lbrace {\bf cap}\rbrace\) as their capture set, and a modal context \(\Delta\) roughly corresponding to bindings with concrete capture sets. Bindings in the modal context are necessarily boxed and annotated with a modality \(x :: A[\Psi ] \in \Delta\). Just like our definition of captured variables in \(\mathsf {CC}_{\lt :\Box }\), the definition of free variables by Nanevski et al. [2008] assigns the empty set to a boxed term (i.e., \(fv(\mathsf {box}(\Psi {}. M)) = \lbrace \rbrace\)). Similar to our unboxing construct, using a variable bound in the modal context requires that the current context satisfies the modality \(\Psi\), mediated by a substitution \(\sigma\). Different to CMTT, \(\mathsf {CC}_{\lt :\Box }\) does not introduce a separate modal context. It also does not annotate modalities on binders—instead, these are kept in the types. Also different from CMTT, in \(\mathsf {CC}_{\lt :\Box }\) unboxing is annotated with a capture set and not a substitution.

Comonadic type systems were introduced to support reasoning about purity in existing, impure languages [Choudhury and Krishnaswami 2020]. Very similar to the box modality of CMTT, a type constructor ‘Safe’ witnesses the fact that its values are constructed without using any impure capabilities. The type system presented by Choudhury and Krishnaswami [2020] only supports a binary distinction between pure values and impure values; however, the authors comment that it might be possible to generalize their system to graded modalities.

In the present work, we use boxing as a practical tool, necessary to obtain concise types when combining capture tracking with parametric type polymorphism.

Coeffect Systems. Coeffect systems also attach additional information to bindings in the environment, leading to a typing judgment of the form \(\Gamma @\; \mathcal {C} \,\vdash \,e :\tau\). Such systems can be seen as similar in spirit to \(\mathsf {CC}_{\lt :\Box }\), where additional information is available about each variable in the environment through the capture set of its type. Petricek et al. [2014] show a general coeffect framework that can be instantiated to track various concepts such as bounded reuse of variables, implicit parameters, and data access. This framework is based on simply typed lambda calculus, and its function types are always coeffect-monomorphic. In contrast, \(\mathsf {CC}_{\lt :\Box }\) is based on \(\textsf {System F}_{\lt :}~\)(thus supporting type polymorphism and subtyping) and supports capture-polymorphic functions.

Object Capabilities. The (object) capability model of programming [Crary et al. 1999; Boyland et al. 2001; Miller 2006] controls security-critical operations by requiring access to a capability. Such a capability can be seen as the constructive proof that the holder is entitled to perform the critical operation. Reasoning about which operations a module can perform is reduced to reasoning about which references to capabilities a module holds.

The Newspeak language [Bracha et al. 2010] features object capabilities. In particular, it features the platform capability, an object that grants access to the underlying platform and allows resolving modules and capabilities. The platform capability is similar to the root capability \({\bf cap}\): a \(\mathsf {CC}_{\lt :\Box }\) value whose capture set is \(\lbrace {\bf cap}\rbrace\) has the authority to access arbitrary capabilities, whereas capturing the Newspeak platform capability grants access to the entire platform.

The Wyvern language [Melicher et al. 2017] implements the object capability model by distinguishing between stateful resource modules and pure modules. Access to resource modules is restricted and only possible through capabilities. Determining the authority granted by a module amounts to manually inspecting its type signature and all of the type signatures of its transitive imports. To support this analysis, Melicher [2020] extends the language with a fine-grained effect system that tracks access of capabilities in the type of methods.

Figueroa et al. [2016] show an intricately engineered encoding of object capabilities in Haskell. A monad transformer’s private operations can only be called from modules with the appropriate authority. The capabilities may be part of a hierarchy—for example, the ReadWrite capability may subsume the Read and Write capabilities. Capabilities may be shared between modules through encoded friend declarations, and one may determine a module’s authority like in Wyvern.

In \(\mathsf {CC}_{\lt :\Box }\), one can statically reason about authority and capabilities simply by inspecting capture sets of types. Additionally, subcapturing naturally allows defining capability hierarchies. If we model modules via function abstraction, the function’s capture set directly reflects its authority. Importantly, \(\mathsf {CC}_{\lt :\Box }\) does not include an effect system and thus tracks mention rather than use.

Skip 9CONCLUSION Section

9 CONCLUSION

We introduced a new type system \(\mathsf {CC}_{\lt :\Box }\) to track captured references of values. Tracked references are restricted to capabilities, where capabilities are references bootstrapped from other capabilities, starting with the universal capability. Implementing this simple principle then naturally suggests a chain of design decisions:

(1)

Because capabilities are variables, every function must have its type annotated with its free capability variables.

(2)

To manage the scoping of those free variables, function types must be dependently-typed.

(3)

To prevent non-variable terms from occurring in types, the programming language is formulated in MNF.

(4)

Because of type dependency, the let-bindings of MNF have to satisfy the avoidance property, to prevent out-of-scope variables from occurring in types.

(5)

To make avoidance possible, the language needs a rich notion of subtyping on the capture sets.

(6)

Because the capture sets represent object capabilities, the subcapture relation cannot just be the subset relation on sets of variables—it also has to take into account the types of the variables, since the variables may be bound to values which themselves capture capabilities.

(7)

To keep the size of the capture sets from ballooning out of control, the article introduces a box connective with box and unbox rules to control when free variables are counted as visible.

We showed that the resulting system can be used as the basis for lightweight polymorphic effect checking, without the need for effect quantifiers. We also identified three key principles that keep notational overhead for capture tracking low:

Variables are tracked only if their types have non-empty capture sets. In practice, the majority of variables are untracked and thus do not need to be mentioned at all.

Subcapturing, subtyping, and subsumption mean that more detailed capture sets can be subsumed by coarser ones.

Boxed types stop propagation of capture information in enclosing types, which avoids repetition in capture annotations to a large degree.

Our experience so far indicates that the presented calculus is simple and expressive enough to be used as a basis for more advanced effect and resource checking systems and their practical implementations.

APPENDICES

A PROOFS

Skip A.1Proof Devices Section

A.1 Proof Devices

[NOTE: The article should already define well-formedness of types.]

We extend type well-formedness to environments.

To prove Preservation (Theorem A.42), we relate the typing derivation of a term of the form \(\sigma [\ t\ ]\) to the typing derivation for the plug term t inside the store \(\sigma\). We do so with the following definition.

Definition A.1 (Evaluation Context Typing (\(\Gamma \vdash e : U \Rightarrow T\))) We say that e can be typed as \(U \Rightarrow T\) in \(\Gamma\) iff for all t such that \(\Gamma \vdash t : U\), we have \(\Gamma \vdash e[\ t\ ] : T\).

Fact A.2.

If \(\sigma [\ t\ ]\) is a well-typed term in \(\Gamma\), then there exists a \(\Delta\) matching \(\sigma\) (i.e., such that \(\Gamma \,\vdash \,\sigma \sim \Delta\)), finding it is decidable, and \(\Gamma ,\Delta\) is well-formed.

Fact A.3.

The analogous holds for \(e[\ t\ ]\).

Skip A.2Properties of Evaluation Contexts and Stores Section

A.2 Properties of Evaluation Contexts and Stores

In the proof, we use the following metavariables: \(C,D\) for capture sets, \(R,S\) for shape types, and \(P,Q,T,U\) for types.

We also denote the capture set fragment of a type as \(\operatorname{cv}(T)\), defined as \(\operatorname{cv}(R \mathrel {\wedge }C) = C\).

In all our statements, we implicitly assume that all environments are well-formed.

Lemma A.4 (Evaluation Context Typing Inversion).

\(\Gamma \,\vdash \,e[\ s\ ] : T\) implies that for some U we have \(\Gamma \vdash e : U \Rightarrow T\) and \(\Gamma \vdash s : U\).

Proof.

By induction on the structure of e. If \(e = [\,]\), then \(\Gamma \vdash s : T\) and clearly \(\Gamma \vdash [\,] : T \Rightarrow T\). Otherwise, \(e = \operatorname{{\bf \textsf {l}et}} x = e^{\prime } \operatorname{{\bf \textsf {i}n}} t\). Proceed by induction on the typing derivation of \(e[\ s\ ]\). We can only assume that \(\Gamma \vdash e[\ s\ ] : T^{\prime }\) for some \(T^{\prime }\) s.t. \(\Gamma \vdash T^{\prime } \lt : T\).

Case (let). Then, \(\Gamma \vdash e^{\prime }[\ s \ ] : U^{\prime }\) and \(\Gamma ,x:U^{\prime } \vdash t : T^{\prime }\) for some \(U^{\prime }\). By the outer IH, for some U we then have \(\Gamma \vdash e^{\prime } : U \Rightarrow U^{\prime }\) and \(\Gamma \vdash s : U\). The former unfolds to \(\forall s^{\prime }.\, \Gamma \vdash s^{\prime } : U \Rightarrow \Gamma \vdash e^{\prime }[\ s^{\prime }\ ] : U^{\prime }\). We now want to show that \(\forall s^{\prime }.\, \Gamma \vdash s^{\prime } : U \Rightarrow \Gamma \vdash e[\ s^{\prime }\ ] : T^{\prime }\). We already have \(\Gamma \vdash e^{\prime }[\ s^{\prime }\ ] : U^{\prime }\) and \(\Gamma ,x:U^{\prime } \vdash t : T^{\prime }\), so we can conclude by (let).

Case (sub). Then, \(\Gamma \vdash e[\ s\ ] : T^{\prime \prime }\) and \(\Gamma \vdash T^{\prime \prime } \lt : T^{\prime }\). We can conclude by the inner IH and (trans).□

Lemma A.5 (Evaluation Context Reification).

If both \(\Gamma \,\vdash \,e : U \Rightarrow T\) and \(\Gamma \,\vdash \,s : U\), then \(\Gamma \,\vdash \,e[\ s\ ] : T\).

Proof.

Immediate from the definition of \(\Gamma \vdash e : U \Rightarrow T\).□

Lemma A.6 (Store Context Reification).

If \(\Gamma \,\vdash \,\sigma \sim \Delta\) and \(\Gamma ,\Delta \,\vdash \,t :T,\) then \(\Gamma \,\vdash \,\sigma [\ t\ ] :T\).

Proof.

By induction on \(\sigma\).

Case \(\sigma = [\,]\). Immediate.

Case \(\sigma = \sigma ^{\prime }[\ \operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} [\,]\ ]\). Then, \(\Delta = \Delta ^{\prime }, x:U\) for some U. Since \(x \not\in \operatorname{fv}(T)\) as \(\Gamma \,\vdash \,T \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\), by (let), we have that \(\Gamma , \Delta ^{\prime } \,\vdash \,\operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} t,\) and hence by the induction hypothesis for some \(U,\) we have that \(\Gamma , x :U \,\vdash \,\sigma ^{\prime }[t] :T\). The result follows directly.□

The preceding lemma immediately gives us the following.

Corollary A.7 (Replacement of Term under a Store Context).

If \(\Gamma \,\vdash \,\sigma [\ t\ ] : T\) and \(\Gamma \,\vdash \,\sigma \sim \Delta\) and \(\Gamma ,\Delta \,\vdash \,t : T\), then for all \(t^{\prime }\) such that \(\Gamma ,\Delta \,\vdash \,t^{\prime } : T\) we have \(\Gamma \,\vdash \,\sigma [\ t^{\prime }\ ] : T\).

Skip A.3Properties of Subcapturing Section

A.3 Properties of Subcapturing

Lemma A.8 (Top Capture Set).

Let \(\Gamma \vdash C \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\). Then, \(\Gamma \vdash C \lt : \lbrace {\bf cap}\rbrace\).

Proof.

By induction on \(\Gamma\). If \(\Gamma\) is empty, then C is either empty or \({\bf cap}\in C\), so we can conclude by (sc-set) or (sc-elem) correspondingly. Otherwise, \(\Gamma = \Gamma ^{\prime },x:S \mathrel {\wedge }D,\) and since \(\Gamma\) is well-formed, \(\Gamma ^{\prime } \vdash D \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\). By (sc-set), we can conclude if for all \(y \in C\) we have \(\Gamma \vdash \lbrace y\rbrace \lt : \lbrace {\bf cap}\rbrace\). If \(y = x\), by IH we derive \(\Gamma ^{\prime } \vdash D \lt : \lbrace {\bf cap}\rbrace\), which we then weaken to \(\Gamma\) and conclude by (sc-var). If \(y \ne x\), then \(\Gamma ^{\prime } \vdash \lbrace y\rbrace \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\), so by IH we derive \(\Gamma ^{\prime } \vdash \lbrace y\rbrace \lt : \lbrace {\bf cap}\rbrace\) and conclude by weakening.□

Corollary A.9 (Effectively Top Capture Set).

Let \(\Gamma \vdash C,D \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\) such that \({\bf cap}\in D\). Then we can derive \(\Gamma \vdash C \lt : D\).

Proof.

We can derive \(\Gamma \vdash C \lt : \lbrace {\bf cap}\rbrace\) by Lemma A.8 and then we can conclude by Lemma A.12 and (sc-elem).□

Lemma A.10 (Universal Capability Subcapturing Inversion).

Let \(\Gamma \vdash C \lt : D\). If \({\bf cap}\in C\), then \({\bf cap}\in D\).

Proof.

By induction on subcapturing. Case (sc-elem) immediate, case (sc-set) by repeated IH, case (sc-var) contradictory.□

Lemma A.11 (Subcapturing Distributivity).

Let \(\Gamma \,\vdash \,C \lt :D\). Then for all \(x \in C,\) we have \(\Gamma \,\vdash \,\lbrace x\rbrace \lt :D\).

Proof.

By inspection of the last subcapturing rule used to derive \(C \lt :D\). All cases are immediate. If the last rule was (sc-set), we have our goal as premise. Otherwise, we have \(C = \lbrace x\rbrace\) and the goal follows directly.□

Lemma A.12 (Subcapturing Transitivity).

If \(\Gamma \,\vdash \,C_1 \lt :C_2\) and \(\Gamma \,\vdash \,C_2 \lt :C_3,\) then \(\Gamma \,\vdash \,C_1 \lt :C_3\).

Proof.

By induction on the first derivation.

Case (sc-elem). \(C_1 = \lbrace x\rbrace\) and \(x \in C_2\), so by Lemma A.11 \(\Gamma \,\vdash \,\lbrace x\rbrace \lt :C_3\).

Case (sc-var). Then \(C_1 = \lbrace x\rbrace\) and \(x : R \mathrel {\wedge }C_4 \in \Gamma\) and \(\Gamma \,\vdash \,C_4 \lt :C_2\). By IH, \(\Gamma \,\vdash \,C_4 \lt :C_3\) and we can conclude by (sc-var).

Case (sc-set). By repeated IH and (sc-set).□

Lemma A.13 (Subcapturing Reflexivity).

If \(\Gamma \,\vdash \,C \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\), then \(\Gamma \,\vdash \,C \lt :C\).

Proof.

By (sc-set) and (sc-elem).□

Lemma A.14 (Subtyping Implies Subcapturing).

If \(\Gamma \,\vdash \,R_1 \mathrel {\wedge }C_1 \lt :R_2 \mathrel {\wedge }C_2\), then \(\Gamma \,\vdash \,C_1 \lt :C_2\).

Proof.

By induction on the subtyping derivation. If (capt), immediate. If (trans), by IH and subcapturing transitivity Lemma A.12. If (refl), then \(C_1 = C_2\) and we can conclude by Lemma A.13. Otherwise, \(C_1 = C_2 = \lbrace \rbrace\) and we can conclude by (sc-set).□

A.3.1 Subtyping Inversion.

Fact A.15.

Both subtyping and subcapturing are transitive.

Proof.

Subtyping is intrisically transitive through (trans), whereas subcapturing admits transitivity as per Lemma A.12.□

Fact A.16.

Both subtyping and subcapturing are reflexive.

Proof.

Again, this is an intrinsic property of subtyping by (refl) and an admissible property of subcapturing per Lemma A.13.□

Lemma A.17 (Subtyping Inversion: Type Variable).

If \(\Gamma \,\vdash \,U \lt :X \mathrel {\wedge }C\), then U is of the form \(X^{\prime } \mathrel {\wedge }C^{\prime }\) and we have \(\Gamma \,\vdash \,C^{\prime } \lt :C\) and \(\Gamma \,\vdash \,X^{\prime } \lt :X\).

Proof.

By induction on the subtyping derivation.

Case (tvar), (refl) . Follow from reflexivity (A.16).

Case (capt). Then we have \(U = S \mathrel {\wedge }C^{\prime }\) and \(\Gamma \,\vdash \,C^{\prime } \lt :C\) and \(\Gamma \,\vdash \,S \lt :X\).

This relationship is equivalent to \(\Gamma \,\vdash \,S \mathrel {\wedge }\lbrace \rbrace \lt :X \mathrel {\wedge }\lbrace \rbrace\), on which we invoke the IH.

By IH, we have \(S \mathrel {\wedge }\lbrace \rbrace = Y \mathrel {\wedge }\lbrace \rbrace\) and we can conclude with \(U = Y \mathrel {\wedge }C^{\prime }\).

Case (trans). Then we have \(\Gamma \,\vdash \,U \lt :U\) and \(\Gamma \,\vdash \,U \lt :X \mathrel {\wedge }C\). We proceed by using the IH twice and conclude by transitivity (A.15).

Other rules are impossible.□

Lemma A.18 (Subtyping Inversion: Capturing Type).

If \(\Gamma \,\vdash \,U \lt :S \mathrel {\wedge }C\), then U is of the form \(S^{\prime } \mathrel {\wedge }C^{\prime }\) such that \(\Gamma \,\vdash \,C^{\prime } \lt :C\) and \(\Gamma \,\vdash \,S^{\prime } \lt :S\).

Proof.

We take note of the fact that subtyping and subcapturing are both transitive (A.15) and reflexive (A.16). The result follows from straightforward induction on the subtyping derivation.□

Lemma A.19 (Subtyping Inversion: Function Type).

If \(\Gamma \,\vdash \,U \lt :(\forall (x:T_1)\,T_2) \mathrel {\wedge }C\), then U either is of the form \(X \mathrel {\wedge }C^{\prime }\) and we have \(\Gamma \,\vdash \,C^{\prime } \lt :C\) and \(\Gamma \,\vdash \,X \lt :\forall (x:T_1)\,T_2\), or U is of the form \((\forall (x:U_1)\,U_2) \mathrel {\wedge }C^{\prime }\) and we have \(\Gamma \,\vdash \,C^{\prime } \lt :C\) and \(\Gamma \,\vdash \,T_1 \lt :U_1\) and \(\Gamma ,x:T_1 \,\vdash \,U_2 \lt :T_2\).

Proof.

By induction on the subtyping derivation.

Case (tvar) . Immediate.

Case (fun), (refl) . Follow from reflexivity (A.16).

Case (capt). Then we have \(\Gamma \,\vdash \,C^{\prime } \lt :C\) and \(\Gamma \,\vdash \,S \lt :\forall (x:T_1)\,T_2\).

This relationship is equivalent to \(\Gamma \,\vdash \,S \mathrel {\wedge }\lbrace \rbrace \lt :(\forall (x:T_1)\,T_2) \mathrel {\wedge }\lbrace \rbrace\), on which we invoke the IH.

By IH, \(S \mathrel {\wedge }\lbrace \rbrace\) might have two forms. If \(S \mathrel {\wedge }\lbrace \rbrace = X \mathrel {\wedge }\lbrace \rbrace\), then we can conclude with \(U = X \mathrel {\wedge }C^{\prime }\).

Otherwise, we have \(S \mathrel {\wedge }\lbrace \rbrace = (\forall (x:U_1)\,U_2) \mathrel {\wedge }\lbrace \rbrace\) and \(\Gamma \,\vdash \,T_1 \lt :U_1\) and \(\Gamma ,x:T_1 \,\vdash \,U_2 \lt :T_2\). Then, \(U = (\forall (x:U_1)\,U_2) \mathrel {\wedge }C^{\prime }\) lets us conclude.

Case (trans). Then we have \(\Gamma \,\vdash \,U \lt :U^{\prime }\) and \(\Gamma \,\vdash \,U \lt :(\forall (x:T_1)\,T_2) \mathrel {\wedge }C\). By IH, U may have one of two forms. If \(U = X \mathrel {\wedge }C^{\prime }\), we proceed with Lemma A.17 and conclude by transitivity (A.15).

Otherwise, \(U = (\forall (x:U_1)\,U_2) \mathrel {\wedge }C^{\prime }\) and we use the IH again on \(\Gamma \,\vdash \,U^{\prime } \lt :(\forall (x:U_1)\,U_2) \mathrel {\wedge }C^{\prime }\). If \(U = X \mathrel {\wedge }C^{\prime \prime }\), we again can conclude by (A.15). Otherwise, if \(U = (\forall (x:U_1)\,U_2) \mathrel {\wedge }C^{\prime \prime }\), the IH only gives us \(\Gamma ,x:U_1 \,\vdash \,U_2 \lt :U_2\), which we need to narrow to \(\Gamma ,x:T_1\) before we can similarly conclude by transitivity (A.15).

Other rules are not possible.□

Lemma A.20 (Subtyping Inversion: Type Function Type).

If \(\Gamma \,\vdash \,U \lt :(\forall [X \lt : S]\,T) \mathrel {\wedge }C\), then U either is of the form \(X \mathrel {\wedge }C^{\prime }\) and we have \(\Gamma \,\vdash \,C^{\prime } \lt :C\) and \(\Gamma \,\vdash \,X \lt :\forall [X \lt : S]\,T\), or U is of the form \((\forall [X \lt : R]\,U^{\prime }) \mathrel {\wedge }C^{\prime }\) and we have \(\Gamma \,\vdash \,C^{\prime } \lt :C\) and \(\Gamma \,\vdash \,T \lt :U^{\prime }\) and \(\Gamma ,X\lt :T \,\vdash \,R \lt :S\).

Proof.

Analogous to the proof of Lemma A.19.□

Lemma A.21 (Subtyping Inversion: Boxed Type).

If \(\Gamma \,\vdash \,U \lt :(\Box T) \mathrel {\wedge }C\), then U either is of the form \(X \mathrel {\wedge }C^{\prime }\) and we have \(\Gamma \,\vdash \,C^{\prime } \lt :C\) and \(\Gamma \,\vdash \,X \lt :\Box T\), or U is of the form \((\Box U^{\prime }) \mathrel {\wedge }C^{\prime }\) and we have \(\Gamma \,\vdash \,C^{\prime } \lt :C\) and \(\Gamma \,\vdash \,U^{\prime } \lt :T\).

Proof.

Analogous to the proof of Lemma A.19.□

A.3.2 Permutation, Weakening, Narrowing.

Lemma A.22 (Permutation).

Permutating the bindings in the environment up to preserving environment well-formedness also preserves type well-formedness, subcapturing, subtyping, and typing.

Let \(\Gamma\) and \(\Delta\) be the original and permutated context, respectively. Then:

(1)

If \(\Gamma \,\vdash \,T \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\), then \(\Delta \,\vdash \,T \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\).

(2)

If \(\Gamma \,\vdash \,C_1 \lt :C_2\), then \(\Delta \,\vdash \,C_1 \lt :C_2\).

(3)

If \(\Gamma \,\vdash \,U \lt :T\), then \(\Delta \,\vdash \,U \lt :T\).

(4)

If \(\Gamma \,\vdash \,t :T\), then \(\Delta \,\vdash \,t :T\).

Proof.

As usual, order of the bindings in the environment is not used in any rule.□

[NOTE: In fact, arbitrary permutation preserves all the preceding judgments, but it might violate environment well-formedness, and we never want to do that.]

Lemma A.23 (Weakening).

Adding a binding to the environment such that the resulting environment is well-formed preserves type well-formedness, subcapturing, subtyping, and typing.

Let \(\Gamma\) and \(\Delta\) be the original and extended context, respectively. Then:

(1)

If \(\Gamma \,\vdash \,T \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\), then \(\Delta \,\vdash \,T \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\).

(2)

If \(\Gamma \,\vdash \,C_1 \lt :C_2\), then \(\Delta \,\vdash \,C_1 \lt :C_2\).

(3)

If \(\Gamma \,\vdash \,U \lt :T\), then \(\Delta \,\vdash \,U \lt :T\).

(4)

If \(\Gamma \,\vdash \,t :T\), then \(\Delta \,\vdash \,t :T\).

Proof.

As usual, the rules only check if a variable is bound in the environment and all versions of the lemma are provable by straightforward induction. For rules which extend the environment, such as (abs), we need permutation. All cases are analogous, so we will illustrate only one.

Case (abs). WLOG, we assume that \(\Delta = \Gamma ,x:T\). We know that \(\Gamma \,\vdash \,\lambda (y:U)\,t^{\prime } :\forall (y:U)\,U\), and from the premise of (abs) we also know that \(\Gamma ,y:U \,\vdash \,t^{\prime } :U\).

By IH, we have \(\Gamma ,y:U,x:T \,\vdash \,t^{\prime } :U\). \(\Gamma ,x:T,y:U\) is still a well-formed environment (as T cannot mention y), and by permutation we have \(\Gamma ,x:T,y:U \,\vdash \,t^{\prime } :U\). Then by (abs), we have \(\Gamma ,x:T \,\vdash \,\lambda (y:U)\,t^{\prime } :\forall (y:U)\,U\), which concludes.□

Lemma A.24 (Type Binding Narrowing).

(1)

If \(\Gamma \,\vdash \,S^{\prime } \lt :S\) and \(\Gamma ,X\lt :S,\Delta \,\vdash \,T \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\), then \(\Gamma ,X\lt :S^{\prime },\Delta \,\vdash \,T \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\).

(2)

If \(\Gamma \,\vdash \,S^{\prime } \lt :S\) and \(\Gamma ,X\lt :S,\Delta \,\vdash \,C_1 \lt :C_2\), then \(\Gamma ,X\lt :S^{\prime },\Delta \,\vdash \,C_1 \lt :C_2\).

(3)

If \(\Gamma \,\vdash \,S^{\prime } \lt :S\) and \(\Gamma ,X\lt :S,\Delta \,\vdash \,T_1 \lt :T_2\), then \(\Gamma ,X\lt :S^{\prime },\Delta \,\vdash \,T_1 \lt :T_2\).

(4)

If \(\Gamma \,\vdash \,S^{\prime } \lt :S\) and \(\Gamma ,X\lt :S,\Delta \,\vdash \,t :T\), then \(\Gamma ,X\lt :S^{\prime },\Delta \,\vdash \,t :T\).

Proof.

By straightforward induction on the derivations. Only subtyping considers types to which type variables are bound, and the only rule to do so is (tvar), which we prove in the following. All other cases follow from IH or other narrowing lemmas.

Case (tvar) . We need to prove \(\Gamma ,X\lt :S^{\prime },\Delta \,\vdash \,X \lt :S\), which follows from weakening the lemma premise and using (trans) together with (tvar).□

Lemma A.25 (Term Binding Narrowing).

(1)

If \(\Gamma \,\vdash \,U^{\prime } \lt :U\) and \(\Gamma ,x:U,\Delta \,\vdash \,T \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\), then \(\Gamma ,x:U^{\prime },\Delta \,\vdash \,T \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\).

(2)

If \(\Gamma \,\vdash \,U^{\prime } \lt :U\) and \(\Gamma ,x:U,\Delta \,\vdash \,C_1 \lt :C_2\), then \(\Gamma ,x:U^{\prime },\Delta \,\vdash \,C_1 \lt :C_2\).

(3)

If \(\Gamma \,\vdash \,U^{\prime } \lt :U\) and \(\Gamma ,x:U,\Delta \,\vdash \,T_1 \lt :T_2\), then \(\Gamma ,x:U^{\prime },\Delta \,\vdash \,T_1 \lt :T_2\).

(4)

If \(\Gamma \,\vdash \,U^{\prime } \lt :U\) and \(\Gamma ,x:U,\Delta \,\vdash \,t :T\), then \(\Gamma ,x:U^{\prime },\Delta \,\vdash \,t :T\).

Proof.

By straightforward induction on the derivations. Only subcapturing and typing consider types to which term variables are bound. Only (sc-var) and (var) do so, which we prove in the following. All other cases follow from IH or other narrowing lemmas.

Case (var). We know that \(U = R \mathrel {\wedge }C\) and \(\Gamma ,x:R \mathrel {\wedge }C,\Delta \,\vdash \,x :R \mathrel {\wedge }\lbrace x\rbrace\). As \(\Gamma \,\vdash \,U^{\prime } \lt :U\), from Lemma A.18 we know that \(U^{\prime } = R^{\prime } \mathrel {\wedge }C^{\prime }\) and that \(\Gamma \,\vdash \,R^{\prime } \lt :R\). We need to prove that \(\Gamma ,x:R^{\prime } \mathrel {\wedge }C^{\prime },\Delta \,\vdash \,x :R \mathrel {\wedge }\lbrace x\rbrace\). We can do so through (var), (sub), (capt), (sc-elem) and weakening \(\Gamma \,\vdash \,R^{\prime } \lt :R\).

Case (sc-var). Then we know that \(C_1 = \lbrace y\rbrace\) and that \(y:T \in \Gamma ,x:U,\Delta\) and that \(\Gamma ,x:U,\Delta \,\vdash \,\operatorname{cv}(T) \lt :C_2\).

If \(y \ne x\), we can conclude by IH and (sc-var).

Otherwise, we have \(T = U\). From Lemma A.18 we know that \(\Gamma \,\vdash \,\operatorname{cv}(U^{\prime }) \lt :\operatorname{cv}(U)\), and from IH we know that \(\Gamma ,x:U^{\prime },\Delta \,\vdash \,\operatorname{cv}(U) \lt :C_2\). By (sc-var), to conclude it is enough to have \(\Gamma ,x:U^{\prime },\Delta \,\vdash \,\operatorname{cv}(U^{\prime }) \lt :C_2\), which we do have by connecting two previous conclusions by weakening and Lemma A.12.□

Skip A.4Substitution Section

A.4 Substitution

A.4.1 Term Substitution.

We will make use of the following fact.

Fact A.26.

If \(x:T \in \Gamma\) and \(\vdash \Gamma \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\), then \(\Gamma = \Delta _1,x:T,\Delta _2\) and \(\Delta _1 \vdash T \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\) and so \(x \not\in \operatorname{fv}(T)\).

Lemma A.27 (Term Substitution Preserves Subcapturing).

If \(\Gamma ,x:P,\Delta \,\vdash \,C_1 \lt :C_2\) and \(\Gamma \,\vdash \,D \lt :\operatorname{cv}(P)\), then \(\Gamma ,[x := D]\Delta \,\vdash \,[x := D]C_1 \lt :[x := D]C_2\).

Proof.

Define \(\theta \triangleq [x := D]\). By induction on the subcapturing derivation.

Case (sc-elem). Then \(C_1 = \lbrace y\rbrace\) and \(y \in C_2\). Inspect if \(y = x\). If no, then our goal is \(\Gamma ,\theta \Delta \,\vdash \,\lbrace y\rbrace \lt :\theta {}C_2\). In this case, \(y \in \theta {}C_2\), which lets us conclude by (sc-elem). Otherwise, we have \(\theta {}C_2 = (C_2 \setminus \lbrace x\rbrace) \cup D\), as \(x \in C_2\). Then our goal is \(\Gamma ,\theta {}\Delta \,\vdash \,D \lt :(C_2 \setminus \lbrace x\rbrace) \cup D\), which can be shown by (sc-set) and (sc-elem).

Case (sc-var). Then \(C_1 = \lbrace y\rbrace\) and \(y : S \mathrel {\wedge }C_3 \in \Gamma ,x:P,\Delta\) and \(\Gamma ,x:P,\Delta \,\vdash \,C_3 \lt :C_2\).

Inspect if \(y = x\). If yes, then our goal is \(\Gamma ,\theta \Delta \,\vdash \,D \lt :\theta {}C_2\). By IH, we know that \(\Gamma ,\theta \Delta \,\vdash \,\theta {}C_3 \lt :\theta {}C_2\). As \(x = y\), we have \(P = S \mathrel {\wedge }C_3,\) and therefore based on an initial premise of the lemma, we have \(\Gamma \,\vdash \,D \lt :C_3\). Then by weakening and IH, we know that \(\Gamma ,\theta \Delta \,\vdash \,\theta {}D \lt :\theta {}C_3\), which means we can conclude by Lemma A.12.

Otherwise, \(x \ne y\), and our goal is \(\Gamma ,\theta {}\Delta \,\vdash \,C_1 \lt :\theta {}C_2\). We inspect where y is bound.

Case \(y \in \operatorname{dom} (\Gamma)\). Then note that \(y \not\in C_3\) by Fact A.26. By IH, we have \(\Gamma ,\theta {}\Delta \,\vdash \,\theta {}C_3 \lt :\theta {}C_2\). We can conclude by (sc-var) as \([x := D]C_3 = C_3\) and \(y: P \mathrel {\wedge }C_3 \in \Gamma ,\theta {}\Delta\).

Case \(y \in \operatorname{dom} (\Delta)\). Then \(y: \theta {}(P \mathrel {\wedge }C_3) \in \Gamma ,\theta {}\Delta\) and we can conclude by IH and (sc-var).

Case (sc-set). Then \(C_1 = \lbrace y_1, \ldots , y_n\rbrace\) and we inspect if \(x \in C_1\).

If not, then for all \(y \in C_1\) we have \(\theta {}\lbrace y\rbrace = \lbrace y\rbrace ,\) and so we can conclude by repeated IH on our premises and (sc-set).

If yes, then we know that \(\forall \,y \in C_1.\, \Gamma ,x:P,\Delta \,\vdash \,\lbrace y\rbrace \lt :C_2\). We need to show that \(\Gamma ,\theta {}\Delta \,\vdash \,\theta {}C_1 \lt :\theta {}C_2\). By (sc-set), it is enough to show that if \(y^{\prime } \in \theta {}C_1\), then \(\Gamma ,\theta {}\Delta \,\vdash \,\lbrace y^{\prime }\rbrace \lt :\theta {}C_2\). For each such \(y^{\prime }\), there exists \(y \in C_1\) such that \(y^{\prime } \in \theta {}\lbrace y\rbrace\). For this y, from a premise of (sc-set) we know that \(\Gamma ,x:P,\Delta \,\vdash \,\lbrace y\rbrace \lt :\theta {}C_2\) and so by IH we have \(\Gamma ,\theta {}\Delta \,\vdash \,\theta {}\lbrace y\rbrace \lt :\theta {}C_2\). Based on that, by Lemma A.12 we also have \(\Gamma ,\theta {}\Delta \,\vdash \,\lbrace y^{\prime }\rbrace \lt :\theta {}C_2\). which is our goal.□

Lemma A.28 (Term Substitution Preserves Subtyping).

If \(\Gamma ,x:P,\Delta \,\vdash \,U \lt :T\) and \(\Gamma \,\vdash \,y:P\), then \(\Gamma ,[x := y]\Delta \,\vdash \,[x := y]U \lt :[x := y]T\).

Proof.

Define \(\theta \triangleq [x := y]\). Proceed by induction on the subtyping derivation.

Case (refl), (top) . By same rule.

Case (capt) . By IH and Lemma A.30 and (capt).

Case (trans), (boxed), (fun), (tfun). By IH and re-application of the same rule.

Case (tvar). Then \(U = Y\) and \(T = S\) and \(Y \lt : S \in \Gamma ,x:U,\Delta\) and our goal is \(\Gamma ,\theta \Delta \,\vdash \,\theta {}Y \lt :\theta {}(S)\). Note that \(x \ne Y\) and inspect where Y is bound. If \(Y \in \operatorname{dom} (\Gamma)\), we have \(Y \lt : S \in \Gamma ,\theta \Delta\) and since \(x \not\in \operatorname{fv}(S)\) (Fact A.26), \(\theta (S) = S\). Then, we can conclude by (tvar). Otherwise, if \(Y \in \operatorname{dom} (\Delta)\), we have \(Y \lt : \theta {}S \in \Gamma ,\theta \Delta\) and again we can conclude by (tvar).□

Lemma A.29 (Term Substitution Preserves Typing).

If \(\Gamma ,x:P,\Delta \,\vdash \,t :T\) and \(\Gamma \,\vdash \,x^{\prime } :P\), then \(\Gamma ,[x := x^{\prime }]\Delta \,\vdash \,[x := x^{\prime }]t :[x := x^{\prime }]T\).

Proof.

Define \(\theta \triangleq [x := x^{\prime }]\). Proceed by induction on the typing derivation.

Case (var). Then \(t = y\) and \(y:S \mathrel {\wedge }C \in \Gamma ,x:P,\Delta\) and \(T = S \mathrel {\wedge }\lbrace y\rbrace\) and our goal is \(\Gamma ,\theta \Delta \,\vdash \,y :\theta (S \mathrel {\wedge }\lbrace y\rbrace)\).

If \(y = x\), then \(P = S \mathrel {\wedge }C\) and \(\theta (S \mathrel {\wedge }\lbrace x\rbrace) = S \mathrel {\wedge }\lbrace x^{\prime }\rbrace\). Our goal is \(\Gamma ,\theta \Delta \,\vdash \,x^{\prime } :S \mathrel {\wedge }\lbrace x^{\prime }\rbrace\) and we can conclude by (var).

Otherwise, \(y \ne x\) and we inspect where y is bound.

If \(y \in \operatorname{dom} (\Gamma)\), then \(x \not\in \operatorname{fv}(S \mathrel {\wedge }C)\) and so \(\theta (S \mathrel {\wedge }\lbrace z\rbrace) = S \mathrel {\wedge }\lbrace z\rbrace\) and we can conclude by (var).

Otherwise, \(y \in \operatorname{dom} (\Delta)\), so \(y:\theta (S \mathrel {\wedge }C) \in \Gamma ,\theta \Delta\) and we can conclude by (var).

Case (sub). By IH, Lemma A.28 and (sub).

Case (abs). Then \(t = \lambda \left(y : Q\right)\!.\; t^{\prime }, T = (\forall (y:Q)\,{T^{\prime }}) \mathrel {\wedge }cv(t)\) and \(\Gamma ,x:P,\Delta ,y:Q \,\vdash \,t^{\prime } :T^{\prime }\).

By IH, we have that \(\Gamma ,\theta \Delta ,y:\theta {}Q \,\vdash \,\theta {}t^{\prime } :\theta {}T^{\prime }\). We note that \(\operatorname{cv}(\theta {}t) = \theta \operatorname{cv}(t)\), which lets us conclude by (abs).

Case (tabs) . Similar to previous rule.

Case (app). Then \(t = z_1\,z_2\) and \(\Gamma ,x:P,\Delta \,\vdash \,z_1 :(\forall (y:Q)\,{T^{\prime }}) \mathrel {\wedge }C\) and \(\Gamma ,x:P,\Delta \,\vdash \,z_1 :Q\) and \(T = [y := z_2]T^{\prime }\).

By IH, we have \(\Gamma ,\theta \Delta \,\vdash \,\theta {}z_1 :\theta ((\forall (y:Q)\,{T^{\prime }}) \mathrel {\wedge }C)\) and \(\Gamma ,\theta \Delta \,\vdash \,\theta {}z_2 :\theta {}Q\).

Then by (app), we have \(\Gamma ,\theta \Delta \,\vdash \,\theta (z_1\,z_2) :[y := \theta {}z_2]\theta {}T^{\prime }\).

As \(y \ne x\) and \(y \ne x^{\prime }\), we have \([y := \theta {}z_2]\theta {}T^{\prime } = \theta ([y := z_2]T^{\prime })\), which concludes.

Case (tapp) . Similar to previous rule.

Case (box). Then \(t = \Box z\) and \(\Gamma ,x:P,\Delta \,\vdash \,z :S \mathrel {\wedge }C\) and \(T = \Box S \mathrel {\wedge }C\).

By IH, we have \(\Gamma ,\theta \Delta \,\vdash \,\theta {}z :\theta {}S \mathrel {\wedge }\theta {}C\). If \(x \not\in C\), we have \(\theta {}C = C\) and \(C \subseteq \operatorname{dom} (\Gamma ,\theta \Delta),\) which lets us conclude by (box). Otherwise, \(\theta {}C = (C \setminus \lbrace x\rbrace) \cup \lbrace y\rbrace .\) As \(\Gamma \,\vdash \,y :U\), \(\theta {}C \subseteq \operatorname{dom} (\Gamma ,\theta \Delta)\), which again lets us conclude by (box).

Case (unbox). Analogous to the previous rule. Note that we just swap the types in the premise and the conclusion.

Case (let). Then \(t = \operatorname{{\bf \textsf {l}et}} y = s \operatorname{{\bf \textsf {i}n}} t^{\prime }\) and \(\Gamma ,x:P,\Delta \,\vdash \,s : Q\) and \(\Gamma ,x:P,\Delta ,y:Q \,\vdash \,t^{\prime } : T\). By the IH, we have \(\Gamma ,\theta \Delta \vdash \theta s : \theta Q\) and \(\Gamma ,\theta \Delta ,y:\theta Q \,\vdash \,\theta t^{\prime } : \theta T\).

Then by (let), we also have \(\Gamma ,\theta \Delta \,\vdash \,\theta (\operatorname{{\bf \textsf {l}et}} y = s \operatorname{{\bf \textsf {i}n}} t^{\prime }) : \theta T\), which concludes. □

A.4.2 Type Substitution.

Lemma A.30 (Type Substitution Preserves Subcapturing).

If \(\Gamma ,X\lt :S,\Delta \,\vdash \,C \lt :D\) and \(\Gamma \,\vdash \,R \lt : S,\) then \(\Gamma ,[X := R]\Delta \,\vdash \,C \lt :D\).

Proof.

Define \(\theta \triangleq [X := R]\). Proceed by induction on the subcapturing derivation.

Case (sc-set), (sc-elem) . By IH and same rule.

Case (sc-var). Then \(C = \lbrace y\rbrace\), \(y : S^{\prime } \mathrel {\wedge }C^{\prime } \in \Gamma ,X\lt :S,\Delta\), \(y \ne X\). Inspect where y is bound. If \(y \in \operatorname{dom} (\Gamma)\), we have \(y : S^{\prime } \mathrel {\wedge }C^{\prime } \in \Gamma ,\theta \Delta\). Otherwise, by definition of substitution we have \(y : \theta {}S^{\prime } \mathrel {\wedge }C^{\prime } \in \Gamma ,\theta \Delta\). In both cases, we can conclude by (sc-var), since y is still bound to a type whose capture set is \(C^{\prime }\).□

Lemma A.31 (Type Substitution Preserves Subtyping).

If \(\Gamma ,X\lt :S,\Delta \,\vdash \,U \lt :T\) and \(\Gamma \,\vdash \,R \lt :S\), then \(\Gamma ,[X := R]\Delta \,\vdash \,[X := R]U \lt :[X := R]T\).

Proof.

Define \(\theta \triangleq [X := R]\). Proceed by induction on the subtyping derivation.

Case (refl), (top) . By same rule.

Case (capt) . By IH and Lemma A.30 and (capt).

Case (trans), (boxed), (fun), (tfun). By IH and re-application of the same rule.

Case (tvar). Then \(U = Y\) and \(T = S^{\prime }\) and \(Y \lt : S^{\prime } \in \Gamma ,X\lt :S,\Delta\) and our goal is \(\Gamma ,X\lt :S,\Delta \,\vdash \,\theta Y \lt :\theta S^{\prime }\). If \(Y = X\), by lemma premise and weakening. Otherwise, inspect where Y is bound. If \(Y \in \operatorname{dom} (\Gamma)\), we have \(Y \lt : S^{\prime } \in \Gamma ,\theta \Delta\) and since \(X \not\in \operatorname{fv}(S^{\prime })\) (Fact A.26), \(\theta S^{\prime } = S^{\prime }\). Then, we can conclude by (tvar). Otherwise, if \(Y \in \operatorname{dom} (\Delta)\), we have \(Y \lt : \theta S^{\prime } \in \Gamma ,\theta \Delta\) and we can conclude by (tvar).□

Lemma A.32 (Type Substitution Preserves Typing).

If \(\Gamma ,X\lt :S,\Delta \,\vdash \,t :T\) and \(\Gamma \,\vdash \,R \lt :S\), then \(\Gamma ,[X := R]\Delta \,\vdash \,[X := R]t :[X := R]T\).

Proof.

Define \(\theta \triangleq [X := R]\). Proceed by induction on the typing derivation.

Case (var). Then \(t = y\), \(y : S^{\prime } \mathrel {\wedge }C \in \Gamma ,X\lt :S,\Delta\), \(y \ne X\), and our goal is \(\Gamma ,\theta \Delta \,\vdash \,y : \theta {}S^{\prime } \mathrel {\wedge }\lbrace y\rbrace\).

Inspect where y is bound. If \(y \in \operatorname{dom} (\Gamma)\), then \(y : S^{\prime } \mathrel {\wedge }C \in \Gamma ,\theta \Delta\) and \(X \not\in \operatorname{fv}(S^{\prime })\) (Fact A.26). Then, \(\theta (S^{\prime } \mathrel {\wedge }C) = S^{\prime } \mathrel {\wedge }C\) and we can conclude by (var). Otherwise, \(y : \theta {}S^{\prime } \mathrel {\wedge }C \in \Gamma ,\theta \Delta\) and we can directly conclude by (var).

Case (abs), (tabs). In both rules, observe that type substitution does not affect \(\operatorname{cv}\) and conclude by IH and rule re-application.

Case (app). Then we have \(t = x\,y\) and \(\Gamma ,X\lt :S,\Delta \,\vdash \,x : (\forall (z:U)\,T_0) \mathrel {\wedge }C\) and \(T = [z := y]T_0\).

We observe that \(\theta [z := y]T_0 = [z := y]\theta {}T_0\) and \(\theta {}t = t\) and conclude by IH and (app).

Case (tapp). Then we have \(t = x\,[S^{\prime }]\) and \(\Gamma ,X\lt :S,\Delta \,\vdash \,x : (\forall [Z \lt : S^{\prime }]\,T_0) \mathrel {\wedge }C\) and \(T = [Z := S^{\prime }]T_0\).

We observe that \(\theta [Z := S^{\prime }]T_0 = [Z := \theta {}S^{\prime }]\theta {}T_0\). By IH, \(\Gamma ,\theta \Delta \,\vdash \,x : (\forall [Z \lt : \theta {}S^{\prime }]\,T_0) \mathrel {\wedge }C\), Then, we can conclude by (tapp).

Case (box). Then \(t = \Box y\) and \(\Gamma ,X\lt :S,\Delta \,\vdash \,y :S^{\prime } \mathrel {\wedge }C\) and \(T = \Box S^{\prime } \mathrel {\wedge }C\), and our goal is \(\Gamma ,\theta \Delta \,\vdash \,y : \Box \theta (S^{\prime } \mathrel {\wedge }C)\).

Inspect where y is bound. If \(y \in \operatorname{dom} (\Gamma)\), then \(y : S^{\prime } \mathrel {\wedge }C \in \Gamma ,\theta \Delta\) and \(X \not\in \operatorname{fv}(S^{\prime })\) (Fact A.26). Then, \(\theta (S^{\prime } \mathrel {\wedge }C^{\prime }) = S^{\prime } \mathrel {\wedge }C^{\prime }\) and we can conclude by (box). Otherwise, \(y : \theta {}S^{\prime } \mathrel {\wedge }C \in \Gamma ,\theta \Delta\) and we can directly conclude by (box).

Case (unbox). Proceed analogously to the case for (box)—we just swap the types in the premise and in the consequence.

Case (sub) . By IH and A.31.

Case (let). Then \(t = \operatorname{{\bf \textsf {l}et}} y = s \operatorname{{\bf \textsf {i}n}} t^{\prime }\) and \(\Gamma ,x:P,\Delta \,\vdash \,s : Q\) and \(\Gamma ,x:P,\Delta ,y:Q \,\vdash \,t^{\prime } : T\). By the IH, we have \(\Gamma ,\theta \Delta \vdash \theta s : \theta Q\) and \(\Gamma ,\theta \Delta ,y:\theta Q \,\vdash \,\theta t^{\prime } : \theta T\).

Then by (let), we also have \(\Gamma ,\theta \Delta \,\vdash \,\theta (\operatorname{{\bf \textsf {l}et}} y = s \operatorname{{\bf \textsf {i}n}} t^{\prime }) : \theta T\), which concludes. □

Skip A.5Main Theorems: Soundness Section

A.5 Main Theorems: Soundness

A.5.1 Preliminaries.

As we state Preservation (Theorem A.42) in a non-empty environment, we need to show canonical forms lemmas in such an environment as well. To do so, we need to know that values cannot be typed with a type that is a type variable, which normally follows from the environment being empty. Instead, we show the following lemma.

Lemma A.33 (Value Typing).

If \(\Gamma \,\vdash \,v :T\), then T is not of the form \(X \mathrel {\wedge }C\).

Proof.

By induction on the typing derivation.

For rule (sub), we know that \(\Gamma \,\vdash \,v :U\) and \(\Gamma \,\vdash \,U \lt :T\). Assuming \(T = X \mathrel {\wedge }C\), we have a contradiction by Lemma A.17 and IH.

Rules (box), (abs), (tabs) are immediate, and other rules are not possible.□

Lemma A.34 (Canonical forms: Term Abstraction).

If \(\Gamma \,\vdash \,v :(\forall (x : U)\,T) \mathrel {\wedge }C\), then we have \(v = \lambda (x : U^{\prime })\,t\) and \(\Gamma \,\vdash \,U \lt :U^{\prime }\) and \(\Gamma ,x:U \,\vdash \,t :T\).

Proof.

By induction on the typing derivation.

For rule (sub), we observe that by Lemma A.19 and by Lemma A.33, the subtype is of the form \((\forall (y:U^{\prime \prime })\,T^{\prime }) \mathrel {\wedge }C^{\prime }\) and we have \(\Gamma \,\vdash \,U \lt :U^{\prime \prime }\). By IH, we know that \(v = \lambda (x:U^{\prime })\,t\) and \(\Gamma \,\vdash \,U^{\prime \prime } \lt :U^{\prime }\) and \(\Gamma ,x:U^{\prime \prime } \,\vdash \,t :T\). By (trans), we have \(\Gamma \,\vdash \,U \lt :U^{\prime }\) and by narrowing we have \(\Gamma ,x:U \,\vdash \,t :T\), which concludes.

Rule (abs) is immediate, and other rules cannot occur.□

Lemma A.35 (Canonical forms: Type Abstraction).

If \(\Gamma \,\vdash \,v :(\forall [X \lt : S]\,T) \mathrel {\wedge }C\), then we have \(v = \lambda [X \lt : S^{\prime }]\,t\) and \(\Gamma \,\vdash \,S \lt :S^{\prime }\) and \(\Gamma ,X\lt :S \,\vdash \,t :T\).

Proof.

Analogous to the proof of Lemma A.34.□

Lemma A.36 (Canonical forms: Boxed Term).

If \(\Gamma \,\vdash \,v :(\Box T) \mathrel {\wedge }C\), then \(v = \Box x\) and \(\Gamma \,\vdash \,x :T\).

Proof.

Analogous to the proof of Lemma A.34.□

Lemma A.37 (Variable Typing Inversion).

If \(\Gamma \,\vdash \,x : S \mathrel {\wedge }C\), then \(x : S^{\prime } \mathrel {\wedge }C^{\prime } \in \Gamma\) and \(\Gamma \,\vdash \,S^{\prime } \lt : S\) and \(\Gamma \vdash \lbrace x\rbrace \lt :C\) for some \(C^{\prime }\) and \(S^{\prime }\).

Proof.

By induction on the typing derivation.

Case (sub). Then \(\Gamma \vdash x : S^{\prime \prime } \mathrel {\wedge }C^{\prime \prime }\) and \(\Gamma \vdash S^{\prime \prime } \mathrel {\wedge }C^{\prime \prime } \lt : S \mathrel {\wedge }C\). By the IH, we have \(\Gamma \vdash x : S^{\prime } \mathrel {\wedge }C^{\prime }\) and \(\Gamma \vdash S^{\prime } \lt : S^{\prime \prime }\) and \(\Gamma \vdash \mathrel {\wedge }x \lt : C^{\prime \prime }\). Then by Lemma A.18, we have \(\Gamma \vdash S^{\prime \prime } \lt : S\) and \(\Gamma \vdash C^{\prime \prime } \lt :C\), which lets us conclude by (trans) and transitivity of subcapturing.

Case (var). Then \(\Gamma \vdash x : S \mathrel {\wedge }C^{\prime }\) and \(C = \lbrace x\rbrace\). We can conclude with \(S^{\prime } = S\) by (refl) and reflexivity of subcapturing. □

Lemma A.38 (Variable Lookup Inversion).

If we have both \(\Gamma \,\vdash \,\sigma \sim \Delta\) and \(x : S \mathrel {\wedge }C \in \Gamma ,\Delta\), then \(\sigma (x) = v\) implies that \(\Gamma ,\Delta \,\vdash \,v :S \mathrel {\wedge }C\).

Proof.

By structural induction on \(\sigma\). It is not possible for \(\sigma\) to be empty.

Otherwise, \(\sigma = \sigma ^{\prime }[\ \operatorname{{\bf \textsf {l}et}} y = v \operatorname{{\bf \textsf {i}n}} [\,]\ ]\) and for some U we have both \(\Delta = \Delta ^{\prime },y:U\) and \(\Gamma ,\Delta ^{\prime } \,\vdash \,v :U\).

[NOTE: We remove bindings from the “inside” to make induction possible in the following.]

If \(y \ne x\), we can proceed by IH as x can also be typed in \(\Gamma ,\Delta ^{\prime }\), after which we can conclude by weakening. Otherwise, \(U = S \mathrel {\wedge }C\) and we can conclude by weakening.□

Lemma A.39 (Term Abstraction Lookup Inversion).

If \(\Gamma \,\vdash \,\sigma \sim \Delta\) and \(\Gamma ,\Delta \,\vdash \,x :(\forall (z: U)\,T) \mathrel {\wedge }C\) and \(\sigma (x) = \lambda (z: U^{\prime })\,t\), then \(\Gamma ,\Delta \,\vdash \,U \lt :U^{\prime }\) and \(\Gamma ,\Delta ,z:U \,\vdash \,t :T\).

Proof.

A corollary of Lemma A.38 and Lemma A.34.□

Lemma A.40 (Type Abstraction Lookup Inversion).

If \(\Gamma \,\vdash \,\sigma \sim \Delta\) and \(\Gamma ,\Delta \,\vdash \,x :(\forall [Z \lt : U]\,T) \mathrel {\wedge }C\) and \(\sigma (x) = \lambda [Z \lt : U^{\prime }]\,t\), then \(\Gamma ,\Delta \,\vdash \,U \lt :U^{\prime }\) and \(\Gamma ,\Delta ,Z\lt :U \,\vdash \,t :T\).

Proof.

A corollary of Lemma A.38 and Lemma A.35.□

Lemma A.41 (Box Lookup Inversion).

If \(\Gamma \,\vdash \,\sigma \sim \Delta\) and \(\sigma (x) = \Box y\) and \(\Gamma ,\Delta \,\vdash \,x :\Box T\), then \(\Gamma ,\Delta \,\vdash \,y :T\).

Proof.

A corollary of Lemma A.38 and Lemma A.36.□

A.5.2 Soundness.

In this section, we show the classical soundness theorems.

Theorem A.42 (Preservation).

If we have \(\Gamma \,\vdash \,\sigma \sim \Delta\) and \(\Gamma ,\Delta \,\vdash \,t : T\), then \(\sigma [\ t\ ] \longrightarrow \sigma [\ t^{\prime }\ ]\) implies that \(\Gamma ,\Delta \,\vdash \,t^{\prime } : T\).

Proof.

We proceed by inspecting the rule used to reduce \(\sigma [\ t\ ]\).

Case (apply). Then we have \(t = e[\ x\,y\ ]\) and \(\sigma (x) = \lambda (z: U)\,s\) and \(t^{\prime } = e[\ [z := y]s\ ]\).

By Lemma A.4, for some Q we have \(\Gamma ,\Delta \vdash e : Q \Rightarrow T\) and \(\Gamma ,\Delta \vdash x\,y : Q\). The typing derivation of \(x\,y\) must start with an arbitrary number of (sub) rules, followed by (app). We proceed by induction on the number of (sub) rules. In both base and inductive cases, we can only assume that \(\Gamma ,\Delta \vdash x\,y : Q^{\prime }\) for some \(Q^{\prime }\) such that \(\Gamma ,\Delta \vdash Q^{\prime } \lt : Q\).

In the inductive case, \(\Gamma ,\Delta \vdash x\,y : Q^{\prime }\) is derived by (sub), so we also have some \(Q^{\prime \prime }\) such that \(\Gamma ,\Delta \vdash x\,y : Q^{\prime \prime }\) and \(\Gamma ,\Delta \vdash Q^{\prime \prime } \lt : Q^{\prime }\). We have \(\Gamma ,\Delta \vdash Q^{\prime \prime } \lt : Q\) by (trans), so we can conclude by using the inductive hypothesis on \(\Gamma ,\Delta \vdash x\,y : Q^{\prime \prime }\).

In the base case, \(\Gamma ,\Delta \vdash x\,y : Q^{\prime }\) is derived by (app), so for some \(Q^{\prime \prime }\) we have \(\Gamma ,\Delta \vdash x : \forall (z:U^{\prime })\,Q^{\prime \prime }\) and \(\Gamma ,\Delta \vdash y : U^{\prime }\) and \(Q^{\prime } = [z := y]Q^{\prime \prime }\). By Lemma A.39, we have \(\Gamma ,\Delta ,z:U^{\prime } \vdash s : Q^{\prime \prime }\). By Lemma A.29, we have \(\Gamma ,\Delta \vdash [z := y]s : [z := y]Q^{\prime \prime }\), and since \(Q^{\prime } = [z := y]Q^{\prime \prime }\), by (sub) we have \(\Gamma ,\Delta \vdash [z := y]s : Q\).

To conclude that \(t^{\prime } = e[\ [z := y]s\ ]\) can be typed as T, we use Lemma A.5.

Case (tapply), (open) . As above.

Case (rename). Then we have \(t = e[\ \operatorname{{\bf \textsf {l}et}} x = y \operatorname{{\bf \textsf {i}n}} s\ ]\) and \(t^{\prime } = e[\ [x := y]s\ ]\).

Again, by Lemma A.4, for some Q we have \(\Gamma ,\Delta \vdash e : Q \Rightarrow T\) and \(\Gamma ,\Delta \vdash \operatorname{{\bf \textsf {l}et}} x = y \operatorname{{\bf \textsf {i}n}} s : Q\).

We again proceed by induction on number of (sub) rules at the start of the typing derivation for \(\operatorname{{\bf \textsf {l}et}} x = y \operatorname{{\bf \textsf {i}n}} s\), again only assuming that we can type the plug as some \(Q^{\prime }\) such that \(Q^{\prime } \lt : Q\). The inductive case proceeds exactly as before.

In the base case, (let) was used to derive that \(\Gamma ,\Delta \vdash \operatorname{{\bf \textsf {l}et}} x = y \operatorname{{\bf \textsf {i}n}} s : Q^{\prime }\). The premises are \(\Gamma ,\Delta \vdash y : U\) and \(\Gamma ,\Delta ,x:U \vdash s : Q^{\prime }\) and \(x \not\in \operatorname{fv}(Q^{\prime })\). By Lemma A.29, we have \(\Gamma ,\Delta \,\vdash \,[x := y]s : [x := y]Q^{\prime }\). Because \(x \not\in \operatorname{fv}(Q^{\prime })\), \([x := y]Q^{\prime } = Q^{\prime }\), which means that we can again conclude by (sub) and Lemma A.5.

Case (lift). Then we have \(t = e[\ \operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} s\ ]\) and \(t^{\prime } = \operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} e[\ s\ ]\).

Again, by Lemma A.4, for some Q we have \(\Gamma ,\Delta \vdash e : Q \Rightarrow T\) and \(\Gamma ,\Delta \vdash \operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} s : Q\).

We again proceed by induction on number of (sub) rules at the start of the typing derivation for \(\operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} s\), again only assuming that we can type the plug as some \(Q^{\prime }\) such that \(Q^{\prime } \lt : Q\). The inductive case proceeds exactly as before.

In the base case, (let) was used to derive that \(\Gamma ,\Delta \vdash \operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} s : Q^{\prime }\). The premises are \(\Gamma ,\Delta \vdash v : U\) and \(\Gamma ,\Delta ,x:U \vdash s : Q^{\prime }\) and \(x \not\in \operatorname{fv}(Q^{\prime })\).

By weakening of typing, we also have \(\Gamma ,\Delta ,x:U \vdash e : Q \Rightarrow T\). Then by (sub) and Lemma A.5, we have \(\Gamma ,\Delta ,x:U \vdash e[\ s\ ] : T\). Since \(\Gamma ,\Delta \vdash T \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\), by Barendregt \(x \not\in \operatorname{fv}(T)\), so by (let) we have \(\Gamma ,\Delta \vdash \operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} e[\ s\ ] : T\), which concludes.□

Definition A.43 (Proper Configuration).

We say that a term form \(\sigma [\ t\ ]\) is a canonical configuration (of the entire term into store context \(\sigma\) and the plug t) if t is not of the form \(\operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} t^{\prime }\).

Fact A.44.

Every term has a corresponding proper configuration, and finding it is decidable.

Lemma A.45 (Extraction of Bound Value).

If \(\Gamma ,\Delta \,\vdash \,x :T\) and \(\Gamma \,\vdash \,\sigma \sim \Delta\) and \(x \in \operatorname{dom} (\Delta)\), then \(\sigma (x) = v\).

Proof.

By structural induction on \(\Delta\). If \(\Delta\) is empty, we have a contradiction. Otherwise, \(\Delta = \Delta ^{\prime },z:T^{\prime }\) and \(\sigma = \sigma ^{\prime }[\ \operatorname{{\bf \textsf {l}et}} z = v \operatorname{{\bf \textsf {i}n}} [\,]\ ]\) and \(\Gamma ,\Delta ^{\prime },z:T^{\prime } \,\vdash \,v :T^{\prime }\). Note that \(\Delta\) is the environment matching \(\sigma\) and can only contain term bindings. If \(z = x\), we can conclude immediately, and otherwise if \(z \ne x\), we can conclude by IH.□

Theorem A.46 (Progress).

If \(\,\vdash \,\sigma [\ e[\ t\ ]\ ] :T\) and \(\sigma [\ e[\ t\ ]\ ]\) is a proper configuration, then either \(e[\ t\ ] = a\), or there exists \(\sigma [\ t^{\prime }\ ]\) such that \(\sigma [\ e[\ t\ ]\ ] \longrightarrow \sigma [\ t^{\prime }\ ]\).

Proof.

Since \(\sigma [\ e[\ t\ ]\ ]\) is well-typed in the empty environment, there clearly must be some \(\Delta\) such that \(\,\vdash \,\sigma \sim \Delta\) and \(\Delta \,\vdash \,e[\ t\ ] : T\). By Lemma A.4, we have that \(\Delta \,\vdash \,t :P\) for some P. We proceed by induction on the derivation of this derivation.

Case (var). Then \(t = x\).

If e is non-empty, \(e[\ x\ ] = e^{\prime }[\ \operatorname{{\bf \textsf {l}et}} y = x \operatorname{{\bf \textsf {i}n}} t^{\prime }\ ]\) and we can step by (rename); otherwise, immediate.

Case (abs), (tabs), (box). Then \(t = v\).

If e is non-empty, \(e[\ v\ ] = e^{\prime }[\ \operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} t^{\prime }\ ]\) and we can step by (lift); otherwise, immediate.

Case (app). Then \(t = x\,y\) and \(\Delta \,\vdash \,x : (\forall (z : U)\,T_0) \mathrel {\wedge }C\) and \(\Delta \,\vdash \,y :U\).

By Lemmas A.45 and A.34, \(\sigma (x) = \lambda (z : U^{\prime })\,t^{\prime }\), which means we can step by (apply).

Case (tapp). Then \(t = x\,[S]\) and \(\Delta \,\vdash \,x : (\forall [Z \lt : S]\,T_0) \mathrel {\wedge }C\).

By Lemmas A.45 and A.35, \(\sigma (x) = \lambda [z \lt : S^{\prime }]\,{t^{\prime }}\), which means we can step by (tapply).

Case (unbox). Then \(t = C ⟜ x\) and \(\Delta \,\vdash \,x : \Box S \mathrel {\wedge }C\).

By Lemmas A.45 and A.36, \(\sigma (x) = \Box y\), which means we can step by (open).

Case (let). Then \(t = \operatorname{{\bf \textsf {l}et}} x = s \operatorname{{\bf \textsf {i}n}} t^{\prime }\) and we proceed by IH on s, with \(e[\ \operatorname{{\bf \textsf {l}et}} x = [\,] \operatorname{{\bf \textsf {i}n}} t^{\prime }\ ]\) as the evaluation context.

Case (sub) . By IH.□

A.5.3 Consequences.

Lemma A.47 (Capture Prediction for Answers).

If \(\Gamma \,\vdash \,\sigma [\ a\ ] :S \mathrel {\wedge }C\), then \(\Gamma \,\vdash \,\sigma [\ a\ ] :S \mathrel {\wedge }\operatorname{cv}(\sigma [\ a\ ])\) and \(\Gamma \,\vdash \,\operatorname{cv}(\sigma [\ a\ ]) \lt :C\).

Proof.

By induction on the typing derivation.

Case (sub). Then \(\Gamma \,\vdash \,\sigma [\ a\ ] :S^{\prime } \mathrel {\wedge }C^{\prime }\) and \(\Gamma \,\vdash \,S^{\prime } \mathrel {\wedge }C^{\prime } \lt :S \mathrel {\wedge }C\). By IH, \(\Gamma \,\vdash \,\sigma [\ a\ ] :S^{\prime } \mathrel {\wedge }\operatorname{cv}(\sigma [\ a\ ])\) and \(\Gamma \,\vdash \,\operatorname{cv}(\sigma [\ a\ ]) \lt :C^{\prime }\). By Lemma A.18, we have that \(\Gamma \,\vdash \,C^{\prime } \lt :C\) and \(\Gamma \,\vdash \,S^{\prime } \lt :S\).

To conclude, we need \(\Gamma \,\vdash \,\sigma [\ a\ ] :S \mathrel {\wedge }\operatorname{cv}(\sigma [\ a\ ])\) and \(\Gamma \,\vdash \,\operatorname{cv}(\sigma [\ a\ ]) \lt :C\), which we respectively have by subsumption and Lemma A.12.

Case (var), (abs), (tabs), (box). Then \(\sigma\) is empty and \(C = \operatorname{cv}(a)\). One goal is immediate, the other follows from Lemma A.13.

Case (let). Then \(\sigma = \operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} \sigma ^{\prime }\) and \(\Gamma ,x:U \,\vdash \,\sigma ^{\prime }[\ a\ ] :S \mathrel {\wedge }C\) and \(x \not\in C\).

By IH, \(\Gamma ,x:U \,\vdash \,\sigma ^{\prime }[\ a\ ] :S \mathrel {\wedge }\operatorname{cv}(\sigma ^{\prime }[\ a\ ])\) and \(\Gamma ,x:U \,\vdash \,\operatorname{cv}(\sigma ^{\prime }[\ a\ ]) \lt :C\).

By Lemma A.27, we have \(\Gamma \,\vdash \,[x := \operatorname{cv}(v)](\operatorname{cv}(\sigma ^{\prime }[\ a\ ])) \lt :[x := \operatorname{cv}(v)]C\).

By definition, \([x := \operatorname{cv}(v)](\operatorname{cv}(\sigma ^{\prime }[\ a\ ])) = \operatorname{cv}(\operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} \sigma ^{\prime }[\ a\ ])\), and we also already know that \(x \not\in C\).

This lets us conclude, as we have \(\Gamma \,\vdash \,\operatorname{cv}(\operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} \sigma ^{\prime }[\ a\ ]) \lt :C\).

Other rules cannot occur.□

Lemma A.48 (Capture Prediction for Terms).

Let \(\,\vdash \,\sigma \sim \Delta\) and \(\Delta \,\vdash \,t :S \mathrel {\wedge }C\).

Then \(e[\ t\ ] \longrightarrow ^{*} e[\ \sigma ^{\prime }[\ a\ ]\ ]\) implies that \(\Delta \,\vdash \,\operatorname{cv}(\sigma ^{\prime }[\ a\ ]) \lt :C\).

Proof.

By preservation, \(\,\vdash \,\sigma ^{\prime }[\ a\ ] :S \mathrel {\wedge }C\), which lets us conclude by Lemma A.47.□

Skip A.6Correctness of Boxing Section

A.6 Correctness of Boxing

A.6.1 Relating cv and Stores.

We want to relate the \(\operatorname{cv}\) of a term of the form \(\sigma [\ t\ ]\) with \(\operatorname{cv}(t)\) such that for some definition of ‘\(\mathrm{resolve}\)’, we have \(\begin{equation*} \operatorname{cv}(\sigma [\ t\ ]) = \mathrm{resolve}(\sigma , \operatorname{cv}(t)). \end{equation*}\) Let us consider term of the form \(\sigma [\ t\ ]\) and a store \(\sigma\) of the form \(\operatorname{{\bf \textsf {let}}} x = v \operatorname{{\bf \textsf {in}}} \sigma ^{\prime }\). There are two rules that could be used to calculate \(\operatorname{cv}(\operatorname{{\bf \textsf {let}}} x = v \operatorname{{\bf \textsf {in}}} \sigma ^{\prime })\): \(\begin{align*} \operatorname{cv}(\operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} t) &= \operatorname{cv}(t) \quad \quad \quad \quad \quad \quad \operatorname{{\bf \textsf {if}}} x \notin \operatorname{cv}(t) \\ \operatorname{cv}(\operatorname{{\bf \textsf {l}et}} x = s \operatorname{{\bf \textsf {i}n}} t) &= \operatorname{cv}(s) \cup \operatorname{cv}(t) \backslash x. \end{align*}\) Observe that since we know that x is bound to a value, we can reformulate these rules as \(\begin{align*} \operatorname{cv}(\operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} t) = [x := \operatorname{cv}(v)]\operatorname{cv}(t), \end{align*}\) which means that we should be able to define ‘\(\mathrm{resolve}\)’ with a substitution. We will call this substitution a store resolver, and we define it as \(\begin{align*} \mathrm{resolver}(\operatorname{{\bf \textsf {let}}} x = v \operatorname{{\bf \textsf {in}}} \sigma) &= [x := \operatorname{cv}(v)] \circ \mathrm{resolver}(\sigma) \\ \mathrm{resolver}([\,]) &= id. \end{align*}\) Importantly, note that we use composition of substitutions. We have \(\begin{equation*} \mathrm{resolver}(\operatorname{{\bf \textsf {let}}} x = a \operatorname{{\bf \textsf {in}}} \operatorname{{\bf \textsf {let}}} y= x \operatorname{{\bf \textsf {in}}} [\,]) \equiv [x := \lbrace a\rbrace , y := \lbrace a\rbrace ]. \end{equation*}\) With the above, we define \(\mathrm{resolve}\) as \(\begin{equation*} \mathrm{resolve}(\sigma , C) = \mathrm{resolver}(\sigma)(C). \end{equation*}\) This definition satisfies our original desired equality with \(\operatorname{cv}\).

Fact A.49.

For all terms t of the form \(\sigma [\ s\ ]\), we have \(\operatorname{cv}(t) = \mathrm{resolve}(\sigma , \operatorname{cv}(s))\)

A.6.2 Relating cv and Evaluation Contexts.

We now relate \(\operatorname{cv}\) to evaluation contexts e. First, note that by definition of \(\operatorname{cv}\) we have the following.

Fact A.50.

For all terms t of the form \(\operatorname{{\bf \textsf {let}}} x = s \operatorname{{\bf \textsf {in}}} t^{\prime }\) such that s is not a value, we have \(\operatorname{cv}(t) = cv(s) \cup \operatorname{cv}(t^{\prime }) \setminus x\).

Accordingly, we extend \(\operatorname{cv}\) to evaluation contexts (\(\operatorname{cv}(e)\)) as follows: \(\begin{align*} \operatorname{cv}(\operatorname{{\bf \textsf {let}}} x = e \operatorname{{\bf \textsf {in}}} t) &= \operatorname{cv}(e) \cup \operatorname{cv}(t) \setminus x\\ \operatorname{cv}([\,]) &= \lbrace \rbrace . \end{align*}\) We then have the following.

Fact A.51.

For all terms t of the form \(e[\ s\ ]\) such that s is not a value, we have \(\operatorname{cv}(t) = \operatorname{cv}(e) \cup \operatorname{cv}(s)\).

A.6.3 Relating cv to Store and Evaluation Context Simultaneously.

Given our definition of ‘\(\mathrm{resolve}\)’ and \(\operatorname{cv}(e)\), we have the following.

Fact A.52.

Let \(\sigma [\ e[\ t\ ]\ ]\) be a term such that t is not a value. Then, \(\begin{equation*} \operatorname{cv}(\sigma [\ e[\ t\ ]\ ]) = \mathrm{resolve}(\sigma , \operatorname{cv}(e) \cup \operatorname{cv}(t)). \end{equation*}\)

The proof proceeds by induction on \(\sigma\) and e, using Facts A.49 and A.51.

A.6.4 Correctness of cv.

Definition A.53 (Platform Environment).

\(\Gamma\) is a platform environment if for all \(x \in \operatorname{dom} (\Gamma)\) we have \(x : S \mathrel {\wedge }\lbrace {\bf cap}\rbrace \in \Gamma\) for some S.

Lemma A.54 (Inversion of Subcapturing under Platform Environment).

If \(\Gamma\) is a platform environment and \(\Gamma \vdash C \lt : D\), then either \(C \subseteq D\) or \({\bf cap}\in D\).

Proof.

By induction on the subcapturing relation. Case (sc-elem) trivially holds. Case (sc-set) holds by repeated IH. In case (sc-var), we have \(C = \lbrace x\rbrace\) and \(x : S \mathrel {\wedge }C^{\prime } \in \Gamma\). Since \(\Gamma\) is a platform environment, we have \(C^{\prime } = \lbrace {\bf cap}\rbrace\), which means that the other premise of (sc-var) is \(\Gamma \vdash \lbrace {\bf cap}\rbrace \lt : D\). Since \(\Gamma\) is well-formed, \({\bf cap}\not\in \operatorname{dom} (\Gamma)\), which means that we must have \({\bf cap}\in D\).□

Lemma A.55 (Strengthening of Subcapturing).

If \(\Gamma ,\Gamma ^{\prime } \vdash C \lt : D\) and \(C \subseteq \operatorname{dom} (\Gamma)\), then we must have \(\Gamma \vdash C \lt : D\).

Proof.

First, we consider that if \({\bf cap}\in D\), we trivially have the desired goal. If \({\bf cap}\not\in D\), we proceed by induction on the subcapturing relation. Case (sc-elem) trivially holds, and case (sc-set) holds by repeated IH.

In case (sc-var), we have \(C = \lbrace x\rbrace\), \(x : S \mathrel {\wedge }C^{\prime } \in \Gamma ,\Gamma ^{\prime }\). This implies that \(\Gamma = \Gamma _1,x:S \mathrel {\wedge }C^{\prime },\Gamma _2\) (as \(x \not\in \operatorname{dom} (\Gamma)\)). Since \(\Gamma ,\Gamma ^{\prime }\) is well-formed, we must have \(\Gamma _1 \vdash C^{\prime } \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\). Since we already know \({\bf cap}\not\in D\), then we must also have \({\bf cap}\not\in C^{\prime }\), which then leads to \(C^{\prime } \subseteq \operatorname{dom} (\Gamma _1)\). This in turn means that by IH and weakening, we have \(\Gamma \vdash C^{\prime } \lt : D\), and since we also have \(x : S \mathrel {\wedge }C^{\prime } \in \Gamma\), we can conclude by (sc-var).□

Then we will need to connect it to subcapturing, because the keys used to open boxes are supercaptures of the capability inside the box. We want the following.

Lemma A.56.

Let \(\Gamma\) be a platform environment, \(\Gamma \vdash \sigma \sim \Delta\) and \(\Gamma ,\Delta \vdash C_1 \lt : C_2\). Then \(\mathrm{resolve}(\sigma , C_1) \subseteq \mathrm{resolve}(\sigma , C_2)\).

Proof.

By induction on \(\sigma\). If \(\sigma\) is empty, we have \(\mathrm{resolve}(\sigma , C_1) = C_1\), likewise for \(C_2\), and we can conclude by Lemma A.54.

Otherwise, \(\sigma = \sigma ^{\prime }[\ \operatorname{{\bf \textsf {let}}} x = v \operatorname{{\bf \textsf {in}}} [\,]\ ]\) and \(\Delta = \Delta ^{\prime },x : S_x \mathrel {\wedge }D_x\) for some \(S_x\). Let \(\theta = \mathrm{resolver}(\sigma)\). We proceed by induction on the subcapturing derivation. Case (sc-elem) trivially holds, and case (sc-set) holds by repeated IH.

In case (sc-var), we have \(C_1 = \lbrace y\rbrace\) and \(y : S_y \mathrel {\wedge }D_y \in \Gamma ,\Delta\) for some \(S_y\), and \(\Gamma ,\Delta \vdash D_y \lt : C_2\). We must have \(\Gamma ,\Delta ^{\prime } \vdash D_y \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\) and so we can strengthen subcapturing to \(\Gamma ,\Delta ^{\prime } \vdash D_y \lt : C_2\), which by IH gives us \(\mathrm{resolver}(\sigma ^{\prime })(D_y) \subseteq \mathrm{resolver}(\sigma ^{\prime })(C_2)\). By definition, we have \(\theta = \mathrm{resolver}(\sigma) = \mathrm{resolver}(\sigma ^{\prime }) \circ [x := \operatorname{cv}(v)]\). Since by well-formedness \(x \not\in D_y\), we now have \(\begin{equation*} \theta D_y \subseteq \theta C_2. \end{equation*}\) By Lemma A.38 and Lemma A.47, we must have \(\Gamma ,\Delta \vdash \operatorname{cv}(v) \lt : D_y\). Since \(\Gamma ,\Delta \vdash \operatorname{cv}(v) \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\), we can strengthen this to \(\Gamma ,\Delta \vdash \operatorname{cv}(v) \lt : D_y\). By outer IH, this gives us \(\mathrm{resolver}(\sigma ^{\prime })(\operatorname{cv}(v)) \subseteq \mathrm{resolver}(\sigma ^{\prime })(D_y)\). Since \(x \notin \operatorname{cv}(v) \cup D_y\), we have \(\begin{equation*} \theta \operatorname{cv}(v) \subseteq \theta D_y, \end{equation*}\) which means we have \(\theta \operatorname{cv}(v) \subseteq \theta C_2\) and we can conclude by \(\theta \operatorname{cv}(v) = \theta \lbrace x\rbrace\), since \(\begin{gather*} \qquad \qquad \theta \lbrace x\rbrace = (\mathrm{resolver}(\sigma ^{\prime }) \circ [x := \operatorname{cv}(v)])(\lbrace x\rbrace) = \mathrm{resolver}(\sigma ^{\prime })(\operatorname{cv}(v)) \qquad \qquad \qquad \\ \qquad \qquad \theta \operatorname{cv}(v) = \mathrm{resolver}(\sigma ^{\prime })(\operatorname{cv}(v)) \quad (\text{since } x \not\in \operatorname{cv}(v)).\qquad \qquad \end{gather*}\)

A.6.5 Core Lemmas.

Lemma A.57 (Program Authority Preservation).

Let \(\Psi [\ t\ ]\) be a well-typed program such that \(\Psi [\ t\ ] \longrightarrow \Psi [\ t^{\prime }\ ]\). Then \(\operatorname{cv}(t^{\prime }) \subseteq \operatorname{cv}(t)\).

Proof.

By inspection of the reduction rule used.

Case (apply). Then \(t = \sigma [\ e[\ x\,y\ ]\ ]\) and \(t^{\prime } = \sigma [\ e[\ [z := y]s\ ]\ ]\). Note that our goal is then \(\begin{equation*} \mathrm{resolver}(\sigma)(\operatorname{cv}(e) \cup \operatorname{cv}([z := y]s)) \quad \subseteq \quad \mathrm{resolver}(\sigma)(\operatorname{cv}(e) \cup \operatorname{cv}(x\,y)). \end{equation*}\)

If we have \(x \in \operatorname{dom} (\Psi)\), then \(\Psi (x) = \lambda (z:U)\,s\). By definition of platform, the lambda is closed and we have \(\operatorname{fv}(s) \subseteq \lbrace z\rbrace\), which in turn means that \(\operatorname{cv}([z := y]s) \subseteq \lbrace y\rbrace \subseteq \operatorname{cv}(x\,y)\). This satisfies our goal.

Otherwise, we have \(x \in \operatorname{dom} (\sigma)\) and \(\sigma (x) = \lambda (z:U)\,s\). Since x is bound in \(\sigma\), we have \(\mathrm{resolver}(\sigma)(\operatorname{cv}(\lambda (z:U)\,s) \cup \lbrace y\rbrace) \subseteq \mathrm{resolver}(\sigma)(\operatorname{cv}(x\,y)))\). Since \(\operatorname{cv}([z := y]s) \subseteq \operatorname{cv}(\lambda (z:U)\,s) \cup \lbrace y\rbrace\), our goal is again satisfied.

Case (tapply). Analogous reasoning.

Case (open). Then \(t = \sigma [\ e[\ C ⟜ x\ ]\ ]\) and \(t^{\prime } = \sigma [\ e[\ z\ ]\ ]\). We must have \(x \in \operatorname{dom} (\sigma)\) and \(\sigma (x) = \Box z\), since all values bound in a platform must be closed and a box form cannot be closed. Since \(\Psi [\ t\ ]\) is a well-typed program, there must exist some \(\Gamma ,\Delta\) such that \(\Gamma\) is a platform environment and \(\vdash \Psi [\ \sigma \ ] \sim \Gamma ,\Delta\).

If \(z \in \operatorname{dom} (\sigma)\), then by Lemma A.38 and Lemma A.41 we have \(\Gamma ,\Delta \vdash z : S_z \mathrel {\wedge }C\) for some \(S_z\). By straightforward induction on the typing derivation, we then must have \(\Gamma ,\Delta \vdash \lbrace z\rbrace \lt : C\). Then by Lemma A.56, we have \(\mathrm{resolver}(\sigma)(\lbrace z\rbrace) \subseteq \mathrm{resolver}(\sigma)(C)\), which lets us conclude by an argument similar to the (apply) case.

Otherwise, \(z \in \operatorname{dom} (\Psi)\). Here we also have \(\Gamma ,\Delta \vdash \lbrace z\rbrace \lt : C\), which implies we must have \(z \in C\), so we have \(\operatorname{cv}(z) \subseteq \operatorname{cv}(C ⟜ x)\) and can conclude by a similar argument as in the (apply) case.

Case (rename), (lift). The lemma is clearly true since these rules only shift subterms of t to create \(t^{\prime }\).□

Lemma A.58 (Single-Step Used Capability Prediction).

Let \(\Psi [\ t\ ]\) be a well-typed program such that \(\Psi [\ t\ ] \longrightarrow \Psi [\ t^{\prime }\ ]\). Then the primitive capabilities used during this reduction are a subset of \(\operatorname{cv}(t)\): \(\begin{equation*} \lbrace \ x \mid x \in \mathrm{used}(\Psi [\ t\ ] \longrightarrow ^\ast \Psi [\ t^{\prime }\ ]), x \in \operatorname{dom} (\Psi)\ \rbrace \subseteq \operatorname{cv}(t). \end{equation*}\)

Proof.

By inspection of the reduction rule used.

Case (apply). Then \(t = \sigma [\ e[\ x\,y\ ]\ ]\). If \(x \in \operatorname{dom} (\sigma)\), the lemma trivially holds. Otherwise, \(x \in \operatorname{dom} (\Psi) \setminus \operatorname{dom} (\sigma)\). From the definition of \(\operatorname{cv}\), we have \(\lbrace x\rbrace \setminus \operatorname{dom} (\sigma) \subseteq \operatorname{cv}(t)\). Since x is bound in \(\Psi\), we then have \(x \in \operatorname{cv}(t)\), which concludes.

Case (tapply). Analogous reasoning.

Case (open), (rename), (lift). Hold trivially, since no capabilities are used by reducing using these rules.□

Theorem A.59 (Used Capability Prediction).

Let \(\Psi [\ t\ ] \longrightarrow ^\ast \Psi [\ t^{\prime }\ ]\), where \(\Psi [\ t\ ]\) is a well-typed program. Then the primitive capabilities used during the reduction are a subset of the authority of t: \(\begin{equation*} \lbrace \ x \mid x \in \mathrm{used}(\Psi [\ t\ ] \longrightarrow ^\ast \Psi [\ t^{\prime }\ ]), x \in \operatorname{dom} (\Psi)\ \rbrace \subseteq \operatorname{cv}(t). \end{equation*}\)

Proof.

By the IH, single-step program trace prediction and authority preservation.□

Skip A.7Avoidance Section

A.7 Avoidance

Here, we restate Lemma 3.3 and prove it.

Lemma A.60.

Consider a term \(\operatorname{{\bf \textsf {l}et}} x = s \operatorname{{\bf \textsf {i}n}} t\) in an environment \(\Gamma\) such that \(\Gamma \,\vdash \,s : R \mathrel {\wedge }C_s\) is the most specific typing for s in \(\Gamma\) and \(\Gamma , x : R \mathrel {\wedge }C_s \,\vdash \,t : T\) is the most specific typing for t in the context of the body of the let, namely \(\Gamma , x : R \mathrel {\wedge }C_s\). Let \(T^{\prime }\) be constructed from T by replacing x with \(C_s\) in covariant capture set positions and by replacing x with the empty set in contravariant capture set positions. Then for every type U avoiding x such that \(\Gamma , x : S \mathrel {\wedge }C_s \,\vdash \,T \lt :U\), we have \(\Gamma \,\vdash \,T^{\prime }\) \(\lt :U\).

Proof.

We will construct a subtyping derivation showing that \(T^{\prime } \lt :U\). Proceed by structural induction on the subtyping derivation for \(T \lt :U\). Since \(T^{\prime }\) has the same structure as T, most of the subtyping derivation carries over directly except for the subcapturing constraints in (capt).

In this case, in covariant positions, whenever we have \(C_T \lt :C_U\) for a capture set \(C_T\) from T and a capture set \(C_U\) from U, we need to show that that \(\,\vdash \,[x := C_s]C_T \lt :C_U\). Conversely, in contravariant positions, whenever we have \(C_U \lt :C_T\), we need to show that \(C_U \lt :[x := \lbrace \rbrace ]C_T\). For the covariant case, since \(x \in C_T\) but not in \(C_U\), by inverting the subcapturing relation \(C_T \lt :C_U\), we obtain \(C_s \lt :C_U\). Hence \([x := C_s]C_T \lt :C_U\), as desired.

The more difficult case is the contravariant case, when we have \(C_U \lt :C_T\). Here, however, we have that \(C_U \lt :[x := \lbrace \rbrace ]C_T\) by structural induction on the subcapturing derivation as x never occurs on the left-hand side of the subcapturing relation as U avoids x.□

B SCOPED CAPABILITY PROOFS

The substitution lemmas leading up to the main correctness theorems need to consider rules (boundary), (break), (label), (sc-label), (scope). Substitutions clearly preserve subcapturing derived via (sc-label), since the capture sets involved are unaffected. Likewise, substitutions clearly preserve judgments derived via (break), (boundary), (break) and (scope), based on arguments similar to the ones for rules (fun), (let) and (apply).

For the progress theorem, we need to add a new canonical forms and lookup inversion lemma.

Lemma B.1 (Subtyping Inversion: Boundary Capability Type).

If \(\Gamma \,\vdash \,U \lt :\operatorname{\mathsf {Break}}[S] \mathrel {\wedge }C\), then U either is of the form \(X \mathrel {\wedge }C^{\prime }\) and we have \(\Gamma \,\vdash \,C^{\prime } \lt :C\) and \(\Gamma \,\vdash \,X \lt :\operatorname{\mathsf {Break}}[S]\), or U is of the form \(\operatorname{\mathsf {Break}}[S^{\prime }] \mathrel {\wedge }C^{\prime }\) and we have \(\Gamma \,\vdash \,C^{\prime } \lt :C\) and \(\Gamma \vdash S \lt :S^{\prime }\).

Proof.

Analogous to the proof of Lemma A.19.□

Lemma B.2 (Canonical forms: Boundary Capability).

If \(\Gamma \,\vdash \,v :\operatorname{\mathsf {Break}}[S] \mathrel {\wedge }\lbrace {\bf cap}\rbrace\), then \(v = {l}_{S^{\prime }}\) and \(\Gamma \vdash S \lt :S^{\prime }\).

Proof.

Analogous to the proof of Lemma A.34, using Lemma B.1.□

Lemma B.3 (Boundary Capability Lookup Inversion).

If \(\Gamma \,\vdash \,\sigma \sim \Delta\) and \(\Gamma ,\Delta \,\vdash \,x :\operatorname{\mathsf {Break}}[S]\) and \(\sigma (x) = {l}_{S^{\prime }}\), and \(\Gamma ,\Delta \vdash S \lt :S^{\prime }\).

Proof.

A corollary of Lemma A.38 and Lemma B.2.□

Theorem B.4 (Preservation).

Let \(\Gamma \,\vdash \,\sigma \sim \Delta\) and \(\Gamma ,\Delta \,\vdash \,t : T\), where \(\sigma [\ t\ ]\) is a proper program. Then \(\sigma [\ t\ ] \longrightarrow \sigma [\ t^{\prime }\ ]\) implies that \(\Gamma ,\Delta \,\vdash \,t^{\prime } : T\) and that \(\sigma [\ t^{\prime }\ ]\) is a proper program.

Proof.

We proceed by inspecting the rule used to reduce \(\sigma [\ t\ ]\).

Case (apply). Then we have \(t = e[\ x\,y\ ]\) and \(\sigma (x) = \lambda (z: U)\,s\) and \(t^{\prime } = e[\ [z := y]s\ ]\).

By Lemma A.4, for some Q we have \(\Gamma ,\Delta \vdash e : Q \Rightarrow T\) and \(\Gamma ,\Delta \vdash x\,y : Q\). The typing derivation of \(x\,y\) must start with an arbitrary number of (sub) rules, followed by (app). We proceed by induction on the number of (sub) rules. In both base and inductive cases, we can only assume that \(\Gamma ,\Delta \vdash x\,y : Q^{\prime }\) for some \(Q^{\prime }\) such that \(\Gamma ,\Delta \vdash Q^{\prime } \lt : Q\).

Inductive Case. \(\Gamma ,\Delta \vdash x\,y : Q^{\prime }\) is derived by (sub), so we also have some \(Q^{\prime \prime }\) such that \(\Gamma ,\Delta \vdash x\,y : Q^{\prime \prime }\) and \(\Gamma ,\Delta \vdash Q^{\prime \prime } \lt : Q^{\prime }\). We have \(\Gamma ,\Delta \vdash Q^{\prime \prime } \lt : Q\) by (trans), so we can conclude by using the inductive hypothesis on \(\Gamma ,\Delta \vdash x\,y : Q^{\prime \prime }\).

Base Case. \(\Gamma ,\Delta \vdash x\,y : Q^{\prime }\) is derived by (app), so for some \(Q^{\prime \prime }\) we have \(\Gamma ,\Delta \vdash x : \forall (z:U^{\prime })\,Q^{\prime \prime }\) and \(\Gamma ,\Delta \vdash y : U^{\prime }\) and \(Q^{\prime } = [z := y]Q^{\prime \prime }\).

By Lemma A.39, we have \(\Gamma ,\Delta ,z:U^{\prime } \vdash s : Q^{\prime \prime }\). By Lemma A.29, we have \(\Gamma ,\Delta \vdash [z := y]s : [z := y]Q^{\prime \prime }\), and since \(Q^{\prime } = [z := y]Q^{\prime \prime }\), by (sub) we have \(\Gamma ,\Delta \vdash [z := y]s : Q\).

By Lemma A.5, we have \(\Gamma ,\Delta \vdash e[\ [z := y]s\ ] :T\), our first goal.

Our second goal is showing that \(\sigma [\ e[\ t^{\prime }\ ]\ ]\) is a proper program. We have \(\begin{align*} \operatorname{cv}(\sigma [\ e[\ t\ ]\ ]) &= \mathrm{resolver}(\sigma)(\operatorname{cv}(e) \cup \operatorname{cv}(t)) \\ \operatorname{cv}(\sigma [\ e[\ t^{\prime }\ ]\ ]) &= \mathrm{resolver}(\sigma)(\operatorname{cv}(e) \cup \operatorname{cv}(t^{\prime })). \end{align*}\) Since \(\mathrm{resolver}(\sigma)(\operatorname{cv}(x)) = \mathrm{resolver}(\sigma)(\operatorname{cv}(\lambda (z: U)\,s))\), we also have \(\begin{equation*} \mathrm{resolver}(\sigma)(\operatorname{cv}(t^{\prime })) \subseteq \mathrm{resolver}(\sigma)(\operatorname{cv}(t)). \end{equation*}\) In other words, the \(\operatorname{cv}\) of the program does not increase, which means we clearly preserve the first criterion of a proper program since the evaluation context remains the same. Similarly, since all the scope forms were part of e, we also preserve the second criterion, which lets us conclude.

Case (tapply) . As above.

Case (open). Then we have \(t = e[\ C ⟜ x\ ]\) and \(\sigma (x) = \Box y\) and \(t^{\prime } = e[\ y\ ]\).

The argument is nearly the same as for rule (apply), with a small difference when showing that the result is a proper program.

In the base induction case, we have \(\Gamma ,\Delta \vdash x : \Box S \mathrel {\wedge }C\). By Lemma A.38 and Lemma A.41, we also have \(\Gamma ,\Delta \vdash y : S \mathrel {\wedge }C\) for some S. Then by a straightforward induction on the typing derivation, we must have \(\Gamma ,\Delta \vdash \lbrace y\rbrace \lt : C\). Then by Lemma A.56, we have \(\mathrm{resolver}(\sigma)(\lbrace y\rbrace) \subseteq \mathrm{resolver}(\sigma)(C)\). This lets us know that for every \(x \in \mathrm{resolver}(\sigma)(\operatorname{cv}(t^{\prime }))\), we also have \(x \in \mathrm{resolver}(\sigma)(\operatorname{cv}(t))\), which lets us carry out the argument from the case for rule (apply).

Case (rename). Then \(t = e[\ \operatorname{{\bf \textsf {l}et}} x = y \operatorname{{\bf \textsf {i}n}} s\ ]\) and \(t^{\prime } = e[\ [x := y]s\ ]\).

Again, by Lemma A.4, for some Q we have \(\Gamma ,\Delta \vdash e : Q \Rightarrow T\) and \(\Gamma ,\Delta \vdash \operatorname{{\bf \textsf {l}et}} x = y \operatorname{{\bf \textsf {i}n}} s : Q\). As in the (apply) case, we proceed by induction, only working with a \(Q^{\prime }\) such that \(Q^{\prime } \lt : Q\). The inductive case remains the same.

Base Case. (let) was used to derive that \(\Gamma ,\Delta \vdash \operatorname{{\bf \textsf {l}et}} x = y \operatorname{{\bf \textsf {i}n}} s : Q^{\prime }\). The premises are \(\Gamma ,\Delta \vdash y : U\) and \(\Gamma ,\Delta ,x:U \vdash s : Q^{\prime }\) and \(x \not\in \operatorname{fv}(Q^{\prime })\). By Lemma A.29, we have \(\Gamma ,\Delta \,\vdash \,[x := y]s : [x := y]Q^{\prime }\) and \(x \not\in \operatorname{fv}(Q^{\prime })\), \([x := y]Q^{\prime } = Q^{\prime }\). We conclude by reasoning similar to the (apply) case: first goal holds by (sub) and Lemma A.5, second goal holds since the \(\operatorname{cv}\) of the program does not increase and e remains the same.

Case (lift). Then we have \(t = e[\ \operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} s\ ]\) and \(t^{\prime } = \operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} e[\ s\ ]\).

Again, by Lemma A.4 for some Q we have \(\Gamma ,\Delta \vdash e : Q \Rightarrow T\) and \(\Gamma ,\Delta \vdash \operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} s : Q\). As in the (apply) case, we proceed by induction, only working with a \(Q^{\prime }\) such that \(Q^{\prime } \lt : Q\). The inductive case remains the same.

Base Case. (let) was used to derive that \(\Gamma ,\Delta \vdash \operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} s : Q^{\prime }\). The premises are \(\Gamma ,\Delta \vdash v : U\) and \(\Gamma ,\Delta ,x:U \vdash s : Q^{\prime }\) and \(x \not\in \operatorname{fv}(Q^{\prime })\).

By weakening of typing, we also have \(\Gamma ,\Delta ,x:U \vdash e : Q \Rightarrow T\). Then by (sub) and Lemma A.5, we have \(\Gamma ,\Delta ,x:U \vdash e[\ s\ ] : T\). Since \(\Gamma ,\Delta \vdash T \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\), by Barendregt \(x \not\in \operatorname{fv}(T)\), so by (let) we have \(\Gamma ,\Delta \vdash \operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} e[\ s\ ] : T\). We conclude by reasoning similar to the (apply) case: first goal was shown, second goal holds since the \(\operatorname{cv}\) of the program does not increase and e remains the same.

Case (enter). Then \(t = e[\ \operatorname{{\bf \textsf {boundary}}}[S]\,x \Rightarrow t \ ]\) and \(t^{\prime } = \operatorname{{\bf \textsf {l}et}} x = {l}_{S} \operatorname{{\bf \textsf {i}n}} e[\ \operatorname{{\bf \textsf {scope}}}_{x}\,t \ ]\).

By Lemma A.4, for some Q we have \(\Gamma ,\Delta \vdash e: Q \Rightarrow T\) and \(\Gamma ,\Delta \vdash \operatorname{{\bf \textsf {boundary}}}[S]\,x \Rightarrow t : Q\). As in the (apply) case, we proceed by induction, only working with a \(Q^{\prime }\) such that \(Q^{\prime } \lt : Q\). The inductive case remains the same.

Base Case. \(\Gamma ,\Delta \vdash \operatorname{{\bf \textsf {boundary}}}[S]\,x \Rightarrow t : Q^{\prime }\) was derived via (boundary), so we have \(Q^{\prime } = S\) and \(\Gamma ,\Delta ,x:\operatorname{\mathsf {Break}}[S] \vdash t : S\) and \(x \notin \operatorname{fv}(S)\). This means that we can derive \(\Gamma ,\Delta \vdash \operatorname{{\bf \textsf {l}et}} x = {l}_{S} \operatorname{{\bf \textsf {i}n}} e[\ \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\,t \ ] : S\) via (scope), (sub) and Lemma A.5, and finally (let).

Similarly to the case for (apply), we have \(\mathrm{resolver}(\varsigma)(t^{\prime }) \subseteq \mathrm{resolver}(\varsigma)(t) \cup \lbrace x\rbrace\). By Barendregt, we must have \(x \notin \mathrm{resolver}(\varsigma)(e)\), which means that \(\sigma [\ e[\ t^{\prime }\ ]\ ]\) remains a proper program.

Case (break). Then \(t = e_1[\ \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\,e_2[\ x\,y\ ]\ ]\) and \(\sigma (x) = {l}_{S}\) and \(t^{\prime } = e_1[\ y\ ]\).

By Lemma A.4, for some Q we have \(\Gamma ,\Delta \vdash e_1[\ \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\,e_2\ ] : Q \Rightarrow T\) and \(\Gamma ,\Delta \vdash x\,y : Q\). As in the (apply) case, we proceed by induction, only working with a \(Q^{\prime }\) such that \(Q^{\prime } \lt : Q\). The inductive case remains the same.

Base Case. \(\Gamma ,\Delta \vdash x\,y : Q^{\prime }\) was derived via (invoke), so for some \(S^{\prime }\) we have \(\Gamma ,\Delta \vdash x : \operatorname{\mathsf {Break}}[S^{\prime }]\) and \(\Gamma ,\Delta \vdash y : S^{\prime }\). By Lemma B.3, we have \(\Gamma ,\Delta \vdash S^{\prime } \lt : S\). We now use Lemma A.4 once again, this time on \(e_1\): for some \(P,\) we have \(\Gamma ,\Delta \vdash e_1 : P \Rightarrow T\) and \(\Gamma ,\Delta \vdash \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\,e_2[\ x\,y\ ] : P\). We proceed by induction yet again just like in the (apply) case, only working with a \(P^{\prime }\) such that \(P^{\prime } \lt : P\). The inductive case remains unchanged.

Base Case. \(\Gamma ,\Delta \vdash \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\,e_2[\ x\,y\ ] : P^{\prime }\) was derived via (scope), which means that \(P^{\prime } = S\), which in turn gives us \(\Gamma ,\Delta \vdash S \lt : P\), and by transitivity \(\Gamma ,\Delta \vdash S^{\prime } \lt : P\), which means that we can conclude that \(\Gamma ,\Delta \vdash e_1[\ y\ ] : T\) by (sub) and Lemma A.5.

We have \(\Gamma ,\Delta \vdash y : S\) and accordingly \(\mathrm{resolver}(\sigma)(y) = \lbrace \rbrace\). This implies that if we have \(x \in \mathrm{resolver}(\sigma)(\operatorname{cv}(e_1[\ y\ ])),\) then \(x \in \mathrm{resolver}(\sigma)(e_1)\). Since \(e_1\) is only one part of the proper program \(\sigma [\ e_1[\ \operatorname{{\bf \textsf {scope}}}_{x}\,e_2\ ]\ ]\), clearly \(\sigma [\ e_1[\ y\ ]\ ]\) remains well-scoped.

Case (leave). Then \(t = e[\ \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\,a\ ]\) and \(t^{\prime } = e[\ a\ ]\).

By Lemma A.4, for some Q we have \(\Gamma ,\Delta \vdash e: Q \Rightarrow T\) and \(\Gamma ,\Delta \vdash \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\,a : Q\). As in the (apply) case, we proceed by induction, only working with a \(Q^{\prime }\) such that \(Q^{\prime } \lt : Q\). The inductive case remains the same.

Base Case. \(\Gamma ,\Delta \vdash \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\,a : Q^{\prime }\) was derived via (scope), which means that \(Q^{\prime } = S\) and that \(\Gamma ,\Delta \vdash a : S\), which lets us conclude that \(\Gamma ,\Delta \vdash e[\ a\ ] : T\) by (sub) and Lemma A.5.

Since \(\Gamma ,\Delta \vdash a : S\), we have \(\operatorname{cv}(\sigma [\ a\ ]) = \lbrace \rbrace\). Since we must have had \(l \notin \operatorname{cv}(\sigma [\ e\ ])\), \(\sigma [\ e[\ a\ ]\ ]\) remains a proper program. □

Theorem B.5 (Progress).

Let \(\,\vdash \,\sigma [\ e[\ t\ ]\ ] :T,\) where \(\sigma [\ e[\ t\ ]\ ]\) is a proper configuration and a proper program. Then either \(e[\ t\ ] = a\) for some a or \(\sigma [\ e[\ t\ ]\ ] \longrightarrow \sigma [\ t^{\prime }\ ]\) for some \(t^{\prime }\).

Proof.

Since \(\sigma [\ e[\ t\ ]\ ]\) is well-typed in the empty environment, there clearly must be some \(\Delta\) such that \(\,\vdash \,\sigma \sim \Delta\) and \(\Delta \,\vdash \,e[\ t\ ] : T\). By Lemma A.4, we have that \(\Delta \,\vdash \,t :P\) for some P. We proceed by induction on the structure of this derivation.

Case (var). Then \(t = x\).

If e is non-empty, \(e[\ x\ ] = e^{\prime }[\ \operatorname{{\bf \textsf {l}et}} y = x \operatorname{{\bf \textsf {i}n}} t^{\prime }\ ]\) and we can step by (rename); otherwise, we conclude with \(a = x\).

Case (abs), (tabs), (box). Then \(t = v\).

If e is non-empty, \(e[\ v\ ] = e^{\prime }[\ \operatorname{{\bf \textsf {l}et}} x = v \operatorname{{\bf \textsf {i}n}} t^{\prime }\ ]\) and we can step by (lift); otherwise, we conclude with \(a = v\).

Case (app). Then \(t = x\,y\) and \(\Delta \,\vdash \,x : (\forall (z : U)\,T_0) \mathrel {\wedge }C\) and \(\Delta \,\vdash \,y :U\).

By Lemmas A.45 and A.34, \(\sigma (x) = \lambda (z : U^{\prime })\,t^{\prime }\), which means we can step by (apply).

Case (tapp). Then \(t = x\,[S]\) and \(\Delta \,\vdash \,x : (\forall [Z \lt : S]\,T_0) \mathrel {\wedge }C\).

By Lemmas A.45 and A.35, \(\sigma (x) = \lambda [z \lt : S^{\prime }]\,{t^{\prime }}\), which means we can step by (tapply).

Case (unbox). Then \(t = C ⟜ x\) and \(\Delta \,\vdash \,x : \Box S \mathrel {\wedge }C\). By Lemmas A.45 and A.36, \(\sigma (x) = \Box y\), which means we can step by (open).

Case (let). Then \(t = \operatorname{{\bf \textsf {l}et}} x = u \operatorname{{\bf \textsf {i}n}} t^{\prime }\) and we proceed by IH on u, with \(e[\ \operatorname{{\bf \textsf {l}et}} x = [\,] \operatorname{{\bf \textsf {i}n}} t^{\prime }\ ]\) as the evaluation context.

Case (sub) . By IH.

Case (boundary). Then \(t = \operatorname{{\bf \textsf {boundary}}}[S]\,x \Rightarrow t^{\prime }\) and \(\Delta ,x:\operatorname{\mathsf {Break}}[S] \vdash t^{\prime } : S\), and we can step by (enter).

Case (scope). Then \(t = \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\,t^{\prime }\) and we proceed by IH on t, with \(e[\ \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\,[\,]\ ]\) as the evaluation context.

Case (invoke). Then \(t = x\,y\) and \(\Delta \vdash x : \operatorname{\mathsf {Break}}[S]\) and \(\Delta \vdash y : S\).

By Lemmas A.45 and B.2, we have \(\sigma (x) = {l}_{S^{\prime }}\); since \(\sigma [\ e[\ t\ ]\ ]\) is a proper program, e must be of the form \(e_1[\ \operatorname{{\bf \textsf {scope}}}_{{l}_{S^{\prime }}}\,e_2\ ]\). Then we can step by (break).□

Skip B.1Predicting Used Capabilities Section

B.1 Predicting Used Capabilities

Lemma B.6 (Program Authority Preservation).

Let \(t \longrightarrow t^{\prime }\), where \(\vdash t : T\). Then, \(\begin{equation*} \operatorname{cv}(t^{\prime }) \subseteq \operatorname{cv}(t) \cup \mathrm{gained}(t \longrightarrow t^{\prime }). \end{equation*}\)

Proof.

We start by inspecting the evaluation rule used.

Case (apply). Then \(t = \sigma [\ e[\ x\,y\ ]\ ]\) and \(u = \sigma [\ e[\ [z := y]s\ ]\ ]\), and \(\mathrm{gained}(t \longrightarrow u) = \lbrace \rbrace\). Then our goal is \(\begin{equation*} \mathrm{resolver}(\sigma)(\operatorname{cv}(e) \cup \operatorname{cv}([z := y]s)) \quad \subseteq \quad \mathrm{resolver}(\sigma)(\operatorname{cv}(e) \cup \operatorname{cv}(x\,y)). \end{equation*}\)

We clearly must have \(x \in \operatorname{dom} (\sigma)\) and \(\sigma (x) = \lambda (z:U)\,s\). Since x is bound in \(\sigma\), we have \(\mathrm{resolver}(\sigma)(\operatorname{cv}(\lambda (z:U)\,s) \cup \lbrace y\rbrace) \subseteq \mathrm{resolver}(\sigma)(\operatorname{cv}(x\,y)))\). Since \(\operatorname{cv}([z := y]s) \subseteq \operatorname{cv}(\lambda (z:U)\,s) \cup \lbrace y\rbrace\), our goal is again satisfied.

Case (tapply). Analogous reasoning.

Case (open). Then \(t = \sigma [\ e[\ C ⟜ x\ ]\ ]\) and \(u = \sigma [\ e[\ z\ ]\ ]\) and \(\mathrm{gained}(t \longrightarrow u) = \lbrace \rbrace\). We must have \(x \in \operatorname{dom} (\sigma)\) and \(z \in \operatorname{dom} (\sigma)\) and \(\sigma (x) = \Box z\). Since t is well-typed, we clearly must have \(\vdash \sigma \sim \Delta\) for some \(\Delta\).

By Lemma A.38 and Lemma A.41, we have \(\Gamma ,\Delta \vdash z : S_z \mathrel {\wedge }C\) for some \(S_z\). By a straightforward induction on the typing derivation, we must then have \(\Gamma ,\Delta \vdash \lbrace z\rbrace \lt : C\). Then by Lemma A.56, we have \(\mathrm{resolver}(\sigma)(\lbrace z\rbrace) \subseteq \mathrm{resolver}(\sigma)(C)\), which lets us conclude by an argument similar to the (apply) case.

Case (rename), (lift). The lemma is clearly true since these rules only shift subterms of t to create u.

Case (enter). Then \(t = \sigma [\ e[\ \operatorname{{\bf \textsf {boundary}}}[S]\,x \Rightarrow u \ ]\ ]\) and \(u = \operatorname{{\bf \textsf {l}et}} x = {l}_{S} \operatorname{{\bf \textsf {i}n}} \sigma [\ e[\ \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\,u \ ] \ ]\) and \(\mathrm{gained}(t \longrightarrow u) = \lbrace x\rbrace\). We have both \(\begin{align*} \operatorname{cv}(t) &= \mathrm{resolver}(\sigma)(\operatorname{cv}(e) \cup (\operatorname{cv}(u) \setminus x)) \\ \operatorname{cv}(u) &= (\mathrm{resolver}(\sigma)\circ [x := \lbrace l\rbrace ])(\operatorname{cv}(e) \cup \operatorname{cv}(u)). \end{align*}\) We have \(x \notin \operatorname{cv}(e)\) by Barendregt, which tells us that \(\begin{equation*} \operatorname{cv}(u) = \mathrm{resolver}(\sigma)(\operatorname{cv}(e) \cup [x := \lbrace l\rbrace ]\operatorname{cv}(u)). \end{equation*}\) Then we have \(\operatorname{cv}(u) \subseteq \mathrm{resolver}(\sigma)(\operatorname{cv}(e) \cup (\operatorname{cv}(u) \setminus x)) \cup \lbrace l\rbrace\), which concludes.

Case (break). Then \(t = \sigma [\ e_1[\ \operatorname{{\bf \textsf {scope}}}_{x}\, e_2[\ x\,y\ ] \ ]\ ]\) and \(u = \sigma [\ e_1[\ y\ ]\ ]\) and \(\sigma (x) = {}_{}\). We have \(\begin{align*} \operatorname{cv}(t) &= \mathrm{resolver}(\sigma)(\operatorname{cv}(e_1) \cup \operatorname{cv}(e_2) \cup \operatorname{cv}(x\,y)) \\ \operatorname{cv}(u) &= \mathrm{resolver}(\sigma)(\operatorname{cv}(e_1) \cup \operatorname{cv}(y)). \end{align*}\) Since \(\operatorname{cv}(y) \subset \operatorname{cv}(x\,y)\), we have \(\operatorname{cv}(t) \subseteq \operatorname{cv}(u)\) and we can conclude.

Case (leave). Then \(t = \sigma [\ e[\ \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\,a\ ]\ ]\) and \(u = \sigma [\ e[\ a\ ]\ ]\).

We have \(\operatorname{cv}(t) = \operatorname{cv}(u) = \mathrm{resolver}(\sigma)(\operatorname{cv}(e) \cup \operatorname{cv}(a))\) and we can conclude. □

Lemma B.7 (Single-Step Used Capability Prediction).

Let \(t \longrightarrow u\), where \(\vdash t : T\). Then the primitive capabilities used during the evaluation are within the authority of t: \(\begin{equation*} \mathrm{used}(t \longrightarrow u) \subseteq \operatorname{cv}(t). \end{equation*}\)

Proof.

By inspection of the reduction rule used.

Case (break). Then \(t = \sigma [\ e_1[\ \operatorname{{\bf \textsf {scope}}}_{{l}_{S}}\, e_2[\ x\,y\ ] \ ]\ ]\) and \(u = \sigma [\ e_1[\ y\ ]\ ]\) and \(\sigma (x) = {l}_{S}\) and \(\mathrm{used}(t \longrightarrow u) = \lbrace l\rbrace\). We have \(\begin{equation*} \operatorname{cv}(t) = \mathrm{resolver}(\sigma)(\operatorname{cv}(e_1) \cup \operatorname{cv}(e_2) \cup \operatorname{cv}(x\,y)). \end{equation*}\) Since \(\sigma (x) = {l}_{S}\), \(\mathrm{resolver}(\sigma)(x) = l\) and so we have \(l \in \operatorname{cv}(t)\).

Case (enter), (leave), (apply), (tapply), (open), (rename), (lift). The lemma holds trivially, since no capabilities are used by reducing using these rules.□

Theorem B.8 (Used Capability Prediction).

Let \(t \longrightarrow ^\ast t^{\prime }\), where \(\vdash t : T\). Then the primitive capabilities used during the evaluation are within the authority of t: \(\begin{equation*} \mathrm{used}(t \longrightarrow ^\ast t^{\prime }) \subseteq \operatorname{cv}(t) \cup \mathrm{created}(t \longrightarrow ^\ast t^{\prime }). \end{equation*}\)

Proof.

We begin by induction on the number of evaluation steps. The theorem is trivially true for the base case of 0 steps. In the inductive case, we have \(t \longrightarrow t^{\prime \prime } \longrightarrow ^\ast t^{\prime }\).

By Lemma B.6, we have \(\begin{equation*} \operatorname{cv}(t^{\prime }) \subseteq \operatorname{cv}(t) \cup \mathrm{gained}({t} \longrightarrow t^{\prime \prime }) \subseteq \operatorname{cv}(t) \cup \mathrm{created}(t \longrightarrow t^{\prime \prime }). \end{equation*}\)

By the IH, we have \(\mathrm{used}(t^{\prime \prime } \longrightarrow ^\ast t^{\prime }) \subseteq \operatorname{cv}(t^{\prime }) \cup \mathrm{created}(t^{\prime \prime } \longrightarrow ^\ast t^{\prime })\), which gives us \(\begin{align*} \mathrm{used}(t^{\prime \prime } \longrightarrow ^\ast t^{\prime }) &\subseteq \operatorname{cv}(t) \cup \mathrm{created}(t \longrightarrow t^{\prime \prime }) \cup \mathrm{created}(t^{\prime \prime } \longrightarrow ^\ast t^{\prime }) \\ &\subseteq \operatorname{cv}(t) \cup \mathrm{created}(t \longrightarrow ^\ast t^{\prime }). \end{align*}\)

By Lemma B.7, we have \(\mathrm{used}(t \longrightarrow t^{\prime \prime }) \subseteq \operatorname{cv}(t)\).

Then \(\mathrm{used}(t \longrightarrow t^{\prime }) \subseteq \operatorname{cv}(t) \cup \mathrm{created}(t \longrightarrow ^\ast t^{\prime })\), which concludes.□

Footnotes

  1. 1 Scala 3.1 with language import saferExceptions enabled.

    Footnote
  2. 2 This class is covariant in both A and B, as denoted by the pluses.

    Footnote
  3. 3 This follows since type variables range over pure types, so cap must appear under a box. But rule (Box) in Figure 3 (shown later) restricts variables in boxed capture sets to be declared in the enclosing environment, which does not hold for cap.

    Footnote
  4. 4 Mutable variables are not covered by the formal treatment of \(\mathsf {CC}_{\lt :\Box }\). We include the discussion anyway to show that escape checking can be generalized to scope extrusions separate from result values.

    Footnote
  5. 5 Specifically, the (open) case in the proof of Lemma 4.13 relies on the boxed capability subcapturing the unboxing key, allowing Lemma A.56 to be used.

    Footnote
  6. 6 For simplicity, this example is neither thread nor exception safe.

    Footnote

REFERENCES

  1. Amin Nada, Grütter Samuel, Odersky Martin, Rompf Tiark, and Stucki Sandro. 2016. The essence of dependent object types. In A List of Successes That Can Change the World. Springer, 249272. Google ScholarGoogle ScholarCross RefCross Ref
  2. Bao Yuyan, Wei Guannan, Bracevac Oliver, Jiang Yuxuan, He Qiyang, and Rompf Tiark. 2021. Reachability types: Tracking aliasing and separation in higher-order functional programs. Proceedings of the ACM on Programming Languages 5, OOPSLA (2021), 132. Google ScholarGoogle ScholarDigital LibraryDigital Library
  3. Barendsen Erik and Smetsers Sjaak. 1996. Uniqueness typing for functional languages with graph rewriting semantics. Mathematical Structures in Computer Science 6, 6 (Dec.1996), 579612. Google ScholarGoogle ScholarCross RefCross Ref
  4. Biernacki Dariusz, Piróg Maciej, Polesiuk Piotr, and Sieczkowski Filip. 2020. Binders by day, labels by night: Effect instances via lexically scoped handlers. In Proceedings of the Symposium on Principles of Programming Languages. ACM, New York, NY.Google ScholarGoogle ScholarDigital LibraryDigital Library
  5. Böhm Corrado and Berarducci Alessandro. 1985. Automatic synthesis of typed \(\lambda\)-programs on term algebras. Theoretical Computer Science 39 (1985), 135154. Google ScholarGoogle ScholarCross RefCross Ref
  6. Boruch-Gruszecki Aleksander, Brachthäuser Jonathan Immanuel, Lee Edward, Lhoták Ondřej, and Odersky Martin. 2021. Tracking captured variables in types. arXiv:2105.11896 [cs] (2021).Google ScholarGoogle Scholar
  7. Boyland John, Noble James, and Retert William. 2001. Capabilities for sharing. In ECOOP 2001—Object-Oriented Programming, Knudsen Jørgen Lindskov (Ed.). Springer, Berlin, Germany, 227. Google ScholarGoogle Scholar
  8. Bracha Gilad, Ahé Peter von der, Bykov Vassili, Kashai Yaron, Maddox William, and Miranda Eliot. 2010. Modules as objects in Newspeak. In ECOOP 2010—Object-Oriented Programming. Lecture Notes in Computer Science, Vol. 6183. Springer, 405–428. Google ScholarGoogle ScholarCross RefCross Ref
  9. Brachthäuser Jonathan Immanuel, Schuster Philipp, Lee Edward, and Boruch-Gruszecki Aleksander. 2022. Effects, capabilities, and boxes: From scope-based reasoning to type-based reasoning and back. Proceedings of the ACM on Programming Languages 6, OOPSLA1 (2022), Article 76, 30 pages. Google ScholarGoogle ScholarDigital LibraryDigital Library
  10. Brachthäuser Jonathan Immanuel, Schuster Philipp, and Ostermann Klaus. 2020a. Effects as capabilities: Effect handlers and lightweight effect polymorphism. Proceedings of the ACM on Programming Languages 4, OOPSLA (Nov. 2020), Article 126, 30 pages. Google ScholarGoogle ScholarDigital LibraryDigital Library
  11. Brachthäuser Jonathan Immanuel, Schuster Philipp, and Ostermann Klaus. 2020b. Effekt: Capability-passing style for type- and effect-safe, extensible effect handlers in Scala. Journal of Functional Programming 30 (2020), e8. Google ScholarGoogle ScholarCross RefCross Ref
  12. Choudhury Vikraman and Krishnaswami Neel. 2020. Recovering purity with comonads and capabilities. Proceedings of the ACM on Programming Languages 4, ICFP (Aug. 2020), Article 111, 28 pages. Google ScholarGoogle ScholarDigital LibraryDigital Library
  13. Clarke David G., Potter John M., and Noble James. 1998. Ownership types for flexible alias protection. In Proceedings of the 13th ACM SIGPLAN Conference on Object-Oriented Programming, Systems, Languages, and Applications (OOPSLA ’98). ACM, New York, NY, 4864. Google ScholarGoogle ScholarDigital LibraryDigital Library
  14. Cook William R.. 2009. On understanding data abstraction, revisited. In Proceedings of the 24th ACM SIGPLAN Conferenceon Object-Oriented Programming, Systems, Language, and Applications (OOPSLA’09). ACM, New York, NY, 557572.Google ScholarGoogle Scholar
  15. Craig Aaron, Potanin Alex, Groves Lindsay, and Aldrich Jonathan. 2018. Capabilities: Effects for free. In Formal Methods and Software Engineering. Lecture Notes in Computer Science, Vol. 11232. Springer, 231–247. Google ScholarGoogle ScholarCross RefCross Ref
  16. Crary Karl, Walker David, and Morrisett Greg. 1999. Typed memory management in a calculus of capabilities. In Proceedings of the 26th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (POPL’99). ACM, New York, NY, 262275. Google ScholarGoogle ScholarDigital LibraryDigital Library
  17. Drossopoulou Sophia, Noble James, Miller Mark S., and Murray Toby. 2016. Permission and authority revisited towards a formalisation. In Proceedings of the 18th Workshop on Formal Techniques for Java-Like Programs (FTfJP’16). ACM, New York, NY, 16. Google ScholarGoogle ScholarDigital LibraryDigital Library
  18. Figueroa Ismael, Tabareau Nicolas, and Tanter Éric. 2016. Effect capabilities for Haskell: Taming effect interference in monadic programming. Science of Computer Programming 119 (April2016), 330. Google ScholarGoogle ScholarDigital LibraryDigital Library
  19. Fourment Joseph and Xu Yichen. 2023. A Mechanized Theory of the Box Calculus. Technical Report. Infoscience. http://infoscience.epfl.ch/record/302949Google ScholarGoogle Scholar
  20. Gordon Colin S.. 2020. Designing with static capabilities and effects: Use, mention, and invariants (Pearl). In Designing with Static Capabilities and Effects: Use, Mention, and Invariants. Leibniz International Proceedings in Informatics, Vol. 166, Hirschfeld Robert and Pape Tobias (Eds.). Schloss Dagstuhl–Leibniz-Zentrum für Informatik, Dagstuhl, Germany, Article 10, 25 pages. Google ScholarGoogle ScholarCross RefCross Ref
  21. Grossman Dan, Morrisett Greg, Jim Trevor, Hicks Michael, Wang Yanling, and Cheney James. 2002. Region-based memory management in Cyclone. In Proceedings of the ACM SIGPLAN 2002 Conference on Programming Language Design and Implementation (PLDI’02). ACM, New York, NY, 282293. Google ScholarGoogle ScholarDigital LibraryDigital Library
  22. Hannan John. 1998. A type-based escape analysis for functional languages. Journal of Functional Programming 8, 3 (May1998), 239273.Google ScholarGoogle ScholarDigital LibraryDigital Library
  23. Hatcliff John and Danvy Olivier. 1994. A generic account of continuation-passing styles. In Proceedings of the 21st ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (POPL’94). ACM, New York, NY, 458471. Google ScholarGoogle ScholarDigital LibraryDigital Library
  24. Launchbury John and Sabry Amr. 1997. Monadic state: Axiomatization and type safety. In Proceedings of the 2nd ACM SIGPLAN International Conference on Functional Programming (ICFP’97). ACM, New York, NY, 227238. Google ScholarGoogle ScholarDigital LibraryDigital Library
  25. Leijen Daan. 2014. Koka: Programming with row polymorphic effect types. Electronic Proceedings in Theoretical Computer Science 153 (June2014), 100126. arxiv:1406.2061Google ScholarGoogle ScholarCross RefCross Ref
  26. Leijen Daan. 2017. Type directed compilation of row-typed algebraic effects. In Proceedings of the Symposium on Principles of Programming Languages. ACM, New York, NY, 486499. Google ScholarGoogle ScholarDigital LibraryDigital Library
  27. Lindley Sam, McBride Conor, and McLaughlin Craig. 2017. Do be do be do. In Proceedings of the 44th ACM SIGPLANSymposium on Principles of Programming Languages (POPL’17). ACM, New York, NY, 500514. Google ScholarGoogle ScholarDigital LibraryDigital Library
  28. Liu Fengyun. 2016. A Study of Capability-Based Effect Systems. Master’s Thesis. Infoscience. infoscience.epfl.ch/record/219173Google ScholarGoogle Scholar
  29. Lucassen J. M. and Gifford D. K.. 1988. Polymorphic effect systems. In Proceedings of the 15th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (POPL ’88). ACM, New York, NY, 4757. Google ScholarGoogle ScholarDigital LibraryDigital Library
  30. Marino Daniel and Millstein Todd D.. 2009. A generic type-and-effect system. In Proceedings of the 2009ACMSIGPLAN International Workshop on Types in Languages Design and Implementation (TLDI’09). ACM, New York, NY, 3950. Google ScholarGoogle ScholarDigital LibraryDigital Library
  31. Melicher Darya. 2020. Controlling Module Authority Using Programming Language Design. Ph.D. Dissertation. Carnegie Mellon University.Google ScholarGoogle Scholar
  32. Melicher Darya, Shi Yangqingwei, Potanin Alex, and Aldrich Jonathan. 2017. A capability-based module system for authority control. In Proceedings of the 31st European Conference on Object-Oriented Programming (ECOOP’17).Google ScholarGoogle Scholar
  33. Miller Mark Samuel. 2006. Robust Composition: Towards a Unified Approach to Access Control and Concurrency Control. Ph.D. Dissertation. Johns Hopkins University.Google ScholarGoogle ScholarDigital LibraryDigital Library
  34. Nanevski Aleksandar, Pfenning Frank, and Pientka Brigitte. 2008. Contextual modal type theory. ACM Transactions on Computational Logic 9, 3 (June 2008), Article 23, 49 pages. Google ScholarGoogle ScholarDigital LibraryDigital Library
  35. Noble James, Vitek Jan, and Potter John. 1998. Flexible alias protection. In ECOOP’98—Object-Oriented Programming. Lecture Notes in Computer Science, Vol. 1445. Springer, 158–185. Google ScholarGoogle ScholarCross RefCross Ref
  36. Odersky Martin, Blanvillain Olivier, Liu Fengyun, Biboudis Aggelos, Miller Heather, and Stucki Sandro. 2018. Simplicitly: Foundations and applications of implicit function types. Proceedings of the ACM on Programming Languages 2, POPL (Dec. 2008), Article 42, 29 pages. Google ScholarGoogle ScholarDigital LibraryDigital Library
  37. Odersky Martin, Boruch-Gruszecki Aleksander, Brachthäuser Jonathan Immanuel, Lee Edward, and Lhoták Ondřej. 2021. Safer exceptions for Scala. In Proceedings of the Scala Symposium. Google ScholarGoogle ScholarDigital LibraryDigital Library
  38. Odersky Martin and Martres Guillaume. 2020. Extension Methods: Scala 3 Language Reference Page. Retrieved September 19, 2023 from https://dotty.epfl.ch/docs/reference/contextual/extension-methods.htmlGoogle ScholarGoogle Scholar
  39. Osvald Leo, Essertel Grégory M., Wu Xilun, Alayón Lilliam I. González, and Rompf Tiark. 2016. Gentrification gone too far? Affordable 2nd-class values for fun and (co-)effect. In Proceedings of the 2016 ACM SIGPLAN International Conference on Object-Oriented Programming, Systems, Languages, and Applications (OOPSLA’16). ACM, New York, NY, 234–251. Google ScholarGoogle ScholarDigital LibraryDigital Library
  40. Petricek Tomas, Orchard Dominic, and Mycroft Alan. 2014. Coeffects: A calculus of context-dependent computation. In Proceedings of the International Conference on Functional Programming (Gothenburg, Sweden). ACM, New York, NY, 123135. Google ScholarGoogle ScholarDigital LibraryDigital Library
  41. Pierce Benjamin C.. 2002. Types and Programming Languages. MIT Press, Cambridge, MA.Google ScholarGoogle ScholarDigital LibraryDigital Library
  42. Rompf Tiark and Amin Nada. 2016. Type soundness for dependent object types (DOT). In Proceedings of the 2016 ACM SIGPLAN International Conference on Object-Oriented Programming, Systems, Languages, and Applications (OOPSLA’16). ACM, New York, NY, 624641. Google ScholarGoogle ScholarDigital LibraryDigital Library
  43. Rytz Lukas, Odersky Martin, and Haller Philipp. 2012. Lightweight polymorphic effects. In ECOOP 2012—Object-Oriented Programming. Lecture Notes in Computer Science, Vol. 7313. Springer, 258–282. Google ScholarGoogle ScholarDigital LibraryDigital Library
  44. Sabry Amr and Felleisen Matthias. 1993. Reasoning about programs in continuation-passing style. In LISP and Symbolic Computation6 (1993), 289–360.Google ScholarGoogle Scholar
  45. Scala. 2022a. Scala 3 API: scala.util.boundary. Retrieved September 19, 2023 from https://www.scala-lang.org/api/3.3.0/scala/util/boundary$.htmlGoogle ScholarGoogle Scholar
  46. Scala. 2022b. Scala 3: Capture Checking. Retrieved September 19, 2023 from https://dotty.epfl.ch/docs/reference/experimental/cc.htmlGoogle ScholarGoogle Scholar
  47. Scala. 2022c. The Scala 3 Compiler, Also Known as Dotty. Retrieved September 19, 2023 from https://dotty.epfl.chGoogle ScholarGoogle Scholar
  48. Scherer Gabriel and Hoffmann Jan. 2013. Tracking data-flow with open closure types. In Proceedings of the International Conference on Logic for Programming Artificial Intelligence and Reasoning. 710726. Google ScholarGoogle ScholarCross RefCross Ref
  49. Siek Jeremy G., Vitousek Michael M., and Turner Jonathan D.. 2012. Effects for funargs. CoRR abs/1201.0023 (2012). http://arxiv.org/abs/1201.0023Google ScholarGoogle Scholar
  50. Tofte Mads and Talpin Jean-Pierre. 1997. Region-based memory management. Information and Computation 132, 2 (Feb.1997), 109176. Google ScholarGoogle ScholarDigital LibraryDigital Library
  51. Wadler Philip. 1990. Linear types can change the world! In Programming Concepts and Methods: Proceedings of the IFIP Working Group 2.2, 2.3 Working Conference on Programming Concepts and Methods, Broy Manfred and Jones Cliff B. (Eds.). North-Holland, 561.Google ScholarGoogle Scholar
  52. Xu Yichen and Odersky Martin. 2023. Formalizing Box Inference for Capture Calculus. Technical Report. EPFL.Google ScholarGoogle Scholar
  53. Zhang Yizhou and Myers Andrew C.. 2019. Abstraction-safe effect handlers via tunneling. Proceedings of the ACM on Programming Languages 3, POPL (Jan. 2019), Article 5, 29 pages. Google ScholarGoogle ScholarDigital LibraryDigital Library

Index Terms

  1. Capturing Types

      Recommendations

      Comments

      Login options

      Check if you have access through your login credentials or your institution to get full access on this article.

      Sign in

      Full Access

      • Published in

        cover image ACM Transactions on Programming Languages and Systems
        ACM Transactions on Programming Languages and Systems  Volume 45, Issue 4
        December 2023
        178 pages
        ISSN:0164-0925
        EISSN:1558-4593
        DOI:10.1145/3633281
        • Editor:
        • Jan Vitek
        Issue’s Table of Contents

        Permission to make digital or hard copies of all or part of this work for personal or classroom use is granted without fee provided that copies are not made or distributed 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 author(s) must be honored. Abstracting with credit is permitted. To copy otherwise, or republish, to post on servers or to redistribute to lists, requires prior specific permission and/or a fee. Request permissions from [email protected].

        Publisher

        Association for Computing Machinery

        New York, NY, United States

        Publication History

        • Published: 20 November 2023
        • Online AM: 13 September 2023
        • Accepted: 1 August 2023
        • Revised: 25 June 2023
        • Received: 23 October 2022
        Published in toplas Volume 45, Issue 4

        Permissions

        Request permissions about this article.

        Request Permissions

        Check for updates

        Qualifiers

        • research-article

      PDF Format

      View or Download as a PDF file.

      PDF

      eReader

      View online with eReader.

      eReader