I would like to use #ClosureParams with a method in a trait, that takes a Closure as input, which will be passed the trait's implementer when called.
Consider the following example:
trait Fooable {
void foo(#ClosureParams(????) Closure callable) {
callable.call(this)
}
}
class Bar implements Fooable {
String baz
}
new Bar().foo { it.baz == "foo'ed" }
How do I tell the static analyser that the it passed to the closure is actually Bar (last line). What should value should I pass to #ClosureParams in the definition of the foo method?
It won't work with traits at the moment (Groovy 2.4.13), because there is no ClosureSignatureHint implementation that allows you to define at a runtime type of a hint that uses class type that implements method from trait interface. If your trait was implemented only by Bar class then you could specify closure parameter type as:
#CompileStatic
#TypeChecked
trait Fooable {
void foo(#ClosureParams(value = SimpleType, options = ["Bar"]) Closure callable) {
callable.call(this)
}
}
But it's not the case.
#ClosureParams won't even recognize generic type if used with trait. Let's consider following definition:
#CompileStatic
#TypeChecked
trait Fooable<T> {
void foo(#ClosureParams(value = SimpleType, options = ["T"]) Closure callable) {
callable.call(this)
}
}
We could expect that Bar class that implements Fooable<Bar> should work like a charm, but it does not unfortunately:
Closure parameter in this case is recognized as T type. It happens because method foo is implemented inside Bar class and #ClosureParams(value = SimpleType.class,options = {"T"}) is also compiled at Bar class level, so it is not aware of generic type T. Let's take a look at compiled Bar class to understand what's going on:
public class Bar implements Fooable<Bar>, GroovyObject {
private String baz;
public Bar() {
String var1 = "test";
this.baz = var1;
MetaClass var2 = this.$getStaticMetaClass();
this.metaClass = var2;
Helper.$init$(this);
Object var10000 = null;
}
#TraitBridge(
traitClass = Fooable.class,
desc = "(Lgroovy/lang/Closure;)V"
)
public void foo(#ClosureParams(value = SimpleType.class,options = {"T"}) Closure arg1) {
Helper.foo(this, arg1);
Object var10000 = null;
}
// some other methods
}
This is what you will see if you open Bar.class as a decompiled file.
Generics will work fine if instead of trait we would use abstract class. In this case abstract generic class Fooable<T> would implement foo method so Bar class would refer to implementation from Fooable<T> class - a class that is aware of T type. In this case IDE would resolve T correctly and suggest Bar instead.
So what are the options when using trait in this case? You could try implementing your own ClosureSignatureHint class, but this is not that easy. I did a small experiment - I have defined NewSimpleType class and I have copied 1:1 sources from SimpleType class. Then I used it as:
#CompileStatic
#TypeChecked
trait Fooable {
void foo(#ClosureParams(value = NewSimpleType, options = ["Bar"]) Closure callable) {
callable.call(this)
}
}
As you can see I've only replaced Groovy's SimpleType with my custom NewSimpleType. It didn't work. My IDE (IntelliJ IDEA Ultimate 2017.3.3) didn't resolve any type. I've even move this class to a separate Maven project and I've build it and added as a dependency - didn't work as well.
I assume it should be possible to implement a hint class that takes caller class type into account. There are some implementations that take closure parameter type from first, second or third parameter. It sounds doable, at least in theory.
Last option that requires least effort is just provide closure parameter type explicitly, e.g.
Bar bar = new Bar()
bar.foo { Bar b -> b.baz }
It supports all code completion features. The downside is that you can specify different type, like:
Bar bar = new Bar()
bar.foo { String b -> b.toLowerCase() }
IDE won't complain about that, but it will fail while compiling.
Custom StringParameterHint use case
I have created for experiments a static closure signature hint that accepts only java.lang.String as a parameter:
public class StringParameterHint extends ClosureSignatureHint {
#Override
public List<ClassNode[]> getClosureSignatures(MethodNode node, SourceUnit sourceUnit, CompilationUnit compilationUnit, String[] options, ASTNode usage) {
final List<ClassNode[]> list = new ArrayList<>();
list.add(GenericsUtils.parseClassNodesFromString("java.lang.String", sourceUnit, compilationUnit, node, usage));
return list;
}
}
Then I've set it up with #ClosureParams in Fooable.foo(Closure cl) method. Unfortunately IDE does not read this hint and does not recognize it as a type of String:
But compiler (in IDE) is aware of this closure parameter hint and if I cast parameter to Bar like:
bar.foo { Bar b -> b.baz }
then IDE does not mark it as an incorrect expression, yet compilation fails and program does not start:
Error:(11, 19) Groovyc: Expected parameter of type java.lang.String but got tld.company.Bar
Error:(11, 28) Groovyc: [Static type checking] - No such property: baz for class: java.lang.String
So it looks like we can force compiler to be closure parameter aware, but this information is not being read by IDE (IntelliJ IDEA 2017.3.3 in my case). I guess this might be an IDE issue. I've even moved this StringParameterHint class to groovy.transform.stc package (I was assuming that maybe IDE loads all hints from this package automatically), but it didn't help.
Related
I just cannot understand how to get closures to work as I would expect them. For example let's say I have a class
class Bar {
public void greeting(String name) {
println "Hello: ${name}"
}
}
When I delegate Bar in a Closure as such:
class Foo {
void helloBob(){
bar {
greeting("Bob")
}
}
def bar(#DelegatesTo(value = Bar, strategy = Closure.DELEGATE_ONLY) Closure cl) {
cl.rehydrate(new Bar(), this, this)
cl.call()
}
static void main(String[] args) {
new Foo().helloBob()
}
}
I get the stacktrace when running:
Exception in thread "main" groovy.lang.MissingMethodException: No signature of method: Foo.greeting() is applicable for argument types: (String) values: [Bob]
Possible solutions: getAt(java.lang.String)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:70)
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:76)
at
at ...
The: No signature of method: Foo.greeting() makes no sense to me because it should be calling Bar.greeting() as it is in a Closure which has #DelegatesTo(value = Bar. Why is the closure not referring to Bar.greeting? How do I get it to do that? My IDE (IntelliJ) seems to think it's Bar.greeting which is what I want, but when I run it I get a stacktrace.
EDIT
Weirdly enough if I remove a whole bunch of type information it then seems to work with this:
Bar bar(closure) {
def bar = new Bar()
closure.delegate = bar
// closure.rehydrate(bar, this, this) // this causes error why?
closure.call()
return bar
}
With the IDE happy and all. I also don't understand why I cannot use rehydrate it seems to cause an error, yet setting the delegate manually is fine.
Rehydrate doesn't modify the closure - it returns a new copy of it. So call the new closure.
cl.rehydrate(new Bar(), this, this).call()
Don't forget to set the resolveStrategy to what you are claiming in the annotation.
I'm aware of #ClosureParams annotation. It seems to be covering more complex use cases only though. I'm looking for something like described here at the annotating closures section. Which is similar to the following snippet:
void doSomething(MyType src, #ClosureParams(MyType) Closure cl) { ... }
This example no longer compiles with more recent groovy versions unfortunately (I'm on 2.5.8 at the moment). I know I can achieve equivalent with:
void doSomething(MyType src, #ClosureParams(FirstParam) Closure cl) { ... }
My use case doesn't have any other argument than closure itself though:
void doSomething(#ClosureParams(/* how? */) Closure cl) { ... }
I can hack it like:
void doSomething(#ClosureParams(SecondParam) Closure cl, MyType ignore = null) { ... }
It's far from clean, is it not?
I can as well go:
void doSomething(#ClosureParams(value = SimpleType, options = ['com.somepackage.MyType']) Closure cl) { ... }
It's not only ugly and noisy but as well having type specified as string prevents some IDE features from working. For example MyType refactor-rename or search for usages won't be picked up here.
I guess, there isn't any cleaner way of achieving this so type could be specified as a type not a string and without an extra unnecessary argument, is there?
Something like originally posted by Cédric Champeau in the blog post linked above would be ideal. Which in my case would look like:
void doSomething(#ClosureParams(MyType) Closure cl) { ... }
You may want to consider FromAbstractTypeMethods signature hint instead of SimpleType. It is quite verbose to use, but it gives you benefits that are missing from SimpleType hint class - you can easily refactor types defined in the signatures class, as well as you can find usages of classes used in the signature hint. The main downside is that you need to create additional abstract class per closure signature hints, and the name of the class that contains signatures as abstract methods need to be defined as a constant string (the same problem exists with the SimpleType signature hint.) However, you get a single parameter doSomething method, without adding the second null parameter just to be able to use SecondParam signature hint.
package com.example
import groovy.transform.Immutable
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.FromAbstractTypeMethods
class MyClass {
static void doSomething(#ClosureParams(value = FromAbstractTypeMethods, options = ["com.example.MySignatures"]) Closure cl) {
cl.call()
}
static void main(String[] args) {
doSomething {
println it.name
}
}
}
#Immutable
class MyType {
String name
int x
int y
}
abstract class MySignatures {
abstract void firstSignature(MyType myType)
abstract void secondSignature(MyType myType, String str)
}
I guess the simple and clean #ClosureParams(String) variant was removed to satisfy other more complex use cases. The API of ClosureParams annotation is fixed and it limits options to array of strings. Maybe it could be achieved by implementing own ClosureSignatureHint - I have tried that several months ago, but I couldn't make IntelliJ IDEA to use my custom class to provide signature hints.
I have two immutable groovy classes that have a few shared values that I'm trying to abstract to a parent class. However when I create the following, the second test case always fails. Although everything compiles correctly and no error is thrown at runtime, when I assign the parent property int he constructor, it is never set, resulting in a null value. I havent found any documentation that forbids this, but I'm wondering is this even possible? I've tried a number of configuration of Annotations and class-types (e.g. removing abstract from the parent) but nothing seems to work short of just removing the #Immutable tag altogether.
abstract class TestParent {
String parentProperty1
}
#ToString(includeNames = true)
#Immutable
class TestChild extends TestParent {
String childProperty1
String childProperty2
}
class TestCase {
#Test
void TestOne() {
TestChild testChild = new TestChild(
childProperty1: "childOne",
childProperty2: "childTwo",
parentProperty1: "parentOne"
)
assert testChild
assert testChild.parentProperty1
}
}
Based on the code for the ImmutableASTTransformation, the Map-arg constructor added by the createConstructorMapCommon method does not include a call to super(args) in the method body.
which means that immutable classes are self contained by default
Now if you want to do it you need to use composition instead of inheritance and this is an example of how you can do it :
import groovy.transform.*
#TupleConstructor
class A {
String a
}
#Immutable(knownImmutableClasses=[A])
class B {
#Delegate A base
String b
}
def b = new B(base: new A("a"), b: "b")
assert b.a
i hope this will help :)
class Base, and class Ext extends Base.
class B<T> with typed method foo<T>(value:T)
Why B<Base>.foo doest not accept instance of B<Ext> (implicit downcast of the type parameter?) by default?
Here is an example
http://try.haxe.org/#d443f
class Test {
static function main() {
var bExt = new B(new Ext());
var bBase = new B(new Base());
bBase.foo(bExt);
//ofc
//bBase.foo(cast bExt);
}
}
class B<T>
{
public function new(v:T)
{
}
public function foo(v:B<T>)
{
//
}
}
class Base {
public function new(){}
}
class Ext extends Base {
public function new(){
super();
}
}
Is there any way to trigger implicit cast of the type parameter for B.foo?
There are three ways to interpret and answer your question:
1. foo(v:B<T>):
This is your example and it doesn't compile because T isn't allowed to be be variant. It happens because of the very existence of foo and because allowing bBase.foo(bExt), that is, unifying bExt with bBase, will then allow bBaseOfbExt.foo(bBase).
It is the fact that foo exists and that it can potentially modify the type that makes the bExt unification with bBase unsafe; you can see a similar (but maybe clearer) explanation in the manual, using arrays: type system – variance.
2. foo(v:T):
This is closer to what's on the body of your question (but not in the example) and it works fine.
3. foo<A>(v:B<A>):
Finally, if you have a type parameterized method, it also works, but you'd probably face other variance issues elsewhere.
I have several classes all implementing an interface IBar. Those classes are BarA, BarB, BarC.
I also have a base class Foo:
abstract class Foo
{
void Do(IBar bar)
{
Handle((dynamic)bar);
}
void Handle(IBar bar)
{
Console.Out.WriteLine("Fallback Scenario");
}
}
I want a child class FooChild like follows:
class FooChild : Foo
{
void Handle(BarA bar) {
Console.Out.WriteLine("Handling BarA");
}
void Handle(BarB bar) {
Console.Out.WriteLine("Handling Bar");
}
}
No I want to do the following, but I don't get the result I expect
var foo = new FooChild();
foo.Handle(new BarA()); // expected: Handling BarA, actual: Fallback Scenario
foo.Handle(new BarB()); // expected: Handling BarB, actual: Fallback Scenario
foo.Handle(new BarC()); // expected: Fallback Scenario, actual: Fallback Scenario
I can solve it by moving the Do(IBar bar) method to the FooChild class, but I don't want to do that. I might have 10 Foo childs and don't want to repeat that code. Is there a solution for this?
I think you want this:
void Do(IBar bar)
{
dynamic dynamicThis = this;
dynamicThis.Handle((dynamic) bar);
}
That way the method will be found against the actual type of this. Otherwise, the compiler remembers that the method was called from Foo, and only treats the argument dynamically, finding methods which would have been available from Foo with the actual type of bar. You want methods which would have been available from the actual type of this, as well as using the actual type of bar (via the cast to dynamic).
(You'll need to make the Handle methods public though.)