当前位置: 首页 > 科技观察

Groovy类型检查扩展,使用扩展

时间:2023-03-12 10:43:16 科技观察

1。简介上一篇介绍了基本的Groovy类型检查扩展,以及扩展的含义和一些API说明。本篇延续上篇未完内容,继续介绍类型检查扩展的相关知识点。2.使用类型检查扩展我们已经解释了如何创建类型检查扩展,这里开始解释各种使用方法。2.1支持类-支持类DSL依赖于一个名为org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport的支持类。此类本身扩展了org.codehaus.groovy.transform.stc.TypeCheckingExtension。这两个类定义了许多辅助方法,使使用AST更容易,尤其是在类型检查方面。一件有趣的事情是我们可以访问类型检查器。这意味着我们可以以编程方式调用类型检查器的方法,包括那些允许抛出编译错误的方法。扩展脚本委托给org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport类,这意味着我们可以直接访问以下变量:类型检查实现者本身,org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitorgeneratedMethods的实例:“生成方法”列表,实际上是“虚拟”方法列表,您可以在类型检查扩展中使用newMethod调用。类型检查上下文包含了很多对上下文中的类型检查器有用的信息。例如,当前封闭的方法调用堆栈、二进制表达式、闭包等。如果我们必须知道发生错误时我们在何处以及我们想用它做什么,这些信息就特别重要。除了GroovyTypeCheckingExtensionSupport和StaticTypeCheckingVisitor提供的功能外,类型检查DSL脚本还从org.codehaus.groovy.ast.ClassHelper和org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport导入静态成员,允许通过OBJECT_TYPE访问public,STRING_TYPE、THROWABLE_TYPE等类型被访问和检查,如missesgenericsttypes(ClassNode)、isClassClassNodeWrappingConcreteType(ClassNode)等。2.2类节点——类节点在使用类型检查扩展时处理类节点时需要特别小心。编译使用抽象语法树(AST),当您检查类的类型时,它可能不完整。这也意味着在引用类型时,不能使用String或HashSet等类字面量,而是使用表示这些类型的类节点。这需要一定程度的抽象和对Groovy如何处理类节点的理解。为了使事情变得更容易,Groovy提供了几个辅助方法来处理类节点。例如,如果您想说“String的类型”,您可以这样写:assertclassNodeFor(String)instanceofClassNode还要注意classNodeFor有一个变体,它接受String而不是Class作为参数。通常,我们不应该使用此方法,因为它会创建一个名为String的类节点,但没有在其上定义任何方法、任何属性等。第一个版本返回已解析的类节点,而第二个版本返回未解析的类节点。所以后者应该保留给非常特殊的情况。您可能遇到的第二个问题是引用尚未编译的类型。这种情况比您想象的更频繁。例如,一起编译一组文件时。在这种情况下,如果你想说“那个变量是Foo类型的”,但是Foo还没有被编译,你仍然可以使用lookupClassNodeFor:assertlookupClassNodeFor('Foo')instanceofClassNode2.3来引用Foo类节点类型检查器的帮助假设你知道变量foo的类型是Foo并且你想告诉类型检查器。然后你可以使用storeType方法,它接受两个参数:第一个参数是你希望存储的类型的节点,第二个参数是节点的类型。如果我们查看storeType的实现,我们会发现它委托给类型检查器等效方法,该方法本身会做很多工作来存储节点元数据。我们还将看到存储类型不限于变量:可以键入任何表达式。同样,获取AST节点的类型只是在该节点上调用getType的问题。这通常是您想要的,但有一些注意事项:getType返回表达式的推断类型。这意味着对于声明为Object类型的变量,它返回的不是Object的类节点,而是代码此时推断出的变量类型(流类型)。如果要访问变量(或字段/参数)的原始类型,则必须在AST节点上调用适当的方法。2.4抛出错误要抛出类型检查错误,只需调用addStaticTypeError方法,该方法有两个参数:一条字符串形式的消息,将显示给负责错误的AST节点的最终用户。最好提供最合适的AST节点,因为它将用于检索行号和列号2.5isXXXExpression表达式通常需要知道AST节点的类型。为了可读性,DSL提供了一个特殊的isXXXExpression方法,它将委托给XXXExpression的x实例。例如:不推荐写法:if(nodeinstanceofBinaryExpression){...}正确,推荐写法:if(isBinaryExpression(node)){...}2.6Virtualmethods——虚方法当我们进行动态代码类型检查时,你可能经常遇到这样的情况,你知道一个方法调用是有效的,但它背后没有“真正的”方法。以Grails动态查找器为例。可以有一个由名为findByName(...)的方法组成的方法调用。由于bean中没有定义findByName方法,所以类型检查器会报错。但是,我们知道这个方法在运行时不会失败,我们甚至可以知道这个方法的返回类型是什么。对于这种情况,DSL支持两种由虚拟方法组成的特殊结构。这意味着将返回一个实际上不存在但在类型检查上下文中定义的方法节点。共有三个方法:newMethod(Stringname,ClassreturnType)newMethod(Stringname,ClassNodereturnType)newMethod(Stringname,CallablereturnType)所有这三个方法都做同样的事情:它们创建一个新的方法节点,其name是提供的名称,并定义方法的返回类型。此外,类型检查器会将这些方法添加到generatedMethods列表中。我们只设置名称和返回类型的原因是这在90%的情况下都是需要的。例如,在上面的findByName示例中,只知道findByName在运行时不会失败,并且它返回一个域类。返回类型的Callable版本很有趣,因为它在类型检查器实际需要时推迟了返回类型的计算。在某些情况下,当类型检查器要求返回类型时,可能不知道实际的返回类型,因此可以使用闭包,只要类型检查器在此方法节点上调用getReturnType时就会调用闭包。如果将其与惰性检查相结合,就可以实现相当复杂的类型检查,包括处理前向引用。newMethod(name){//每次调用此方法节点上的getReturnType时都会调用此闭包!println'类型检查器叫我!lookupClassNodeFor(Foo)//返回类型}如果您需要的不仅仅是名称和返回类型,您可以自己创建一个新的MethodNode。2.7作用域——作用域在DSL类型检查中非常重要,这是我们不能使用基于切入点的方法进行DSL类型检查的原因之一。基本上,必须能够非常精确地定义何时应用扩展,何时不应用扩展。此外,必须能够处理常规类型检查器无法处理的情况,例如前向引用:pointa(1,1)linea,b//bisafterreferenced!点b(5,2)比如你要处理Abuilder:builder.foo{barbaz(bar)}所以我们的extension应该只有在进入foo方法的时候是active的,在这个作用域之外是inactive的。但是,可能会出现复杂的情况,例如同一文件中的多个构建器或嵌入式构建器(构建器中的构建器)。虽然不应该从一开始就尝试解决所有这些问题(必须接受类型检查的局限性),但类型检查器确实提供了一种很好的机制来处理这个问题:使用newScope和scopeExit方法的作用域堆栈。newScope:创建一个新的作用域并将其放在栈顶scopeExits:将作用域从栈中弹出.typecheckingscope),但它非常强大。例如,这样的范围可用于存储退出范围时要执行的闭包列表。这是处理前向引用的方式:defscope=newScope()scope.secondPassChecks=[]//...scope.secondPassChecks<<{println'稍后执行'}//...scopeExit{secondPassChecks*.run()//Performlazychecking}即如果在某个时候无法确定表达式的类型,或者此时无法检查赋值,后面仍然可以检查...这是一个非常强大的特性。现在,newScope和scopeExit提供了一些有趣的语法糖:newScope{secondPassChecks=[]}在??DSL中的任何一点,都可以使用getCurrentScope()或更简单的currentScope://...currentScope访问当前范围。secondPassChecks<<{println'稍后执行'}//...一般模式是:确定入口点将新作用域压入堆栈,并在此作用域中初始化自定义变量使用各种事件,可以使用存储信息在自定义作用域中执行检查、延迟检查...确定退出作用域的入口点,调用scopeExit最后执行附加检查2.8其他有用的方法有关辅助方法的完整列表,请参阅类org.codehaus。groovy.transform.stc.GroovyTypeCheckingExtensionSupport和类org.codehaus.groovy.transform.stc.TypeCheckingExtension。但是,要特别注意以下方法:isDynamic:接受一个VariableExpression作为参数,如果变量是DynamicExpression则返回true,这意味着在脚本中,它不是使用type或def定义的。isGenerated:接受MethodNode作为参数,并告知该方法是否由类型检查器扩展使用newMethod方法生成例如:isAnnotatedBy(node,NotNull)getTargetMethod:接受方法调用作为参数并返回MethodNodelegatesTo:模拟@DelegatesTo注释的行为。可以让我们判断参数会被委托给具体的类型(也可以指定委托策略)3.总结类型检测扩展使用相关的知识要点介绍到这里。以上相关内容请参考Groovy官方文档:https://img.ydisp.cn/news/20230106/wtccbhbsrco