New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extensibility API for AutoValue #202
Comments
As far as I understand, this would allow me to customize the object creation, too, because I can insert code into the constructor of the class that my extension generates, correct? This would be good. The use case I have is that I want to automatically create an So my constructor would not have parameters for all properties but only for the map. |
I've started playing around with implementing this feature, and one thing that I've come across is this error:
In my implementation, I'm creating an auto-extension that implements Android Parcelable, but the fact that my class implements an interface means there are extra abstract methods (from the interface). It seems reasonable to me that other extensions might also want to add abstract methods that they can act on, so the question is, should this error simply go away? The problem with that is that you then lose the compile time error reporting in the case where there are any abstract methods that a generator didn't implement. One possible solution I can see is to record all abstract methods on the annotated class, track which ones have been implemented in the processing step, and throw an error if there are any abstract methods not implemented by autovalue or it's extensions. Thoughts? |
Good point. It would make sense for the extension API to include a way for extensions to see and claim abstract methods. Then the error above would be produced only after extensions have run if there are still methods that were not claimed by any of them. |
(Does it keep things simpler to just not care, and let the generated On Mon, May 4, 2015 at 7:56 AM, Éamonn McManus notifications@github.com
Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb@google.com |
That's also a fair point. Since the unclaimed methods are abstract, there On Mon, May 4, 2015, 10:02 AM kevinb9n notifications@github.com wrote:
|
I should note that the message in question is actually a warning rather than an error, and does not in itself cause compilation to fail. The reason for that is that the unhelpful Eclipse compiler sometimes fails to realize that a concrete method overrides an abstract one. The philosophy has been for AutoValue never to generate code that doesn't compile, unless it is accompanied by at least one error message that points to the area in the source code where the problem was. That's particularly important for IDE integration, because you really want an error message that is attached to the source you are looking at, so it can be redlined. |
Ah, got it. On Mon, May 4, 2015 at 8:14 AM, Éamonn McManus notifications@github.com
Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb@google.com |
Those are some really good points, @eamonnmcmanus. I've got a start on the extensibility support. Would you mind taking a look at my branch and tell me if you think I'm on the right track. This is sort of in the POC stage right now and could use a little bit of cleanup (and still has problems with builders), but I just wanted to make sure I'm on the right track before moving too far. I wasn't really sure how to test the extensions, so I added a constructor so that I could inject the extensions. I left it package private so it won't be a part of the public API, but perhaps there is a better way to do this. |
@rharter, I had a quick look through your branch and it does look pretty much like what I had in mind. I don't have time right now to do a more thorough review but I hope to be able to get to it within the next week. Please nag me if you don't hear from me soon. |
After discussion with @eamonnmcmanus, I think we've come up with a working final architecture. I'm documenting it here. In the new architecture the AutoValue generated class will be the only direct subclass of the @AutoValue class Foo {
public abstract String bar();
}
@Generated(AutoValue.class)
class $$AutoValue_Foo extends Foo {
public String bar() {...}
@Override public String toString() {...}
}
@Generated(CustomToStringExtension.class)
class $AutoValue_Foo extends $$AutoValue_Foo {
@Override public String toString() {...}
}
@Generated(ParcelableExtension.class)
class AutoValue_Foo extends $AutoValue_Foo {
// parcelable bits...
} The special case here is that for any given A couple of questions I have for this architecture:
|
Ok - not going to lie, the million superclasses doesn't make me feel very Having only rudimentarily scanned the solution, has any consideration been On Mon, 1 Jun 2015 at 15:43 Ryan Harter notifications@github.com wrote:
|
The extent of consideration for the order if extensions has been that one can require itself be the final class in the chain, but that's about it. The current solution places the AutoValue generated class as the basest of the concrete base classes, and allows a single extension to require its subclass be the final implementation. Are you suggesting allowing some extensions to be dependent on others? |
I think builders should Just Work? The code generated for |
I'm not precisely "suggesting" it, so much as asking how much consideration I know we are going to face this in Dagger, since some processor extensions AutoValue is a smaller problem, but I have a hunch that people will find On Mon, 1 Jun 2015 at 17:43 Éamonn McManus notifications@github.com wrote:
|
I can imagine some sort of ordering constraint, of which the must-be-lowest constraint would be a special case. But I propose that we should make an API without one first, if only because I don't see a very clean way of doing it while maintaining the property that extensions are independent of each other and mostly opaque to each other too. |
SGTM. On Mon, 1 Jun 2015 at 17:59 Éamonn McManus notifications@github.com wrote:
|
It sounds like making the builder Just Work (which I completely agree with) adds a few rules to which extensions must adhere.
Does that sound about right? |
Rule 1 looks necessary. Rule 2 doesn't, though. Static classes are inherited, so |
Aside from ordering, what other validation concerns would we need to address? Certainly the API could include a Then it just becomes a matter of determining how much contextual information to give to each |
The idea of a wrapper extension is intriguing. Since the plan is for AutoValue to find extensions using ServiceLoader, it would need to have a way to preempt that if there is a MetaAutoValueExtension present (itself loaded through ServiceLoader). However, again, I think we should postpone thinking about this level of complexity at first. |
@eamonnmcmanus I've got this just about ready to go into a PR, just have to add some documentation. |
One thing I'm wrestling with now is the idea that all generated classes (AutoValue generated and Extension generated) will be abstract, and the final one will be final. This allows the annotated class to declare an interface that and extension will fulfill. The problem here is that the extension classes then have to mirror the constructor of the AutoValue generated class, since it will be abstract and there is no default constructor. This seems unfortunate to me, as every extension will have to mirror some functionality of AutoValue, but I'm not sure of a better way. Any ideas @eamonnmcmanus? You can check out where things are here: https://github.com/rharter/auto/commits/extensibility |
I think part of the contract of an extension is that the class it generates must have a constructor whose parameters correspond to |
Well my initial thought was that you would just not add a constructor and it would inherit one, but that doesn't work if the AutoValue class is abstract. The problem with just stepping through the keys, I believe, is that in the constructor the user can write order is important. Map doesn't guarantee order of it's keys, right? Perhaps if we used a SortedMap? |
Constructors aren't inherited. Map iteration order is unspecified in general but we would obviously want to specify that the properties map is iterated in the correct order. (In practice it would be a LinkedHashMap or an ImmutableMap, both of which iterate in the order the entries were added.) |
Okay, I'll just make that a requirement and get this in. |
I know it's kind of late to bring this up, but does anyone else think |
Yes, I think that would be a better name. I'd be fine with just changing it. |
PR #237 created all sorts of practical problems in our build inside google, and probably shouldn't have been put in the com.google.auto.value package, but maybe in a com.google.auto.value.processor.api package or something. :( I can fix the build issues, and that's not THAT big a deal, but I actaully don't think this should be in the same package as Thoughts? Can we consider moving it there? |
How about com.google.auto.value.processor.extension? On Fri, Aug 14, 2015 at 11:23 PM Christian Edward Gruber <
|
SGTM. |
Sounds good to me too. Since I'm on vacation, perhaps @cgruber can take care of it? |
Shall do. I expected to have to wait to hear from you until you were done On Sat, 15 Aug 2015 at 08:54 Éamonn McManus notifications@github.com
|
Now that we have our RC1, do you think this is completed and can be closed? |
Yes. As you know, there's further work to be done on the API. I'm not forgetting the work you have already done, but I'd like to get 1.2 out before integrating it. |
Issues #87, #162, and #201 are examples of use cases that are a bit too specialized to put into the core of AutoValue, but which are still important when they apply. We should define an extensibility API so that users can build extensions that run at the same time as AutoValue. The general idea would be that there would be an interface, say AutoValueExtension, and that the AutoValue processor would use ServiceLoader to find implementations of this interface. So if you include on your
-processorpath
a jar that contains such an implementation, AutoValue will invoke it when it runs.A possible sketch of the interface is this:
The idea is that every extension gets its
applicable
method called, and those that return true get theirgenerateClass
method called. That method can return a String that is an implementation of the class called classToImplement that extends the class called classToExtend; or it can return null (for example if it has generated some related code but doesn't need to intervene in the AutoValue hierarchy). So if you have two extensions,@AutoValue class Foo
might result in a hierarchyAutoValue_Foo extends $AutoValue_Foo extends $$AutoValue_Foo extends Foo
, whereAutoValue_Foo
is generated by one of the extensions,$AutoValue_Foo
is generated by the other, and$$AutoValue_Foo
contains the logic generated by the AutoValue processor. This means that extensions can override the implementations oftoString()
etc.The text was updated successfully, but these errors were encountered: