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.*;
}
}
- 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();
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
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;
return getString(targetValue);
}
+ @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) {
--- /dev/null
+// 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
<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"
def x = new <error>Cde</error>("mem", 1, true)
}
+}"""
+ }
+
+ @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() {}
}"""
}
}