When Subtyping Constraints Liberate: A Novel Type Inference Approach for First-Class Polymorphism

Type inference in the presence of first-class or “impredicative” second-order polymorphism à la System F has been an active research area for several decades, with original works dating back to the end of the 80s. Yet, until now many basic problems remain open, such as how to type check expressions like (𝜆𝑥. (𝑥 123, 𝑥 True)) id reliably. We show that a type inference approach based on multi-bounded polymorphism, a form of implicit polymorphic subtyping with multiple lower and upper bounds, can help us resolve most of these problems in a uniquely simple and regular way. We define F{≤}, a declarative type system derived from the existing theory of implicit coercions by Cretin and Rémy (LICS 2014), and we introduce SuperF, a novel algorithm to infer polymorphic multi-bounded F{≤} types while checking user type annotations written in the syntax of System F. We use a recursion-avoiding heuristic to guarantee termination of type inference at the cost of rejecting some valid programs, which thankfully rarely triggers in practice. We show that SuperF is vastly more powerful than all first-class-polymorphic type inference systems proposed so far, significantly advancing the state of the art in type inference for general-purpose programming languages.


INTRODUCTION
Consider the function foo defined as: foo f = (f 123, f True).
For the past several decades, researchers have been unable to decide which type to infer for this function, should one wish to go beyond the classical Hindley-Milner type inference discipline of ML languages (which yields a unification error here), and should one want to support some form of first-class polymorphism.In particular, no system was proposed where such a term could be given a "satisfactory" type.A satisfactory type for foo should let us type check expressions like foo (fun x → x) at type (Int, Bool) and foo (fun x → Some x) at type (Option Int, Option Bool). 1n this paper, we propose SuperF, a system which can infer the following type for foo: This type is satisfactory in that it lets us type check the function call examples above at the desired respective types (Int, Bool) and (Option Int, Option Bool).
Our approach is reminiscent of bounded polymorphism, but our type system F { ≤ } diverges from most research on this topic in the following crucial respects: • We assume implicit, erased polymorphism, meaning that polymorphic types do not need to be introduced and eliminated in terms and do not participate in the term's runtime semantics.Consequently, we can have subtyping relationships such as ∀ .→ ≤ Int → Int, so that any term with the left-hand side type is known to also have the right-hand side type.
• We allow lower bounds as well as upper bounds, rather than only upper bounds (like in System F <: ) or only lower bounds (like in ML F ), making our system symmetric, but introducing possible inconsistencies between bounds, which need to be dealt with carefully.
• We allow associating an arbitrary number of bounds to the same type variable, instead of just one, 2 reminiscent of the old idea of constrained types [Odersky et al. 1999], which was previously studied mostly in the context of ML-style (i.e., not first-class) polymorphism.
Fortunately, this combination of type system features was already formally described as part of System F by Cretin and Rémy [2014] in their groundbreaking work laying the foundations for general implicit polymorphism with subtyping.This allows us to define F { ≤ } by translation to F and focus on the algorithmic aspects of the system in this paper, noting that Cretin and Rémy only presented a declarative type system for System F and did not investigate type inference.While SuperF crucially relies on multi-bounded types internally, these types are non-denotable: they are not accessible to users, who are limited to writing type annotations in the syntax of System F, i.e., where polymorphic types cannot have bounds on their quantified type variables.Indeed, to make subtype constraint solving tractable, SuperF internally follows a polarized type syntax, whereby bounds are only allowed in positive polymorphic types.Annotated types cannot use bounds because these types are both positive and negative, since they provide a type for the annotated expression but also are checked against the expression's inferred type This is an atypical property of our system: it means that users cannot always provide explicit type signatures that are as general as the types that are internally inferred by the system.Nevertheless, we argue that in practice users rarely want to use bounds in their type signatures anyway, and that System F types are sufficient for a large number of practical functional programming use cases, including a majority of use cases studied in previous work.
Subtyping for System F types was historically realized in System F , which relates polymorphic types by subtyping based on their specificity.F has an " rule" to subtype function types covariantly in their results and contravariantly in their arguments, which makes System F typing complete (Option (Int ∨ Bool), Option (Int ∨ Bool)), which are imprecise and thus unsatisfactory. 2Intuitively, this is equivalent in power to allowing at most one upper bound and at most one lower bound on each type variable together with allowing intersection types in negative positions and union types in positive positions [Parreaux 2020], which is why we could represent the type of foo more concisely via an intersection in the example above.
When Subtyping Constraints Liberate 48:3 with respect to conversion.Subtyping in F is known to be undecidable [Chrząszcz 1998;Tiuryn and Urzyczyn 1996], and F { ≤ } is a strict generalization of F (i.e., all well-typed System F and F terms are well-typed F { ≤ } terms, but the converse does not hold), so it is natural to conjecture that F { ≤ } subtyping is also undecidable, making type inference necessarily incomplete.
SuperF is a terminating but incomplete type inference algorithm for F { ≤ } that relies on a simple recursion-detection heuristic to raise errors in tricky recursive-looking cases.Without this check, type inference would diverge on terms that exhibit indirectly-recursive structures, such as the Ω combinator (fun x → x x) (fun x → x x).Thankfully, we find that this check rarely triggers in practical, real-world examples.On the other hand, even when it succeeds, SuperF does not always infer principal types.This is mainly because it always distributes polymorphic types over function types even though that can sometimes lead to worse outcomes.
From the programmer's perspective, the main limitation of SuperF type inference can be understood as: SuperF never assumes parametrically-polymorphic types for function parameters.Type annotations must be used when such polymorphic parameters are needed.Nevertheless, SuperF is appreciably more powerful than all other existing systems; we show that it can seamlessly handle most examples previously considered in the literature (a first), and that it does so without requiring any annotations whatsoever.Indeed, multi-bounded polymorphic subtyping already covers a lot of additional ground compared to existing unification-based approaches, allowing programs like the one at the beginning of this introduction to type check without having to resort to higher-rank parametric polymorphism where other systems would have to.
Our specific contributions are as follows: • We describe the main problems of first-class-polymorphic type inference as well as our main ideas to address these problems (Section 2).To the best of our knowledge, although they are quite natural in retrospect, these ideas are novel and have not been developed before.
• We formalize F { ≤ } , a lambda calculus with first-class, implicit, erased, and what we call multi-bounded polymorphism (Section 3).We show that the soundness of this system can be obtained by translation into the existing System F , whose semantic soundness was mechanically verified in Coq by Cretin [2014].
• We formalize our SuperF type inference system, emphasizing the novel and crucial concept of subtype extrusion and avoidance (Section 4).We prove that this type inference system is sound for F { ≤ } and terminating.
• We present a practical implementation of SuperF (Section 5), which can be tried in a web demo available at https://hkust-taco.github.io/superf/.This implementation was evaluated on all examples described in previous work as well as many new and less trivial ones, demonstrating that our system is considerably more powerful than previously proposed approaches.We also show that SuperF infers precise yet concise types for existing ML programs by porting the List module from OCaml's standard library to SuperF.

PRESENTATION
We first present our approach from a high-level point of view focused on intuition.

