You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@groovy.apache.org by "Eric Milles (Jira)" <ji...@apache.org> on 2022/10/29 16:32:00 UTC

[jira] [Commented] (GROOVY-10801) AST transform for simple utility classes (only static fields and methods)

    [ https://issues.apache.org/jira/browse/GROOVY-10801?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17626081#comment-17626081 ] 

Eric Milles commented on GROOVY-10801:
--------------------------------------

If this idea has merit, I have a basic implementation to try out:
{code:groovy}
import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedConstructor
import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX
import static org.codehaus.groovy.ast.tools.GeneralUtils.throwS
import static org.codehaus.groovy.control.CompilePhase.SEMANTIC_ANALYSIS

import java.lang.annotation.*
import java.lang.reflect.Modifier

import org.codehaus.groovy.ast.*
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformationClass

@GroovyASTTransformationClass(classes = NamespaceTransform)
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@interface Namespace {
}

//------------------------------------------------------------------------------

@AutoFinal @GroovyASTTransformation(phase = SEMANTIC_ANALYSIS)
class NamespaceTransform implements ASTTransformation {

  @Override
  void visit(ASTNode[] nodes, SourceUnit source) {
    if (!nodes || !nodes[0] || !nodes[1]) return
    if (!(nodes[1] instanceof ClassNode)) return
    if (!(nodes[0] instanceof AnnotationNode)) return
    if (nodes[0].classNode?.name != Namespace.name) return
    try {
      ClassNode classNode = nodes[1]

      // check preconditions
      if ((classNode.modifiers & 0x6600) || classNode.methods.any{ it.isAbstract() }) // TODO: record, sealed
        source.addFatalError('Namespace cannot apply to abstract, interface, annotation, or enumeration', nodes[0])
      if (classNode.declaredConstructors)
        source.addFatalError('Namespace cannot have explicit constructor(s)', classNode.declaredConstructors.first())

      xform(classNode)
    } catch (e) {
      source.addException(e)
    }
  }

  private static void xform(ClassNode classNode) {
    // adjust modifiers
    classNode.modifiers = (classNode.modifiers & 0x807) | Modifier.FINAL
    if (classNode.outerClass) {
      classNode.removeField('this$0') // TODO: stop MOP method generation
      classNode.staticClass = true; classNode.modifiers |= Modifier.STATIC
    }

    // make fields static and final
    for (field in classNode.fields) {
      field.modifiers |= Modifier.FINAL | Modifier.STATIC
      if (field.isSynthetic()) { // property
        field.synthetic = false
        field.modifiers ^= Modifier.PUBLIC | Modifier.PRIVATE
        classNode.properties.removeIf { it.name == field.name }
      }
    }

    // make methods static
    for (method in classNode.methods) {
      method.modifiers |= Modifier.STATIC
    }

    // add private no-arg constructor
    addGeneratedConstructor(classNode, Modifier.PRIVATE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, throwS(ctorX(ClassHelper.make(AssertionError))))

    // TODO: disable "implements GroovyObject"
  }
}
{code}

I'd like to stop the addition of {{GroovyObject}} and {{getMetaClass}}/{{setMetaClass}} but it is somewhat baked into the compiler.  I don't think they harm usability any.

> AST transform for simple utility classes (only static fields and methods)
> -------------------------------------------------------------------------
>
>                 Key: GROOVY-10801
>                 URL: https://issues.apache.org/jira/browse/GROOVY-10801
>             Project: Groovy
>          Issue Type: New Feature
>          Components: ast builder
>            Reporter: Eric Milles
>            Assignee: Eric Milles
>            Priority: Minor
>
> Similar to the {{@Category}} transform, I'd like to have a local transform for utility classes.  Consider the following:
> {code:groovy}
> @Namespace
> class C {
>   int constant = 1
>   def method() {
>     // ...
>   }
> }
> {code}
> This would be Groovy shorthand for the canonical "utility class":
> {code:groovy}
> final class C {
>   private C() { throw new AssertionError() }
>   public static final int constant = 1
>   static method {
>     // ...
>   }
> }
> {code}



--
This message was sent by Atlassian Jira
(v8.20.10#820010)