Bug 112105 - summary of ITD generics work.
Summary: summary of ITD generics work.
Status: RESOLVED FIXED
Alias: None
Product: AspectJ
Classification: Tools
Component: Compiler (show other bugs)
Version: DEVELOPMENT   Edit
Hardware: PC Windows XP
: P2 normal (vote)
Target Milestone: 1.5.0RC1   Edit
Assignee: Andrew Clement CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2005-10-10 07:11 EDT by Andrew Clement CLA
Modified: 2005-11-25 08:42 EST (History)
0 users

See Also:


Attachments
Patch of some stuff I was trying out... (5.69 KB, application/octet-stream)
2005-11-01 05:14 EST, Andrew Clement CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Andrew Clement CLA 2005-10-10 07:11:40 EDT
 
Comment 1 Andrew Clement CLA 2005-10-10 07:16:16 EDT
First aim is to get the binary form on the disk correct, so that it captures all
the necessary information that was originally in the source code.  This will
enable the weaver to do the correct thing.  The kind of information that is
important relates to type variables and bounds for ITDs.

Problem1.  A type variable reference type was attempting to serialize the
declaring element for the type variable when an attempt was made to serialize
it.  Unfortunately what goes on to disk when a typemunger is written into the
weaver state info object is the collapsed form (collapsed to bounds) of the type
variables.  So for this "<T> C.m(T t)" we would write out the type variable
"Ljava/lang/Object;-1"  where -1 means we didn't happen to know the declaring
element.  When UnresolvedType is used to deserialize this, it sees
Ljava/lang/Object; and just reads in a regular type.  it leaves -1 on the stream
for the next poor bit of logic to crash into. The symptoms of this are a
StreamCorruptedException and NegativeArraySizeException.
I'm not sure why the declaring element needs to be written out as it should be
easy to determine for any given type variable - it is either a type variable for
the member, a type variable for the enclosing type or a type variable in the
target type (for an ITD).
Comment 2 Andrew Clement CLA 2005-10-10 09:34:06 EDT
Problem2.  Why on earth do ResolvedMembers capture their typeVariables are
'UnresolvedType[] typeVariables' - that suggests it is an array of
UnresolvedTypeVariableReferenceTypes or TypeVariableReferenceTypes.  The
reference types are suitable for return type or parametertypes but the set
recorded directly against the resolvedmember *must* be the real type variables.
 So I'm changing those to be 'TypeVariable[] typeVariables'.  This also means
changing (improving!) the serialization logic for typevariables - TypeVariable
now has proper write/read methods that record the name and bounds.  On
deserializing a resolvedMember we now recover typevariables directly rather than
TypeVariableReferenceTypes.  Also the resolve(world) method on resolvedmember
has been joined by a resolve(world,typevariablescope).  This method delegates to
the resolve method everywhere that type variables *arent* involved.  Where type
variables *are* involved the typevariablescope can be used to look up a type
variable that we only know by name and connect it to the one on the declaring
member or type.  So, for example, on deserializing '<T> void m(T t)' we would
have a typevariable called T stored against the resolvedmember and the parameter
would be an unresolvedtypevariablereferencetype for a typevariable T - at this
stage the T's aren't the same T.  When resolve() is called, we lookup the
parameter type variable T on the declaring element and discover the real T, we
then return a TypeVariableReferenceType from resolution that embeds a reference
to the real T.  This means post resolution:

member.getTypeVariables()[0]==((TypeVariableReference)member.getParameters[0])).getTypeVariable()

i.e. identity equality works

now to get the tests passing with these enhancements...
Comment 3 Andrew Clement CLA 2005-10-11 04:25:55 EDT
Ok, the new tests are passing but there are still a few situations where
equality isn't correct - bcelmethods(fields) that unpack signatures currently
won't resolve correctly to all use the same type variable instance, and itds
that share type vars won't work either.  Hence the resolve code in allows for this.

