Bug 491687 - [active annotation] - Type Parameters during register-globals phase
Summary: [active annotation] - Type Parameters during register-globals phase
Status: UNCONFIRMED
Alias: None
Product: Xtend
Classification: Tools
Component: Core (show other bugs)
Version: 2.9.1   Edit
Hardware: PC Windows 7
: P3 major (vote)
Target Milestone: ---   Edit
Assignee: Project Inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2016-04-14 07:58 EDT by Kirsten M. Z. CLA
Modified: 2016-04-25 10:54 EDT (History)
2 users (show)

See Also:


Attachments
Example project including active annotation processor (74.43 KB, application/x-zip-compressed)
2016-04-14 07:58 EDT, Kirsten M. Z. CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Kirsten M. Z. CLA 2016-04-14 07:58:17 EDT
Created attachment 260950 [details]
Example project including active annotation processor

We are currently struggling over problems with our own implementation of an active annotation called @ExtractInterface. In principle, it does what "@Extract" from the "xtend-annotation-example" does, but has some additional features, e.g. considering type parameters.

Consider the following code (in one xtend file "test.xtend"):

    @ExtractInterface
    public class A {	
	    override public void method(IB<Integer> test)  {}
    }

    @ExtractInterface
    public class B<T> {}

This should result in:

    public interface IA {
      public abstract void method(final IB<Integer> test);
    }

and

    public interface IB<T> {
    }

Unfortunately, this doesn't work and some errors are reported inside "test.xtend", e.g. "IB is not generic; it cannot be parameterized with arguments <Integer>".

I have attached a complete example project including active annotation processor, just import.

Please note, that the example perfectly works, if
  - classes "A" and "B" are contained within separate xtend files
  - the declared "method" does not use "IB<Integer>" but "IB". However, also the interface "IA" does not declare "IB<Integer>" then, of course.

My assumption is that the register globals mechanism would require information about type parameters in addition. If this information is not available, it is unknown that type parameters for the interface "IB" will be supported and some phases are executed in a wrong order or whatever. I could be totally wrong as well, of course...
Comment 1 Sven Efftinge CLA 2016-04-14 10:40:13 EDT
The processing in a file is done in the order of declaration, while between files it is done lazily, e.g. in the order they are needed. That is why IB is ready when defined in a separate file but not if it's part of the local one. If you reverse the order of declarations, it should work also. Does it?
Comment 2 Kirsten M. Z. CLA 2016-04-14 14:20:00 EDT
You are right, and the workaround you mention is working for the posted example.

I oversimplified the code for reproducing the problem.
However, consider the following. The mentioned workaround is not applicable for 

    @ExtractInterface
    public class A<T> {	
	    override public void method(IB<Integer> test)  {}
    }

    @ExtractInterface
    public class B<T> {
	    override public void method(IA<Integer> test)  {}
    }

Looks like a "constructed example", but this is actually (still) an abstraction of our concrete code.
Comment 3 Kirsten M. Z. CLA 2016-04-23 13:18:39 EDT
My first idea, that there should be the possibility to register type declarations together with type parameter seems to be right.

I've got my example working with the following code after 


  context.registerInterface(annotatedClass.mirrorInterfaceName)


i.e. during the registration phase and directly after registering my new interface I add the type parameters using the following code:

  val typeLookup = context.class.getMethod("getTypeLookup").invoke(context) as TypeLookup
  val mirrorInterface = typeLookup.findInterface(annotatedClass.mirrorInterfaceName) as MutableInterfaceDeclaration
  val compilationUnit = mirrorInterface.compilationUnit
  val lastPhaseField = compilationUnit.class.getDeclaredField("lastPhase")
  lastPhaseField.accessible = true
  val lastPhaseOldValue = lastPhaseField.get(compilationUnit)
  try {
    val newValue = lastPhaseOldValue.class.enumConstants.get(1) // AnnotationCallback.INFERENCE
    lastPhaseField.set(compilationUnit, newValue)
    for (typeParameter : annotatedClass.typeParameters) {
      mirrorInterface.addTypeParameter(typeParameter.simpleName, typeParameter.upperBounds)
    }
  } finally {
    lastPhaseField.set(compilationUnit, lastPhaseOldValue)
    lastPhaseField.accessible = false
  }

Afterwards, everything works fine, even within our quite complex scenarios.

It is clear to me that the code above heavily violates given encapsulation (accessing internal and private fields) and is very dangerous. I don't know the implications of intervening in the generation phases in such a way. I am surprised myself that it works like a charm (so far!).

Anyways, I would vote for a official and stable way to enable the given scenario.
Comment 4 Sven Efftinge CLA 2016-04-25 02:13:15 EDT
Please note, that TypeLookup is not exposed for a reason. It doesn't reliably contain local types at this point in time, as they are being created. Also #findTypeGlobally doesn't work at this point (in case you are considering to use that).
Comment 5 Kirsten M. Z. CLA 2016-04-25 10:54:40 EDT
Yes, I already stumbled over this fact, because setting upper bound did cause trouble. I have to perform a two step approach. 

Step #1 within the registration phase:
add type parameters without upper bounds (so just register the simple names)

Step #2 within transformation phase:
revise type parameters and set upper bounds

This way there should be less dependencies to the type lookup. Maybe, it is only used for finding the interface directly registered before.

In the end, it is clear to anyone that it is not valid to access any internal data and there are reasons for that, as I said as well. Therefore, we have this issue filed. 
It would be nice, if there is an official API to register such an interface in an appropriate way. I expect my posted example valid and that there should be a way to make it working (considering also the possibility of cyclic dependencies as in my second post).