Decidable Subtyping of Existential Types for Julia

Julia is a modern scientific-computing language that relies on multiple dispatch to implement generic libraries. While the language does not have a static type system, method declarations are decorated with expressive type annotations to determine when they are applicable. To find applicable methods, the implementation uses subtyping at run-time. We show that Julia's subtyping is undecidable, and we propose a restriction on types to recover decidability by stratifying types into method signatures over value types---where the former can freely use bounded existential types but the latter are restricted to use-site variance. A corpus analysis suggests that nearly all Julia programs written in practice already conform to this restriction.


INTRODUCTION
Julia is a scientific-computing language carefully designed so that an implementation can generate efficient code for performance-critical abstractions.The central abstraction mechanism offered by the language is multiple dispatch with an expressive type-annotation language and a complex subtype relation.Multiple dispatch is a mechanism dating back to Lisp [Bobrow et al. 1986], which allows generic functions to have multiple implementations, called methods, tailored to different argument types.The following code snippet illustrates the expressive power of Julia's type-annotation language, in this case defining the binary "-" operator for various types: § ¤ -(x::BigInt, y::BigInt) = ... -(x::T, y::T) where T <: Union{Int16, Int32} = ... -(m::Missing, n::Number) = ... -(A::AbstractArray{T,N}) where {T,N} = ...