Also, I changed from having resolve(world,declaringelement) back to the old
resolve(world) method *but* I added the ability to set the current lookupscope
for type variables to the world.  So when resolving parts of a resolvedmember we
set the lookupscope and with a finally clause we unset it - this works so far!
Comment 4 Andrew Clement CLA 2005-10-14 06:41:08 EDT
damn slow progress ....  but this mornings problem was interesting, I hit this
signature on a method:

(LGenericAspect<TT;>.Simple<TN;>;)V

and the signature parser crashed and burned - it didnt before because we never
unpacked this thing.

It failed because after we hit the '.' we didn't set the flag
'couldSeePrimitive' to false and since it was true, the tokenized stream looked
like this:

'.' 'S' 'imple'

because 'S' represents the primitive type String.  I changed the parsing of '.'
to indicate it couldn't be followed by a primitive and the tokenstream became:

'.' 'Simple'

which works ;)

A fluke that the inner type started with an 'S' 
Comment 5 Andrew Clement CLA 2005-10-14 14:07:04 EDT
Ok ... all tests passing again, what am I changing this time:

1. An intertypedeclaration knows about the intertype scope it sticks in the
scope hierarchy.

2. When an intertypescope is asked for an aliased type variable, e.g. for the
'P' in the parameter to this method:

void C<P>.m(List<P> ns) {}

where the target type is

interface C<N extends Number> {}

then the intertypescope returns 'N extends Number' *and* records that it did so,
this is so that later on when building the entries to go into the class files
representing the ITD we can go back from what we have in the binding after
resolution (N extends Number) to the original P.  It is important for separate
compilation that P is recorded in the file rather than the letter it happened to
match with.

3. Modified the makeTypeVariableBinding() code in eclipse factory to work
directly with TypeVariables rather than TypeVariableReference objects.

4. Modified makeMethodBinding() to allow for aliases that may be specified for a
generic ITD.

5. Modified the ctor for InterTypeMethodBinding to take a munger rather than
just the signature for the munger so that it can access the type variable aliases.

6. Fixed a bug in GenericSignatureParser when using inner types within a generic
type that will be included in a generic signature attribute.

7. AjcMemberMaker - copies any type variables across when creating the helper
methods for ITDs.  Not yet complete - only done ITDMs so far.

8. toDebugString() added to the member hierarchy, gives out all the juicy
information it can.

9. MemberImpl - changed typesToSignature() to fully respect the 'useRawTypes'
flag - and if you specify that it will not only collapse parameterized types to
their erasure, it will collapse type variable references to their erasure.

10. NewMethodTypeMunger - serializes the aliases

11. ResolvedMemberImpl - remembers generic information about return type and
parameters ! Bit more tolerant in parameterizedWith

12. ResolvedTypeMunger - remembers alias list.

13. TypeVariable - has a getFirstBound() which might be the upper bound or the
first interface bound if the upperbound is object

14. Improved the code that looks for the ITD member that will hold any
annotations actually attached to an ITD (BcelTypeMunger)


still need to work on fields and constructors and ensure that each method
created for an ITD collapses its type variables in the right way for binary weaving.
Comment 6 Andrew Clement CLA 2005-10-17 09:23:06 EDT
Slight change of tack now, I wanted to use that infrastructure to get something
working.  So I looked at one of the cases in bug 110307 to do with providing
default implementations of ITDs when generics are involved.  Under that bug
number there are 6 cases checked into the test suite.  I now have one working:

interface I<T> {
}

class A {
}

aspect X {

  List<T> I<T>.foo() { return null; }  // should be ok...

  declare parents: A implements I<String>;
}

this did not work at all before because the particular application of the ITD to
the class A which implements I was not parameterized according to the usage of I
(I<String>) in A's hierarchy.  this now works - it builds on the support that
persists type variable aliases and ensures resolvedmembers record generic info.

Main changes are:

