II. Co-contextual Type Checkers for Functional Languages
9. Co-contextual Featherweight Java
10.3. Operations on Class tables Requirements
In the following, we describe the merge and remove operations for class table requirements with generic types.
10.3.1. Merge Operation
In co-contextual FJ, the merge of two requirements, which have the same field name or method symbol, is performed if the receiver classes of these two requirements have the same type. However, in FGJ, classes are parametrized and they are considered to be the same if and only if they have the same name and equal types on the generic parameters. This affects the merging of the class table requirements.
To illustrate the merge operation consider the following two field requirements CR1 = (T1.f : Tf, cond1) and CR2 = (T2.f : Tf0, cond2). Both requirements operate on the
10.3. Operations on Class tables Requirements CRm = {(T1.f : Tf, cond1∪ (T1 6= T2)) ∪ (T2.f : T 0 f, cond2∪ (T1 6= T2)) ∪ (T1.f : Tf, cond1∪ cond2∪ (T1= T2)) | (T1.f : Tf, cond1) ∈ CR1∧ (T2.f : Tf0, cond2) ∈ CR2} Sm = {(Tf = T 0 f if T1= T2) | (T1.f : Tf, cond1) ∈ CR1∧ (T2.f : Tf0, cond2) ∈ CR2}
Figure 10.7.: Merge operation of field requirements CR1 and CR2.
Figure 10.7 illustrates the result of merging the two field requirements. For the merge to succeed, both requirements must have the same receiver classes. However, the actual type of the receiver classes is not known because of the nominal typing. Hence, we generate type equalities and inequalities to foresee the cases when the receiver classes are the same or not (T1 = T2 and T1 6= T2), which are added to the conditions of the requirements. If the receiver classes are the same then the requirements are merged and a conditional constraint is generated, stating that the types of these two fields should be equal. The merge operation is similar to the merging in co-contextual FJ. However, in FGJ, we have to take care when we compare the receiver classes. In addition to the names, we have to compare the generic parameters of the classes. If the receiver classes are objects, then the instances of the generic parameters of the two receivers must be the same. Otherwise, the type variables of the generic parameters of the two receiver classes must be the same.
10.3.2. Remove Operation
We now adapt the remove operation for generic types. In the case of generics, a declared field can have as type a ground type as in FJ or a type variable. This type variable is one of the generic types of a parametrized class that declares the given field as a member. Hence, we have to consider these two cases while applying the remove operation for field clauses. Likewise, in the case of methods, the parameter and return types can be either ground types or type variables. These type variables are part of the generic types of the given method or the class, declaring the method as member. To illustrate the remove operation with generic types, we consider the remove of field requirements.
Figure 10.8 shows add operation for field clauses to the class table CT and the corresponding dual operation of removing field requirements. The field clause has the same structure as in FJ, namely C <X / N>.f : T . The remove operation finds the field requirements that have the same name as the declared field under scrutiny. Because of nominal typing, the requirement (T0.f : Tf, cond ) cannot be instantly removed from the requirements set given the declaration of the field f . That is, the type of the receiver class T0 is not known and we cannot decide whether it is equal to C, or not. Therefore, we update the condition by introducing the inequality (T0. name 6= C), which will indicate
adds(C<X / N>, T f , CT ) = C<X / N>.f : T ∪ CT
removeF(C<X / N>, T f, CR) = CR0|S
where CR0 = {(T0.f : Tf, cond ∪ (T0. name 6= C)) | (T0.f : Tf, cond ) ∈ CR} ∪ (CR \ (T0.f : Tf, cond )) S = ( T = Tf if T0. name = C if T.isGround T = getTypeF(C<X / N>, T0, Tf) if T 0 . name = C otherwise removeFs(C, T f , CR) = CR0|S where CR0 = {CRm| (T f ) ∈ T f ∧ removeF(C <X / N>, T f, CR) = CRm|S m} S = {Sm | (T f ) ∈ T f ∧ removeF(C<X / N>, T f, CR) = CRm|S m}
Figure 10.8.: Add and remove operations of field clauses.
that the requirement can be discharged if its condition is unsatisfiable. At the point in time, when we will know the actual type of T0, we apply substitution to the condition and can compare the two types.
However, in this comparison we ignore the generic parameters and only check if classes have the same name. We do so because the receiver class of the requirement contains parameters for the generic types. These parameters should be subtypes of the bounds of the generic types, but this check is done in ∆. While comparing the receiver class of f requirement and the class where f is declared to, we consider only their names. This name-based comparison is achieved via the auxiliary function name, e.g., C<X / N>.name = C. As a result, if T0. name = C, then we have found a corresponding declaration for the required field and know its actual type. Consequently, the requirement is removed. For a requirement to be removed from the requirements set its condition should be unsatisfiable, i.e., at least one of the type equalities or inequalities in the condition should not hold.
In order to deduce the actual type of the required field the conditional constraint S is generated. This constraint ensures that the receiver class of the required field has the same name as the class, where the declaration of this field belongs to. Depending on the type of the declared field f , we distinguish two cases, the field f has 1) a ground type, or 2) a type variable. For any of these cases to hold, the condition T0. name = C should hold. Then, the cases are scrutinized. In the first case, a conditional equality constraint is generated, as for co-contextual FJ and no further changes are required. In the second case, we use an auxiliary function to deduce the actual type of the required field, as shown below:
getTypeF(c : C <X / N >, obj: T, tf : X): Type= { if (T == C <X / N >)