如何从trait引用带有@ClosureParams的实现者类

时间:2018-01-27 05:58:07

标签: groovy

我想将@ClosureParams与特征中的方法一起使用,将Closure作为输入,在调用时将传递特征的实现者。

考虑以下示例:

trait Fooable {
    void foo(@ClosureParams(????) Closure callable) {
        callable.call(this)
    }
}

class Bar implements Fooable {
    String baz
}

new Bar().foo { it.baz == "foo'ed" }

如何告诉静态分析器传递给闭包的it实际上是Bar(最后一行)。在foo方法的定义中,我应该将值传递给@ClosureParams?

1 个答案:

答案 0 :(得分:2)

目前它不适用于特征(Groovy 2.4.13 ),因为没有ClosureSignatureHint实现允许您在运行时类型中定义提示使用从trait接口实现方法的类类型。如果您的特征仅由Bar类实现,那么您可以将closure参数类型指定为:

@CompileStatic
@TypeChecked
trait Fooable {
    void foo(@ClosureParams(value = SimpleType, options = ["Bar"]) Closure callable) {
        callable.call(this)
    }
}

但事实并非如此。

如果与特质一起使用,

@ClosureParams甚至不会识别泛型类型。让我们考虑以下定义:

@CompileStatic
@TypeChecked
trait Fooable<T> {
    void foo(@ClosureParams(value = SimpleType, options = ["T"]) Closure callable) {
        callable.call(this)
    }
}

我们可以预期实现Bar的{​​{1}}类应该像魅力一样工作,但不幸的是:

enter image description here

此情况下的Closure参数被识别为Fooable<Bar>类型。这是因为方法Tfoo类中实现,Bar也在@ClosureParams(value = SimpleType.class,options = {"T"})类级别编译,因此它不知道泛型类型Bar。让我们看一下编译好的T类来了解发生了什么:

Bar

如果您将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 } 作为反编译文件打开,您将会看到这一点。

泛型将正常工作,如果不是特性,我们将使用抽象类。在这种情况下,抽象泛型类Bar.class将实现Fooable<T>方法,因此foo类将引用Bar类的实现 - 一个知道Fooable<T>类型的类。在这种情况下,IDE将正确解析T并建议T

那么在这种情况下使用特质时有哪些选择?您可以尝试实现自己的Bar类,但这并不容易。我做了一个小实验 - 我定义了ClosureSignatureHint类,我从NewSimpleType类复制了1:1来源。然后我用它作为:

SimpleType

正如您所看到的,我只使用自定义@CompileStatic @TypeChecked trait Fooable { void foo(@ClosureParams(value = NewSimpleType, options = ["Bar"]) Closure callable) { callable.call(this) } } 替换了Groovy的SimpleType。它没用。我的IDE(IntelliJ IDEA Ultimate 2017.3.3)没有解析任何类型。我甚至将这个类移到一个单独的Maven项目中,并且我已经构建它并添加为依赖项 - 也没有用。

我认为应该可以实现一个将调用者类类型考虑在内的提示类。有一些实现从第一个,第二个或第三个参数获取闭包参数类型。听起来可行,至少在理论上是这样。

需要最少努力的最后一个选项只是明确提供闭包参数类型,例如

NewSimpleType

它支持所有代码完成功能。缺点是您可以指定不同的类型,例如:

Bar bar = new Bar()
bar.foo { Bar b -> b.baz }

IDE不会抱怨,但在编译时会失败。

自定义Bar bar = new Bar() bar.foo { String b -> b.toLowerCase() } 用例

我为实验创建了一个静态闭包签名提示,它只接受StringParameterHint作为参数:

java.lang.String

然后我在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; } } 方法中使用@ClosureParams进行了设置。遗憾的是,IDE不会读取此提示,也不会将Fooable.foo(Closure cl)识别为it的类型:

enter image description here

但编译器(在IDE中)知道这个闭包参数提示,如果我将参数转换为String,如:

Bar

然后IDE不会将其标记为不正确的表达式,但编译失败并且程序无法启动:

bar.foo { Bar b -> b.baz }

因此看起来我们可以强制编译器识别闭包参数,但IDE不读取此信息(在我的情况下为IntelliJ IDEA 2017.3.3)。我想这可能是一个IDE问题。我甚至将这个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 类移到了StringParameterHint包(我假设IDE可能会自动从这个包中加载所有提示),但它没有帮助。