From 4a52e2dbbfda51efd0e4553c4133101bac9489de Mon Sep 17 00:00:00 2001 From: Maxim Medvedev Date: Tue, 6 Sep 2011 12:03:22 +0400 Subject: [PATCH] IDEA-73779 Infer the generic property types --- .../GrReferenceExpressionImpl.java | 5 +- .../groovy/lang/resolve/ResolveUtil.java | 3 +- .../processors/AccessorResolverProcessor.java | 19 +- .../AccessorSubstitutorComputer.java | 55 +++++ .../processors/MethodResolverProcessor.java | 186 +-------------- .../processors/MethodSubstitutorComputer.java | 61 +++++ .../processors/SubstitutorComputer.java | 219 ++++++++++++++++++ .../lang/resolve/TypeInferenceTest.java | 4 + .../inference/typeArgsInAccessor/a.groovy | 5 + 9 files changed, 374 insertions(+), 183 deletions(-) create mode 100644 plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/AccessorSubstitutorComputer.java create mode 100644 plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/MethodSubstitutorComputer.java create mode 100644 plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java create mode 100644 plugins/groovy/testdata/resolve/inference/typeArgsInAccessor/a.groovy diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceExpressionImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceExpressionImpl.java index bc40672a7ab4..001649ee29ab 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceExpressionImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceExpressionImpl.java @@ -175,7 +175,8 @@ public class GrReferenceExpressionImpl extends GrReferenceElementImpl accessorResults = new ArrayList(); for (String accessorName : accessorNames) { - AccessorResolverProcessor accessorResolver = new AccessorResolverProcessor(accessorName, this, !isLValue); + AccessorResolverProcessor accessorResolver = + new AccessorResolverProcessor(accessorName, this, !isLValue, false, getThisType(), getTypeArguments()); GrReferenceResolveUtil.resolveImpl(accessorResolver, this); final GroovyResolveResult[] candidates = accessorResolver.getCandidates(); //can be only one correct candidate if (candidates.length > 0 && candidates[candidates.length - 1].isStaticsOK()) { @@ -286,7 +287,7 @@ public class GrReferenceExpressionImpl extends GrReferenceElementImpl applicable = new ArrayList(); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/AccessorResolverProcessor.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/AccessorResolverProcessor.java index fc78e4e9ccaf..97139613dc80 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/AccessorResolverProcessor.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/AccessorResolverProcessor.java @@ -16,6 +16,8 @@ package org.jetbrains.plugins.groovy.lang.resolve.processors; import com.intellij.psi.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyResolveResultImpl; import org.jetbrains.plugins.groovy.lang.psi.util.GdkMethodUtil; @@ -26,10 +28,21 @@ import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils; */ public class AccessorResolverProcessor extends ResolverProcessor { private final boolean mySearchForGetter; + private final SubstitutorComputer mySubstitutorComputer; - public AccessorResolverProcessor(String name, PsiElement place, boolean searchForGetter) { + public AccessorResolverProcessor(String name, GroovyPsiElement place, boolean searchForGetter) { + this(name, place, searchForGetter, false, null, PsiType.EMPTY_ARRAY); + } + + public AccessorResolverProcessor(String name, + GroovyPsiElement place, + boolean searchForGetter, + boolean byShape, + @Nullable PsiType thisType, + @NotNull PsiType[] typeArguments) { super(name, RESOLVE_KINDS_METHOD, place, PsiType.EMPTY_ARRAY); mySearchForGetter = searchForGetter; + mySubstitutorComputer = byShape ? null : new AccessorSubstitutorComputer(thisType, typeArguments, place); } public boolean execute(PsiElement element, ResolveState state) { @@ -52,6 +65,10 @@ public class AccessorResolverProcessor extends ResolverProcessor { private boolean addAccessor(PsiMethod method, ResolveState state) { PsiSubstitutor substitutor = state.get(PsiSubstitutor.KEY); if (substitutor == null) substitutor = PsiSubstitutor.EMPTY; + + if (mySubstitutorComputer != null) { + substitutor = mySubstitutorComputer.obtainSubstitutor(substitutor, method, state); + } boolean isAccessible = isAccessible(method); final GroovyPsiElement resolveContext = state.get(RESOLVE_CONTEXT); boolean isStaticsOK = isStaticsOK(method, resolveContext); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/AccessorSubstitutorComputer.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/AccessorSubstitutorComputer.java new file mode 100644 index 000000000000..ec642a1767ce --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/AccessorSubstitutorComputer.java @@ -0,0 +1,55 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.groovy.lang.resolve.processors; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiType; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrReturnStatement; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod; + +/** + * @author Max Medvedev + */ +public class AccessorSubstitutorComputer extends SubstitutorComputer { + public AccessorSubstitutorComputer(PsiType thisType, PsiType[] typeArguments, GroovyPsiElement place) { + super(thisType, PsiType.EMPTY_ARRAY, typeArguments, false, place); + } + + @Nullable + @Override + protected PsiType getContextType() { + final PsiElement parent = myPlace.getParent(); + if (parent instanceof GrReturnStatement || exitsContains(myPlace)) { + final GrMethod method = PsiTreeUtil.getParentOfType(parent, GrMethod.class, true, GrClosableBlock.class); + if (method != null) { + return method.getReturnType(); + } + } + else if (parent instanceof GrAssignmentExpression && myPlace.getParent().equals(((GrAssignmentExpression)parent).getRValue())) { + return ((GrAssignmentExpression)parent).getLValue().getType(); + } + else if (parent instanceof GrVariable) { + return ((GrVariable)parent).getDeclaredType(); + } + return null; + } +} diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/MethodResolverProcessor.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/MethodResolverProcessor.java index 9ef427897481..90c8c78ba844 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/MethodResolverProcessor.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/MethodResolverProcessor.java @@ -16,35 +16,18 @@ package org.jetbrains.plugins.groovy.lang.resolve.processors; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.scope.JavaScopeProcessorEvent; import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.containers.hash.HashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils; -import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrReturnStatement; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrThrowStatement; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinitionBody; import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrGdkMethod; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod; -import org.jetbrains.plugins.groovy.lang.psi.api.types.GrClosureParameter; -import org.jetbrains.plugins.groovy.lang.psi.api.types.GrClosureSignature; import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyResolveResultImpl; import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; -import org.jetbrains.plugins.groovy.lang.psi.impl.types.GrClosureSignatureUtil; import org.jetbrains.plugins.groovy.lang.psi.util.GdkMethodUtil; import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; import org.jetbrains.plugins.groovy.lang.resolve.DominanceAwareMethod; @@ -56,20 +39,18 @@ import java.util.*; * @author ven */ public class MethodResolverProcessor extends ResolverProcessor { - private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.lang.resolve.processors.MethodResolverProcessor"); private final PsiType myThisType; @Nullable private final PsiType[] myArgumentTypes; - private final PsiType[] myTypeArguments; private final boolean myAllVariants; private final Set myInapplicableCandidates = new LinkedHashSet(); private final boolean myIsConstructor; private boolean myStopExecuting = false; - private Set myExitPoints; - private final boolean canNotBeExitPoint; private final boolean myByShape; + + private final SubstitutorComputer mySubstitutorComputer; public MethodResolverProcessor(String name, GroovyPsiElement place, boolean isConstructor, PsiType thisType, @Nullable PsiType[] argumentTypes, PsiType[] typeArguments) { this(name, place, isConstructor, thisType, argumentTypes, typeArguments, false, false); @@ -85,20 +66,12 @@ public class MethodResolverProcessor extends ResolverProcessor { myIsConstructor = isConstructor; myThisType = thisType; myArgumentTypes = argumentTypes; - myTypeArguments = typeArguments; myAllVariants = allVariants; - canNotBeExitPoint = !canBexExitPoint(place); myByShape = byShape; + + mySubstitutorComputer = new MethodSubstitutorComputer(thisType, argumentTypes, typeArguments, allVariants, place); } - private static boolean canBexExitPoint(PsiElement place) { - while (place != null) { - if (place instanceof GrMethod || place instanceof GrClosableBlock) return true; - if (place instanceof GrThrowStatement || place instanceof GrTypeDefinitionBody || place instanceof GroovyFile) return false; - place = place.getParent(); - } - return false; - } public boolean execute(PsiElement element, ResolveState state) { if (myStopExecuting) { @@ -110,7 +83,9 @@ public class MethodResolverProcessor extends ResolverProcessor { if (method.isConstructor() != myIsConstructor) return true; if (substitutor == null) substitutor = PsiSubstitutor.EMPTY; - substitutor = obtainSubstitutor(substitutor, method, state); + if (!myByShape) { + substitutor = mySubstitutorComputer.obtainSubstitutor(substitutor, method, state); + } boolean isAccessible = isAccessible(method); GroovyPsiElement resolveContext = state.get(RESOLVE_CONTEXT); boolean isStaticsOK = isStaticsOK(method, resolveContext); @@ -128,153 +103,6 @@ public class MethodResolverProcessor extends ResolverProcessor { return true; } - protected PsiSubstitutor obtainSubstitutor(PsiSubstitutor substitutor, PsiMethod method, ResolveState state) { - if (myByShape) { - return substitutor; - } - - final PsiTypeParameter[] typeParameters = method.getTypeParameters(); - if (myTypeArguments.length == typeParameters.length) { - for (int i = 0; i < typeParameters.length; i++) { - PsiTypeParameter typeParameter = typeParameters[i]; - final PsiType typeArgument = myTypeArguments[i]; - substitutor = substitutor.put(typeParameter, typeArgument); - } - return substitutor; - } - - if (argumentsSupplied() && method.hasTypeParameters()) { - PsiType[] argTypes = myArgumentTypes; - final GroovyPsiElement resolveContext = state.get(RESOLVE_CONTEXT); - assert argTypes != null; - if (method instanceof GrGdkMethod) { - //type inference should be performed from static method - PsiType[] newArgTypes = new PsiType[argTypes.length + 1]; - if (GdkMethodUtil.isInWithContext(resolveContext)) { - newArgTypes[0] = ((GrExpression)resolveContext).getType(); - } - else { - newArgTypes[0] = myThisType; - } - System.arraycopy(argTypes, 0, newArgTypes, 1, argTypes.length); - argTypes = newArgTypes; - - method = ((GrGdkMethod)method).getStaticMethod(); - LOG.assertTrue(method.isValid()); - } - else if (GdkMethodUtil.isInUseScope(resolveContext, method)) { - PsiType[] newArgTypes = new PsiType[argTypes.length + 1]; - newArgTypes[0] = myThisType; - System.arraycopy(argTypes, 0, newArgTypes, 1, argTypes.length); - argTypes = newArgTypes; - } - - return inferMethodTypeParameters(method, substitutor, typeParameters, argTypes); - } - - return substitutor; - } - - private PsiSubstitutor inferMethodTypeParameters(PsiMethod method, PsiSubstitutor partialSubstitutor, final PsiTypeParameter[] typeParameters, final PsiType[] argTypes) { - if (typeParameters.length == 0) return partialSubstitutor; - - if (argumentsSupplied()) { - final GrClosureSignature erasedSignature = GrClosureSignatureUtil.createSignatureWithErasedParameterTypes(method); - - final GrClosureSignature signature = GrClosureSignatureUtil.createSignature(method, partialSubstitutor); - final GrClosureParameter[] params = signature.getParameters(); - - final GrClosureSignatureUtil.ArgInfo[] argInfos = - GrClosureSignatureUtil.mapArgTypesToParameters(erasedSignature, argTypes, (GroovyPsiElement)myPlace, myAllVariants); - if (argInfos == null) return partialSubstitutor; - - int max = Math.max(params.length, argTypes.length); - - PsiType[] parameterTypes = new PsiType[max]; - PsiType[] argumentTypes = new PsiType[max]; - int i = 0; - for (int paramIndex = 0; paramIndex < argInfos.length; paramIndex++) { - PsiType paramType = params[paramIndex].getType(); - - GrClosureSignatureUtil.ArgInfo argInfo = argInfos[paramIndex]; - if (argInfo != null) { - if (argInfo.isMultiArg) { - if (paramType instanceof PsiArrayType) paramType = ((PsiArrayType)paramType).getComponentType(); - } - for (PsiType type : argInfo.args) { - argumentTypes[i] = handleConversion(paramType, type); - parameterTypes[i] = paramType; - i++; - } - } else { - parameterTypes[i] = paramType; - argumentTypes[i] = PsiType.NULL; - i++; - } - } - final PsiResolveHelper helper = JavaPsiFacade.getInstance(method.getProject()).getResolveHelper(); - PsiSubstitutor substitutor = helper.inferTypeArguments(typeParameters, parameterTypes, argumentTypes, LanguageLevel.HIGHEST); - for (PsiTypeParameter typeParameter : typeParameters) { - if (!substitutor.getSubstitutionMap().containsKey(typeParameter)) { - substitutor = inferFromContext(typeParameter, PsiUtil.getSmartReturnType(method), substitutor, helper); - } - } - - return partialSubstitutor.putAll(substitutor); - } - - return partialSubstitutor; - } - - private PsiType handleConversion(PsiType paramType, PsiType argType) { - final GroovyPsiElement context = (GroovyPsiElement)myPlace; - if (!TypesUtil.isAssignable(TypeConversionUtil.erasure(paramType), argType, context.getManager(), context.getResolveScope(), false) && - TypesUtil.isAssignableByMethodCallConversion(paramType, argType, context)) { - return paramType; - } - return argType; - } - - private PsiSubstitutor inferFromContext(PsiTypeParameter typeParameter, PsiType lType, PsiSubstitutor substitutor, PsiResolveHelper helper) { - if (myPlace != null) { - final PsiType inferred = helper.getSubstitutionForTypeParameter(typeParameter, lType, getContextType(), false, LanguageLevel.HIGHEST); - if (inferred != PsiType.NULL) { - return substitutor.put(typeParameter, inferred); - } - } - return substitutor; - } - - @Nullable - private PsiType getContextType() { - PsiElement call = myPlace.getParent(); - final PsiElement parent = call.getParent(); - PsiType rType = null; - if (parent instanceof GrReturnStatement || exitsContains(call)) { - final GrMethod method = PsiTreeUtil.getParentOfType(parent, GrMethod.class, true, GrClosableBlock.class); - if (method != null) rType = method.getReturnType(); - } - else if (parent instanceof GrAssignmentExpression && myPlace.getParent().equals(((GrAssignmentExpression)parent).getRValue())) { - rType = ((GrAssignmentExpression)parent).getLValue().getType(); - } - else if (parent instanceof GrVariable) { - rType = ((GrVariable)parent).getDeclaredType(); - } - return rType; - } - - private boolean exitsContains(PsiElement call) { - if (canNotBeExitPoint) return false; - if (myExitPoints == null) { - final GrMethod method = PsiTreeUtil.getParentOfType(myPlace, GrMethod.class, true, GrClosableBlock.class); - myExitPoints = new HashSet(); - if (method != null) { - myExitPoints.addAll(ControlFlowUtils.collectReturns(method.getBlock())); - } - } - return myExitPoints.contains(call); - } - @NotNull public GroovyResolveResult[] getCandidates() { if (!myAllVariants && super.hasCandidates()) { diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/MethodSubstitutorComputer.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/MethodSubstitutorComputer.java new file mode 100644 index 000000000000..24afacdd2c01 --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/MethodSubstitutorComputer.java @@ -0,0 +1,61 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.groovy.lang.resolve.processors; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiType; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrReturnStatement; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod; + +/** + * @author Max Medvedev + */ +public class MethodSubstitutorComputer extends SubstitutorComputer { + + public MethodSubstitutorComputer(PsiType thisType, + PsiType[] argumentTypes, + PsiType[] typeArguments, + boolean allVariants, + GroovyPsiElement place) { + super(thisType, argumentTypes, typeArguments, allVariants, place); + } + + @Override + @Nullable + protected PsiType getContextType() { + PsiElement call = myPlace.getParent(); + final PsiElement parent = call.getParent(); + if (parent instanceof GrReturnStatement || exitsContains(call)) { + final GrMethod method = PsiTreeUtil.getParentOfType(parent, GrMethod.class, true, GrClosableBlock.class); + if (method != null) { + return method.getReturnType(); + } + } + else if (parent instanceof GrAssignmentExpression && myPlace.getParent().equals(((GrAssignmentExpression)parent).getRValue())) { + return ((GrAssignmentExpression)parent).getLValue().getType(); + } + else if (parent instanceof GrVariable) { + return ((GrVariable)parent).getDeclaredType(); + } + return null; + } +} diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java new file mode 100644 index 000000000000..e546fd277a16 --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java @@ -0,0 +1,219 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.groovy.lang.resolve.processors; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.*; +import com.intellij.psi.util.TypeConversionUtil; +import com.intellij.util.containers.hash.HashSet; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils; +import org.jetbrains.plugins.groovy.lang.psi.GrControlFlowOwner; +import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; +import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrClassInitializer; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrThrowStatement; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinitionBody; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrGdkMethod; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod; +import org.jetbrains.plugins.groovy.lang.psi.api.types.GrClosureParameter; +import org.jetbrains.plugins.groovy.lang.psi.api.types.GrClosureSignature; +import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; +import org.jetbrains.plugins.groovy.lang.psi.impl.types.GrClosureSignatureUtil; +import org.jetbrains.plugins.groovy.lang.psi.util.GdkMethodUtil; +import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; + +import java.util.Set; + +/** + * @author Max Medvedev + */ +public abstract class SubstitutorComputer { + private static final Logger LOG = Logger.getInstance(SubstitutorComputer.class); + + protected final GroovyPsiElement myPlace; + + private final PsiType myThisType; + @Nullable private final PsiType[] myArgumentTypes; + private final PsiType[] myTypeArguments; + + private final boolean myAllVariants; + + private final GrControlFlowOwner myFlowOwner; + + + public SubstitutorComputer(PsiType thisType, + PsiType[] argumentTypes, + PsiType[] typeArguments, + boolean allVariants, + GroovyPsiElement place) { + myThisType = thisType; + myArgumentTypes = argumentTypes; + myTypeArguments = typeArguments; + myAllVariants = allVariants; + myPlace = place; + + if (canBexExitPoint(place)) { + myFlowOwner = ControlFlowUtils.findControlFlowOwner(place); + } + else { + myFlowOwner = null; + } + } + + private static boolean canBexExitPoint(PsiElement place) { + while (place != null) { + if (place instanceof GrMethod || place instanceof GrClosableBlock || place instanceof GrClassInitializer) return true; + if (place instanceof GrThrowStatement || place instanceof GrTypeDefinitionBody || place instanceof GroovyFile) return false; + place = place.getParent(); + } + return false; + } + + public PsiSubstitutor obtainSubstitutor(PsiSubstitutor substitutor, PsiMethod method, ResolveState state) { + final PsiTypeParameter[] typeParameters = method.getTypeParameters(); + if (myTypeArguments.length == typeParameters.length) { + for (int i = 0; i < typeParameters.length; i++) { + PsiTypeParameter typeParameter = typeParameters[i]; + final PsiType typeArgument = myTypeArguments[i]; + substitutor = substitutor.put(typeParameter, typeArgument); + } + return substitutor; + } + + if (myArgumentTypes != null && method.hasTypeParameters()) { + PsiType[] argTypes = myArgumentTypes; + final GroovyPsiElement resolveContext = state.get(ResolverProcessor.RESOLVE_CONTEXT); + if (method instanceof GrGdkMethod) { + //type inference should be performed from static method + PsiType[] newArgTypes = new PsiType[argTypes.length + 1]; + if (GdkMethodUtil.isInWithContext(resolveContext)) { + newArgTypes[0] = ((GrExpression)resolveContext).getType(); + } + else { + newArgTypes[0] = myThisType; + } + System.arraycopy(argTypes, 0, newArgTypes, 1, argTypes.length); + argTypes = newArgTypes; + + method = ((GrGdkMethod)method).getStaticMethod(); + LOG.assertTrue(method.isValid()); + } + else if (GdkMethodUtil.isInUseScope(resolveContext, method)) { + PsiType[] newArgTypes = new PsiType[argTypes.length + 1]; + newArgTypes[0] = myThisType; + System.arraycopy(argTypes, 0, newArgTypes, 1, argTypes.length); + argTypes = newArgTypes; + } + + return inferMethodTypeParameters(method, substitutor, typeParameters, argTypes); + } + + return substitutor; + } + + private PsiSubstitutor inferMethodTypeParameters(PsiMethod method, + PsiSubstitutor partialSubstitutor, + final PsiTypeParameter[] typeParameters, + final PsiType[] argTypes) { + if (typeParameters.length == 0 || myArgumentTypes == null) return partialSubstitutor; + + final GrClosureSignature erasedSignature = GrClosureSignatureUtil.createSignatureWithErasedParameterTypes(method); + + final GrClosureSignature signature = GrClosureSignatureUtil.createSignature(method, partialSubstitutor); + final GrClosureParameter[] params = signature.getParameters(); + + final GrClosureSignatureUtil.ArgInfo[] argInfos = + GrClosureSignatureUtil.mapArgTypesToParameters(erasedSignature, argTypes, myPlace, myAllVariants); + if (argInfos == null) return partialSubstitutor; + + int max = Math.max(params.length, argTypes.length); + + PsiType[] parameterTypes = new PsiType[max]; + PsiType[] argumentTypes = new PsiType[max]; + int i = 0; + for (int paramIndex = 0; paramIndex < argInfos.length; paramIndex++) { + PsiType paramType = params[paramIndex].getType(); + + GrClosureSignatureUtil.ArgInfo argInfo = argInfos[paramIndex]; + if (argInfo != null) { + if (argInfo.isMultiArg) { + if (paramType instanceof PsiArrayType) paramType = ((PsiArrayType)paramType).getComponentType(); + } + for (PsiType type : argInfo.args) { + argumentTypes[i] = handleConversion(paramType, type); + parameterTypes[i] = paramType; + i++; + } + } + else { + parameterTypes[i] = paramType; + argumentTypes[i] = PsiType.NULL; + i++; + } + } + final PsiResolveHelper helper = JavaPsiFacade.getInstance(method.getProject()).getResolveHelper(); + PsiSubstitutor substitutor = helper.inferTypeArguments(typeParameters, parameterTypes, argumentTypes, LanguageLevel.HIGHEST); + for (PsiTypeParameter typeParameter : typeParameters) { + if (!substitutor.getSubstitutionMap().containsKey(typeParameter)) { + substitutor = inferFromContext(typeParameter, PsiUtil.getSmartReturnType(method), substitutor, helper); + } + } + + return partialSubstitutor.putAll(substitutor); + } + + private PsiType handleConversion(PsiType paramType, PsiType argType) { + final GroovyPsiElement context = myPlace; + if (!TypesUtil.isAssignable(TypeConversionUtil.erasure(paramType), argType, context.getManager(), context.getResolveScope(), false) && + TypesUtil.isAssignableByMethodCallConversion(paramType, argType, context)) { + return paramType; + } + return argType; + } + + private PsiSubstitutor inferFromContext(PsiTypeParameter typeParameter, + PsiType lType, + PsiSubstitutor substitutor, + PsiResolveHelper helper) { + if (myPlace != null) { + final PsiType inferred = + helper.getSubstitutionForTypeParameter(typeParameter, lType, getContextType(), false, LanguageLevel.HIGHEST); + if (inferred != PsiType.NULL) { + return substitutor.put(typeParameter, inferred); + } + } + return substitutor; + } + + @Nullable + protected abstract PsiType getContextType(); + + private Set myExitPoints; + + protected boolean exitsContains(PsiElement call) { + if (myFlowOwner == null) return false; + if (myExitPoints == null) { + myExitPoints = new HashSet(); + myExitPoints.addAll(ControlFlowUtils.collectReturns(myFlowOwner)); + } + return myExitPoints.contains(call); + } +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.java b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.java index 66e16b332d62..e8f777c7161c 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.java +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.java @@ -237,4 +237,8 @@ public class TypeInferenceTest extends GroovyResolveTestCase { public void testMultiTypeParameter() { assertTypeEquals("Base", "a.groovy"); } + + public void testTypeArgsInAccessor() { + assertTypeEquals("Foo", "a.groovy"); + } } diff --git a/plugins/groovy/testdata/resolve/inference/typeArgsInAccessor/a.groovy b/plugins/groovy/testdata/resolve/inference/typeArgsInAccessor/a.groovy new file mode 100644 index 000000000000..120d91deb008 --- /dev/null +++ b/plugins/groovy/testdata/resolve/inference/typeArgsInAccessor/a.groovy @@ -0,0 +1,5 @@ +class Foo { + static Foo getInstance() { new Foo() } +} + +Foo f = Foo.instance