[groovy] IDEA-207415: Support super constructor call in `pre` attribute
authorKonstantin Nisht <konstantin.nisht@jetbrains.com>
Tue, 11 Aug 2020 15:21:57 +0000 (18:21 +0300)
committerintellij-monorepo-bot <intellij-monorepo-bot-no-reply@jetbrains.com>
Thu, 13 Aug 2020 00:36:29 +0000 (00:36 +0000)
GitOrigin-RevId: e54b890d40245a8426b559ee561c1b94e21c4184

plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrAnnotationUtil.java
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/ast/contributor/SyntheticKeywordConstructorContributor.kt [new file with mode: 0644]
plugins/groovy/src/META-INF/plugin.xml
plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TupleConstructorTest.groovy

index 119f795370b4d8c258aa55b2e7cbf30a4102049d..01a671b6f58b3ca9f35c00d631f7ed29408aedff 100644 (file)
@@ -89,6 +89,7 @@ import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
 import org.jetbrains.plugins.groovy.lang.resolve.api.GroovyConstructorReference;
 import org.jetbrains.plugins.groovy.lang.resolve.ast.GeneratedConstructorCollector;
 import org.jetbrains.plugins.groovy.lang.resolve.ast.InheritConstructorContributor;
+import org.jetbrains.plugins.groovy.lang.resolve.ast.contributor.SyntheticKeywordConstructorContributor;
 import org.jetbrains.plugins.groovy.transformations.immutable.GrImmutableUtils;
 
 import java.util.*;
@@ -444,26 +445,34 @@ public class GroovyAnnotator extends GroovyElementVisitor {
     }
   }
 
