[JSR308] annotations as arguments to annotations
Jonathan Aldrich
jonathan.aldrich at cs.cmu.edu
Sat Oct 27 23:13:37 EDT 2007
Dear JSR 308 team,
First, I'd like to express my appreciation to Michael and the JSR 308
team for their work in improving the Java annotations system! My
students are all using JSR 175 annotations in our static analysis
research, but have found them to be sorely lacking, and JSR 308
addresses many of the issues.
I think one addition, however, would really increase expressiveness in a
major way: allowing arbitrary annotations as arguments to annotations.
This is mentioned in Section D.4.2. of the current JSR 308 proposal, as
a possible extension that is currently out of scope. I would argue we
should add it to the scope of the proposal.
The main benefit of this is that it allows one to define little
recursive data structures with annotations. Annotation constants are
useful, but if you want to describe any interesting type system--which
is a very common mode of usage for annotations--it's very likely that
you'll want this. There are two basic use cases: type parameterization
and structured annotations.
1) Type Parameterization. Every interesting type system has
parameterization, because of the reasoning it enables in the case of
reused library code.
Of course, JSR 308 allows you to annotate type parameters directly.
That's really important and convenient, but it doesn't solve the problem
in the general case. In particular, the type system you are layering on
top of Java using annotations may require parameters in different places
than Java's. In that case, the JSR 308 support doesn't help you, unless
you want to add a gratuitous type parameter to Java (almost certainly a
bad idea). In fact, allowing annotations as arguments to annotations is
expressive enough that it can simulate JSR 308's support for annotating
type parameters. Thus from an expressiveness point of view, annotations
as annotation arguments is more important that annotating type
parameters (but of course annotating type parameters is still really
valuable from a usability perspective).
To illustrate the need for this feature, imagine you have a little
effect system for tracking whether code does I/O, and you want to encode
this with annotations. An IO method might be written as:
@IOEffect void println(String data);
Whereas a method without an IO effect might be written:
@NoIOEffect int computeIt(int data);
Now let's say you've got a Listener interface with a single callback
method. The callback method in the Listener interface may or may not
have effects, so we want to have an "effect parameter" on the Listener
that expresses whether the callback has I/O effects. That way the
interface can be reused across implementations that are effectful or
not. The declaration might be:
@IOEffectParam("E") interface Listener {
@IOEffectParam("E") void notify();
}
Now, we there are several ways we might use the listener:
@IOEffectActual(@IOEffect) Listener effectfulListener = ...;
@IOEffectActual(@NoIOEffect) Listener notEffectfulListener = ...;
@IOEffectActual(@IOEffectParam("F")) Listener effectDependsOnContext = ...;
In the first case, the listener's callback method has an effect, in the
second it doesn't, and in the third it may have one depending on some
effect parameter "F" that is currently in scope.
Note that the Listener class, like many Java listeners, doesn't have or
need type parameters. We're using parameterization in the IOEffect type
system in a place where there's no parameterization in the underlying
Java type system, so we can't just piggyback on Java's generics.
If JSR 308 doesn't support this, the only way to simulate it is going
out of the type annotation system by encoding parameters in strings.
All of my annotation based research projects are doing this right now,
but it's really ugly, and takes away a lot of the benefits that
annotations were supposed to yield. You start getting annotations like:
@OwnedBy("unique<A<shared>,owned>")
(This example is from our ownership system; here the pointer is unique,
but the type has two ownership parameters, one of which is owned by the
ownership parameter A and the other is owned by the "owned" domain, and
the thing that is owned by A has its own parameter which is owned by the
global "shared" domain. See my student Marwan's paper in IWACO '07 for
details...it's truly ugly. JSR308 will help the cases where the
ownership and type parameters match, but we know there are many cases
where there are ownership parameters that are not type parameters, and
in these cases we'll still have to use the string hack.)
For the effect type system described above, the situation isn't quite as
bad as this--mainly because the type system itself is dead simple--but
you still get a frustrating system where you have to write actual
parameters in string notation whereas concrete declarations are
annotations. In other words, you write things like:
@IOEffect void println(String data);
@IOEffectActual("IOEffect") Listener effectfulListener = ...;
and it's highly annoying that "IOEffect" can't be written as @IOEffect
as it is when it's directly on a method.
I'm speculating here, but I think if you look at the proposals for using
annotations as a type refinement mechanism, you'll find that most of
them need type parameterization to handle real programs well, and you'll
also find plenty of examples where the type refinement needs to
parameterize in ways that don't match Java's type parameterization.
We've found that's certainly true for ownership types. I suspect lots
of people are going to need this, even if they haven't tested their type
system on enough real-world examples to realize it yet, and we're sure
to regret it if we don't put it in the spec.
2) Structured Annotations. Sometimes annotations themselves have an
interesting internal structure. Again, this internal structure could be
represented as strings, but if a concept can be either an annotation
directly on a declaration or part of a more complex annotation, it's
much cleaner to represent it as an annotation in both cases. For
example, one could imagine encoding a logic quite nicely:
@Implies(@Predicate("I think"), @Predicate("I am"))
instead of
@Formula("(I think) => (I am)")
We have a real (research) application of this in my group: the
lightweight typestate verification system my student Kevin Bierhoff is
developing (see his OOPSLA '07 paper) requires little linear logic
formulas to be embedded in annotations. An implementation based on JSR
175 would use strings, but it would be nicer to use @Annotations to
express the internal structure.
------------------
I'm really curious about why this feature was not supported in the
original spec; it seems like an "obvious" extension but perhaps the
developers of JSR 175 didn't see the compelling benefits for
representing type refinement systems, or were uncertain about whether it
might cause implementation issues. If anyone has insight I'd be
interested to hear it.
I don't see any immediate "gotchas" in implementing such a proposal but
I'd be happy to lead or contribute to an experimental implementation to
be sure.
Thanks,
Jonathan
More information about the JSR308
mailing list