Through the ConcreteTypeMunger and ResolvedTypeMunger hierarchy I added
'parameterizeFor()' which can take any ITD that targets a generic type and fills
in the type variables as appropriate.  For example for "List<T> I<T>.m() {return
null;}" where the target interface is used as I<String> in some type, the return
value of m() will become List<String> after parameterizeFor() is called.

Changed 'addInterTypeMunger' in ResolvedType to do its type system consistency
checking based on the parameterized form of an ITD rather than the original form.
Comment 7 Andrew Clement CLA 2005-10-18 04:20:07 EDT
To get the final tests for 110307 to pass I had to modify the logic in
BcelTypeMunger.enforceDecpRule1_abstractMethodsImplemented() - this didn't take
account of ITDs that needed parameterization in order to be considered as
overriding inherited abstract methods.

Only one case for 110307 still fails - thats to do with using the same named
type variable as the target type, i.e. if targetting type 'interface I<T> {}'
with an ITD 'public void I<T>.m(List<T> lts) {}' we can get into trouble because
of how the eclipse compiler finds the wrong T.  We need to change getType() in
the scope hierarchy so that Intertypescope can get in to the loop.
Comment 8 Andrew Clement CLA 2005-10-18 10:18:30 EDT
We had a recent bug where Adrian had to comment out some tests - they are now
back in because of the changes i've made to make the system more generics aware.
 I've also modified ITD ctors so that in serialized form they remember any type
variable aliases used - this is exactly the same as what I did for methods.  I
still need to finish it for fields tho.

A new case I'm worried about is this:

interface I<N extends Number> {}

aspect X {
  public Base<Z>.new(Z z) {}
}

since we still appear to be creating members (ajc$members) in X based on the
matched type variable (so we get a method that takes a parameter of 'Number' -
the upper bound) when we should have created a method with type Object - the
fact that it was Number is irrelevant since that isn't expressed in the aspect,
its expressed in the target type and may be changed later.  I'm not sure what to
do about this yet!
Comment 9 Andrew Clement CLA 2005-10-20 04:32:55 EDT
Ok, final bit of work for now is to do fields since methods/ctors are already
covered.  basically to ensure when sharing type variables with a target generic
type, the form of the ITD in the class file refers to the 'alias' and is not
tied to the letter in the actual target class.

Usual changes for this:
  NewFieldTypeMungers remember their aliases when (de)serialized
  makeFieldBinding in eclipseFactory takes account of aliases

other changes:
changes isAssignable() test when comparing two type variable references.

ResolvedTypeMunger - when parameterized it now stashes the 'original' form of
the munger.  this isn't used yet but I think we'll need it for bridge method
creation when an ITD targets an interface and is 'picked up' by implementors.

parameterizedFor() on all the type mungers changed.  Now cheats a little -
parameterizedFor is used to change the aliases to the real type variables for
some target type.
Comment 10 Andrew Clement CLA 2005-10-20 06:25:54 EDT
Now I'm going through the GenericsTests JUnit tests and trying to include the
tests that are commented out.  First failing case was to do with the
ParentChildRelationship aspect and why the ordering of a set of 3 types passed
to the compiler made a difference as to whether the program works.  The types are:

Blob - simple class
ParentChildRelationship<Parent,Child> - abstract generic aspect
BlobContainment extends ParentChildRelationship<Blob,Blob> - concrete subaspect

If the compiler processes BlobContainment before ParentChildRelationship then
things just don't work ... it shouldnt be possible for this to happen but there
was a bug in AjLookupEnvironment.collectAllITDsAndDeclares() which didn't allow
for the supertype being a parameterizedtypebinding - in the case above the
supertype of BlobContainment is a parameterizedtypebinding.  All the testcases
we must have written so far work because we declare the abstract aspect ahead of
the concrete aspect (thats reasonable...) - however you really get into trouble
when the concrete and the abstract are in separate files since the compiler is
much more likely to see them in a different order.  Anyway, I made the logic
cope with PTBs and now it all works, do Ctrl-Shift-R and open TheBigOne.java -
yes, that passes!!!  The aspect is suffering from cast-overload at the moment
... and that needs fixing.

