[groovy] IDEA-207415: Better interaction of final fields with @TupleConstructor
authorKonstantin Nisht <konstantin.nisht@jetbrains.com>
Wed, 12 Aug 2020 23:48:43 +0000 (02:48 +0300)
committerintellij-monorepo-bot <intellij-monorepo-bot-no-reply@jetbrains.com>
Thu, 13 Aug 2020 00:36:30 +0000 (00:36 +0000)
GitOrigin-RevId: 7e71187a9a568ee013e918fd5addb588f274dde9

plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/annotator/checkers/TupleConstructorAnnotationChecker.kt
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/control/finalVar/GrFinalVariableAccessInspection.java
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/GroovyCommonClassNames.java
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/ast/GeneratedConstructorCollector.kt
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/ast/GrTupleConstructorUtils.kt [new file with mode: 0644]
plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/transformations/immutable/immutable.kt
plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TupleConstructorTest.groovy

index 3073a39fdcd4af330370f3002704f9e8785ca20d..30382252ade4236c65d5e30c4695e6d39e43781d 100644 (file)
@@ -89,7 +89,7 @@ import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
 import org.jetbrains.plugins.groovy.lang.psi.util.*;
 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.GrTupleConstructorUtils;
 import org.jetbrains.plugins.groovy.lang.resolve.ast.InheritConstructorContributor;
 import org.jetbrains.plugins.groovy.transformations.immutable.GrImmutableUtils;
 
