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
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
The second version expresses analogous information as a capability: function
Aside. The link between the “effect” and the “capability” version of
The context function type
An important benefit of this switch from effects to capabilities is that it gives us polymorphism for free. For instance, consider the
Here,
However, here is the type of
Interestingly, this is exactly the same as the type of
The reason this works is that in an effects-as-capabilities discipline, the type
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.
To see why, consider that effect capabilities are often scoped and therefore have a limited lifetime. For instance a
A question answered in this article is how to rule out
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 “
As an example how capabilities are defined and used, consider a typical try-with-resources pattern:
The
We can accept the first usage and reject the second by marking the output stream passed to
This example used a capability parameter that was directly derived from
The
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.
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
Here, the
The second variable defined in
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
2.2 Function Types
The function type
The impure function type
Note. Like other object-functional languages, Scala distinguishes between functions and methods (which are defined using
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
Note. On the term level, function values are always written with
A closure also captures all capabilities that are captured by the functions it calls. For instance, in
the result of
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:
Example. Given
we have
The set consisting of the root capability
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
What happens if we pass arguments to the constructor of
Here the arguments
In other words, the outer capture set is empty and it neither mentions
Although assigning
Even though
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
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
Specifically, if a capturing type is an instance of a type variable, that capturing type is not allowed to carry the universal capability
Using this principle, we can show why the introductory example in Section 1 reported an error. To recall, function
The capture checker rejects the illegal definition of
with the following error message
This error message was produced by the following reasoning steps:
Parameter
f has typeFileOutputStream^{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 variableT .We cannot instantiate
T withInt ->{f} Unit since the expected function type is non-dependent. The smallest supertype that matches the expected type is thusFileOutputStream^{cap} => Int ->{cap} Unit .Hence, the type variable
T is instantiated toInt ->{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
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:
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. 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.
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.
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.
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.
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).
Evaluation is deterministic. If \(t \longrightarrow u_1\) and \(t \longrightarrow u_2\), then \(u_1 = u_2\).
By a straightforward inspection of the reduction rules and definitions of contexts.□
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.
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.
(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\).
\(\Gamma \,\vdash \,e[\ s\ ] : T\) implies that for some \(U,\) we have \(\Gamma \vdash e : U \Rightarrow T\) and \(\Gamma \vdash s : U\).
If both \(\Gamma \,\vdash \,e : U \Rightarrow T\) and \(\Gamma \,\vdash \,s : U\), then \(\Gamma \,\vdash \,e[\ s\ ] : T\).
\(\Gamma \,\vdash \,\sigma [\ t\ ] :T\) implies that for some \(\Delta ,\) we have \(\Gamma \,\vdash \,\sigma \sim \Delta\) and \(\Gamma ,\Delta \,\vdash \,t :T\).
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.
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.
(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 }\).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
A
The mapped function
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
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
However, this mechanism fails if the instance of
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
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
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
Contrast this with some of the same methods for iterators:
Here, methods
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.
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
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
This looks suspicious since
This invocation clearly executes an effect on the formal parameter
Boxing the bound of
Now any attempt to invoke
Indeed,
because the unbox operation in the lambda’s body charges the closure with the capture set
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
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
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.
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.
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\ ]\ ])\):
scope forms in \(\sigma [\ e[\ t\ ]\ ]\) only occur in \(e.\)
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.
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.
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.
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*}\)
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
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.
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
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\).
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.
\(\Gamma \,\vdash \,e[\ s\ ] : T\) implies that for some U we have \(\Gamma \vdash e : U \Rightarrow T\) and \(\Gamma \vdash s : U\).
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).□
If both \(\Gamma \,\vdash \,e : U \Rightarrow T\) and \(\Gamma \,\vdash \,s : U\), then \(\Gamma \,\vdash \,e[\ s\ ] : T\).
If \(\Gamma \,\vdash \,\sigma \sim \Delta\) and \(\Gamma ,\Delta \,\vdash \,t :T,\) then \(\Gamma \,\vdash \,\sigma [\ t\ ] :T\).
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.
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\).
A.3 Properties of Subcapturing
Let \(\Gamma \vdash C \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\). Then, \(\Gamma \vdash C \lt : \lbrace {\bf cap}\rbrace\).
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.□
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\).
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).□
Let \(\Gamma \vdash C \lt : D\). If \({\bf cap}\in C\), then \({\bf cap}\in D\).
By induction on subcapturing. Case (sc-elem) immediate, case (sc-set) by repeated IH, case (sc-var) contradictory.□
Let \(\Gamma \,\vdash \,C \lt :D\). Then for all \(x \in C,\) we have \(\Gamma \,\vdash \,\lbrace x\rbrace \lt :D\).
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.□
If \(\Gamma \,\vdash \,C_1 \lt :C_2\) and \(\Gamma \,\vdash \,C_2 \lt :C_3,\) then \(\Gamma \,\vdash \,C_1 \lt :C_3\).
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).□
If \(\Gamma \,\vdash \,C \;\mbox{$\operatorname{{\bf \textsf {wf}}}$}\), then \(\Gamma \,\vdash \,C \lt :C\).
By (sc-set) and (sc-elem).□
If \(\Gamma \,\vdash \,R_1 \mathrel {\wedge }C_1 \lt :R_2 \mathrel {\wedge }C_2\), then \(\Gamma \,\vdash \,C_1 \lt :C_2\).
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.
Both subtyping and subcapturing are transitive.
Subtyping is intrisically transitive through (trans), whereas subcapturing admits transitivity as per Lemma A.12.□
Both subtyping and subcapturing are reflexive.
Again, this is an intrinsic property of subtyping by (refl) and an admissible property of subcapturing per Lemma A.13.□
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\).
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.□
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\).
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.□
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\).
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.□
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\).
Analogous to the proof of Lemma A.19.□
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\).
Analogous to the proof of Lemma A.19.□
A.3.2 Permutation, Weakening, Narrowing.
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:
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.]
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:
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.□
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).□
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.□
A.4 Substitution
A.4.1 Term Substitution.
We will make use of the following fact.
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)\).
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\).
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.□
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\).
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).□
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\).
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.
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\).
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 }\).□
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\).
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).□
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\).
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. □
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.
If \(\Gamma \,\vdash \,v :T\), then T is not of the form \(X \mathrel {\wedge }C\).
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.□
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\).
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.□
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\).
Analogous to the proof of Lemma A.34.□
If \(\Gamma \,\vdash \,v :(\Box T) \mathrel {\wedge }C\), then \(v = \Box x\) and \(\Gamma \,\vdash \,x :T\).
Analogous to the proof of Lemma A.34.□
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 }\).
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. □
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\).
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.□
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\).
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\).
A.5.2 Soundness.
In this section, we show the classical soundness theorems.
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\).
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.□
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 }\).
Every term has a corresponding proper configuration, and finding it is decidable.
If \(\Gamma ,\Delta \,\vdash \,x :T\) and \(\Gamma \,\vdash \,\sigma \sim \Delta\) and \(x \in \operatorname{dom} (\Delta)\), then \(\sigma (x) = v\).
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.□
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 }\ ]\).
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.
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.
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\).
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.□
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\).
By preservation, \(\,\vdash \,\sigma ^{\prime }[\ a\ ] :S \mathrel {\wedge }C\), which lets us conclude by Lemma A.47.□
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}\).
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.
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.
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.
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.
\(\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.
If \(\Gamma\) is a platform environment and \(\Gamma \vdash C \lt : D\), then either \(C \subseteq D\) or \({\bf cap}\in D\).
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\).□
If \(\Gamma ,\Gamma ^{\prime } \vdash C \lt : D\) and \(C \subseteq \operatorname{dom} (\Gamma)\), then we must have \(\Gamma \vdash C \lt : D\).
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.
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)\).
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.
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)\).
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 }\).□
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*}\)
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.□
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*}\)
By the IH, single-step program trace prediction and authority preservation.□
A.7 Avoidance
Here, we restate Lemma 3.3 and prove it.
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\).
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.
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 }\).
Analogous to the proof of Lemma A.19.□
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 }\).
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 }\).
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.
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. □
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 }\).
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.
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).□
B.1 Predicting Used Capabilities
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*}\)
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. □
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*}\)
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.□
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*}\)
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 Scala 3.1 with language import
FootnotesaferExceptions enabled.2 This class is covariant in both A and B, as denoted by the pluses.
Footnote3 This follows since type variables range over pure types, so
Footnotecap 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 forcap .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.
Footnote5 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.
Footnote6 For simplicity, this example is neither thread nor exception safe.
Footnote
- 2016. The essence of dependent object types. In A List of Successes That Can Change the World. Springer, 249–272. Google ScholarCross Ref .
- 2021. Reachability types: Tracking aliasing and separation in higher-order functional programs. Proceedings of the ACM on Programming Languages 5, OOPSLA (2021), 1–32. Google ScholarDigital Library .
- 1996. Uniqueness typing for functional languages with graph rewriting semantics. Mathematical Structures in Computer Science 6, 6 (
Dec. 1996), 579–612. Google ScholarCross Ref . - 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 ScholarDigital Library .
- 1985. Automatic synthesis of typed \(\lambda\)-programs on term algebras. Theoretical Computer Science 39 (1985), 135–154. Google ScholarCross Ref .
- 2021. Tracking captured variables in types. arXiv:2105.11896 [cs] (2021).Google Scholar .
- 2001. Capabilities for sharing. In ECOOP 2001—Object-Oriented Programming, (Ed.). Springer, Berlin, Germany, 2–27. Google Scholar .
- 2010. Modules as objects in Newspeak. In ECOOP 2010—Object-Oriented Programming. Lecture Notes in Computer Science, Vol. 6183. Springer, 405–428. Google ScholarCross Ref .
- 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 ScholarDigital Library .
- 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 ScholarDigital Library .
- 2020b. Effekt: Capability-passing style for type- and effect-safe, extensible effect handlers in Scala. Journal of Functional Programming 30 (2020), e8. Google ScholarCross Ref .
- 2020. Recovering purity with comonads and capabilities. Proceedings of the ACM on Programming Languages 4, ICFP (Aug. 2020), Article 111, 28 pages. Google ScholarDigital Library .
- 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, 48–64. Google ScholarDigital Library .
- 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, 557–572.Google Scholar .
- 2018. Capabilities: Effects for free. In Formal Methods and Software Engineering. Lecture Notes in Computer Science, Vol. 11232. Springer, 231–247. Google ScholarCross Ref .
- 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, 262–275. Google ScholarDigital Library .
- 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, 1–6. Google ScholarDigital Library .
- 2016. Effect capabilities for Haskell: Taming effect interference in monadic programming. Science of Computer Programming 119 (
April 2016), 3–30. Google ScholarDigital Library . - 2023. A Mechanized Theory of the Box Calculus.
Technical Report . Infoscience. http://infoscience.epfl.ch/record/302949Google Scholar . - 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, and (Eds.). Schloss Dagstuhl–Leibniz-Zentrum für Informatik, Dagstuhl, Germany, Article 10, 25 pages. Google ScholarCross Ref .
- 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, 282–293. Google ScholarDigital Library .
- 1998. A type-based escape analysis for functional languages. Journal of Functional Programming 8, 3 (
May 1998), 239–273.Google ScholarDigital Library . - 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, 458–471. Google ScholarDigital Library .
- 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, 227–238. Google ScholarDigital Library .
- 2014. Koka: Programming with row polymorphic effect types. Electronic Proceedings in Theoretical Computer Science 153 (
June 2014), 100–126.arxiv:1406.2061 Google ScholarCross Ref . - 2017. Type directed compilation of row-typed algebraic effects. In Proceedings of the Symposium on Principles of Programming Languages. ACM, New York, NY, 486–499. Google ScholarDigital Library .
- 2017. Do be do be do. In Proceedings of the 44th ACM SIGPLANSymposium on Principles of Programming Languages (POPL’17). ACM, New York, NY, 500–514. Google ScholarDigital Library .
- 2016. A Study of Capability-Based Effect Systems. Master’s Thesis. Infoscience. infoscience.epfl.ch/record/219173Google Scholar .
- 1988. Polymorphic effect systems. In Proceedings of the 15th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (POPL ’88). ACM, New York, NY, 47–57. Google ScholarDigital Library .
- 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, 39–50. Google ScholarDigital Library .
- 2020. Controlling Module Authority Using Programming Language Design. Ph.D. Dissertation. Carnegie Mellon University.Google Scholar .
- 2017. A capability-based module system for authority control. In Proceedings of the 31st European Conference on Object-Oriented Programming (ECOOP’17).Google Scholar .
- 2006. Robust Composition: Towards a Unified Approach to Access Control and Concurrency Control. Ph.D. Dissertation. Johns Hopkins University.Google ScholarDigital Library .
- 2008. Contextual modal type theory. ACM Transactions on Computational Logic 9, 3 (June 2008), Article 23, 49 pages. Google ScholarDigital Library .
- 1998. Flexible alias protection. In ECOOP’98—Object-Oriented Programming. Lecture Notes in Computer Science, Vol. 1445. Springer, 158–185. Google ScholarCross Ref .
- 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 ScholarDigital Library .
- 2021. Safer exceptions for Scala. In Proceedings of the Scala Symposium. Google ScholarDigital Library .
- 2020. Extension Methods: Scala 3 Language Reference Page. Retrieved September 19, 2023 from https://dotty.epfl.ch/docs/reference/contextual/extension-methods.htmlGoogle Scholar .
- 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 ScholarDigital Library .
- 2014. Coeffects: A calculus of context-dependent computation. In Proceedings of the International Conference on Functional Programming (Gothenburg, Sweden). ACM, New York, NY, 123–135. Google ScholarDigital Library .
- 2002. Types and Programming Languages. MIT Press, Cambridge, MA.Google ScholarDigital Library .
- 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, 624–641. Google ScholarDigital Library .
- 2012. Lightweight polymorphic effects. In ECOOP 2012—Object-Oriented Programming. Lecture Notes in Computer Science, Vol. 7313. Springer, 258–282. Google ScholarDigital Library .
- 1993. Reasoning about programs in continuation-passing style. In LISP and Symbolic Computation6 (1993), 289–360.Google Scholar .
- 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 Scholar .
- 2022b. Scala 3: Capture Checking. Retrieved September 19, 2023 from https://dotty.epfl.ch/docs/reference/experimental/cc.htmlGoogle Scholar .
- 2022c. The Scala 3 Compiler, Also Known as Dotty. Retrieved September 19, 2023 from https://dotty.epfl.chGoogle Scholar .
- 2013. Tracking data-flow with open closure types. In Proceedings of the International Conference on Logic for Programming Artificial Intelligence and Reasoning. 710–726. Google ScholarCross Ref .
- 2012. Effects for funargs. CoRR abs/1201.0023 (2012). http://arxiv.org/abs/1201.0023Google Scholar .
- 1997. Region-based memory management. Information and Computation 132, 2 (
Feb. 1997), 109–176. Google ScholarDigital Library . - 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, and (Eds.). North-Holland, 561.Google Scholar .
- 2023. Formalizing Box Inference for Capture Calculus.
Technical Report . EPFL.Google Scholar . - 2019. Abstraction-safe effect handlers via tunneling. Proceedings of the ACM on Programming Languages 3, POPL (Jan. 2019), Article 5, 29 pages. Google ScholarDigital Library .
Index Terms
- Capturing Types
Recommendations
Cayenne—a language with dependent types
Cayenne is a Haskell-like language. The main difference between Haskell and Cayenne is that Cayenne has dependent types, i.e., the result type of a function may depend on the argument value, and types of record components (which can be types or values) ...
Path dependent types with path-equality
Scala 2018: Proceedings of the 9th ACM SIGPLAN International Symposium on ScalaWhile the Scala type system provides expressive features like objects with type members, the lack of equality checking between path-dependent types prohibits some programming idioms. One such an example is abstract domain combinators in implementing ...
Implementing higher-kinded types in Dotty
SCALA 2016: Proceedings of the 2016 7th ACM SIGPLAN Symposium on Scaladotty is a new, experimental Scala compiler based on DOT, the calculus of Dependent Object Types. Higher-kinded types are a natural extension of first-order lambda calculus, and have been a core construct of Haskell and Scala. As long as such types are ...
Comments