Oh, fixing this also uncovered a hopeless piece of logic I put into
ResolvedType.discoverActualOccurrenceOfTypeInHierarchy() which I've now fixed. 
Comment 11 Andrew Clement CLA 2005-10-20 10:46:26 EDT
Ok, next problem was generictestK which attempted to violate bounds, we weren't
getting an error message when we should have - this is now fixed.
Comment 12 Andrew Clement CLA 2005-10-21 12:25:08 EDT
Amazing !!
I was looking at GenericAspectsV which uses the parent child relationship
abstract aspect.  However, it is full of casts.  I wanted to remove the casts. 
I've done it - finally - and this is the finished working aspect:
 
abstract aspect ParentChildRelationship<Parent,Child> {

  // The interfaces
  interface ParentHasChildren<C extends ChildHasParent>{
    List<C> getChildren();
    void addChild(C child);
    void removeChild(C child);
  }

  interface ChildHasParent<P extends ParentHasChildren>{
    P getParent();
    void setParent(P parent);
  }


  // Put the interfaces on the relevant people
  declare parents: Parent implements ParentHasChildren<Child>;
  declare parents: Child  implements ChildHasParent<Parent>;

  public E ChildHasParent<E>.getParent() {
    return parent;
  }

  public List<A> ParentHasChildren<A>.children = new ArrayList<A>();
  public       B ChildHasParent<B>.parent;

  public List<D> ParentHasChildren<D>.getChildren() {
    return Collections.unmodifiableList(children);
  }

  public void ChildHasParent<F>.setParent(F parent) {
    this.parent = parent;
  }

  public void ParentHasChildren<G>.addChild(G child) {
    if (child.getParent() != null) {
      child.getParent().removeChild(child);
    }
    children.add(child);
  }

  public void ParentHasChildren<H>.removeChild(H child) {
    if (children.remove(child)) {
      child.setParent(null);
    }
  }

}

but its going to take me a little while to get it into CVS.  I had to modify
some declare parents hierarchy checking and also add the bridge method support
to method and field munging...
Comment 13 Andrew Clement CLA 2005-10-26 12:35:37 EDT
er .... well integrating some of that took longer than anticipated.

Let's talk implementation:

1. Tailored mungers
We now tailor mungers for paramaterized types (if they were sharing type
variables with the generic type).  For example, an 'interface I<N>' targeted
with an ITD 'public N I<N>.field' then implemented with 'implements I<String>'
will have a munger 'String field' attached to the implementor - inside the
munger is a declaredSignature that enables you to get back to the original
declaration (required for creating the bridge methods).

2. Bridge methods
I'm doing some work on this under another bug too but here I was really
interested in the case where you ITD onto a generic interface.  For example:

interface I<N extends Number> {
}
public class FieldQ implements I<Double> {
}
aspect X {
  Z I<Z>.list;
}

Now, FieldQ will get a field 'list' of type 'Double' - and for manipulating the
list we will have created two ajc$ methods, the getter and the setter:

public java.lang.Double ajc$interFieldGet$X$I$list();
  Code:
   Stack=1, Locals=1, Args_size=1
   0:   aload_0
   1:   getfield        #28; //Field ajc$interField$X$I$list:Ljava/lang/Double;
   4:   areturn


public void ajc$interFieldSet$X$I$list(java.lang.Double);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   aload_1
   2:   putfield        #28; //Field ajc$interField$X$I$list:Ljava/lang/Double;
   5:   return

//
Notice that these talk in terms of 'Double' - the actual parameterization of I
in FieldQ.  The problem is that at the interface level we create:

public java.lang.Number ajc$interFieldGet$X$I$list();
public void ajc$interFieldSet$X$I$list(java.lang.Number);

