Extension Overview
Now that we have Polyglot up and running, we can start implementing language
extensions. This section describes the example language we will use to explore
Polyglot features through the remainder of this tutorial.
Problems with array covariance
The covariance of array types in Java leads to performance overhead and
ArrayStoreException
exceptions.
Covariance of array types permits the following subtyping rule:
A ≤ BThat is, if A is a subtype of B, an array of type A is also a subtype of an array of type B. This subtyping rule allows the following program to type-check:A[] ≤ B[]
While the above subtyping rule is convenient for historical reasons, run-time type safety cannot be guaranteed without extra run-time checks, because the underlying array can only be assigned elements whose type is a subtype of the constructed array type. As a result, this program type-checks but fails at run time:
On line 5, the assignment to an element of
oa
will compile, because
elements of oa
is of type Object
, determined by its
declaration Object[]
, and we are assigning an Object
to oa[1]
. At run time, the underlying array represented by
oa
is in fact a String
array. Storing an object in a
String
array will result in a run-time error.
Let's develop a Java language extension called
CArray
that removes the
troublesome subtyping rule above, permitting array covariance only if
array elements cannot be modified. We describe the semantics of
CArray
below.
Introduction to CArray
The above example program shows that array covariance and array assignments can
interact in an undesirable way. A separation of these two properties will
eliminate the run-time error. We must enforce these rules:
- If assignments to array elements are to be allowed, arrays cannot be subtyped covariantly.
- If arrays are to be subtyped covariantly, their elements cannot be changed.
CArray
embraces both rules by (1)
disallowing subtyping in traditional arrays, and (2) introducing an array type
whose elements are constant, permitting array covariance.
An array whose elements are constant can be declared using keyword
const
before the first square bracket, such as
int const[]which denotes an integer array whose elements are not modifiable.
Multidimensional constant arrays are supported:
String const[][] csm = { { "Hello", "World" }, { "Kitty", "Cup" } };This code declares a constant array of constant arrays of
String
, whose elements (i.e.,
arrays of String
) are unmodifiable. That is, the keyword const
applies to both the inner and outer arrays.
Example CArray
programs
Constant arrays can be used as formal parameters. Elements in constant arrays
can always be read, but can never be written:
class CArrayAccess { int const[] a = { 1, 2, 3 }; int addNine(int const[] a) { a[2] = 6; // error, cannot change array elements return a[1] + 9; // okay } }Constant arrays are covariant, but nonconstant arrays are invariant:
class CArrayCovariance { String const[] csa; CharSequence const[] cca; Object const[] coa; String[] sa; CharSequence[] ca; Object[] oa; void foo() { cca = csa; // String const[] ≤ CharSequence const[] coa = cca; // CharSequence const[] ≤ Object const[] coa = csa; // String const[] ≤ Object const[] /* These assignments are illegal: ca = sa; // String[] ≤ CharSequence[] oa = ca; // CharSequence[] ≤ Object[] oa = sa; // String[] ≤ Object[] */ } }We can assign a nonconstant array to a constant array. This prevents updates to the array elements via the constant array. We cannot assign a constant array to a nonconstant array, however, or we would make nonconstant arrays covariant through a series of assignments:
class CArrayAssign { int const[] cia; String const[] csa; CharSequence const[] cca; Object const[] coa; int[] ia; String[] sa; CharSequence[] ca; Object[] oa; void foo() { cia = ia; csa = sa; cca = sa; // via covariance cca = ca; coa = sa; // via covariance coa = ca; // via covariance coa = oa; /* These assignments are illegal: ia = cia; sa = csa; ca = cca; oa = coa; If oa = coa were legal, these three assignments would make nonconstant arrays covariant! csa = sa; // okay coa = csa; // okay oa = coa; // Were this legal, transitivity would yield oa = sa! */ } void bar(int const[] a) { } void baz(int[] a) { } void quux() { bar(cia); bar(ia); baz(ia); /* This invocation is illegal: baz(cia); */ } }
The results of array initializers and array creation expressions are considered
nonconstant, as they can be assigned to both traditional and constant arrays:
class CArrayInit { int const[][] cim1 = { { 1, 2, 3 } }; String const[] csa = { "Hello", "World!" }; int const[][] cim2 = { new int[] { 1, 2, 3 }, new int[] { 4, 5, 6 }, new int[] { 7, 8, 9 } }; /* These initializations are illegal because of incorrect types: int const[][] cimx = { 1, 2, 3 }; String const[] csax = { { "Hello", "World!" } }; */ void foo() { int const[][] cim = new int[][] { new int[] { 1, 2, 3 }, new int[] { 4, 5, 6 }, new int[] { 7, 8, 9 } }; } }Constant arrays can be cast to other constant array types as long as the corresponding non-constant array types have that subtyping relationship in Java. (See JLS 3rd Ed, 4.10.3. This does not, for example, permit
int[]
to be cast to long[]
.)
Nonconstant arrays cannot be cast to nonconstant arrays of different types.
Constant arrays cannot be cast to nonconstant arrays; otherwise, we could make
nonconstant arrays covariant through a series of casts and assignments:
class CArrayCast { Object const[] coa; String const[] csa; Object[] oa; String[] sa; int const[] cia; void foo() { Object const[] coa1 = (Object const[]) csa; String const[] csa1 = (String const[]) coa; /* These casts are illegal: int const[] cia1 = (int const[]) coa; Object[] oa1 = (Object[]) sa; String[] sa1 = (String[]) oa; int[] ia1 = (int[]) cia; oa = (Object[]) coa; If oa = (Object[]) coa were legal, these three assignments would make nonconstant arrays covariant! csa = sa; // okay coa = csa; // okay oa = (Object[]) coa; // Were this legal, transitivity would yield oa = sa! */ } }