¦ ¥
Julia has both nominal type constructors, such as Number and AbstractArray{T,N}, and a variety of structural types.Any is a supertype of all types.Finite unions of types are written Union{Int16, Int32}.Tuple types such as Tuple{String, Number} are covariant in their element types.Finally, bounded existential types, called union-all in Julia, are written t wherel<:T<:u, and represent the union of types t[t'/T] for all valid instantiations l<:t'<:u of the type variable T.
While it has a type-annotation language, Julia does not have a static type system.Thus, subtyping is only used at run-time, in particular, to find an applicable method for each function call.The reference definition of subtyping lies in a highly optimized, and evolving, C implementation.Zappa Nardelli et al. [2018] give a faithful account; the departures from the implementation are believed to be bugs in the C code.Chung et al. [2019] argue that the complexity of the implementation comes in equal parts from the type language's expressive power and from efficiency concerns.
As we will demonstrate, Julia subtyping is undecidable.When a language has an undecidable static type system, compile-time errors can typically be fixed by adding annotations to the program as needed.For Julia, incomplete subtyping can incorrectly change the execution of a program, either during dynamic dispatch, or when adding new method definitions, or when generating code.When subtyping fails to terminate, the implementation raises an exception with little insight as to how to fix the offending source code.The subtyping algorithm is still under development, as these issues suggest: #41948 (a StackOverflowError caused by a function definition), #33137 (a problem with Julia's "diagonal rule"); #24166 (a problem with reflexivity and transitivity); #39099 (a problem with transitivity of variadic tuple arguments).
Our goal is to develop a clear formalization of the intended subtype relation-one that can be understood and adopted by programmers, and one for which an algorithm can be proved sound and complete.The breadth of Julia's type features makes this difficult to do all at once, so in this paper we focus on bounded existential types.Fig. 1 is a roadmap for this paper.We start with the implementation of subtyping, <∶ Julia , used in practice.The 2018 paper gave a formal definition of that relation, <∶ '18 , with some small differences (e.g. it ignored variadic tuples and singleton types).To validate <∶ '18 , the authors performed extensive testing of both definitions.The handful of differences between them were either ascribed to bugs-some of which were fixed by the Julia team-or to undefined behavior.Much of the effort was in establishing a better understanding of Julia's diagonal rule, which restricts how existential types can be instantiated in certain situations that were only informally described.While interesting, the feature is a layer on top of existential types, which we show here are challenging enough on their own.One of our contributions is that, by ignoring the diagonal rule in particular, we demonstrate that one can recover a much simpler and more familiar declarative formalization of Julia's subtyping.The language intends its types to approximate sets and its subtyping to approximate set inclusion, a transitive relation.Therefore, the declarative formalization, <∶ rule, that subtyping is transitive.While this trivially captures the intended behavior, it also makes reasoning about subtyping difficult.
1 Thus, our second contribution is to establish an invertible formalization of subtyping, <∶ ji , where only one rule can act on a given side at a given time.
Invertible subtyping is proved equivalent to the declarative formalization of subtyping when restricted to conservative types, meaning types whose quantified type variables' lower bounds are subtypes of their corresponding upper bounds.At this point, it is rather easy to show that subtyping is undecidable.More specifically, we prove the undecidability of invertible subtyping between conservative types.Our proof proceeds by reduction of subtyping of one of Pierce [1992]'s deterministic fragments of System  ≤ to invertible subtyping.We do this by translating   ≤ types to conservative types, and by showing   ≤ subtyping holds if and only if their translations are invertible supertypes (where the only-if direction relies critically on invertibility of the rules).The key insight is this flipping of subtyping into supertyping and, likewise, flipping upper-bounded universal quantification in   ≤ to lower-bounded existential quantification in Julia.To find a decidable yet practical fragment of Julia types, we conduct an empirical study, demonstrating that the types actually written by users are stratified.In particular, method type annotations can be stratified as method signatures that predicatively quantify over non-quantifying value types 2 with use-site variance, which Julia already has a shorthand for (e.g.Vector{<:Number}).The key observation is that use-site variance is the only employed application of impredicative existential quantification where an existential type variable is instantiated with an existential type.One nice property of the stratification is that, aside from checking conservativity of bounds, it is syntactic.Our corpus analysis of all the source code of 9,335 Julia packages finds only a handful of stratification violations in 16.5 million lines of code.We conclude with the definition of algorithmic subtyping, <∶ sa , which we prove specifies a sound and complete algorithm for subtyping between conservative stratified types.The key insight is that stratification ensures that only one of the two types being compared contains flexible variables, i.e. existentially quantified variables that need to be instantiated, preventing major complications like recursive constraints.Combined with the above contributions, this provides the foundation for a sound and complete algorithm, upon which future work can expand to encompass the full feature set.Belyakova [2023] in her thesis discusses many of the missing features in the context of a restricted subtype relation that is shown to be decidable but not complete.The step-wise approach presented here is key to be able to prove that important property.

BACKGROUND ON JULIA
Julia is a high-level, dynamically typed programming language-originally designed for scientific computing-that addresses the, so-called, "two-language problem" by providing both productivity features and performance [Bezanson et al. 2018[Bezanson et al. , 2017]].For productivity, the language provides garbage collection, dynamic typing, and multiple dispatch-resolved at run-time using subtyping.For performance, it relies on an optimizing compiler that specializes multiple dispatches to direct calls [Pelenitsyn et al. 2021].Subtyping largely follows the combination of nominal subtyping for user-defined nominal types and semantic subtyping3 for covariant tuples, unions, and bounded existential types.While the language has no formal definition of its subtyping algorithm, Zappa Nardelli et al. [2018] attempted to reverse-engineer an algorithmic definition and test it empirically.That definition is mostly accurate, with most observed differences due to bugs in Julia.

Fig. 2. Datatype declarations
Nominal types are induced by user-defined datatype declarations and constitute a single-parent inheritance hierarchy.Abstract types can be inherited from, and concrete types can be instantiated.Fig. 2 illustrates a definition of several datatypes.Both Real and Ref{t} are abstract; the remaining are concrete.Parametric types can have non-recursive lower and upper bounds on type variables, and they are invariant with respect to their type parameters.Thus, Ref{t1} is a subtype of Ref{t2} only if the arguments are equivalent.Tuples are immutable, and their type parameters are covariant.Type Union{t . . .} describes a union (not sum) of types.For instance, Real is a subtype of Union{ Number,String}, and Union{t1,t2} is a subtype of t if all components are subtypes: t1<:t and t2<:t.Following the semantic-subtyping mindset, tuples distribute over unions.For example, Tuple{String,Union{Int32,Int64}} represents binary tuples where the first component is a string, and the second is either a 32-or 64-bit integer.Due to distributivity, this type is equivalent to Union{Tuple{String,Int32},Tuple{String,Int64}}.Bounded existential types, called unionall in Julia, have the form t where l<:T<:u, where the lower and upper bounds can be omitted and default to the bottom type-Union{}-and top type-Any-respectively.Existentials can model Java wildcards but are more expressive.Intuitively, they denote a union of t[t'/T] for all instantiations of the type variable T such that l<:t'<:u.Similarly to subtyping of union types, the intent is that • (t where l<:T<:u)<:t2 if t[t'/T]<:t2 for all valid instantiations t' of T, • t1<:(t where l<:T<:u) if there exists a valid instantiation t' with t1<:t[t'/T].For example, Vector{Int32} is a subtype of Vector{T} whereT<:Number because T can be instantiated with Int32, and Vector{T} whereT<:Number is a subtype of Vector{S} whereS because for all valid instantiations t' of T, type variable S can be instantiated with the type t'.Tuples distribute over existential types; types Tuple{Vector{T} whereT} and Tuple{Vector{T}} whereT are equivalent.
Existential types are impredicative: quantifiers can appear anywhere in a type, and type variables can be instantiated with any type.Type Vector{Ref{T} whereT} denotes a heterogeneous vector of references, whereas Vector{Ref{S}} whereS denotes a union of homogeneous vectors of references.Thus, a vector containing integer references, Vector{Ref{Int32}}, is a subtype of the latter but not the former as the type arguments Ref{Int32} and Ref{T} whereT are not equivalent; in particular, a Ref{String} could be put into a Vector{Ref{T} whereT} but not into a Vector{Ref{Int32}}.Top-level existential types appear in signatures of polymorphic method definitions.Recall that types are only used for multiple dispatch.To call a method, one provides arguments that inhabit the corresponding existential type.In the method body, the existential is implicitly unpacked, with the witness type being some valid instantiation induced by subtyping.Consider a function f(v::Vector{T}) whereT: its signature is Tuple{Vector{T}} whereT.For a call such as f ([5,7,5]), represented with the type Tuple{Vector{Int32}}, dispatch resolution relies on tuple subtyping-which succeeds in this case-and instantiates T with Int32.

SPECIFICATION OF SUBTYPING
As there is no official definition of subtyping, we face the problem of choosing a baseline in our exploration of decidability of subtyping.One option is to use Zappa Nardelli et al. [2018], as it matches the implementation, but the complexity of their rules is daunting.Furthermore, being accurate to the implementation is not necessarily what one wants; the implementation has bugs, so it can be unclear whether accepting a program is intentional or accidental.Thus, we formalize declarative subtyping based on the intuitions provided by the Julia documentation.To keep the paper focused, we omit features not relevant to undecidability, such as distributivity of tuples, the diagonal rule, nominal inheritance, variadic tuples, "plain bits" values, and singleton types.Our paper lays the foundations that such extensions can be built upon and establishes a practical means to address the key source of undecidability.For the remainder of the paper, we depart from the Julia type syntax and adopt a more standard notation.The type grammar is given by Fig. 3.The shorthand C is for nullary datatypes.Datatype declarations are implicit and do not restrict type parameters.A kind context is denoted Σ and is a, possibly empty, sequence of type variables with explicit bounds  ℓ <∶X<∶  .As a shorthand, we omit ⊥ lower bounds and ⊤ upper bounds.

Julia syntax
Julia has a notion of type validity: it rejects types with unbound type variables.However, this notion is too permissive, as it allows a type variable to have a lower bound that is not a subtype of its upper bound; we call such bounds nonconservative.This causes algorithmic problems and is an unnecessary degree of freedom; our corpus analysis reveals that nonconservative bounds are not used in practice.Thus, we consider only types that are well-scoped and conservative.Fig. 4 formalizes type validity, which is parameterized by the subtype relation that enforces conservativity.

Declarative Subtyping
Our declarative formalization of subtyping is given in Fig. 5.It is parameterized by a validity predicate and includes explicit rules for reflexivity and transitivity.The latter make subtyping reflexive and transitive by definition.Each of the remaining rules is standard for the relevant feature.Observe , one would often like to deduce that  1 is a subtype of  ′ 1 and that  2 is a subtype of  ′ 2 , conceptually inverting the rule for covariant tuples.However, this is not necessarily true.The kind context could contain a bounded variable , and transitivity could use X as its intermediate type without ever connecting the respective projections.So, without conservativity of bounds, transitivity makes such desirable subtyping inversions impossible.To this end, we restrict the type system to conservative types.With this restriction, our problematic bounded variable From this proof of conservativity, one then hopes to extract the expected subtypings between the respective projections.Indeed, we prove that, when restricted to conservative types and kind contexts, declarative subtyping is equivalent to the invertible subtype relation Σ ⊢  <∶ ji , formalized in Fig. 6, which does not contain an explicit transitivity rule.Theorem 3.1.For any kind context Σ satisfying ⊢ ji Σ, and for any pair of types  and  ′ satisfying Σ ⊢ ji  and Σ ⊢ ji  ′ , the following equivalence holds: The leftward implication is trivially proven by induction on Σ ⊢  <∶ ji  ′ , with applications of reflexivity and transitivity rules of <∶ c jd .The bulk of the proof by far is in the rightward implication.First, we observe that, if its bounds are conservative, an existential type ∃ ℓ <∶X<∶  . is equivalent to the type ∃⊥<∶X Then, for the simpler type space with only upper bounds, we define transitivity-eliminating reductions and show that repeatedly applying them necessarily terminates.In particular, we translate the system to Girard's cut-elimination reductions for proof nets of second-order classical linear logic (PN2) [Girard 1987 Invertible subtyping is derived from declarative subtyping by baking transitivity into each rule directly.It allows for an easy use of inversion: when given a subtype relation Σ ⊢  <∶ ji  ′ , by design only one rule can "act on" a given side.For example, only the tuple rule is applicable to the subtyping guaranteeing the respective projections are necessarily subtypes, just as one would hope for.For other features formalized using left and right rules, one can often use induction to dig up an application of the desired one-sided rule.For example, one can easily prove that a union is a subtype of another type only if its components are.However, not all inversions are always possible; for example, a subtype of a union is not necessarily a subtype of either component of the union.Similarly, invertible subtyping is not quite syntax-directed; in particular, the right rule for existentials can apply in an infinite number of ways depending on the instantiating types.So, ⊢ Int32 <∶ ji ∃X.X can be shown by instantiating X with Int32, or with Int32 ∪ Int64, or with many other types.Nonetheless, invertible subtyping is easier to reason about than declarative subtyping.The equivalence is valuable for both proving undecidability and establishing completeness of our algorithm on restricted types.

UNDECIDABILITY OF JULIA SUBTYPING
This section provides a proof of undecidability of Julia subtyping.Consider this code: This valid Julia program causes a StackOverflowError when executed.A method with a parameter of type Ref{<:Theta} would similarly fail if called with a value of type Ref{Kappa(Theta)}.Furthermore, adding a second method with Ref{Kappa(Theta)} as parameter would also overflow, as Julia tries to prioritize method definitions by subtyping.The code fragment is a contravariant translation of Curien and Ghelli [1990]'s gadget for non-termination in  ≤ .This singular example does not prove undecidability, but our translation does.Our proof proceeds by showing that System   ≤ 's subtyping is equivalent under a translation into our subtyping calculus, thereby demonstrating undecidability by reduction.

System 𝐹 𝑃 ≤
We remove arrow types from  ≤ to form the reduced   ≤ as described by Pierce [1992] Fig. 8 gives the subtyping rules of   ≤ .As in this paper, the rules treat -equivalent types as implicitly equivalent.As such, the rule   ≤ -All does not require the two types to use the same variable name; if the variable names are different, they are implicitly -renamed.
to Julia For this reduction, we shall use the invertible subtype relation of Fig. 6 and show that there exists a suitable contravariant translation ⟦⟧ of   ≤ types and environments.Our translation is defined in Fig. 9.We use the nominal type constructors Neg⟨⟩ and All⟨, ⟩ to create invariant contexts that force type equivalence.For simplicity, we treat   ≤ type variables as Julia type variables.
The key insight of the translation is that subtyping of universally quantified types is dual to subtyping of existentially quantified types.The translation flips an , replacing upper-bounded universal quantification with lower-bounded existential quantification.The translation targets invertible subtyping, rather than declarative subtyping.We made this choice since, for the translation to transfer undecidability, we need both that translation preserves subtyping ( . Showing that the translation reflects subtyping relies heavily on invertibility.This then leads us into the proof of undecidability.Theorem 4.1 (Undecidability of Subtyping).For any valid kind contexts Γ − and any pair of negative type  − and positive type  + valid in Γ − , the following equivalence holds: Because the former is undecidable, this implies that the latter is also undecidable.[1992], we can conclude that our subset of Julia's subtype relation is undecidable.Chung [2023] generalizes undecidability onto the broader Julia subtype relation when considering the other subtyping features as described by Zappa Nardelli et al. [2018].

STRATIFYING EXISTENTIAL TYPES FOR JULIA-IN-PRACTICE
We identify a subset of Julia types within which subtyping is decidable and to which existing programs already conform.In particular, one can stratify types into method signatures over value types, where quantification in method signatures can only use value types for bounds, and quantification within value types is restricted to use-site variance.In the next section, we demonstrate that this stratification indeed makes subtyping decidable.We start with an empirical study of Julia programs to show they already conform to this stratification. 4Technically, invertibility only directly implies the instantiations are equivalent to the corresponding arguments.However, one can extend the intermediate type and proof system and the reduction process employed in the proof of Th. 3.1 to furthermore eliminate such equivalences and ensure direct instantiations.This extension adds a specialized form of existential quantification for precisely the above pattern, with its right rule restricted in the desired manner.Due to invariance of nominal constructors, transitivity-elimination reduction can be extended to accommodate this restriction while staying in line with PN2.And because  and  ′ have no lower bounds, there is no need to replace them with a union to integrate conservativity.Altogether, this means we can get a proof in the desired form for induction.

Stratified Types
The stratified grammar is defined in Fig. 10.A method signature,  , is a union of existential quantifications of lists of value types as its method parameters.Thus,  can represent generic method signatures for the purpose of multiple dispatch.A value type, , is limited to use-site variance, rather than full existential quantification, but can use all other type constructors freely.A type argument  ℓ ≪  for a nominal constructor indicates that the run-time argument must be a supertype of  ℓ and a subtype of   .When a type argument is of the form ≪, we abbreviate it as .Upper and lower bounds, for both existentially quantified variables and type arguments, are always value types.
Julia already provides syntactic support for use-site variance.For the type Vector{T} where T, one can instead simply write Vector.Similarly, one can write types Vector{<:Number} and Vector{>:Int32} for, respectively, Vector{T} whereT<:Number and Vector{T} whereT>:Int32.
Crucially for the decidability of subtyping, our restriction rules out types where existential types inside invariant constructors do not match use-site variance, e.g., Ref{Pair{T, T} where T}.In particular, the Theta6 type from Ghelli's looping gadget is not expressible as a stratified type because, in the existential Kappa(Z)7 , variable Y occurs twice under the invariant Pair constructor.
Thus, Kappa(Z) (and, consequently, Neg(Kappa(Z))) is not encodable as use-site variance and does not constitute a value type, so it is not allowed as a bound in Theta.
In addition to syntactic stratification, we also require stratified types and kind contexts to be conservative, where lower bounds must be subtypes of upper bounds (for both variables and type arguments of datatypes).The validity judgments Θ ⊢   , Θ ⊢   , and ⊢  Θ are all defined in the obvious manner, parameterized by a subtype relation on just value types (since method signatures do not occur in bounds).
There is an obvious syntactic definition for when an existential type corresponds to a nominal constructor with use-site variance (for example, ∃⊥<∶X<∶⊤.Ref⟨X⟩ is Ref⟨⊥≪⊤⟩).This then extends to a syntactic correspondence between Julia types and value types, and then between Julia types and method signatures as well.Although, technically, multiple Julia types can correspond to a given stratified type, all of these types are trivially equivalent; so, as an abuse of notation, we treat stratified types as a subset of Julia types.We say a type conforms to stratification if there exists a stratified type that it corresponds to.We say a type conforms to stratification up to equivalence if it is equivalent-according to Julia's entire feature set-to some type that conforms to stratification.In particular, one can use distributivity to either pull a nested existential up to the top level or to push an existential down inside a tuple so that it exemplifies use-site variance.This is why method signatures have unions, whereas in practice just the method parameters would have unions that would be automatically distributed up to the signature level.We discuss such equivalences in more detail below.

Empirical Evaluation
To estimate the potential impact of imposing this stratification on existing programs, we conducted a corpus analysis over nearly all of the packages listed in the official Julia registry.Out of more than 2 million type annotations, only 4 do not conform to stratification up to equivalence.All type annotations have conservative bounds.

Methodology.
The corpus used in our analysis is the entire General Registry, which is the default source of packages used by Julia programs.The list of packages, obtained from JuliaHub, contained 9,383 entries as of 2023-05-20.Out of those, 9,335 packages were successfully downloaded.Some entries were not valid registered packages, or were duplicates, or were no longer publicly available.The resulting corpus has 172K files with 16.5M lines of code as reported by CLOC 1.9.0.Our analysis code is written in Julia 1.8.5.It extracts type annotations from all .jlfiles in the corpus and reports annotations that do not conform to stratification up to equivalence.The extraction uses the Julia parser and the MacroTools.jlpackage for convenient pattern matching over abstract syntax trees.Source code and the results of the analysis are publicly available.8 5.2.2 Results.Some types do not directly conform to stratification, but they do up to equivalence.Such types are not flagged by the analysis because their equivalent rewriting could be automated.At the method-signature level, if a method parameter has an existential at a distributive location, we first distribute the existential up to the signature level.Then, at the value-type level, we employ equivalences from the following two categories: • An existential variable essentially encoding use-site variance but separated from its binding by distributive constructors.Some examples are Tuple{Vector{T}} whereT and Union{ Vector{T}, Missing} whereT.These types are equivalent to the stratified types Tuple{ Vector{T} whereT} and Union{Vector{T} whereT, Missing}.• An existential variable used completely unnecessarily as a single component of a tuple.For example, Tuple{T} whereT<:u is equivalent to Tuple{u}.This category is already automatically rewritten by Julia into the equivalent existential-free form.The analysis was run on our corpus.One package failed to process; we manually confirmed its types conform to stratification up to equivalence.There were 206 packages with at least one file failing to parse; such files were ignored.In the remaining files, the analysis identified a total of 2,283,011 type annotations.Out of these, 1,887 were not processed because a type-variable binding contained a macro or quoted expression, and 26,385 were partially processed due to a macro or quoted expression in a part of the type.Of the 2,281,124 fully-or-partially analyzable type annotations, 2,281,117 were identified as conforming to stratification up to equivalence, and 7 annotations were flagged as potentially problematic.Three of these seven annotations were false positives related to Vararg.Variadic arguments are represented as Vararg in Tuple types.For example, Tuple{Vararg{Int32}} stands for a tuple of arbitrarily many integers.According to Julia subtyping, Vararg is covariant in its type argument, whereas the analysis reported it as if it were invariant.
Of the remaining four type annotations, two are instances of the same non-conforming type that can be rewritten into a semantically equivalent type that conforms to stratification.The original type is the following from package Muon.jl: This type describes dictionaries of arbitrary key type whose elements are either arrays of numbers, arrays of elements that are either of homogeneous numeric type or missing, or an arbitrary data frame.This type has the following semantically equivalent conforming type, but the equivalence is not derivable according to Julia subtyping: The other two remaining types do not conform to stratification even up to semantic equivalence.The first type is the following from package Alicorn.jl: Here we have an existential quantifier inside a nominal constructor, and its variable occurs more than once so that it expresses more than just use-site variance.This type requires that the array contains tuples where the type of the first projection matches the second projection's element type.The second type is the following from package UnitfulEquivalences.jl: Here T and U, used by Quantity, are quantified outside of the containing Level.
To check for conservative bounds, we extracted all type annotations that explicitly declare both a lower and an upper bound on at least one variable.There were only 9 such annotations, all of which we inspected manually and found to be conservative.
In sum, our analysis shows that the types programmers write conform to our proposed stratification, or do so at least up to equivalences that are easy to recognize.

Stratified Subtyping
With our stratification empirically justified, we proceed to define invertible subtyping on stratified types, which we use as a bridge to invertible subtyping on arbitrary types.Fig. 11 gives the rules for method signatures, and Fig. 12 gives the rules for value types.Subtyping itself has become stratified; not only is method-signature subtyping layered over value-type subtyping, but for method signatures, the left rules are layered over the right rules, taking advantage of the stratification boundary to signal when to move from left to right.The right existential rule now only permits instantiation with value types , which we prove is faithful to subtyping between the corresponding  conservative stratified types is reflexive and transitive, which will be useful for proving that our algorithm is complete.

DECIDING SUBTYPING FOR STRATIFIED EXISTENTIAL TYPES
At last, we present our algorithm for deciding subtyping of stratified types.Invertible subtyping brought us substantially closer than declarative subtyping by limiting the rules that can apply to any pair of types.Stratification brought us closer still by making recursive search terminating, as we illustrate next.The final step, addressed here, is to deal with the fact that certain rule applications still involve significant choices.In particular, the right existential rule for method signatures has to conjure an instantiating type.It is important to understand, though, that the algoritm is not particularly surprising; it is, for the most part, what one would expect for a constraint-collecting algorithm.It also aligns with Julia's implementation, aside from various heuristics, e.g. for picking which path to explore first.What is new is the insight that stratification and conservativity offer guarantees that ensure decidability and completeness.That is, stratification and conservativity explain why Julia's existing algorithm works well in practice.
In the following, we differentiate between rigid type variables, whose bounds are given by the kind context Θ, and flexible variables, on which constraints are collected and eventually solved to produce a corresponding instantiating type.The terminology carries over to types: a rigid (resp.flexible) type is a one that has only rigid (resp.flexible) type variables.

Backtracking Proof Search
Invertible subtyping for value types is decidable.The rules in Fig. 12 prescribe a backtracking proof search algorithm which is trivially sound and complete, provided it terminates.This latter condition is critical.While   ≤ satisfies the requirements for backtracking proof search, the search can fail to terminate.In <∶ ji -subtyping of value types, termination is ensured by a simple decreasing measure.
Consequently, we can simply define algorithmic subtyping on value types Θ ⊢  <∶ sa  as invertible subtyping on value types <∶ si .

Marshalling Type Variables
Because of stratification, complexities typically associated with rigid and flexible variables (such as determining how to solve recursive constraints on flexible variables) are absent from our system.
Recall that stratified subtyping layers the left method-signature rules over the right methodsignature rules, which, in turn, are layered over value-type subtyping.Since the only algorithmic rules that would need to introduce rigid or flexible variables for Julia are, respectively, the left and right existential rules, this means we can introduce all rigid variables, and then all flexible variables, and then proceed to value subtyping wherein neither get introduced.Consequently, only rigid variables occur in the left method signature and only flexible variables occur in the right method signature, which is a valuable property.While contravariance might cause left and right to swap, if we track such directionality, we can know whether a variable is rigid or flexible-and what we should do with it-simply by knowing the correct direction and which side of the subtyping the variable is occurring on.Furthermore, when the variable is flexible and we need to collect the other side as a constraint on that variable, the constraint is known to contain only rigid variables.In particular, this means that the constraint cannot be recursive.Thus, we can exploit stratification to carefully marshal rigid and flexible variables and thereby prevent all major complexities for constraint solving.
Fig. 13 presents the algorithmic subtyping rules for method signatures.The left rules, interpreted using backtracking proof search, introduce rigid variables on the left until reaching method parameters.The right rules are then also interpreted using backtracking proof search, but with the constraint set K as an output of the search.These right rules introduce flexible variables on the right until reaching method parameters, at which point they defer to constraint-collecting value-type subtyping with the direction superscript (→) indicating the flexible variables are on the right.After those constraints are collected, the respective backtracking proof search returns the resulting constraint set.Furthermore, the right existential rules each solve the constraints for the flexible variables they introduced-in reverse order-using the constraint-solving algorithm discussed in Section 6.4.Due to explicit flexible-variable bounds possibly referring to previously introduced flexible variables, constraint resolution can result in more constraints on those previously introduced variables.Theorem 6.2.For any pair of method signatures  and  ′ satisfying ⋅ ⊢ ws  and ⋅ ⊢ ws  ′ , the following is decidable: ⋅ ⊢  <∶ sa  ′ Theorem 6.3.For any pair of method signatures  and  ′ satisfying ⋅ ⊢ sa  and ⋅ ⊢ sa  ′ , the following equivalence holds: After method-signature subtyping takes care of introducing, and later solving, all variables, only value types are needed to determine the constraints flexible variables need to satisfy.The constraint sets K collected during this process are finite sets of constraints of either the form X ≥  ℓ or X ≤   , where X is a flexible variable and  ℓ and   are rigid types.In order to maintain these invariants, constraint-collecting subtyping is directed.A direction  is either ← or →, with the arrow pointing from the side with the rigid type to the side with the flexible type.In the case of contravariance, the direction is reversed, denoted −, in the obvious manner.Using these new concepts, the rules for constraint-collecting algorithmic subtyping for value types are presented in Fig. 14  These rules have a clear correspondence with (non-constraint-collecting) algorithmic (i.e.invertible) subtyping.Most of them simply furthermore propagate the constraints collected from the premises.The only interesting rules are those for variables.If the direction points to the variable, then the corresponding constraint on that necessarily flexible variable is generated-this is the only way in which constraints are introduced.Otherwise, if the direction points away from the variable, the appropriate bound on that necessarily rigid variable is employed.
In order to discuss the properties of constraint collection formally, we need to introduce notions of assignments, substitutions, and satisfaction.An assignment  is a finite partial mapping of type variables X to value types  (X).Assignments extend to substitutions on value types [ ] in the obvious manner.An assignment between kind contexts is one that satisfies ⊢  ∶ Θ  → Θ  if, for each  ℓ <∶X<∶  ∈ Θ  , a corresponding type  (X) exists and is conservative in Θ  (i.e.Θ  ⊢ sa  (X) holds) and lies between its substituted bounds (i.e.Θ  ⊢  ℓ [ ] <∶ sa  (X) and Θ  ⊢  (X) <∶ sa   [ ] hold), where Θ  and Θ  bind flexible and rigid variables, respectively.
A constraint set is valid, Θ  ⊢ K ⊣ Θ  , when the bound variable in each constraint is declared in Θ  and the constraining type in each constraint is rigid (i.e.conservative in Θ  ).An assignment  from Θ  to Θ  satisfies a constraint set, Θ  ⊢ sa  ⊣ K , if for each constraint the value type assigned to the variable by  is an algorithmic supertype/subtype of the constraining type in Θ  .Lemma 6.4.For any kind contexts Θ  and Θ  satisfying ⊢ sa Θ  and ⊢ sa Θ  , for any pair of value types  and  ′ satisfying Θ  ⊢ sa  and Θ  ⊢ sa  ′ , the following hold:

Constraint Solving
If constrained subtyping succeeds and generates a constraint set K, the constraints on the most recently introduced unsolved flexible variable X are then solved by employing backtracking proof search on the rule for the judgment Θ ⊢ ∃ ℓ <∶X<∶  .K ↝ K ′ given in Fig. 15.To understand the design of this rule, it is important to be mindful of where rigid and flexible variables can and cannot occur.All types that are constraining variables in K necessarily contain only rigid variables.On the other hand, the bounds  ℓ and   of X necessarily contain only flexible variables (and do not contain X).
Each premise of the rule in Fig. 15 corresponds to a step of the (backtracking) algorithm: (1) For each pair of (necessarily rigid) collected lower bound  (3) For each (necessarily rigid) collected upper bound  ′  on X in K, let K   ′  be a constraint set collected from checking that the (necessarily flexible) given lower bound  ℓ of X is a subtype of  ′  , which again transitivity implies must hold for any instantiation of X. (4) Let K ℓ be the set of (necessarily rigid) collected lower bounds on variables other than  , whose rigidity ensures they do not contain X. (5) Let K  be the set of (necessarily rigid) collected upper bounds on variables other than  , whose rigidity ensures they do not contain X.Then the algorithm returns the union of all the constructed constraint sets.One might be surprised that the algorithm never actually constructs a satisfying instantiation of X.This is because stratification and consistency were able to ensure that none of the remaining constraints contain X, and so instantiating it would have no effect on the constraint set.Nonetheless, instantiating X with the union of its given and collected lower bounds necessarily satisfies its constraints (relying on the fact that consistency ensures its given lower bound is a subtype of its given upper bound), and as such the following holds.Lemma 6.5.For any kind contexts Θ  and Θ  satisfying ⊢ sa Θ  and ⊢ sa Θ  , for any pair of value types  ℓ and   satisfying Θ  ⊢ sa  ℓ and Θ  ⊢ sa   and Θ  ⊢  ℓ <∶ sa   , for any type variable X not declared by either Θ  or Θ  , and for any constraint set K satisfying Θ  ⊢ K ⊣ Θ  ,  ℓ <∶X<∶  , the following hold: Completeness For any assignment  satisfying ⊢  ∶ Θ  → Θ  and any value type  X satisfying Θ  ⊢ sa  X and ′ is finite and computably enumerable.

Example of Constraint Collection and Solving
To illustrate how the algorithm works more concretely, consider the subtyping between signatures (String, Ref⟨Int⟩) and ∃⊥<∶X<∶⊤.∃⊥<∶Y<∶X.(X, Ref⟨Y⟩).This subtyping should hold because X and Y can be instantiated with value types String ∪ Int and Int.The following derivation illustrates the generation of the necessary constraints on type variables X and Y. Once all constraints induced by subtyping of value types are collected, they are solved for the innermost flexible variable, Y in the example, incorporating its explicitly given bounds.Here, (b) denotes a constraint-solving step for the innermost variable Y: It checks that the (single) collected lower bound is a subtype of the (single) collected upper bound, the given lower bound is a subtype of the (single) collected upper bound, and the (single) collected lower bound is a subtype of the given upper bound.In the first check, both types are necessarily rigid, so no contraint-collection is performed.The second check succeeds without additional constraints.But the third check imposes a new constraint on X, which is added to the collection of preexisting constraints on X.These checks confirm that Y can be correctly instantiated as the union of its given and (single) collected lower bound: ⊥ ∪ Int.
Next, (a) denotes a constraint-solving step for the outermost variable X: It checks that the (two) collected lower bounds are subtypes of the given upper bound, which both succeed without additional constraints (as would always be the case when solving the final flexible variable), and then it has no more checks to perform because there are no collected upper bounds on X.These checks confirm that X can be correctly instantiated as the union of its given and collected lower bounds: ⊥ ∪ String ∪ Int.
After the outermost variable, the invariants granted by stratification and consistency ensure the resulting constraint set is empty.So, if this point is reached by the backtracking proof search, the subtyping between method signatures necessarily holds.

Sound and Complete Subtyping Algorithm
Putting all the pieces together, we show that algorithmic subtyping for method signatures provides a sound and complete decision procedure for declarative subtyping on conservative stratified types.Theorem 6.6 (Decidability of Conservative and Stratified Julia).For any pair of method signatures  and  ′ satisfying ⋅ ⊢ sa  and ⋅ ⊢ sa  ′ , the following is decidable: Proof.By Th. 3.1, Th. 5.2, and Th.6.3, the above subtyping holds iff ⋅ ⊢  <∶ sa  ′ .By Th. 6.2, the latter is decidable.□

RELATED WORK
Designing decidable subtyping for production languages is challenging.Recent results include proofs of undecidability for Java generics by Grigore [2017] and Scala 3 by Hu and Lhoták [2019].
Expressiveness and decidability exist in a trade-off space.Users may prefer more expressive types even if the compiler may fail, as long as failures are rare cases.Failures that can manifest at run-time are more serious.Mainstream languages with subtyping usually restrict run-time subtype queries [Kennedy and Pierce 2007].This is not so in Julia, as its full subtype relation is exercised at run-time.
Aiming for decidability, Julia's designers deliberately avoided features already established to be problematic, such as F-bounded polymorphism, contravariant nominal constructors, and multiple inheritance.Based on those restrictions, Bezanson [2015] conjectured decidability.However, he did point out that the combination of nominal constructors and contravariance in lower bounds of existential types is akin to the source of undecidability in  ≤ , which we have now formally established.
System  ≤ .Introduced by Cardelli et al. [1991], System  ≤ combines System F and subtyping.As already mentioned,  ≤ provides bounded universal quantification, whereas Julia provides bounded existential quantification.This difference is dual in nature and so not particularly impactful upon algorithmic concerns.However, there is another much more algorithmically-impactful difference: subtyping in  ≤ is restricted so that the only subtypes/supertypes of universally quantified types are universally quantified types-that is, quantifications must align.There is a good reason for this difference: unlike Julia,  ≤ supports explicit type application, which only makes sense if quantifications stay aligned.Regardless of the reason, this difference means  ≤ avoids a major challenge that Julia faces: whereas Julia needs to find a suitable instantiating type for a quantified variable, in  ≤ that instantiating type is explicitly restricted to be the quantified variable of the other type.This makes  ≤ 's invertible subtype truly syntax-directed, whereas Julia must resort to collecting and solving constraints on flexible variables.
Yet, despite  ≤ 's restrictive subtyping, Pierce [1992] proved it can encode the halting problem for two-counter machines [Hopcroft and Ullman 1990] and therefore is undecidable.Yet there are variations of  ≤ for which subtyping is decidable.
Restricting Subtyping.Kernel  ≤ [Cardelli and Wegner 1985] forces even more alignment between subtypes: not only must quantifications align, they must have the identical bounds as well.This restriction is decidable, though rather limiting.Hu and Lhoták [2019] and Mackay et al. [2019] have shown that one can relax this by splitting the kind context into left and right parts so that, effectively, each bound is only used by the type that introduced it.However, this causes subtyping to no longer satisfy transitivity.
Restricting Types.Instead of restricting subtyping, Mackay et al. [2020] achieved decidability while retaining transitivity by instead restricting types.In fact, their solution is to stratify types into impredicative and predicative layers.The impredicative layer has more restrictive subtyping, akin to Kernel  ≤ , whereas the predicative layer has more restrictive types.It is interesting that we arrived at a similar stratification though for a very different reason.In particular, we discovered that stratification eliminated the complexities Julia faced in constraint solving by, in particular, entirely avoiding the possibility of recursive constraints.On the other hand, Mackay et al. [2020] still rely on the quantification alignment inherent in  ≤ and as such have no concern for constraint solving.This difference in concerns explains the difference in where stratification is imposed in the two systems.
Beyond  ≤ , this approach of restricting types has been applied to prior practical systems as well.Kennedy and Pierce [2007] identified three decidable fragments of undecidable subtyping in the context of nominal inheritance and variance: the fragments can be obtained by restricting either contravariance, expansive class tables, or multiple-instantiation inheritance.Greenman et al. [2014] proposed a material-shape separation for Java generics that recovers decidability: it limits F-bounded polymorphism to the subset of types, called shapes, used exclusively as constraints.As we have, they conducted a corpus analysis to demonstrate that this restriction would be compatible with how programmers were using types in practice.Mackay et al. [2019] extend the material-shape separation to path-dependent and recursive types.
Bounded Existentials.In Java, a variable of type List<?extends Number> can be assigned any list whose elements are a subtype of Number.The "?" is known as a wildcard, and this wildcard-typed list effectively represents ∃X<:Number.List<X>.The wildcard mechanism of Java generics [Torgersen et al. 2004] is an encoding of use-site variance [Igarashi and Viroli 2002;Krab Thorup and Torgersen 1999], which is another widely used restricted form of bounded existential types.There have been multiple formalizations of Java wildcards [Cameron et al. 2008;Torgersen et al. 2005], though they focused on type soundness rather than decidability.Smith and Cartwright [2008] found inconsistencies in Java's type inference and subtyping algorithms and proposed a solution using a limited form of union types, with a conjecture on the decidability of subtyping.Wehr and Thiemann [2009] identified multiple undecidable subtype relations for bounded existential types in formal models inspired by Java.Tate et al. [2011] highlighted multiple sources of non-termination in Java subtyping, e.g.recursive constraints on type variables and wildcards in the inheritance hierarchy.With some practical restrictions, they provide a terminating subtyping algorithm, though this has been mostly superseded by the aforementioned material-shape separation [Greenman et al. 2014].
Rather than encode use-site variance, Morrisett et al. [1998]'s typed assembly language (TAL) uses constrained existential types to track that the unknown exact type of a closure can be passed to the code pointer extracted from that closure.Early works required manually coercing between existential types, but Tate et al. [2010] developed a system with decidable subtyping for and even inferability of its existential types.However, the algorithmic framework they developed [Tate et al. 2008] relies heavily on rigidly structured types, and they illustrate how even ⊥-like types (such as the type of a null pointer) cause fundamental problems due to violating this structure.
Subtype Constraints.Constraint generation and solving techniques are used in type inference.Most famously, equality constraints and unification were employed by Hindley [1969] and Damas and Milner [1982] in the context of a functional language with parametric polymorphism.With subtyping, equality constraints become subtype constraints.For example, Aiken and Wimmers [1993] extended Damas-Hindley-Milner inference with subtyping for recursive, union, and intersection types, and gave an algorithm for solving a system of constraints with restricted union and intersection types.Trifonov and Smith [1996] considered polymorphic types with explicit recursive constraints on type variables: they studied a corresponding subtype relation and provided its decidable approximation.Later, Pottier [1998] demonstrated how to improve the performance of inference with subtype constraints.Bourdoncle and Merz [1997] used a restricted form of constrained polymorphic types in a language with multi-methods and decidable subtyping.Castagna et al. [2015] dealt with subtype constraints for set-theoretic types with negation types.Chandra et al. [2016] and Chaudhuri et al. [2017] tackled flow-sensitive type inference with unusual constraint languages going beyond typical subtype constraints.

CONCLUSION
Decidability of Julia subtyping can be recovered by restricting types to a stratified grammar for the core of Julia's type-annotation language, most importantly, bounded existential types.This restriction is practical, as the vast majority of Julia programs already conform to it.However, the formalism presented here is still incomplete; it is missing types such as variadic arguments, and it is missing rules such as distributivity.More work needs to be done to develop a sound and complete algorithm for Julia's entire feature set.

′ℓ
and collected upper bound  ′  on X in K, fail unless  ′ ℓ is a subype of  ′  , since transitivity implies this must hold for any instantiation of X. (2) For each (necessarily rigid) collected lower bound  ′ ℓ on X in K, let K ℓ  ′ ℓ be a constraint set collected from checking that  ′ ℓ is a subtype of the (necessarily flexible) given upper bound   of X, which again transitivity implies must hold for any instantiation of X.
r sa (X, Ref⟨Y⟩) ↝ {X ≥ String, Y ≥ Int, Y ≤ Int} (b) ⊢ (String, Ref⟨Int⟩) <∶ r sa ∃⊥<∶Y<∶X.(X, Ref⟨Y⟩) ↝ {X ≥ String, X ≥ Int} (a) ⊢ (String, Ref⟨Int⟩) <∶ r sa ∃⊥<∶X<∶⊤.∃⊥<∶Y<∶X.(X, Ref⟨Y⟩) ↝ ∅ ⊢ (String, Ref⟨Int⟩) <∶ sa ∃⊥<∶X<∶⊤.∃⊥<∶Y<∶X.(X, Ref⟨Y⟩) Since there is no existential quantification on the left, subtyping starts by opening right existential types (X first, then Y) until method parameters are reached.For value types, directed constraintcollecting subtyping <∶ → sa generates constraints on flexible variables X and Y. Initially, flexible variables appear only on the right of the judgment, and thus, <∶ → sa used for tuple components points to the right; furthermore, constraints themselves are free from flexible variables due to constraining types coming from the opposite side of the judgment.Whenever constraint-collecting subtyping hits an invariant constructor, flexible variables move to the opposite side in one half of checking the required equivalence, which is why left-facing <∶ ← sa is used for ⊢ Y <∶ ← sa Int ↝ Y ≥ Int.
<∶X<∶  .  where  ℓ <∶ X <∶   bounded existential Type and kind-context validity: Σ ⊢   and ⊢  Σ for a given subtype relation <∶ . Pierce shows that subtyping in   ≤ is equivalent to subtyping in  ≤ and, therefore, that ≤ , where types are restricted depending on whether they occur in a negative or positive position.Kind contexts Γ − are (possibly empty) sequences of upper-bounding type-variable declarations ≤ − that restrict upper bounds to only be negative types.+ positive negation ∀≤Top.− negative quantification Fig. 7.   ≤ grammar Proc.ACM Program.Lang., Vol. 8, No. PLDI, Article 191.Publication date: June 2024.Decidable Subtyping of Existential Types for Julia 191:9 The goal is to prove this holds if and only if the dual judgment holds: ⟦Γ The universal quantification is translated into an existentially quantified term of the same variable with upper bounds flipped into (translated) lower bounds, and with another variable with an upper bound in order to encode covariance.Because of the unique inversion properties of nominal constructors, having the body All⟨,  ′ ⟩ for the right existential implies  and  ′ are necessarily instantiated with the corresponding arguments in the left type.This is the translation of the premise of   ≤ -All.Translation for negated types follows the same concept (dualization into existentiallybounded variables) but is simplified since there is no reference to the introduced type variable from within the translated terms.Rule − ⟧ ⊢ ⟦∀≤ − 2 .+ ⟧ <∶ ji ⟦∀≤Top.− 2 ⟧<∶ .∃ ′ <∶⟦ + ⟧.All⟨,  ′ ⟩ <∶ ji ∃ .∃ ′ <∶⟦ − 1 ⟧.All⟨,  ′ ⟩ 4 Thus the above judgment holds if and only if those instantiations satisfy their bounds in the appropriate kind context.Only  ′ has a bound, which invertibility then quickly implies is satisfied if and only if in the kind context ⟦Γ − ⟧, ⟦ − 2 ⟧<∶ (since we can drop  ′ once it no longer occurs in either type), the following holds: ⟦Γ − ⟧, ⟦ − 2 ⟧<∶ ⊢ ⟦ + ⟧ <∶ ji ⟦ − 1 ⟧. ≤ -Neg concludes with Γ − ⟧ ⊢ Neg⟨⟦ − 2 ⟧⟩ <∶ ji ∃⟦ + 1 ⟧<∶ .Neg⟨⟩.By reasoning similar to   ≤ -All, invertibility implies this holds if and only if  is instantiated with ⟦ − 2 ⟧, though this time with the additional requirement that the instantiation satisfies its lower-bound constraint. 5Thus this holds if and only if ⟦Γ − ⟧ ⊢ ⟦ + 1 ⟧ <∶ ji ⟦ − 2 ⟧ holds, which is the translation of the premise of   ≤ -Neg.□ Therefore,   ≤ subtyping holds if and only if the dualized translated version holds.By Pierce 12. Stratified invertible subtyping for value types: Θ ⊢  <∶ si  Julia types.The rule for nominal constructors now directly supports use-site variance without existential types.In particular, it ensures that, for each type argument, the left-hand use-site range is contained within the right-hand use-site range.For example, the subtyping ⊢ C⟨⊥≪Int32⟩ <∶ si C⟨⊥≪⊤⟩ holds, whereas the subtyping / ⊢ C⟨⊥≪Int32⟩ <∶ si C⟨Int32≪⊤⟩ does not.Lemma 5.1.For any kind context Θ satisfying ⊢ si Θ, and for any pair of value types  and  For any kind context Θ satisfying ⊢ si Θ, and for any pair of method signatures  and ′satisfying Θ ⊢ si  and Θ ⊢ si  ′ , the following equivalence holds:Θ ⊢  <∶ ji  ′ ⟺ Θ ⊢  <∶ si  ′ Theorem 5.2.′ satisfying Θ ⊢ si  and Θ ⊢ si  ′ ,the following equivalence holds: Θ ⊢  <∶ ji  ′ ⟺ Θ ⊢  <∶ si  ′ In addition to bridging invertible subtyping across Julia types and stratified types, Th. 3.1 extends that bridge to declarative subtyping.Consequently, we know that invertible subtyping for Proc.ACM Program.Lang., Vol. 8, No. PLDI, Article 191.Publication date: June 2024. .