Motivation
Before we delve into technical details, it is worth considering why one might care about firstclass polymorphism and why impredicative polymorphic type inference is a useful addition to a functional programming language's type system.
Early on, Peyton Jones et al. [2007] presented many convincing use cases for higher-rank polymorphism, a restricted form of first-class polymorphism.These examples included data structure fusion, encapsulation of state and other effects, existential type encodings, generic programming, folding and mapping functions respecting data structure invariants, internal type class desugaring, etc. Nowadays, higher-rank polymorphism is used pervasively throughout the Haskell ecosystem.
Peyton Jones et al. [2007, §3.4] also presented several use cases for impredicative polymorphic type inference (i.e., unrestricted first-class polymorphism, as studied in this paper).Their examples rely on the observation that without impredicative polymorphism, several abstractions that work well in the monomorphic case start failing in the polymorphic case, requiring tedious manual specialization.For instance, consider the following definitions: While both of these functions fit into classical higher-rank polymorphism, applying one to the other, as in (revapp (fun x → x) poly), requires impredicatively instantiating the type variable a to type ∀ v. v → v.A similar thing happens with the fix-point combinator, whose usual type, fix : (a → a) → a, cannot be applied to a polymorphic function without impredicative polymorphism.
As another example, recent versions of the Haskell language started supporting abstracting over record fields through a special type class so that, for example, it became possible to write functions working over any input type that contains a field named "size" of type a. Allowing this useful abstraction infrastructure to extend to the setting of records with polymorphic fields requires impredicative polymorphism, as it requires instantiating a in our example with a polymorphic type.
Altogether, supporting first-class polymorphism in its "full glory" has many important real-world benefits, which also explains why the Haskell community has spent over a decade trying to add such generalized support to the Glasgow Haskell Compiler (GHC) [Serrano et al. 2020[Serrano et al. , 2018;;Vytiniotis et al. 2006] and why even languages like Scala 3 are starting to add the feature. 3 The solution we propose in this paper is a general one: SuperF supports higher-rank polymorphism as a side effect of its support for unrestricted first-class polymorphism, and all classical examples of predicative higher-rank polymorphism are still supported out of the box in SuperF.

Parametricity and Subtyping
Recall the definition of function foo shown in the introduction: foo f = (f 123, f True) To be polymorphic or not to be.Clearly, function parameter f should be polymorphic one way or another in order for foo to be well-typed, because f is applied to arguments of completely different types Int and Bool.So an intuitive type for foo could have the following form: The simplest answer that comes to mind would be to have it return the same type as its input: While this is possible, it is obviously not general enough -what if the caller wanted to pass to foo the function fun x → Some x?So far, most previous state-of-the-art approaches to first-class polymorphism already throw in the towel and ask the user to provide an explicit type annotation for f.The user could annotate f as ∀ a. a → a or they could annotate it as ∀ a. a → Option a, or as ∀ a. a → List a, etc., etc., depending on their intended use of foo.Moreover, these parametric polymorphism considerations are not stable in the face of small changes.Consider: where E0 and E1 are two arbitrary expressions.If E0 and E1 happen to be typeable at the same type T, f no longer needs to be polymorphic, which allows calling foo1 with less powerful non-polymorphic function arguments of type T → R for some R.This may in turn be needed later, depending on the uses of foo1.For example, taking E0 = E1 = fun x → x, we have: This typing of foo2 would let us write expressions like foo2 (fun f → f 1 > 0), of type (Bool, Bool).But the parameter-polymorphic type is still on the table, and is neither more nor less general: which would allow us to type foo2 id as ((∀ a. a → a), (∀ a. a → a)).The fundamental instability of this kind of reasoning gets even worse whenever we pass to f argument that may or may not be typed identically depending on polymorphism decisions made earlier in type inference, as in: Above, we could type both arguments to f at the same type ∀ a. (a, a) → (a, a), or we could type them at unrelated types ∀ a b.(a, b) → (b, a) and ∀ a b.(a, b) → (a, b) respectively, and again there is no most general choice.Many types seem possible, and there is no obvious way to proceed.This leads us to formulate another major problem: when should inferred types be polymorphic and when should they not be?In practice, these two problems make the job of a type inference engine for System F unpleasantly cumbersome, and the need for a form of subtyping is felt [Rémy 2005].
Subtype polymorphism and intersection types.Our failure to find a most general type for foo was due to a belief that f should be assigned a parametric polymorphic type.Parametric polymorphism is a form of infinitary polymorphism [Aiken and Wimmers 1993;Leivant 1990] -that is, a function f of type ∀ a. a → a can be seen as a function having at the same time all the types f : Int → Int; f : Bool → Bool; f : Option Int → Option Int; etc.In type theory, we can express such overloaded function types through intersection types (∧), as in: It should now become clear that assigning to the f parameter of foo an infinitary intersection type is definitely "overkill".After all, f is only applied to Int and Bool arguments, so a type such as (Int → S) ∧ (Bool → T) should suffice.Since there are no constraints on S and T, these can be taken to be type variables of foo, yielding the type: This type (call it 0 ) happens to be a principal or most general type of foo,4 meaning that all other types that can be assigned to foo can be obtained by widening 0 through subtyping.This is proved in the extended version of the paper.
Notice that 0 does not even involve any higher-rank parametric polymorphism!(Instead, it has a higher-rank intersection type.)Yet, a function of this type can be called with parametricallypolymorphic arguments, such as functions of type id = ∀ c. c → c which should not come as a surprise, as we saw that id stands for (Int → Int) ∧ (Bool → Bool) ∧ . .., which is clearly a subtype of (Int → Int) ∧ (Bool → Bool).Similarly, we can now also call foo with argument fun x → Some x, of type ∀ c. c → Option c, which yields a result of type (Option Int, Option Bool).As a less semantic and more syntactic way to see why the latter works, instantiate a to Option Int and b to Option Bool in the type of foo, and notice that ∀ c. c → Option c is indeed a subtype of (Int → Option Int) ∧ (Bool → Option Bool) because that is the same as saying that ∀ c. c → Option c is a subtype of Int → Option Int and also a subtype of Bool → Option Bool, which is true as in each cases we can instantiate the polymorphic type by substituting c accordingly.Furthermore, a function of type 0 can also be called with monomorphic arguments, of types such as ⊤ → Str (where Int ≤ ⊤ and Bool ≤ ⊤).So it is more general than all System F types we have previously considered for it.
This idea generalizes smoothly to the abstracted example foo1 mentioned earlier: where T0 and T1 are the inferred types of respectively E0 and E1.Thanks to subtyping, whether the types chosen for E0 and E1 happen to be compatible or not has become irrelevant.This effectively removes the "discontinuity" in reasoning between these two very similar cases: we no longer have to reason about awkward cases where different, incomparable types suddenly become possible due to small decisions made when inferring the types of subterms.All in all, picking a parametrically-polymorphic type for f makes it more powerful than strictly necessary, and this is a problem because that type occurs negatively (i.e., in input position), meaning that its excessive power leads to a weaker overall type, preventing the discovery of a sufficiently general one.All we had to do was to abandon the infinitary context of parametric polymorphism and assume the finitary context of intersection-based subtype polymorphism instead.
Union types.A dual phenomenon happens with union types in positive positions.Unions give us a natural least upper bound between arbitrary types, even when these types have very different shapes and structures, while other approaches would have to approximate each type to a common and often less precise super type, a process that may not always have good solutions.Consider: where id has type ∀ a. a → a.Because x and id flow together into the result type, most previous approaches would try to unify their respective types into a unique result type.But there is no best way of performing such unification: we could make x have the same polymorphic type as id, but this would prevent legitimate uses of bar such as bar succ 0 (where succ: Int → Int), which could be typed at Int if we instantiated the type of id to Int → Int while typing bar.In SuperF, we have: Going beyond System F. System F only allows expressing the infinitary form of polymorphism (i.e., parametric polymorphism).We prefer inferring types in a slightly more powerful language which smoothes out the rugged edges and discontinuities of System F's type language.The idea is not new: it goes back at least to ML F , which used bounded quantification to allow a notion of subsumption (referred to as generic instance) to be used in inferred types, so as to abstract over the different possible generalization choices made by users of polymorphic functions.Yet, ML F still required type annotations in many places, notably for all parameters used polymorphically, and it could not type check the foo function variations shown above (except for foo2) because it only allowed one lower bound per type variable, instead of multiple lower and upper bounds.So we argue that ML F did not go far enough, and that what we actually need is full-blown polymorphic subtyping and full-blown multi-bounded polymorphism.Because it is a superset of System F, we call the type inference system presented in this paper SuperF.

SuperF and Implicit Multi-Bounded Polymorphism
We saw that intersections were better at expressing requirements on the types of inputs, since making these requirements as weak as possible conversely strengthens the overall type.On the other hand, in this paper we are not interested in using intersections in positive (output) position, although such intersections can be used to represent various features, such as ad-hoc function overloading.For example, in some programming languages like TypeScript and in semantic subtyping approaches [Frisch et al. 2002], one can write a definition like dup x = (x, x) and assign it several type signatures, such as Int → (Int, Int) and Bool → (Bool, Bool), with the effect of giving dup the combined intersection type (Int → (Int, Int)) ∧ (Bool → (Bool, Bool)).By contrast, in SuperF, we only assign dup the more general type ∀ a. a → (a, a), which is fine to do in output position as this makes strictly more programs type check than picking a specific intersection. 5imilarly, SuperF does not support unions in negative positions, though these can be used to represent a structural form of algebraic data types [Castagna et al. 2016;Parreaux and Chau 2022].
Polarized unions & intersection desugaring.When unions are restricted to positive positions and intersections to negative ones, subtype inference remains simple and tractable [Dolan and Mycroft 2017].In fact, this restricted use of unions and intersections turn out to be equivalent [Parreaux 2020] to a form of bounded polymorphism with both upper and lower bounds on type variables (multi-bounded polymorphism).Consider baz below and two equivalent types for it: bar f x = if f x then f else fun x → x --using unions and intersections : bar : The equivalence of (A) and (B) is proved in the extended version of the paper.Using bounds internally is more convenient than directly using unions and intersections because (1) it facilitates both the implementation and formal specification of type inference; and (2) it allows avoiding some repetition in inferred types, as identical requirements attached to different occurrences of a type variables can be shared as part of a single bound.(For example, ∀ c {c ≤ ∀ a. a → a}.(c, c) → c is more concise than the equivalent ∀ c. (c ∧ (∀ a. a → a), c ∧ (∀ a. a → a)) → c.)So in this paper we focus on pure multi-bounded polymorphism and regard unions and intersections as syntactic sugar for the former.Nevertheless, it is often useful to think of and describe bounded polymorphic types in terms of unions and intersections, which are often more intuitive.

Type Inference Approach
We now describe type inference in SuperF.
Subtyping over unification.It was historically recognized that subtype inference is notoriously harder than traditional unification-based type inference as in ML.Therefore, it was natural to expect that the difficulty of subtype inference would compound with that of first-class-polymorphic type inference, which is probably why the vast majority of previous work stuck with unificationindeed, even the venerable ML F system, which features lower-bounded polymorphism and therefore has a core notion of subtyping (based on generic instances), uses first-order unification as the workhorse of its type inference engine.We believe that this is fundamentally a mistake, and that subtype inference in fact alleviates most of the intricacies of unification-based approaches to firstclass-polymorphic type inference.Indeed, subtyping is better at tracking the flow of values through a program, which becomes important in this context. 6ain ideas of SuperF type inference.Generally, our main ideas are as follows: • Never infer parametrically-polymorphic types in negative position, since it is often enough to infer intersection types there.7 • Segregate the syntax of types between positive and negative types, similar to the approaches of Dolan and Mycroft [2017]; Pottier [1998], among others.
• Let users provide type annotations that include System F-style polymorphic types, and make sure we can check our inferred types against these types.
• Only infer polymorphic types for lambda abstractions, as we are mostly interested in polymorphic functions, and subtyping usually alleviates the need for other forms of polymorphic values in a call-by-value system.(This point is minor and could easily be relaxed.) • Offload generated subtyping constraints to a dedicated subtype constraint solver (described in Section 2.5); solve these constraints down to type variable bounds upon generalizing the types of lambda expressions.The approach described above essentially allows us to infer types for many well-typed System F terms commonly encountered in functional programs, as well as for some terms that are well-typed in our system but ill-typed in System F.
Checking restricted user annotations.While SuperF internally supports multi-bounded polymorphism in positive positions, which allows type inference to use subtyping to delay polymorphism choices, we believe most programmers rarely think in terms of such bounded types.To make checking programmer type annotations against inferred types practical, we restrict the language of type annotations to System F types, meaning that users may specify type signatures containing first-class-polymorphic types, but these may not specify bounds.Indeed, if we allowed bounds on user-provided type signatures, we would encounter much more difficult constraints to solve -e.g., consider foo (add : (Int → Int) ∧ (Str → Str)) : = . . .where the type annotations are equivalent to giving foo type ∀ a {a ≤ Int → Int, a ≤ Str → Str}. a → .While type checking the body of foo and its uses of the add parameter, we would need to deal with constraints of the form a ≤ where a is a rigid type variable with upper bounds; which is equally as hard as dealing with overloading constrains like (Int → Int) ∧ (Str → Str) ≤ .We are not aware of any constraint-solving approaches able to deal with such types without having to either approximate them 8 or rely on backtracking, which we argue makes type inference impractical in a real-world context.Subtype Extrusion.A major difficulty of inferring subtypes in the context of nested polymorphism -whether first-class or ML-style -is to avoid mixing up type variables coming from different polymorphism levels.For example, consider typing an expression of the form fun x → (fun y → x (y, y)) or equivalently fun x → let f y = x (y, y) in f, where is some arbitrary subexpression.The variable y introduced by the inner argument function will have type , for some fresh type variable , which will be generalized locally as part of the overall type of the inner function, yielding a type of the form ∀ . . .{. ..}. → , assuming x (y, y) returns type .Because is generalized in this nested local function, it is more polymorphic than the type of x (call it ), so leaking into a bound of as in ≤ ( , ) → would be unsound.To see why, consider that may later be instantiated into several unrelated types as part of its use in the body of , by which point we would lose the connection between these specific instances and .
We solve this problem by extruding types that are too polymorphic, like ( , ), into some less polymorphic types like ( ′ , ′ ), where ′ is added to the same quantifier as .Crucially, the extrusion is made to be an approximation of the original type by adding the bound ≤ ′ onto the original . 9This way, whenever is later instantiated in -say, successively to Int and Bool -then by transitivity we will get constraints Int ≤ ′ and Bool ≤ ′ , which will ensure that the final type inferred for x will be essentially (Int ∨ Bool, Int ∨ Bool) → , the type of a function that accounts for the fact that both integers and booleans may flow into its parameters.
The original idea of subtype extrusion is due to Parreaux [2020], who informally presented a level-based algorithm for subtype inference with nested polymorphism.This approach was itself inspired by the older level-based unification algorithm implemented for the Caml and later OCaml compilers [Kiselyov 2013], which finds its origin in the work of Rémy [1992], who initially called these polymorphism levels ranks, or "degrés" in French [Rémy 1990].In this paper, we formalize a simplified version of Parreaux's extrusion algorithm, which does not use explicit levels but can be implemented with levels as an optimization (our SuperF implementation does so).

Subtype Constraint Solving
The constraint-solving approach of SuperF is relatively straightforward, which we consider to be one of the main contributions of this paper.
First, we decompose concrete type constraints following the structure of types, for example decomposing ( 1 , 2 ) ≤ ( 1 , 2 ) into 1 ≤ 1 and 2 ≤ 2 , and decomposing 1 → 2 ≤ 1 → 2 , by following function parameter contravariance and function result covariance, into 1 ≤ 1 and Type Avoidance.when polymorphic types are found on the right, as in ≤ ∀ ., we need to turn the type variable into a so-called skolem, i.e., a type variable on which no bounds can be added (because we are not allowed to assume anything about this type).We continue by constraining ≤ , during which we may end up trying to add to existing (outer) type variables bounds that refer to this skolem, which makes no sense outside of the corresponding polymorphic type.Therefore, we need to approximate these bounds until they no longer refer to .A simple solution, which we settle on in this paper, is to widen all positive occurrences of to ⊤ and to narrow all its negative occurrences to ⊥.As a concrete example, consider passing some argument of type → to a function of type (∀ .→ ) → , where and are some type variables coming from the outside.This leads to → ≤ (∀ .→ ) and to constraining sk.≤ and ≤ sk.where sk. is the skolem, resulting in overall inferred bounds {⊤ ≤ , ≤ ⊥}.The argument function, whose type must subtype → , would thus essentially be restricted to non-terminating computations (of type ⊥) or delayed non-terminating computations like fun _ → failwith "oops" (of type ⊤ → ⊥).
Delaying instantiation.Instantiating polymorphic types found on the left too early and those found on the right too late may lead to unnecessary failures [Zhao et al. 2019].For example, in ∀ .→ ≤ ∀ .→ , if we instantiate the left-hand-side first, we proceed to constraining → ≤ sk.→ sk., which leads to bounds sk.≤ ≤ sk., and we end up with type-avoided bounds ⊤ ≤ ≤ ⊥ on , which are inconsistent (thus failing the process).But instantiating the right-hand-side first works out because since is then a locally-instantiated variable, it is allowed to refer to sk. , and we can forget about both and sk.after the constraining is done -that is, in general, unless leaked into an outer constraint while constraining, in which case we would have to keep an extruded version of it around, which would fail for the same reason as above.
Distributivity.It is sometimes possible to either delay the instantiation or rush the skolemization of polymorphic types by using the distributivity property, which states that ∀ .→ is equivalent to → ∀ .when does not occur in (this is called deep skolemization by Peyton Jones et al. [2007]).For example, to successfully constrain ∀ .Int → → ≤ Int → ∀ .→ , we can either distribute the left-hand side, resulting in the equivalent constraint Int → ∀ .→ ≤ Int → ∀ .→ , which succeeds, or distribute the right-hand side, resulting in ∀ .Int → → ≤ ∀ .Int → → , which also succeeds.10This is because in both cases, after distributing, we are able to skolemize the right-hand side before instantiating the left-hand side.Therefore, we try to make use of distributivity whenever possible in SuperF.
Cycle checking.Our implementation does not support recursive types, so it rejects cyclic constraints such as ≤ Int → , using a form of subtyping-aware "occurs-check".This check prevents typing some programs that could have meaningful types even without recursive types, such as let rec f a = f, which can be typed as ⊤ → ⊤, as well as ⊤ → ⊤ → ⊤, as well as ⊤ → ⊤ → ⊤ → ⊤, etc. and in general .(⊤ → ) where is the recursive type binder.However, the loss of expressiveness is not very significant; indeed, ML programmers are already used to "ill-formed" recursive definitions like these being rejected, and their use is of limited interest when more usual forms of type-level recursion can be achieved through standard data type definitions.
Unfortunately, even when we reject cyclic constraints, the subtype constraining process described thus far may still not terminate on inputs involving indirect forms of recursion, such as the standard Ω = ( . ) ( . ) term and other recursion combinators like Y = ( .( )) ( .( )).Therefore, we propose the use of a heuristic which we refer to as the "suspiciously recursive-looking criterion" (SRLC).Constraints that fail the SRLC are simply rejected conservatively, terminating type inference with a failure.

Suspiciously Recursive-Looking Criterion (SRLC)
The core challenge for ensuring the termination of type inference is that constraint solving may end up comparing an infinite number of distinct types, which are generated by instantiating and skolemizing universal types, as well as copying types during subtype extrusion.
Going back to the roots.Our idea is to assign what we call a "root" to each type and to make sure that all types ever created during constraint solving only have a finite number of roots.We abort constraint solving with an error upon comparing a pair of types with the same roots as another pair of types currently being compared, which effectively bounds the depth of recursive constraining calls.The root of a type variable is: • itself if already existed at the start of the constraint-solving run; • if was created by instantiating, skolemizing, or extruding a type variable with root during the current constraint-solving run.
The roots of other types are formed by substituting all type variables with their respective roots.
Termination argument.Because the only way to create new types during constraint solving is to substitute the type variables of existing types with fresh type variables (which by construction yields a type with the same root as the original) or with ⊤ and ⊥, it is clear that the number of roots we will ever reach is finite, ensuring the termination of constraint solving.
Practicality.We found that in practice, the SLRC seldom gets in the way of typing correct terms, the only exception being terms that make use of self-application (such as G9 in Table 3, presented in Section 5.5).Crucially, we assume monomorphic recursion as a primitive of the language, meaning that we type recursive definitions by first assigning them a type variable and then constraining that type variable to be a supertype of the inferred result type. 11In this context, the programs the SRLC rules out are mostly ones that use indirect recursion (in the style of the Y combinator).

Expressiveness and Limitations
SuperF is uniquely expressive, but it does naturally have limitations.
Expressiveness.HML [Leijen 2009] is a restriction of ML F where type annotations are restricted to System F types and where all polymorphic parameters must be annotated (even when not used polymorphically within the function body).There are small differences between HML/ML F and SuperF in the places where generalization occurs,12 which are usually smoothed over by distributivity.Modulo these differences, we conjecture that SuperF subsumes HML, which in turn subsumes ML type inference.So SuperF subsumes ML type inference as well, and type annotations are not needed by SuperF in programs where ML type checking succeeds.While we have not yet proved it, we experimentally verified this claim by porting OCaml's List module to our SuperF implementation, requiring no type annotations (see Section 5.3).ML F (even in its "shallow" variant [Le Botlan and Rémy 2009]) allows unrestricted type annotations and can therefore accept programs with annotated lower bounds that are not syntactically valid in SuperF.Conversely, SuperF accepts programs that do not type check in ML F .Therefore, SuperF and ML F are technically incomparable in terms of expressiveness.However, we conjecture that a syntactic restriction of ML F source programs to using only System F type annotations would be subsumed by SuperF even without the additional polymorphism restriction of HML.Since functional programmers do not normally write type annotations with lower bounds (we are not aware of any functional programs using such annotations in the wild), one could argue that SuperF is more expressive than ML F in practice.
Limitation: needed annotations.While it is enough to infer non-parametric intersection types in negative positions in many cases, it is not enough when a parametrically-polymorphic input type is needed.For instance, the following refactored version of foo no longer admits a precise type: This is because fooLet makes both arguments to f pass through the bottleneck of g's x parameter, thereby merging their originally-separate data flows.It is not possible to recover the precise type of foo through type annotations, even in the declarative system. 13On the other hand, type annotations can be used if a different type is desired, for example annotating (f : ∀ a. a → a).
Limitation: restricted type syntax.As explained in the introduction, SuperF is atypical in that it infers types that cannot always be written down (inferred types are generally non-denotable) due to the polarity restriction of its internal type syntax.Users may only provide System F type signatures, in which polymorphic types do not have bounds, so that for example the principal type of foo cannot be used as an explicit type signature.However, any well-typed SuperF term admits a number of possible System F types.These types are often less precise than the SuperF type, and there is often not a best one to pick, but since it is the user who is providing the type signature, this is not a problem in practice.It appears that users seldom want to use bounds in their annotations anyway (indeed, much of the work following up on MLF was based on this very assumption).Moreover, in a typical program, a large number of functions, if not most of them, will not be associated with an explicit type signatures.For instance, even tough the standard practice in Haskell is to annotate exported top-level definitions with type signatures, typical Haskell code contains many unannotated helper functions, notably in where clauses and in non-exported definitions, as well as lambda expressions passed as arguments to other functions or stored in data structures.When working with such local definitions, the full power of SuperF type inference is available, and previously-untypeable functions like foo can now be used.Therefore, our restricted type syntax should not be thought of chiefly as a limitation: the SuperF algorithm it enables is a strict improvement over most existing approaches for inferring System F-style first-class polymorphism.

Practical Considerations: Stability and Error Messages
We now briefly explain why we think SuperF is a useful and practical type inference system.

Robustness and
Stability.An important design consideration for first-class-polymorphic type inference is robustness against innocuous changes to the program, i.e., type inference stability [Le Botlan and Rémy 2009, §4.5].Fragile type inference leads to suboptimal user experience as it becomes harder for programmers to predict when type annotations will be needed.Thanks to subtyping, which faithfully encodes program data flows without having to approximate types through unification, SuperF achieves a high degree of stability.However, just like for expressiveness, SuperF is neither more nor less stable than MLF in general.Table 1 summarizes some of the important stability properties of ML F and SuperF.While inferring precise data flow types generally improves stability, it also makes the system too expressive for supporting stability properties that more rigid systems like ML F possess, as exemplified in M1.A counterexample showing that M1 does not hold in SuperF is the fooLet function mentioned in Section 2.7, whose let-reduced form has the same (more general) type as foo.The reason this is not a problem in ML F is that ML F is able to type check neither foo nor fooLet, nor any similar functions where unannotated parameters are used at distinct types in the function's body.We conjecture that M1 holds in SuperF if we make it a side condition that x be used linearly in a2, though proving this is quite challenging and we have not done so yet.Note that the side condition of MS5 is crucial, as neither ML F nor SuperF are stable under general expansion.Here, "f has function type" should be understood in a restrictive, syntactic sense: for instance, neither a type variable with function type bounds nor a polymorphic type with a function type body is a function type as required by the condition.The side condition that x should not appear under lambdas in S3 can be intuitively understood as follows: lambdas introduce nested polymorphic scopes, so for x to interact with the corresponding nested polymorphic type variables without leaking them and causing imprecision, x may need to be parametrically polymorphic, but we do not infer parametrically polymorphic types for function parameters.
User-friendly type error messages.A non-obvious advantage of type inference systems like SuperF based on subtyping (in the school of algebraic subtyping [Dolan 2017]) rather than unification is that they are easily adapted to track and propagate type provenance information relating all inferred types to the relevant source code locations from which these types arose.This information can in turn be used to produce precise and informative type error messages [Bhanuka et al. 2023].In the Table 1.Example typeability equivalences demonstrating some stability properties of ML F and SuperF.In each case, the le -hand side is typeable if and only if the right-hand side is, with equivalent types.Rows labeled with "MS * " show properties shared by both systems while those labeled with "M * " show properties enjoyed by ML F but not SuperF and those labeled with "S * " show properties enjoyed by SuperF but not ML F .Metavariables , , , range over terms while , range over variables.
Reorder arguments first-class polymorphism setting, one has to additionally take care of the interaction between rigid variables and skolems, which can leak and cause problems down the line.SuperF is well-equipped to deal with these problems thanks to its fine-grained tracking of data flows.

DECLARATIVE TYPE SYSTEM
We now present the declarative F { ≤ } type system formally.
The syntax of F { ≤ } is presented in Figure 1.The only non-standard syntax is that of polymorphic types ∀ {Σ}., which include a set of bounds Σ on the quantified variables , and the box and unbox ⟨ ⟩ forms, which are explained next.For simplicity of presentation, we do not have special forms for concrete features such as product and sum types, as their addition is straightforward; the bare-bone features presented here are sufficient to demonstrate our main ideas.
Notations and Shorthands.Notation denotes a repetition of = 0 to occurrences of , and we omit when it is unambiguous.We also make use of the following notations:

Declarative Typing Rules
System F { ≤ } is a mostly standard polymorphic type system whose main distinctive features are multibounded polymorphic types and boxed types.The latter prevents unannotated function parameters from being used polymorphically, which reflects the limitations of the SuperF algorithm.
The declarative typing rules of F { ≤ } are presented in  an arbitrary term . 14There are two requirements for generalization: first, the free type variables of must not occur in Γ, which is standard; second, we require that the quantification ∀ {Σ} be consistent in Γ, written B (Γ) ⊢ ∀ {Σ} cons.and read "in context Γ, the quantification of variables with bounds Σ is consistent".We define consistency as the existence of a solution to Σ, i.e. a substitution of such that the variable bounds Σ hold in B (Γ).This makes sure no parts of a typing derivation can rely on unsound assumptions.In particular, subtyping assumptions cannot be used to carry subtyping proofs in the same way as dependent type systems and GADTs can be used to carry type equality or subtyping proofs [Boruch-Gruszecki et al. 2022;Parreaux et al. 2019;Scherer and Rémy 2015].We disallow inconsistent constraints because these could be used to make the type system accept obviously ill-typed terms.For example, ∀ {Nat ≤ ≤ Int}.→ (a shorthand for ∀ {Nat ≤ , ≤ Int}.→ ) is consistent because Nat ≤ Int.It defines an identity type that works on any type between Nat and Int.On the other hand, ∀ {⊤ ≤ ≤ , ≤ Int}.→ is inconsistent because the (transitive) bounding on and , ⊤ ≤ Int, cannot be derived.

Declarative Subtyping Rules
The subtyping rules of F { ≤ } are presented in Figure 3. Rules S-Top, S-VarRefl, S-Trans, and S-Fun are standard.S-Hyp is used to leverage any type variable bounds present in the typing context.S-Forall-R introduces polymorphic types in the middle of subtyping.Its premise makes sure that the added quantified type variables are not in the type being quantified -the idea is that these type variables will appear later through the combined use of S-Forall-Cov,15 to widen a type under quantifiers, and S-Forall-L, to instantiate the type variables of a polymorphic type.This way, we can use the subtyping rules to rearrange the quantifiers of a type -for example, we can derive subtyping relationships such as ∀ .∀{ ≤ }. → → ≤ ∀ { ≤ }. → → ⊤.S-Forall-Distr describes the distributivity of polymorphic types over arrow types; note that the other direction is already admissible by covariance of function results and uses of S-Forall-R/S-Forall-Cov/S-Forall-L.All these rules require that the corresponding quantifications be consistent, to avoid introducing bad types during subtyping.Finally, S-Unbox1 and S-Unbox2 are the rules that make type ascription work.The latter is used when the ascribed type needs not be unboxed, and the former is used to open a boxed function parameter type.Note that boxes only block polymorphic types and they congrue with type variables and functions:16 S-CongBound allows upcasting a boxed type variable to one of its bounds and S-CongFun allows pushing a box into the result type.
Derived la ice types.As hinted previously, we can encode bottom and union types.
Theorem 3.2.For all wf where ∉ FV ( ), the type defined as { } ≜ ∀ { ≤ }. is the least upper bound of all , i.e., • (A) { } is an upper bound of , meaning that for all we have ≤ { }; and • (B) for all wf such that is an upper bound of , we have { } ≤ .
Remark 1.By contrast, we cannot encode intersections in the same way as unions, because that would require the use of existential quantification, which is not supported by F { ≤ } nor by the underlying System F .On the other hand, using universal quantification, we can still encode intersections that occur negatively in some outer type, i.e., if occurs negatively in [ ], then where ∉ FV ( ) ∪ BV ( [•]).
Example 3.3.Consider term = .let = (choose ( . ) : ∀ .→ ) in ( 0, True) in context Ξ = (choose : ∀ .→ → ).As expected intuitively, can be typed in F { ≤ } at type (∀ .→ ) → (Int, Bool).First note that .can be typed at ∀ .→ .We can also assign to parameter the type ∀ .→ , so in the lambda body is typed at ∀ .→ .We need to pass arguments of mismatched boxed and unboxed types to choose.This is no problem: instantiate choose's type variable to the union ∀ .→ ∨ (∀ .→ ), letting us type the call choose ( .), which returns the same union type.We can then upcast the result by instantiating the union type to ⟨∀ .→ ⟩ (since each side is a subtype of it) and use that type in T-Asc.
Example 3.4.Consider again the fooLet function from Section 2.7.Observe that we cannot type g polymorphically enough because it lacks a type annotation on f to that effect.

Metatheory
To ensure the soundness of F { ≤ } , we translate our terms into valid System F terms.System F is a very general declarative type system of so-called implicit coercions (which are essentially polymorphic subtyping assumptions parameterized with a context), designed by Cretin [2014]; Cretin andRémy [2012, 2014].The soundness of our translation is stated in the following theorem: The definition of type translation ⟦ ⟧ and the proof of the above theorem are enclosed in the technical report version of this paper.While the theorem only allows translating terms well-typed in the empty context, a term well-typed in an arbitrary Γ can be translated after wrapping it in abstractions to capture its free variables.

TYPE INFERENCE
We now formally describe the SuperF type inference algorithm.

Restricted Polar Syntax
The syntax of types used by SuperF is presented in Figure 4. We now separate positive types + from negative types − .This is reminiscent of the way that Le Botlan and Rémy disallow polymorphic types with non-trivial bounds "below arrows" in Shallow ML F [Le Botlan and Rémy 2009]. 17We require that all type variables quantified in negative positions be boundless.Notice that negative types are a subset of positive types, so negative types can be used in positive positions.Type variables , include rooted variables .Each is a distinct, standalone variable with two components: a name and a root .We have FV ( ) = { }.We use the root as metadata to ensure type inference terminates (see Section 4.3).As a shorthand, we define ≜ .18

Type Inference Rules
The type inference rules of SuperF are presented in Figure 5. Judgment Γ ⊢ : + ⇒ Δ is read "under context Γ, term can be typed at + by generating constraints Δ".The constraints in the output context Δ are not resolved yet and may thus be inconsistent.The subtype constraining judgment , ⊢ Ξ ≫ Δ ≫ Σ ?, read "under rigid variables , skolems , and assumptions Ξ, the constraints Δ produce a set of bounds or an error Σ ?" and presented in the next section, is used to ensure that they are not.Type inference derivations make use of freshness premises.We only consider well-formed derivations, which are those where ' fresh' occurs at most once for each .Rule I-Top, I-Var, I-Asc, and I-Let are straightforward.Rule I-App infers the types of both parts 1 and 2 of an application 1 2 , assumes some function type + 2 → for the former (where is a fresh type variable), and finally returns as a result along with the constraint that the type inferred for 1 indeed is a subtype of + 2 → .Rule I-Abs is the most interesting.It type checks the body of a lambda in a context Γ extended by ( : ) where is fresh, which so far is standard.Then, it resolves the inferred constraints Δ, reducing them down to some bounds Ξ.These comprise bounds on the type variable and on type variables created during lambda body typing or while resolving the constraints, as well as bounds on outer type variables (bound by other lambdas).The goal is to generalize the resulting polymorphic type "as much as possible" without leaking references to locally-quantified type variables into the outer polymorphic context, as we explained in Section 2.4.The rigid variable set contains all variables which should not be bound in the abstraction's type, and which will be bound in some outer context.This includes all type variables already in Γ, as well as additional fresh variables which are used by extrusion as approximants (see Section 4.3).To ensure constraining terminates, some type variables in Ξ may have been ascribed with roots; these ascriptions are unnecessary after subtype constraining is done, so they can be removed by uproot.
Definition 4.2 (Context splitting).Let us generalize the set difference operator '\' to contexts.We split a context between outer bounds (those that mention only outer variables) and the rest using:

Subtype Constraining
The subtype constraining rules are defined in Figure 6.
Constraining algorithm.We first describe how to interpret the subtype constraining rules as an algorithm.Our algorithm uses the first variable context both as an input and an output: we input rigid variables from the context ( ) and get back approximants from extrusion ( ′ ), combining both in the judgment as a single context ( • ′ ).The former is an immutable set of rigid variables while the latter is a supply of rigid variables to be used as approximants in extrusion, formally represented by the subset premises in I-Abs and C-Forall-R.In an implementation of constraining, ′ would be a mutable set.In a constraining derivation ( • ′ ), ⊢ Ξ ≫ Δ ≫ Ξ ?, ′ and Ξ ?are the only output; everything else is an input.The order in which the constraining rules are applied does not affect the correctness of the corresponding constraining judgments, but our algorithm always applies the rules in the order given in the figure.(Crucially, C-Forall-R should be applied before C-Forall-L when possible, as explained in Section 2.5.)In order to algorithmically construct a derivation of constraining given the inputs ( , ′ , , Ξ, Δ), we begin by attempting to use the first rule whose conclusion shape matches the inputs.If the matching rule has constraining premises, we recurse on them.If deriving any of them results in err, the algorithm backtracks and uses C-Fail to construct the derivation.Otherwise, we construct a successful derivation.In particular, if the SRLC (premise of C-Flex-L/C-Flex-R) fails, the output is err.
Given rigid variables , skolems , and assuming bounds Ξ, the constraints in Δ can be solved by introducing new bounds Ξ ?(if Given rigid variables and skolems , the extrusion of ± is ± in Σ. X-1 Fig. 6.Subtype constraining and extrusion rules of SuperF. Acyclicity.For type inference to be sound, the constructed bounds graphs must remain acyclic, because F does not support "full" recursive types (i.e., ones where recursive occurrences may appear in bounds).We define the semantic acyclic check for this purpose in the technical report version of this paper.That definition does not count two kinds of "spurious" cycles: • Immediate type variable bound cycles.Indeed, the variables involved in an immediate cycle are simply equivalent (and could be unified).For instance, the judgment acyclic(( ≤ ) • ( ≤ )) holds.A type like ∀ { ≤ , ≤ }. ( → ) → ( → ) is equivalent to ∀ .( → ) → ( → ) and does not contain a 'real' or semantic cycle.
. Indeed, such syntactically-cyclic contexts can always be rewritten into equivalent acyclic ones.For example, here, ∀ .(( → ) ∧ ) → is an equivalent type for . .Constraint annotations.Constraints have the form + ≤ − , where is a set of subtyping relationships, some possibly guarded by , which are currently in the process of being constrained.This annotation has two uses: First, it allows catching, in C-Skip, constraints that go through immediate type variable cycles (consider what happens with, e.g., Δ = ( ≤ ) • ( ≤ ) • ( ≤ ⊥)).Note that ∈ does not look past in , so that C-Skip is prevented from triggering on previously seen constraints after going through a type constructor (see how C-Fun adds in its sub-constraints).Indeed, we do not wish the constraining algorithm to admit recursive types because our declarative system F { ≤ } does not support them, but this would happen if we did not guard the constraints in while going into type constructors.The effect of is to prevent C-Skip from picking up constraints that are being solved at an outer constructor level.Second, it allows the SRLC to catch constraints applying on roots currently being constrained.The reason why cannot be a simple context in the constraining judgment is that constraining is specified as a "tail-recursive" worklist algorithm, where subderivations do not necessarily correspond to implied constraints, since they also concern "sibling" constraints in Δ.We also write ( + ≤ − ) as a shorthand for + ≤ − .Constraining rules.Rules C-Empty, C-Top, C-VarRefl, and C-Fun are straightforward.There are four nontrivial rules for type variables.C-Flex-L and C-Flex-R register new bounds on locallyflexible type variables and traverse the transitive consequences of these bounds.They perform a cyclicity check to ensure that cyclic bounds such as ≤ → are rejected and a roots-check to guarantee termination.Moreover, they augment the sub-constraint annotations with the bound currently being propagated.Finally, they output as part of the result.C-Rigid-L and C-Rigid-R register extruded bounds on locally-rigid variables.Locally-rigid variables are those bound by outer lambdas as well as those currently rigidified by an outer application of C-Forall-R; extrusion ensures that inner variables do not leak through the bounds of outer ones.C-Forall-L instantiates the type variables and bounds of a polymorphic type found on the left.C-Forall-R skolemizes a quantified variable found on the right-hand side, as described in Section 2.5.We first compute the set of free variables ′ involved in the subsequent + ≤ − comparison in order to locally rigidify them.We recurse on that comparison, which gives us the intermediate result Ξ.Finally, we continue constraining, adding the delayed rigid variable bounds outer ′ (Ξ) as new constraints to solve.We can discard inner ′ (Ξ) because these bounds only concern purely local variables that were created while constraining the skolemized right-hand-side type.For instance, when constraining ∀ .→ ≤ ∀ .→ , these inner bounds will contain ′ ≤ ′ and ′ ≤ ′ , which are irrelevant outside the subderivation.Note that there is no need to relate ′ and : it is enough to only have all free type variables in + ≤ − .We suspend the constraining of any type variables other than those that are locally introduced while instantiating nested polymorphic types.In the second subderivation, we resume constraining the delayed bounds on ′ induced by + ≤ − .Extrusion.Extrusion is a critical part of polymorphic type inference.Its task is to avoid adding to a type variable bounds that are "more polymorphic" than the type variable itself.We distinguish between locally-rigid type variables and skolems : the former are less polymorphic than the current polymorphism level, while the latter are more polymorphic.Skolems are introduced by C-Forall-R and represent arbitrary future instantiations; less polymorphic types should thus not be allowed to mention them.Rules C-Rigid-L and C-Rigid-R use extrusion to ensure that the bounds added to rigid type variables mention neither flexible variables nor skolems.Extrusion itself is defined with two rules: X-1 and X-2.The first one simply filters out types that already only mention rigid variables.In X-2, set is the list of flexible variables to extrude.Flexible variables are "more polymorphic" than rigid variables, thus flexible variables should not appear in the bounds of rigid variables.Thankfully, we are allowed to add new bounds to flexible variables, so that we can create less polymorphic approximants for them to replace their uses in the bounds of rigid variables.An approximant is related to the original polymorphic type variable through the latter's bounds.Concretely, each ∈ is extruded into a lower approximant (for the negative occurrences of ) and an upper approximant (for the positive occurrences of ), and we register the bounds ≤ , ≤ as constraints -these should be viewed as bounds registered on , as the approximants cannot themselves refer to the more polymorphic in their own bounds.Notation [ − ↦ → , + ↦ → ] denotes the corresponding polar substitution.The X-fresh premise denotes that each is always approximated into the same approximants in the whole constraining derivation and that each approximant is an otherwise fresh rigid variable.We also use an X-ok premise to ensure that the bounds of universal types remain consistent after extrusion.In contrast to flexible variables, skolems are "too polymorphic" and their occurrences can only be approximated to either ⊤ or ⊥, depending on each occurrence's polarity.We cannot register approximants for skolems because we are not allowed to add bounds to skolems, which denote unknown, arbitrary types.Both skolems and flexible variables are approximated by extrusion such that the rigid variable bound resulting from C-Rigid-L and C-Rigid-R is at least as constraining as the corresponding type from the original constraint.For instance, if is rigid and is flexible, then the constraint ≤ ⊤ → results in a bound ≤ ⊤ → such that ≤ , where is the lower approximant of , a special locally-rigid variable.The approximant is bound at the same polymorphism level as , making it equally polymorphic as and generalized alongside it, for example in I-Abs (see the description of I-Abs in Section 4.2).Approximants always end up being bounded by the opposite bounds of their root variable: the lower approximant of inherits 's upper bounds, and the upper approximant of inherits 's lower bounds, which results from adding the approximant bounds as constraints to be solved later (rather than as solved bounds).This ensures that bounding a flexible variable with its approximants yields consistent bounds, correctly making constraints like → ≤ ∀ .→ fail.
Example 4.3.Assuming : (∀ .→ ) → , notice that let = .in ( . ) can be typed in SuperF because ∀ .→ is inferred as the type of and subsequently used parametrically in .On the other hand, ( .( )) ( . ) cannot be typed by SuperF because we never assume parametrically-polymorphic types for parameters.In this case, the type variable assigned to and the type variable assumed for the result of the application are leaked outside the scope of the abstraction because they are involved in constraint ≤ → , where is the type variable assigned to .Because of this constraint, both and need to be extruded to the lower polymorphic level of .Therefore, .
, whose inferred type is, → , cannot be assigned the polymorphic type required by .Note also that after -contracting .
Termination and the SRLC.The Suspiciously Recursive-Looking Criterion (SRLC) is formally represented as the two roots premises in C-Flex-L and C-Flex-R.As explained in Section 2.5, the goal of the SRLC is to ensure that for all inputs, deriving subtype constraining always terminates.

Distributivity
Distributivity, which is supported by F { ≤ } , adds flexibility to the type inference system.The basic idea of using distributivity in type inference is described in the two following rules: C-Forall-Distr-R is straightforward: it simply pushes quantifiers out of arrow-type right-hand sides so as to make skolemization happen as early as possible.C-Forall-Distr-L does the reverse, pushing quantifiers into arrow-type right-hand sides to delay their instantiation.The latter is more complicated to specify because it has to deal with splitting multi-bound quantifiers, filtering out those type variables whose bounds transitively refer to the arrow type's left-hand side and which can therefore not be distributed.These rules are straightforwardly generalized to look deeply inside arrow types, so that they can push quantifiers across multiple nested arrow types; while this is implemented in our prototype, we omit the formal details here for the sake of brevity.
If type inference infers a type for a closed term, we obtain a typing that can be translated to System F .We state the accompanying correctness theorem of constraining as follows: Theorem 4.7 (Soundness of Constraining).Let , ⊢ ≫ Δ ≫ Σ ′ and Σ = uproot (Σ ′ ).Then for all where FV ( ) # and = FV (Σ) \ { • } we have This theorem is only stated for derivations whose assumptions are empty ; this matches how constraining is used in I-Abs and C-Forall-R.Intuitively, the results Σ of such derivations satisfy two properties: they entail (or resolve) the input constraints Δ, and their inner flexible variable bounds inner (Σ) are consistent if we assume the outer rigid variable bounds outer (Σ).These properties hold for any skolem replacement [ ↦ → ], which is crucially important for C-Forall-R.We prove Theorems 4.5 and 4.7 in the technical report version of this paper.

PRACTICAL IMPLEMENTATION OF SUPERF
In this section, we describe MLscriptF, our implementation of SuperF in the existing MLscript language, and empirically evaluate it on existing test suites and examples from previous work literature.There are a few inessential differences between MLscriptF and SuperF as formalized in Section 4, the main ones being that MLscriptF: • supports type checking recursive definitions, which is not shown in Section 4 but is straightforward: we bind the definition's name to a fresh type variable while typing the definition's body and then constrain that type variable to be a supertype of the inferred body type.We also refrain from generalizing the functions constituting the bodies of these recursive definitions, as that would lead to polymorphism extrusion and unnecessary failures. 19 19 Another way of understanding this is that we do not support type inference for polymorphically-recursive functions.
• does not generalize lambdas nested under other lambdas (as in curried functions); doing so is redundant since distributivity can always be used to push polymorphic types back inside arrow types when needed.More subtly, we also do not want to incur too much polymorphism in the presence of recursive terms, as that would degrade the type checker's performance.
• uses explicit polymorphism levels to track extrusion and type avoidance, as described in Section 2.4.This can be seen as an optimization (to minimize the number of type traversals) that does not affect the functional properties of the system.
• has top-level def bindings with call-by-name semantics which are always generalized.

Implementation and Validation on MLscript Test Suite
We have implemented our approach in MLscript, a new ML-family language currently in active development.The MLscript codebase already had a significant amount of regression tests written for it, amounting to about 8000 lines of test code (not counting comments nor blank lines), which runs (in parallel) in about 7 seconds on a recent x86 MacBook laptop. 20Many of these tests produce statistics, such as the number of calls to the subtype constraining method, to check that the amount of work remains reasonable.About 70% of these tests were written before implementing first-class polymorphism (FCP); they test various aspects of the language through small functions constructed in it.This makes us confident that the addition of our FCP technique to an existing language is practical and does not introduce performance problems in the form of, for example, pathological cases that would blow up the time spent type checking a given file.

Validation on ML F Test Suite
The test suite of ML F was graciously provided to us by its authors Le Botlan and Rémy [2009].It provided us with a lot of interesting and tricky examples, which SuperF all managed to type check.Because ML F is the previously-unbeaten champion in FCP type inference expressiveness, we are quite satisfied that we could handle its test suite better than ML F itself, which required manual type annotations in more places.

Validation on Existing OCaml Code
We have experimentally evaluated our claim that SuperF subsumes ML type inference by porting the List module implementation from OCaml's standard library (list.ml),which is about 500 lines of non-empty, non-comment lines of ML code.Our implementation • did not require any type annotations; • inferred relatively concise/compact and readable types; • inferred types that were often more precise than the OCaml ones, thanks to subtyping; and • inferred types which were successfully checked against the explicit type signatures provided in the OCaml standard library's list.mliinterface file.
These claims are substantiated in supplementary material (see the file OCamlList.md).SuperF seamlessly handles all these previous examples without the need for any type or other polymorphism/freezing annotations, with or without distributivity.

RELATED WORK
We now discuss some related work.
First-Class Polymorphism in ML-style languages.First-class polymorphism has been studied extensively in the setting of ML-style languages, where polymorphism is normally implicit.Historically, first-class polymorphism was achieved via boxed polymorphism.Poly-ML [Garrigue and Rémy 1997] and its predecessors [Läufer and Odersky 1994;O'Toole and Gifford 1989;Rémy 1994] augment ML type schemes with boxed polytypes which wrap type schemes into simple ML types, preventing implicit instantiation.Boxed polytype introduction and elimination require explicit annotations, which may be quite burdensome.In these systems, the order of the terms being processed may affect the result of type inference, and proposed solutions to this typically either introduce an exponential blowup or reject many otherwise valid programs.ML F [Le Botlan and Rémy 2003] has been the "undefeated champion" of type inference for first-class polymorphism (until now).ML F uses a more flexible type language than System F that includes lower-bounded quantification, whereby types are ordered based on their polymorphism "power", akin to our subtyping relation.In ML F , lambda parameters that are used polymorphically require annotations, since type inference does not guess second-order types.SuperF does not guess second-order polymorphic types either, but uses multiple bounds (which can be represented as intersections) to accommodate finitarily-polymorphic uses of unannotated parameters.Le Botlan and Rémy note that ML F infers principal types for types which are typeable in their system, but that typing some terms may require type annotations -and in fact, different annotations may lead to incomparable principal types.For example, they remark that .
is not typeable in ML F while both ( : ∀ .) and ( : ∀ .→ ) are, but neither has a more general type (their types are unrelated).On the other hand, .
is typeable in SuperF, inferring type ∀ .(( → ) ∧ ) → (i.e., ∀ { ≤ → , ≤ }. → or equivalently ∀ { ≤ → }. → ), which is a subtype of both ML F types mentioned above.Function parameters are contravariant in SuperF but are invariant in ML F .Contravariance in the subtyping relation is crucial to type inference.For example, consider the two terms = ( : id → auto ).and = ( : auto )., where = ., id = ∀ .→ and auto = id → id .The application is typeable in a straightforward manner in SuperF thanks to subtyping.The same term is, however, not typeable in ML F .Moreover, SuperF allows multiple bounds on the same variable, including multiple upper bounds and multiple lower bounds, whereas ML F only allows one lower or rigid bound on a variable.Supporting multiple bounds for each type variable allows us to effectively infer intersection types in negative positions (such as function parameters) and union types in positive positions (such as function results).Rémy and Yakobowski [2007] proposed a graphical representation of ML F types that allows for an efficient unification algorithm.Le Botlan and Rémy further investigated a "shallow" version of ML F where type annotations and rigid bounds are restricted to System F types [Le Botlan and Rémy 2009] which, while less expressive, still has interesting properties, and more importantly admits simpler semantics and metatheory.Finally, Rémy and Yakobowski [2012] designed xML F , a "Church-style" intermediate language for MLF which would be amenable to intrinsically-typed compilation as in compilers like the Glasgow Haskell Compiler (GHC).HML [Leijen 2009] is a simplification of ML F restricting type variable bindings to flexible bounds (discarding rigid bounds).HML retains much of the expressiveness of ML F but requires annotations on all polymorphic function arguments, which is marginally higher than the requirement in ML F .The author believes that this requirement is justified by the simpler types they work with.Like ML F , HML is robust against most program transformations, including abstracting and inlining let-bindings, and abstraction with higher-order functions (most notably with app and revapp).An exception to this is -expansion, which may require a type annotation on a polymorphic parameter type.The author believes that robustness is an important property as it forms the basis of equational reasoning.As for the comparison with ML F , SuperF supports many of these robustness properties, though not all, due to its extra expressiveness (see Section 2.8), but it also supports stability properties not available to ML F and HML.HMF [Leijen 2008] is another, more drastic restriction of ML F discarding flexible bindings, i.e., a simple extension of the Hindley-Milner type system with System F types, supporting type inference through a straightforward extension of algorithm W. The author believes that a simple type system is beneficial in that it eases the burden on programmers in understanding the types, and in simplifying the metatheory and the implementation of type inference.To achieve this, HMF always predicatively instantiates ambiguous applications eagerly.Annotations are thus required when impredicative instantiation is needed, in addition to the usual annotation requirement on function parameters that are used polymorphically.This has the adverse effect of introducing a dependency of typability on the order of parameter, which is alleviated in some cases by considering application chains all at once, instead of treating each of their constituent applications individually.This system has the major drawbacks that type inference is less stable than in ML F -style systems.FPH [Vytiniotis et al. 2008] is yet another proposed type inference system which uses System F types but can still be understood as a restriction of ML F .QML [Russo and Vytiniotis 2009] extends ML types with universal and existential quantified types, which co-exist with type schemes.While type schemes are implicitly introduced by let-bindings and eliminated when applications happen, quantified types are explicitly introduced and eliminated by type annotations.For example, let = .in will be typed as a type scheme Π( )( → ).To have a first-class-polymorphic type, an explicit type annotation is necessary as let pid = {∀ .→ } in pid, here pid has the type Π( )(∀ .→ ).On the other hand, it is also necessary to provide an explicit type annotation to eliminate this quantified type as (pid {∀ .→ }) 3. Like many systems, QML also does not guess polymorphic types for function parameters.Parameters can only be polymorphic when they are annotated with quantified types.As an example, let poly = .let = {∀ .→ } in ( 1, true) in poly has the type (∀ .→ ) → ( , ).The treatment of existentially-quantified types is similar, requiring explicit type annotations to introduce and eliminate these types.FreezeML [Emrich et al. 2020[Emrich et al. , 2022] ] introduces a "freeze" operator to explicitly disable instantiation.Otherwise, like in ML, polymorphic types are implicitly instantiated.For example, single where single : ∀ .→ List and : ∀ .→ is typed as List ( → ) since 's type is implicitly instantiated.In contrast, with frozen using syntax ⌈ ⌉, then single ⌈ ⌉ is typed as List (∀ .→ ).With the frozen operator and some macro expansions, users may explicitly control whether the types of terms are generalized or instantiated.FreezeML's type inference algorithm extends the traditional algorithm W and achieves principal type inference of first-class polymorphism.However, it requires a significant number of annotations for polymorphic code: users need to manually control the explicit freezing, instantiation, and generalization of polymorphic types, in addition to the need of annotating the full type of polymorphic function parameters, which altogether amounts to a significant burden for programmers.It seems SuperF is more general than FreezeML -indeed, we conjecture that most (if not all) FreezeML programs can be typed by SuperF even after stripping all their freezing and instantiation annotations.For example, in FreezeML, the example E2 can only be typed with two explicit operators (as $( .(ℎ))@ ) while SuperF requires no annotations at all.Table 2 in appendix shows that such annotations are required for the majority of examples from the existing literature, although these examples are all quite simple.FreezeML annotations, while more concise than having to specify full types, are arguably not very intuitive.The E2 annotations above are needed to explicitly adapt the quantification of ℎ from → ∀ .→ to ∀ .→ → , so it becomes compatible with a type that only happens to flow into the same result variable.Users are effectively reduced to retyping by expansion and adding freezing/instantiation in a way that may seem quite obscure to whoever reads the code afterward.Furthermore, FreezeML does not support reordering quantifiers implicitly.For example, ∀ .→ and ∀ . → and ∀ .→ are all considered distinct, incompatible types [Emrich et al. 2022].One may use explicit generalization to restore a canonical order of quantifiers, but this still results in a quite inflexible system where many valid programs fail due to syntactic details, rather than semantic ones.While FreezeML can type the foo examples from the introduction using various sets of annotations, it cannot assign a principal type to it, so there is no best set of annotations to use for that example.In particular, no set of annotations can let one type check both foo (fun x → x) and foo (fun x → Some x) with the expected result types, whereas our system type checks both "out of the box", without any annotations.Boxy types [Vytiniotis et al. 2006] were used to implement GHC's original ImpredicativeTypes extension.The system incorporates bidirectional type inference by distinguishing types to be inferred and types to be checked using boxes.Boxy-types retains the idea of not guessing polymorphic types, but refines it by inferring polytypes for function arguments with locally known type information.The system as presented was very fragile against local transformations and thus difficult to use in practice, which is why it was eventually replaced by the more predictable Quick Look.Guarded instantiation (GI) [Serrano et al. 2018] focuses on simplicity and tries to balance between complexity, expressiveness, and annotation burden.In GI, polymorphic instantiation only happens on guarded type variables.The guardedness is decided in function applications by examining whether the type variable is guarded by type constructors in any parameter types, for example both type variables in ∀ .( → ) → List → List are guarded but neither in ∀ .→ is.Unguarded type variables may only be instantiated by monomorphic types or polymorphic types without top-level quantifiers, such as Int → ∀ . .Note that only the types of those parameters that are given corresponding arguments are considered.As an example, in append where append : ∀ .→ List → List , though is guarded in the second parameter type, in this partial application only the first parameter type is considered for the guardedness, so cannot be polymorphicly instantiated.In append , the second argument is now given so is considered guarded and therefore may be instantiated polymorphicly.Type instantiation in GI takes no account of the context of the call and each argument in a call is treated independently between different function applications.As usual, for lambdas, GI will not guess polymorphic parameters.Parameters may only be polymorphic when annotated.Quick Look [Serrano et al. 2020] is a type inference system for Haskell that further simplifies the first-class-polymorphic type system.The authors claim that it is eminently practical for Haskell programming.However, it is quite inexpressive when compared to most systems mentioned previously, and in particular it removes all forms of subtyping (so-called "deep subsumption") from Haskell, needing manual expansions instead even to just reorder or distribute quantifiers.Since the official support of Quick Look in GHC around 2021, users of GHC have already complained about its inflexibility, as it makes them manually eta-expand code in many places where they did not need to before and where it appears "clear" that the compiler should be able to figure things out. 21Predicative polymorphic inference is much easier than the full first-class polymorphism setting.Odersky and Läufer [1996] originally demonstrated this approach in their seminal work on type inference for higher-rank polymorphism.Many works have followed up on the idea, bringing various improvements [Cui et al. 2023;Dunfield andKrishnaswami 2013, 2019;Peyton Jones et al. 2007;Vytiniotis et al. 2006;Xie 2021;Xie and Oliveira 2018;Xue and Oliveira 2021] many of which relying on the idea of bidirectional typing [Dunfield and Krishnaswami 2021;Dunfield and Pfenning 2004].
Stability considerations.Le Botlan and Rémy [2009] originally investigated the stability of first-class polymorphic type inference in the context of ML F .As Table 1 shows, we support most of their stability guarantees and some more.Bottu and Eisenberg [2021] recently tried to characterize such a notion of stability for Haskell.They developed 6 "similarity" properties and showed that in the context of GHC, all combinations of shallow or deep and eager or lazy instantiation break some of these similarities, though the lazy and shallow combination breaks the least.Of these similarities, one does not hold in SuperF due to the restricted language of annotations (similarity 2, about inferred types being usable as type signatures).The rest of these similarities trivially pass the stability criterion in SuperF, although SuperF is deep (and not shallow) as well as being lazy. 22  Type Inference for System F. Boehm [1985] and Pfenning [1988] proposed partial type inference procedures based on second-order unification which is undecidable but has a practical semialgorithm due to Huet [1975].Their systems required placeholder annotations (without type information) for all type abstractions and type applications, which they called a partial type inference approach.Mitchell [1988] introduced the now-usual notion of type containment for System F, a notion of subtyping involving polymorphic types (what was known as subtyping at the time did not include polymorphic subtyping).He showed how to make System F complete with respect to expansion in System F , meaning all terms that System F can type after expanding some of their subterms are typeable in System F through subsumption.Type inference for System F was later shown undecidable, due to an undecidable type containment relation Wells [1996].Rémy [2005] proposed a simpler partial type inference system for System F based on a similar notion of type containment, but the approach was not very satisfactory, pushing Rémy to investigate the more promising appraoch of ML F .In parallel, there has been much work on systems like F <: which combined first-class polymorphism, type variable bounds, and subtyping, and even also sometimes included intersections [Pierce 1997].However, these systems usually assume explicit polymorphism, whereby polymorphic types have to be introduced and eliminated in terms, and where subtyping between them is "structural, " i.e., there are no relationships between types such as ∀ a. a → a and Int → Int.This is by contrast to the traditional ML approach to polymorphism, which is implicit.In this paper, we follow the ML tradition of implicit polymorphism, which is more appropriate as a foundation for generalizing the ML type system to first-class polymorphism.More specifically, we follow an approach closely inspired by algebraic subtyping [Dolan 2017], and its reformulation as Simple-sub [Parreaux 2020], including its novel approach to nested polymorphism with extrusion in the presence of subtyping.
Polyvariant Flow Analysis.Palsberg and Pavlopoulou [1998] showed that polyvariant analysis (also known as context-sensitive analysis) can be related formally to a subtyping system with union, intersection, and recursive types.Unions model sets of abstract values and intersections model each usage of an abstract value.Their system conspicuously does not feature polymorphism.Faxén [1997]; Smith and Wang [2000] propose inferring polymorphic types rather than intersections for function definitions, which is more flexible and composable as it can process unrelated definitions separately, whereas the approach based solely on intersections is a global process.Smith and Wang develop a polymorphic system with subtype constraint solving designed for flow analysis.Their constraint closure rules are evocative of our C-Flex-L/C-Flex-R, C-Fun, and C-Forall-L.However, they have no rule analogous to C-Forall-R, nor do they need any notions of type avoidance and extrusion, which are proper to a type inference view of constraint solving (as opposed to the restrictive view of flow analysis).They give functions polymorphic types that contain all locallyinferred unsolved subtyping constraints. 23The fact they do not solve constraints locally means they do not check for the consistency of inferred types (so they may fail to report type errors at definition 22 The reason Bottu and Eisenberg feel the need to be shallow in their approach rather than deep can be attributed to the presence of Haskell-specific features in their system, such as explicit type applications, and to their lack of subtyping. 23Faxén proposes simplifying these constraints incrementally, but this does not change the problem fundamentally.
When Subtyping Constraints Liberate 48:29 sites) and it means their constraints are resolved over and over again every time the polymorphic types that capture them are instantiated.Similarly to us, their basic system is non-terminating and they introduce a termination condition which "detects a certain kind of self-referential flow in the constraints".They show that termination can be guaranteed by merging some instantiations in this case.Wang and Smith [2001] adapt this approach to object-oriented programming but abandon the first-class polymorphism part.Rehof and Fähndrich [2001] also rely on polymorphic subtyping; they design a control-flow analysis that can be reduced to the problem of context-free language reachability.They represent constraint sets as substitutions to avoid copying constraints around and obtain better scalability.Whether this insight can be applied to SuperF remains to be determined.
Intersection and mixed type systems.Jim's polarized type system called "P" [Jim 2000] mixes universal quantification with intersection types in a way that strongly resembles ours, restricting the former to positive type positions and the latter to negative type positions.However, to retain decidability, the types of P are much more restricted syntactically.For example, they do not admit universal quantifiers nor intersections on the right of arrow types.P has special typing rules for variable and lambda applications.The former uses in its premise a rewritten lambda term (not a subterm of the term being typed), which makes is difficult to explain and characterize welltyped terms on an intuitive level. 24Moreover, since the syntax of negative types does not include polymorphic types, users cannot check their programs against provided System F type signatures.
Implicit Coercion Constraints.Motivated by the goal of designing a type system that could generalize all previous bounded quantification approaches (mainly ML-style constrained types [Odersky et al. 1999], System F <: [Pierce 1991], and ML F [Le Botlan and Rémy 2003]), Cretin and Rémy [2014] developed a calculus of implicit coercions called System F .Coercion constraints are very expressive and designed in a way that is more general than bounded quantification -notably, they do not require the use of a dedicated typing rule for generalization, lifting generalization to the coercion relation (which is a form of generalized subtyping).Thankfully, it is straightforward to encode our multi-bounded polymorphism type system into F .Scherer and Rémy [2015] later investigated versions of coercion constraints allowing abstracting over possibly-inconsistent coercions, to support applications like GADTs [Xi et al. 2003].

CONCLUSION AND FUTURE WORK
We presented SuperF, a novel type inference algorithm for first-class polymorphism based on multi-bounded polymorphism.SuperF is uniquely expressive, stable under small program changes, and allows for understandable error messages to be reported.We also presented F { ≤ } , a simple type system that serves as the declarative basis of SuperF and that is founded on the theory of implicit coercions from the existing F .SuperF was implemented as part of a real-world programming language and evaluated on previous test suites as well as examples from the literature.
SuperF could serve as a starting point for more advanced type systems features, such as higherorder subtyping, existentials and generalized algebraic data types (GADTs), as well as dependent types.For example, SuperF could provide an adequate algorithmic basis to the cDOT calculus of Boruch-Gruszecki et al. [2022] and to the ∀ calculus of Xue and Oliveira [2021], who left algorithmic formulations of their systems for future work.Unlike SuperF, ∀ uses predicative polymorphism.Restricting SuperF to predicative type instantiation should be straightforward and may allow dropping the SRLC.We would also like to support unrestricted type annotations in the future, which may be helped by integrating some form of bidirectional typing.
foo : ∀ a b.(( Int → a) ∧ ( Bool → b )) → (a , b) which is really just syntax sugar for the following polymorphic type that incorporates several bounds for its extra type parameter c: foo : ∀ a b c {c ≤ Int → a , c ≤ Bool → b }. c → (a , b) foo : (∀ a. a → ?) → (? , ?)This leads us to a first problem: what type should the parameter function f be expected to return?
bar : ∀ a. a → (a ∨ ∀ b. b → b) which our implementation automatically simplifies by distributing the inner quantifier out: bar : ∀ a b. a → (a ∨ (b → b )) and which does not impose any premature decisions on the polymorphism of the x parameter and on the instantiation of id's type.SuperF correctly infers type ∀ b. (Int → Int) ∨ (b → b) for bar succ, which our implementation simplifies to Int → Int (because ∀ b. b → b is a subtype of Int → Int).

Figure 2 .
Rules T-Unit, T-Var, T-App, T-Subs, and T-Let are all standard.T-Abs extends the typing context with a boxed type for its parameter , which prevents the parameter from being used polymorphically without an annotation, as we shall see next.T-Unbox allows erasing leftover boxes whose variables are no longer in scope, using the box erasure syntax \ formally defined and exemplified in the technical report version of this paper.T-Asc expects the annotated expression to have type ⟨ ⟩, which is a type used to unbox polymorphic types, making them available for use.Rule T-Forall allows generalizing the type of Γ ⊢ :

Table 2 .
Emrich et al. [2020] the examples that we could gather from previous work on FCP.(Most of them were already summarized byEmrich et al. [2020]for their paper on FreezeML.)Weobservethat20Allrunning times in this section include the time to actually run the tests (which is done via JavaScript code generation and executed through nodejs), as MLscript is a real programming language and not just a type checker.Proc.ACM Program.Lang., Vol. 8, No. POPL, Article 48.Publication date: January 2024.Typeability comparisons in different systems.' ' means the term type checks; ' *' means its inferred type is not as general as it could be (assuming SuperF subtyping); ' ' means the term type checks only a er adding some type-free polymorphism annotations (e.g., FreezeML's freezing annotations); ' ' means a type error is raised. SuerF−D is SuperF without distributivity, while SuperF has distributivity.