II. Co-contextual Type Checkers for Functional Languages
9. Co-contextual Featherweight Java
10.2. Co-Contextual Structures for Featherweight Java with Generics
context ∆ is created using these two sets of bounded type variables; ∆ = X <: N , Y <: P . Given this context ∆, the parameter and return types of the method m, and the generic parameters of the method are type checked to be ok. Next, the type checker encounters the body e0 of the method and ensures that its type is a subtype of the return type of m. Finally, it checks that the overriding of the method m is valid. The overriding is checked if a declaration of m exists also in superclasses of C. In FJ, a method m must have the same signature in all its declarations, in the class C or its superclasses. However, FGJ allows that the return type of the overriding method m in C is a subtype of the return type of the overridden method m in superclasses of C (covariant return types). Parameter types must be the same in all declarations of m. To ensure a proper overriding, the type checker uses the override function.
Finally, we consider the ruleGT-Classfor class typing. This rule has the same judgements
as the corresponding rule in FJ. The only difference is that classes have generic parameters, which construct the ∆. Then, the type checker ensures that all generic parameters, the superclass N of the class C, and the field types are ok under the context ∆.
10.2. Co-Contextual Structures for Featherweight Java with
Generics
In this section, we describe the required steps to construct a co-contextual type checker for FGJ. We use the dualism technique to remove the class table and all other contexts, and instead introduce the corresponding dual requirements. FGJ in particular introduces the bounds context ∆ in addition to the class table CT and the typing context Γ. We introduce a new set requirements dual to ∆ and extend the operations on requirements, with duals to the operations on ∆. Moreover, FGJ uses the well-formedness judgement, in addition to the judgements used in FJ. We construct the co-contextual well-formedness rules applying our dualism technique.
In the following, we first extend the previous co-contextual formulation of FJ in terms of constraints. We then discuss the new set of requirements and judgement.
10.2.1. Co-contextual Constraints
The set of constraints used in co-contextual FJ is extended with new constraints to support generics, which are shown below:
s ::= . . . | T ={X7→T }T | T ={U 7→T }T | T <:{X7→T }T | T <:{U 7→T }T | (T < T )opt We require new constraints of the form T1={X7→T2}T3 and T1<:{X7→T2}T3. The latter expresses that T1 is subtype of substituting T2 for X in T3. We define similar constraints for substituting unification variables. The subtyping substitution constraint T1<:{X7→T2}T3 is equivalent to the subtyping constraint S <: [V /Y ]U in FGJ. These
constraints are similar to the constraints introduced in parametric polymorphism (Chap- ter 4) and, hence, are not discussed in more detail in this chapter.
The last constraint (T <: T )opt is introduced to ensure a valid method overriding in the presence of generics, which allows methods to have covariant return types. We will discuss this constraint in more detail, when showing the co-contextual typing rule for method declarations.
However, the types of co-contextual FGJ include generic types in addition to the types described for co-contextual FJ. Generic types are the same as for FGJ, described in the previous section.
In the rest of this section, we construct and describe the additional set of requirements and judgement used by co-contextual FGJ. We apply our technique (dualism) to build a co-contextual FGJ type checker. Therefore, all contexts are removed; instead we introduce the dual structure of requirements. As shown above, FGJ uses the contexts Γ and ∆, and the class table CT while type checking. To construct a co-contextual type system, we remove the context Γ and class table CT , instead we introduce the dual structures of context and class table requirements correspondingly, as previously discussed for co-contextual FJ. The context requirements have the structure and operations as for co-contextual FJ. The class table requirements have the same structure as the class table requirements used in co-contextual FJ, with the difference that classes and methods are parametrized. Moreover, the operations of merging and removing requirements are adapted for the generic types, which we will describe in the next section.
10.2.2. Bounded requirements
In addition to Γ and CT , FGJ uses the context ∆ for type bounds. Therefore, we intro- duce the dual structure of bounded requirements BR, which are a mapping from types to types. These requirements as for the other sets of requirements have the operations of merging and removing. However, the merge and remove cannot be performed instantly, as in case of context requirements because bounded requirements are requirements on types. Because of nominal typing, as for the class table requirements, we do not know the actual type variables and their bounds. Therefore, we introduce conditions for these requirements. These conditions operate similar to the conditions in class table require- ments. The structure of bounded requirements is shown below:
BR = {(T T, cond)}
The operations on the bounded requirements are merge and remove. Merge of two sets of bounded requirements results in a new set of bounded requirements and a set of constraints:
mergeBR(BR1, BR2) = BRm|S
m
where the sets CRm and Sm are shown in Figure 10.4. Two bounded requirements are merged, if the required types variables are the same. The co-contextual type checker does not now the actual type variables because of nominal typing. The types T1 and T2 of the
10.2. Co-Contextual Structures for Featherweight Java with Generics BRm= {(T1 T 0 1, cond1∪ (T16= T2)) ∪ (T2 T 0 2, cond2∪ (T1 6= T2)) ∪ (T1 T10, cond1∪ cond2∪ (T1 = T2)) | (T1 T10, cond1) ∈ BR1∧ (T2 T20, cond2) ∈ BR2} Sm = {(T 0 1 = T 0 2 if T1= T2) | (T1 T 0 1, cond1) ∈ BR1∧ (T2 T 0 2, cond2) ∈ BR2}
Figure 10.4.: Merge operation on bounded requirements.
bounded requirements could still be unification variables U . Consequently, we cannot decide whether the two requirements can be merged or not. To solve this, we add type equalities and inequalities to the conditions to cover the cases when the type variables of two requirements are the same or not, respectively T1 = T2 and T1 6= T2. The first case indicates that the two requirements can be merged.
Adding a declaration for a bounded type variable is dual to removing the bounded requirements corresponding to that type variable.
removeB(X / N, BR) = BR0|S
where BR0= {(T T0, cond ∪ (T 6= X)) | (T T0, cond ) ∈ BR} S = (T0 = N if T = X) | (T T0, cond) ∈ BR
removeBR(X / N , BR) = BR0|S
where BR0= {BRr | X / N ∈ X / N ∧ removeB(X / N, BR) = BRr|S
r}
S = {Sr| X / N ∈ X / N ∧ removeB(X / N, BR) = BRr|Sr}
Figure 10.5.: Remove operation on bounded requirements.
Figure 10.5 shows the remove operation for a given bounded variable X / N . All bounded requirements in BR are considered and their conditions are updated. The type checker does not know the actual type variable corresponding to T , therefore, it updates the condition with the type inequality T 6= X. Moreover, we generate the conditional constraint S. Thus, if the actual type of T is learned to be X, then we have satisfied the requirement. Consequently, the bounded requirement is discharged because its condition cond ∪ (T 6= X) is unsatisfiable; (T 6= X) does not hold.
10.2.3. Co-Contextual Well-Formedness Rules
FGJ uses an additional well-formedness judgement. To systematically co-contextualize FGJ, we have to co-contextualize also the rules for well-formed types. We construct co-contextual well-formedness rules, which are dual to the rules shown in Figure 10.2.
The contextual well-formedness rules take as input the context ∆, class table CT and do not have an output. They only ensure that the types are ok. Applying dualism, we remove the context ∆ and the class table CT . The co-contextual rules output sets of bounded requirements, class table requirements, and constraints. Constraints are generated because our co-contextual formulations are constraints-based.
CWF-Object ` Object ok | ∅ | ∅ | ∅ CWF-Var U is fresh ` X ok | ∅ | ∅ | X U CWF-Class ` T ok | St| CRt| BRt Ud, Ux, Un are f resh S = {T <:{Ux7→T }Un} CRn= (C<Ux/ Un>.extends : Ud) CRSm = mergeCR(CRt, CRn) ` C<T> ok | St∪ S ∪ Sm | CR | BRt
Figure 10.6.: Co-Contextual Well-Formedness Rules.
The ruleCWF-Objectis straightforward. The rule CWF-Varchecks the well-formedness
of the type variable X. For X to be well-formed it should exist a bound of X in ∆. However, the co-contextual type checker cannot check the existence of X in ∆. To ensure that a declaration of X will be found in ∆, we generate a fresh unification variable and and assign it to X. X U is added to the bounded requirements. The ruleCWF-Class
checks the well-formedness of classes. Dual to looking up a class declaration in CT is introducing a requirements for the extends clause. The unification variables Ux and Un are placeholders for the type variables X and their bounds N , respectively.