and so we need bridge methods in FieldQ, that look like this:

public void ajc$interFieldSet$X$I$list(java.lang.Number);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   aload_1
   2:   checkcast       #34; //class java/lang/Double
   5:   invokevirtual   #38; //Method ajc$interFieldSet$X$I$list:(Ljava/lang/Dou
ble;)V
   8:   return

public java.lang.Number ajc$interFieldGet$X$I$list();
  Code:
   Stack=1, Locals=1, Args_size=1
   0:   aload_0
   1:   invokevirtual   #32; //Method ajc$interFieldGet$X$I$list:()Ljava/lang/Do
uble;
   4:   areturn


they delegate to the local 'Double' versions. all this works (shock horror)

3. Type parameter checking
There is one place I wimped out and its a reason why the aspect I appended in
the previous comment won't work right now.  The problem is these two lines

  declare parents: Parent implements ParentHasChildren<Child>;
  declare parents: Child  implements ChildHasParent<Parent>;

Now, WildTypePattern does some validation of type parameters to check they meet
the bounds.  In the case above it checked that Child meets the bounds for the
first type variable in 'ParentHasChildren'  and that Parent meets the bounds for
the first type variable in 'ChildHasParent'.   The two interfaces are declared as:
  interface ParentHasChildren<C extends ChildHasParent>
  interface ChildHasParent<P extends ParentHasChildren>

Now... because of those declare parents, the type parameters *do* meet the type
variable bounds for the interfaces but only after they have both been applied to
the types Child and Parent.  The problem is that WildTypePattern
resolveBindings() which verifies type parameters actually runs way before the
delcare parents have been applied.  So we get an error for both declare parents
statements - even though after they have been applied everything is ok.  Moving
the type parameter checking is a real pain so I'll look at that after this stuff
has gone in.  Right now, because the testcases are such uber-testcases for
generics/bridge methods/etc I have made the failing check in WildTypePattern
configurable and the testcases turn it off (yes, yes, start throwing rocks at me
now...).  I need to think some more about this but I really want to get
everything I've done so far into CVS. phew.  I'm about to check it all in.

Besides the type param checking above, I'll investigate why the generic aspect
Matthew Webster wrote a little while ago still fails.  I suspect it's because of
unusual bounds - we still have very few tests for the "whacky bounds"(tm) that
are allowed with Java5.
Comment 14 Andrew Clement CLA 2005-10-31 09:05:35 EST
matthews aspect is now in.  It was incorrect but the error messages were
hopeless - so they've been improved.
Comment 15 Andrew Clement CLA 2005-11-01 05:13:07 EST
I modified the type parameter checking to allow the complex ParentChild aspect
through.  This has damaged another test, genericaspectK but that covers
something that is rather less likely to occur in the wild, so that is now
commented out.  The problem is merely that an error message doesnt come out
right for 'K' so its not the end of the world.
Comment 16 Andrew Clement CLA 2005-11-01 05:14:06 EST
Created attachment 29048 [details]
Patch of some stuff I was trying out...

This creates the 'potentialproblem' support which allows potentialproblems to
be recorded against a world and verified later.  Might help with the type
parameter checking which is currently done too early.
Comment 17 Andrew Clement CLA 2005-11-07 08:11:38 EST
I'm not going to do much more with this bug.

I've just added GenericAspectX test and that confirms that the generic aspect
from the AJDK compiles fine and is basically usable.  However, it doesn't check
that the pointcuts in the generic aspect behave as expected.  I also added
GenericAspectY to check the pointcuts were OK and it fails.  I suspect the
reason is something similar to the problem I had when verifying declare parents
were OK before they had been applied.  see the end of comment #13.

Comment 18 Andrew Clement CLA 2005-11-25 08:42:49 EST
Finally... I am closing this.  We have generic tests Y and Z written - Y does some basic pointcut stuff, Z does the more advanced pointcut stuff. and I've updated the docs to agree with the testcases.