@@ -489,9 +489,8 @@ public class GroovyAnnotator extends GroovyElementVisitor {
 
     PsiAnnotation anno = typeDefinition.getAnnotation(GroovyCommonClassNames.GROOVY_TRANSFORM_TUPLE_CONSTRUCTOR);
     if (anno != null) {
-      if (!TupleConstructorAnnotationChecker.isSuperCalledInPre(anno)) {
-        PsiNameValuePair preAttribute = AnnotationUtil.findDeclaredAttribute(anno, "pre");
-        Objects.requireNonNull(preAttribute);
+      PsiNameValuePair preAttribute = AnnotationUtil.findDeclaredAttribute(anno, "pre");
+      if (preAttribute != null && !TupleConstructorAnnotationChecker.isSuperCalledInPre(anno)) {
         holder.newAnnotation(HighlightSeverity.ERROR, GroovyBundle.message("there.is.no.default.constructor.available.in.class.0", qName))
           .range(preAttribute).create();
       }
@@ -606,9 +605,7 @@ public class GroovyAnnotator extends GroovyElementVisitor {
     PsiAnnotation tupleConstructor = containingClass.getAnnotation(GroovyCommonClassNames.GROOVY_TRANSFORM_TUPLE_CONSTRUCTOR);
     if (tupleConstructor == null) return;
     if (!Boolean.FALSE.equals(GrAnnotationUtil.inferBooleanAttribute(tupleConstructor, "defaults"))) return;
-    List<String> excludes = GeneratedConstructorCollector.getIdentifierList(tupleConstructor, "excludes");
-    List<String> includes = GeneratedConstructorCollector.getIdentifierList(tupleConstructor, "includes");
-    if ((excludes != null && !excludes.contains(field.getName())) || (includes != null && includes.contains(field.getName()))) {
+    if (GrTupleConstructorUtils.isFieldAccepted(tupleConstructor, field)) {
       myHolder.newAnnotation(HighlightSeverity.ERROR, GroovyBundle.message("initializers.are.forbidden.with.defaults"))
         .range(initializer)
         .create();
index 81ee550678d48c2c0c0ab9596a7e0d4a46df259b..f5404d7f8c1ea409fb4141df0904d44d3bed505e 100644 (file)
@@ -13,8 +13,8 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethod
 import org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtil
 import org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtil.inferClosureAttribute
 import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames
-import org.jetbrains.plugins.groovy.lang.resolve.ast.GeneratedConstructorCollector
 import org.jetbrains.plugins.groovy.lang.resolve.ast.contributor.SyntheticKeywordConstructorContributor.Companion.isSyntheticConstructorCall
+import org.jetbrains.plugins.groovy.lang.resolve.ast.getIdentifierList
 
 class TupleConstructorAnnotationChecker : CustomAnnotationChecker() {
 
@@ -44,7 +44,7 @@ class TupleConstructorAnnotationChecker : CustomAnnotationChecker() {
     if (annotation.qualifiedName != GroovyCommonClassNames.GROOVY_TRANSFORM_TUPLE_CONSTRUCTOR) {
       return false
     }
-    val excludes = GeneratedConstructorCollector.getIdentifierList(annotation, "excludes")
+    val excludes = getIdentifierList(annotation, "excludes")
     val includes = AnnotationUtil.findDeclaredAttribute(annotation, "includes")
     if (includes != null && excludes != null && excludes.isNotEmpty()) {
       registerIdentifierListError(holder, AnnotationUtil.findDeclaredAttribute(annotation, "excludes")!!)
index 2ed46c27131d9f99c7c9ed2ef7f78433c9034c8e..3e79ccc60f354100366fe875fb3a54e5c4a48393 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 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.
+// 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.codeInspection.control.finalVar;
 
 import com.intellij.codeInspection.LocalQuickFix;
@@ -30,7 +30,9 @@ import org.jetbrains.plugins.groovy.lang.psi.controlFlow.VariableDescriptor;
 import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.ControlFlowBuilder;
 import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.GrFieldControlFlowPolicy;
 import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.ResolvedVariableDescriptor;
+import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
+import org.jetbrains.plugins.groovy.lang.resolve.ast.GrTupleConstructorUtils;
 import org.jetbrains.plugins.groovy.transformations.immutable.GrImmutableUtils;
 
 import java.util.*;
@@ -299,6 +301,8 @@ public class GrFinalVariableAccessInspection extends BaseInspection {
 
     if (isImmutableField(field)) return true;
 
+    if (isInitializedInTupleConstructor(field)) return true;
+
     final boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC);
 
     final GrTypeDefinition aClass = ((GrTypeDefinition)field.getContainingClass());
@@ -356,6 +360,18 @@ public class GrFinalVariableAccessInspection extends BaseInspection {
     return true;
   }
 
+  private static boolean isInitializedInTupleConstructor(@NotNull GrField field) {
+    var containingClass = field.getContainingClass();
+    if (containingClass == null) {
+      return false;
+    }
+    PsiAnnotation anno = containingClass.getAnnotation(GroovyCommonClassNames.GROOVY_TRANSFORM_TUPLE_CONSTRUCTOR);
+    if (anno == null) {
+      return false;
+    }
+    return GrTupleConstructorUtils.isFieldAccepted(anno, field);
+  }
+
   private static boolean isImmutableField(@NotNull GrField field) {
     GrModifierList fieldModifierList = field.getModifierList();
     if (fieldModifierList != null && fieldModifierList.hasExplicitVisibilityModifiers()) return false;
index 68dcfa68ee017b270ffb0aac910d20e7a2a1fbb9..7467c1f473f03dfee7d79a7bc952c3a10e03cf00 100644 (file)
@@ -29,6 +29,7 @@ public interface GroovyCommonClassNames {
   String JAVA_UTIL_REGEX_MATCHER = "java.util.regex.Matcher";
   String GROOVY_TRANSFORM_FIELD = "groovy.transform.Field";
   String GROOVY_TRANSFORM_TUPLE_CONSTRUCTOR = "groovy.transform.TupleConstructor";
+  String GROOVY_TRANSFORM_PROPERTY_OPTIONS = "groovy.transform.PropertyOptions";
   String GROOVY_TRANSFORM_IMMUTABLE = "groovy.transform.Immutable";
   String GROOVY_TRANSFORM_CANONICAL = "groovy.transform.Canonical";
   String GROOVY_LANG_REFERENCE = "groovy.lang.Reference";
index dff960d394859860298e8006221d521ad78ab327..b1bc4522f2219541b640a92fd6ce7e3017a9e06d 100644 (file)
@@ -3,10 +3,9 @@ package org.jetbrains.plugins.groovy.lang.resolve.ast
 
 import com.intellij.psi.*
 import com.intellij.psi.util.PropertyUtilBase
-import groovy.transform.Undefined
+import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifier
 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField
 import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter
-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.impl.synthetic.GrLightParameter
 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil
@@ -31,7 +30,7 @@ class GeneratedConstructorCollector(tupleConstructor: PsiAnnotation?,
   }
 
 
-  fun accept(clazz: PsiClass, includePropertes: Boolean, includeBeans: Boolean, includeFields: Boolean) {
+  fun accept(clazz: PsiClass, includeProperties: Boolean, includeBeans: Boolean, includeFields: Boolean) {
     val (properties, setters, fields) = getGroupedClassMembers(clazz)
 
     fun addParameter(origin: PsiField) {
@@ -44,7 +43,7 @@ class GeneratedConstructorCollector(tupleConstructor: PsiAnnotation?,
       collector.add(lightParameter)
     }
 
-    if (includePropertes) {
+    if (includeProperties) {
       for (property in properties) {
         addParameter(property)
       }
@@ -61,6 +60,7 @@ class GeneratedConstructorCollector(tupleConstructor: PsiAnnotation?,
 
     if (includeFields) {
       for (field in fields) {
+        if (field.hasModifierProperty(GrModifier.FINAL) && field.initializer != null) continue
         addParameter(field)
       }
     }
@@ -95,32 +95,5 @@ class GeneratedConstructorCollector(tupleConstructor: PsiAnnotation?,
       return Triple(properties, setters, fields)
     }
 
-    private fun String.isInternal(): Boolean = contains("$")
-
-    @JvmStatic
-    fun getIdentifierList(annotation: PsiAnnotation, attributeName: String): List<String>? {
-      annotation.takeIf { it.hasAttribute(attributeName) } ?: return null
-      val rawIdentifiers = GrAnnotationUtil.inferStringAttribute(annotation, attributeName)
-      return rawIdentifiers?.split(',')?.mapNotNull { it.trim().takeUnless(CharSequence::isBlank) }?.toList()
-             ?: GrAnnotationUtil.getStringArrayValue(annotation, attributeName, false)
-    }
-
-    private fun collectNamesOrderInformation(tupleConstructor: PsiAnnotation): Pair<(String) -> Boolean, List<String>?> {
-
-      val excludes: List<String> = getIdentifierList(tupleConstructor, "excludes") ?: emptyList()
-
-      val includes: List<String>? = getIdentifierList(tupleConstructor, "includes")
-        ?.takeUnless { Undefined.isUndefined(it.singleOrNull()) }
-
-      val allowInternalNames = GrAnnotationUtil.inferBooleanAttribute(tupleConstructor, "allNames") ?: false
-
-      val filter: (String) -> Boolean = { name: String ->
-        val internalFilter = allowInternalNames || !name.isInternal()
-        val excludesFilter = !excludes.contains(name)
-        val includesFilter = includes == null || includes.contains(name)
-        internalFilter && excludesFilter && includesFilter
-      }
-      return filter to includes
-    }
   }
 }
\ No newline at end of file
diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/ast/GrTupleConstructorUtils.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/ast/GrTupleConstructorUtils.kt
new file mode 100644 (file)
index 0000000..e5a6eda
--- /dev/null
@@ -0,0 +1,57 @@
+// 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.
+@file:JvmName("GrTupleConstructorUtils")
+
+package org.jetbrains.plugins.groovy.lang.resolve.ast
+
+import com.intellij.psi.PsiAnnotation
+import groovy.transform.Undefined
+import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifier
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField
+import org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtil
+import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames
+
+fun getIdentifierList(annotation: PsiAnnotation, attributeName: String): List<String>? {
+  annotation.takeIf { it.hasAttribute(attributeName) } ?: return null
+  val rawIdentifiers = GrAnnotationUtil.inferStringAttribute(annotation, attributeName)
+  return rawIdentifiers?.split(',')?.mapNotNull { it.trim().takeUnless(CharSequence::isBlank) }?.toList()
+         ?: GrAnnotationUtil.getStringArrayValue(annotation, attributeName, false)
+}
+
+
+private fun String.isInternal(): Boolean = contains("$")
+
+internal fun collectNamesOrderInformation(tupleConstructor: PsiAnnotation): Pair<(String) -> Boolean, List<String>?> {
+
+  val excludes: List<String> = getIdentifierList(tupleConstructor, "excludes") ?: emptyList()
+
+  val includes: List<String>? = getIdentifierList(tupleConstructor, "includes")
+    ?.takeUnless { Undefined.isUndefined(it.singleOrNull()) }
+
+  val allowInternalNames = GrAnnotationUtil.inferBooleanAttribute(tupleConstructor, "allNames") ?: false
+
+  val filter: (String) -> Boolean = { name: String ->
+    val internalFilter = allowInternalNames || !name.isInternal()
+    val excludesFilter = !excludes.contains(name)
+    val includesFilter = includes == null || includes.contains(name)
+    internalFilter && excludesFilter && includesFilter
+  }
+  return filter to includes
+}
+
+
+fun isFieldAccepted(annotation: PsiAnnotation, field: GrField): Boolean {
+  if (field.hasModifierProperty(GrModifier.STATIC)) return false
+  if (field.isProperty) {
+    val hasCustomPropertyHandler = field.containingClass?.hasAnnotation(GroovyCommonClassNames.GROOVY_TRANSFORM_PROPERTY_OPTIONS) ?: false
+    if (hasCustomPropertyHandler) return false
+    val includeProperties = GrAnnotationUtil.inferBooleanAttribute(annotation, "includeProperties") ?: true
+    if (!includeProperties) return false
+  }
+  else {
+    val includeFields = GrAnnotationUtil.inferBooleanAttribute(annotation, "includeFields") ?: false
+    if (!includeFields) return false
+  }
+  val (namesFilter, _) = collectNamesOrderInformation(annotation)
+  return namesFilter(field.name)
+}
+
index 870de3df438a88a021bd0e8fea2470de2f99e1b7..a3fbbdf41d448875560d8635efecfeaece893a66 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2018 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.
+// 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.
 @file:JvmName("GrImmutableUtils")
 
 package org.jetbrains.plugins.groovy.transformations.immutable
@@ -84,6 +84,7 @@ fun collectImmutableAnnotations(alias: GrAnnotation, list: MutableList<in GrAnno
   list.add(GrLightAnnotation(owner, alias, GROOVY_TRANSFORM_IMMUTABLE_OPTIONS, emptyMap()))
   list.add(GrLightAnnotation(owner, alias, GROOVY_TRANSFORM_KNOWN_IMMUTABLE, emptyMap()))
   list.add(GrLightAnnotation(owner, alias, GroovyCommonClassNames.GROOVY_TRANSFORM_TUPLE_CONSTRUCTOR, mapOf("defaults" to "false")))
+  list.add(GrLightAnnotation(owner, alias, GroovyCommonClassNames.GROOVY_TRANSFORM_PROPERTY_OPTIONS, emptyMap()))
 }
 
 fun isImmutable(field: GrField): Boolean {
index ac45a7a064236030312c246bed956921f990d8cd..1836c04ee14539a6a6de88ba28530b89fc63b65e 100644 (file)
@@ -178,7 +178,7 @@ class NN {
 
 @groovy.transform.TupleConstructor(defaults = false, includeSuperProperties = true)
 class Rr extends NN {
-    String actionType = ""
+    String actionType
     long referrerCode;
     boolean referrerUrl;
 }
@@ -216,7 +216,7 @@ static void main(String[] args) {
     new Rr({}, 1)
     new Rr({}, 1, true)
     new Rr({}, 1, true, "")
-    new Rr(actionType: {}, referrerUrl: true, referrerCode: 1)
+    new Rr(actionType: {}, referrerUrl: true, referrerCode: 1, prop: "a")
 }"""
   }
 
@@ -366,6 +366,25 @@ class NN { }
 @groovy.transform.CompileStatic
 @groovy.transform.TupleConstructor(pre = { super() }, <error>callSuper = true</error>)
 class Rr extends NN {
+}"""
+  }
+
+
+  @Test
+  void 'final fields in constructor'() {
+    highlightingTest """
+@groovy.transform.CompileStatic
+@groovy.transform.TupleConstructor(includeFields = true)
+class Rr {
+  private final int a = 1
+  private final boolean b
+  String c
+}
+
+@groovy.transform.CompileStatic
+static void main(String[] args) {
+    new Rr<error>("", 2)</error>
+    new Rr("", true)
 }"""
   }
 }