-  private static void checkConstructors(AnnotationHolder holder, GrTypeDefinition typeDefinition) {
+  private static void checkConstructors(@NotNull AnnotationHolder holder, @NotNull GrTypeDefinition typeDefinition) {
     if (typeDefinition.isEnum() || typeDefinition.isInterface() || typeDefinition.isAnonymous() || typeDefinition instanceof GrTypeParameter) return;
     final PsiClass superClass = typeDefinition.getSuperClass();
     if (superClass == null) return;
 
     if (InheritConstructorContributor.hasInheritConstructorsAnnotation(typeDefinition)) return;
 
-    PsiMethod defConstructor = getDefaultConstructor(superClass);
-    boolean hasImplicitDefConstructor = superClass.getConstructors().length == 0;
-
     final PsiMethod[] constructors = typeDefinition.getCodeConstructors();
+    checkDefaultConstructors(holder, typeDefinition, superClass, constructors);
+    checkRecursiveConstructors(holder, constructors);
+  }
+
+  private static void checkDefaultConstructors(@NotNull AnnotationHolder holder,
+                                               @NotNull GrTypeDefinition typeDefinition,
+                                               @NotNull PsiClass superClass,
+                                               PsiMethod @NotNull[] constructors) {
+    PsiMethod defConstructor = getDefaultConstructor(superClass);
+    boolean needExplicitSuperCall = superClass.getConstructors().length != 0 && (defConstructor == null || !PsiUtil.isAccessible(typeDefinition, defConstructor));
+    if (!needExplicitSuperCall) return;
     final String qName = superClass.getQualifiedName();
-    if (constructors.length == 0) {
-      if (!hasImplicitDefConstructor && (defConstructor == null || !PsiUtil.isAccessible(typeDefinition, defConstructor))) {
-        final TextRange range = GrHighlightUtil.getClassHeaderTextRange(typeDefinition);
-        holder.newAnnotation(HighlightSeverity.ERROR, GroovyBundle.message("there.is.no.default.constructor.available.in.class.0", qName)).range(range)
-          .withFix(QuickFixFactory.getInstance().createCreateConstructorMatchingSuperFix(typeDefinition)).create();
-      }
-      return;
+
+    if (typeDefinition.getConstructors().length == 0) {
+      final TextRange range = GrHighlightUtil.getClassHeaderTextRange(typeDefinition);
+      holder.newAnnotation(HighlightSeverity.ERROR, GroovyBundle.message("there.is.no.default.constructor.available.in.class.0", qName))
+        .range(range)
+        .withFix(QuickFixFactory.getInstance().createCreateConstructorMatchingSuperFix(typeDefinition)).create();
     }
+
     for (PsiMethod method : constructors) {
       if (method instanceof GrMethod) {
         final GrOpenBlock block = ((GrMethod)method).getBlock();
@@ -472,14 +481,22 @@ public class GroovyAnnotator extends GroovyElementVisitor {
         if (statements.length > 0) {
           if (statements[0] instanceof GrConstructorInvocation) continue;
         }
-
-        if (!hasImplicitDefConstructor && (defConstructor == null || !PsiUtil.isAccessible(typeDefinition, defConstructor))) {
-          holder.newAnnotation(HighlightSeverity.ERROR, GroovyBundle.message("there.is.no.default.constructor.available.in.class.0", qName)).range(GrHighlightUtil.getMethodHeaderTextRange(method)).create();
-        }
+        holder.newAnnotation(HighlightSeverity.ERROR, GroovyBundle.message("there.is.no.default.constructor.available.in.class.0", qName))
+          .range(GrHighlightUtil.getMethodHeaderTextRange(method)).create();
       }
     }
 
-    checkRecursiveConstructors(holder, constructors);
+    PsiAnnotation anno = typeDefinition.getAnnotation(GroovyCommonClassNames.GROOVY_TRANSFORM_TUPLE_CONSTRUCTOR);
+    if (anno == null) return;
+    GrClosableBlock block = GrAnnotationUtil.inferClosureAttribute(anno, "pre");
+    if (block == null) return;
+    GrStatement[] statements = block.getStatements();
+    if (!(statements.length != 0 &&
+          statements[0] instanceof GrMethodCall &&
+          SyntheticKeywordConstructorContributor.isSyntheticConstructorCall((GrMethodCall)statements[0]))) {
+      holder.newAnnotation(HighlightSeverity.ERROR, GroovyBundle.message("there.is.no.default.constructor.available.in.class.0", qName))
+        .range(block).create();
+    }
   }
 
   @Override
index f731f0a64740e133f25e7bb9b9c0041372f8688c..ae5b869a1a879c95b17e0eda287e3cfded4bcc01 100644 (file)
@@ -7,6 +7,7 @@ import com.intellij.util.containers.ContainerUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
 
@@ -27,6 +28,15 @@ public final class GrAnnotationUtil {
   }
 
   @Nullable
+  public static GrClosableBlock inferClosureAttribute(@NotNull PsiAnnotation annotation, @NotNull String attributeName) {
+    PsiAnnotationMemberValue targetValue = annotation.findAttributeValue(attributeName);
+    if (targetValue instanceof GrClosableBlock) {
+      return (GrClosableBlock)targetValue;
+    }
+    return null;
+  }
+
+  @Nullable
   public static String getString(@Nullable PsiAnnotationMemberValue targetValue) {
     if (targetValue instanceof PsiLiteral) {
       final Object value = ((PsiLiteral)targetValue).getValue();
diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/ast/contributor/SyntheticKeywordConstructorContributor.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/ast/contributor/SyntheticKeywordConstructorContributor.kt
new file mode 100644 (file)
index 0000000..455b643
--- /dev/null
@@ -0,0 +1,82 @@
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package org.jetbrains.plugins.groovy.lang.resolve.ast.contributor
+
+import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiElement
+import com.intellij.psi.ResolveState
+import com.intellij.psi.scope.ElementClassHint
+import com.intellij.psi.scope.PsiScopeProcessor
+import com.intellij.psi.util.parentOfType
+import com.intellij.util.SmartList
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod
+import org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtil
+import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightMethodBuilder
+import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames.GROOVY_TRANSFORM_TUPLE_CONSTRUCTOR
+import org.jetbrains.plugins.groovy.lang.resolve.ClosureMemberContributor
+import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil
+
+class SyntheticKeywordConstructorContributor : ClosureMemberContributor() {
+
+  override fun processMembers(closure: GrClosableBlock, processor: PsiScopeProcessor, place: PsiElement, state: ResolveState) {
+    if (!ResolveUtil.shouldProcessMethods(processor.getHint(ElementClassHint.KEY))) return
+    val nameHint = ResolveUtil.getNameHint(processor)
+    if (nameHint != null && nameHint != "super") return
+
+    if (closure != place.parentOfType<GrClosableBlock>()) return
+    val anno = closure.parentOfType<PsiAnnotation>()?.takeIf { it.qualifiedName == GROOVY_TRANSFORM_TUPLE_CONSTRUCTOR } ?: return
+    if (GrAnnotationUtil.inferClosureAttribute(anno, "pre") != closure) return
+
+
+    val syntheticMethods = createSyntheticConstructors(closure)
+
+    for (method in syntheticMethods) {
+      if (!processor.execute(method, state)) {
+        return
+      }
+    }
+  }
+
+  private fun createSyntheticConstructors(closure: GrClosableBlock): List<GrMethod> {
+    val outerClass = closure.parentOfType<PsiClass>() ?: return emptyList()
+    val superClass = outerClass.superClass
+    val methods = SmartList<GrMethod>()
+    if (superClass != null) {
+      val constructors = superClass.constructors
+      if (constructors.isEmpty()) {
+        val method = SyntheticKeywordConstructor(outerClass, superClass, "super")
+        methods.add(method)
+      }
+      else {
+        for (constructor in constructors) {
+          val method = SyntheticKeywordConstructor(outerClass, superClass, "super")
+          for (param in constructor.parameterList.parameters) {
+            method.addParameter(param.name, param.type)
+          }
+          methods.add(method)
+        }
+      }
+    }
+    return methods
+  }
+
+  private class SyntheticKeywordConstructor(containingClass: PsiClass, superClass: PsiClass, name: String) :
+    GrLightMethodBuilder(containingClass.manager, name) {
+    init {
+      assert(name.isReserved())
+      isConstructor = true
+      navigationElement = superClass
+      this.containingClass = containingClass
+    }
+  }
+
+  companion object {
+    private fun String?.isReserved(): Boolean = this == "super"
+
+    @JvmStatic
+    fun isSyntheticConstructorCall(call: GrMethodCall?): Boolean =
+      call?.callReference?.methodName.isReserved() && call?.resolveMethod() is SyntheticKeywordConstructor
+  }
+}
\ No newline at end of file
index 38784b847aaff609f218cfdb93bcc01f7d9be5b8..f9af57562439ab7ff0b88fb311fe6aefe2d554e4 100644 (file)
     <membersContributor implementation="org.jetbrains.plugins.groovy.builder.StreamingJsonBuilderDelegateContributor"/>
     <membersContributor implementation="org.jetbrains.plugins.groovy.dsl.GdslMemberContributor" order="last"/>
 
+    <membersContributor implementation="org.jetbrains.plugins.groovy.lang.resolve.ast.contributor.SyntheticKeywordConstructorContributor"/>
+
     <closureMissingMethodContributor implementation="org.jetbrains.plugins.groovy.lang.resolve.PluginXmlClosureMemberContributor"/>
 
     <astTransformationSupport id="groovyObjectTransformation" order="first"
index 41199283e649b4eb4117859451220e46e294e2cc..e07b0d2c0e26238d401643253239acf9a3a9b1b8 100644 (file)
@@ -335,4 +335,32 @@ class X {
 
 }"""
   }
+
+  @Test
+  void 'super resolve for pre'() {
+    highlightingTest """
+class NN { NN(String s) {} }
+
+@groovy.transform.CompileStatic
+@groovy.transform.TupleConstructor(pre = { super("") })
+class Rr extends NN {
+    int q
+    
+    def foo() {}
+}"""
+  }
+
+  @Test
+  void 'pre highlighting'() {
+    highlightingTest """
+class NN { NN(String s) {} }
+
+@groovy.transform.CompileStatic
+@groovy.transform.TupleConstructor(pre = <error>{ }</error>)
+class Rr extends NN {
+    int q
+    
+    def foo() {}
+}"""
+  }
 }