Skip to content
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

Annotate closure args in Project with @DelegatesTo. #930

Closed
wants to merge 2 commits into from

Conversation

rhencke
Copy link
Contributor

@rhencke rhencke commented Nov 26, 2016

Makes code completion in IDE less bad. (also fixed a tiny typo)

Any of the checked boxes below indicate that I took action:

I believe this is a very trivial change. (No API changes or functionality changes. Just a better IDE experience). But, please let me know if more is needed.

example

This allows for IDEs like IntelliJ IDEA to provide better
code completion inside these closures.
@eriwen
Copy link
Contributor

eriwen commented Dec 16, 2016

@melix Could I request a very quick review from you here (given that you wrote @DelegatesTo)? This seems like such an obviously good thing that I'm sure we have good reasons for not using @DelegatesTo here, I just don't know what they are.

@melix
Copy link
Contributor

melix commented Jan 26, 2017

Sorry I forgot to answer this PR. I think it is valuable to add @DelegatesTo as well @ClosureParams. The only reason the codebase is not annotated with this is that the annotations didn't exist when the code was written. It provides valuable information, not only for IDE completion, but also for static compilation of plugins. But is also needs to be reviewed carefully.

Copy link
Contributor

@melix melix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a very nice improvement but it needs further refinements:

  • the strategy (optional parameter of @DelegatesTo) needs to be set to Closure.DELEGATE_FIRST
  • it would be nice to have tests highlighting the fact that static compilation works with this (but not required)

@rhencke
Copy link
Contributor Author

rhencke commented Jan 26, 2017

@melix Thank you for your review!

I will work on addressing your requests and get you an updated version of this pull request.

@rhencke
Copy link
Contributor Author

rhencke commented Jan 27, 2017

@melix It turns out many delegates are actually OWNER_ONLY. However, the owner is set to org.gradle.internal.metaobject.ConfigureDelegate in these cases, and its implementation of GroovyObjectSupport does appears to attempt property access/method invocation in the order of delegate first, and then owner second, which matches the spirit of DELEGATE_FIRST.

For example (pardon my bad Groovy):

HashMap<Integer,String> rs = [:]
rs[Closure.DELEGATE_FIRST] = "DELEGATE_FIRST"
rs[Closure.DELEGATE_ONLY] = "DELEGATE_ONLY"
rs[Closure.OWNER_FIRST] = "OWNER_FIRST"
rs[Closure.OWNER_ONLY] = "OWNER_ONLY"
rs[Closure.TO_SELF] = "TO_SELF"

task('q') {
    println "task():"
    println "strategy is         ${rs[getResolveStrategy()]}"
    println "delegate.getClass() ${delegate.getClass()}"
    println "it.getClass()       ${it.getClass()}"
    println "this.getClass()     ${this.getClass()}"
    println "owner.getClass()    ${owner.getClass()}"
    println "(but owner.class is ${owner.class}...?)"
}

yields:

task():
strategy is         OWNER_ONLY
delegate.getClass() class org.gradle.api.DefaultTask_Decorated
it.getClass()       class org.gradle.api.DefaultTask_Decorated
this.getClass()     class build_3919psp5ai3nt8u1758a2qpse
owner.getClass()    class org.gradle.internal.metaobject.ConfigureDelegate
(but owner.class is class org.gradle.api.DefaultTask_Decorated...?)

I'm assuming, for cases like this, it's closer in spirit to mark this as @DelegatesTo(value = Task.class, strategy = DELEGATE_FIRST) than it would be to mark it @DelegatesTo(value = ConfigureDelegate.class, strategy = OWNER_ONLY), for the sake of IDE completion. But, I don't know how this information gets used during static compilation, and this was quirky enough I just wanted to give you a heads up in case this wasn't the right way to proceed.

@melix
Copy link
Contributor

melix commented Jan 27, 2017

Yes the delegation system of Gradle is a bit complex. Basically for methods that take an Action<T>, we automatically generate a Closure version where the strategy is set to DELEGATE_FIRST. I think it's fine to use DELEGATE_FIRST here. In practice it should be the case.

Also, add a test verifying Groovy static compilation's type-
checking of these annotations.
information provided by closures annotated with @DelegatesTo and @ClosureArgs
is actually _correct_.
*/
// TODO? How to validate @DelegatesTo strategy?
Copy link
Contributor Author

@rhencke rhencke Feb 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't figure out a way to validate @DelegatesTo with this approach, unfortunately, nor how to do any kind of meaningful check for static compilation failures. But, you can experiment with modifying the @DelegatesTo and @ClosureParams in Project, and watch the effects on this test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@melix Any thoughts on this TODO?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we need to call methods from the delegate directly in the closure (without it).

*
* <p>Here, the sole parameter for Closure c would be of type R, as R is the return type of foo.</p>
*/
public final class ReturnType extends SingleSignatureClosureHint {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't figure out a way to use @ClosureParams and directly specify the type in a strongly-typed way, as done with @DelegatesTo. Since it seems to be a common pattern in Gradle to have the closure's argument be the same as the method's return type, I created this. For cases this did not fit, I used SimpleType. If there's a cleaner way to do this or you'd prefer a different approach just let me know.

@@ -1138,7 +1141,7 @@
*
* @param closure The closure to call.
*/
void beforeEvaluate(Closure closure);
void beforeEvaluate(@ClosureParams(value = SimpleType.class, options = {"org.gradle.api.Project"}) Closure closure);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The delegate for this seems to be the build script class itself, which didn't end up being all that useful to specify here, and was also marked internal. So, I left off the @DelegatesTo. Same with afterEvaluate and container.

@rhencke
Copy link
Contributor Author

rhencke commented Feb 21, 2017

@melix This should be ready for review but if you need any other changes please just let me know.

@bmuschko
Copy link
Contributor

@melix Would you mind finishing off this PR?

@bmuschko
Copy link
Contributor

bmuschko commented May 5, 2017

@melix Any chance this can still make it into 4.0-RC1?

@melix
Copy link
Contributor

melix commented May 16, 2017

@bmuschko Finally took time to finish the review. There are some pitfalls, which I discovered with the statically compiled Gradle build script experiment months ago, but the changes LGTM.

@bmuschko
Copy link
Contributor

This PR has been merged manually by @melix. Closing the PR. @melix Please don't forget to add the contributor to the release notes.

@bmuschko bmuschko closed this May 17, 2017
@bmuschko bmuschko added this to the 4.0 RC1 milestone May 17, 2017
@ov7a ov7a removed this from the 4.0 RC1 milestone Mar 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in:core DO NOT USE
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants